app.dart was 1187 LOC mixing five concerns. It now keeps ClideApp +
the WidgetsApp root (~60 LOC); the shell moved to lib/src/shell/:
- root_shell.dart — keyboard/intent routing (keymap resolution,
double-tap modifiers, menu mnemonics), the overlay stack, and the
welcome overlay
- hat_bar.dart + project_switcher.dart — the window-chrome bar and
its recents/file-actions dropdown (now in src/shell, not builtin/ —
they're app chrome, not extension-shaped contributions)
- slot_host.dart — slot mounting, focus-scope integration, the
per-slot bodies incl. the workspace split + editor drag handle;
_SlotBody's static title resolver became the shared resolveTabTitle
- layout.dart — the three-column grid, status bar, collapse toggles,
bottom icon rails
app.dart re-exports RootLayout, SlotHost, StatusbarHost, and
StatusbarCollapseToggle, so every existing import (incl. the three
app-level test files) is unchanged. Pure move + minimal publics
(RootShell, HatBar, ProjectSwitcherButton); full suite green with no
test edits.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The chord matcher couldn't represent a bare or double-tapped modifier:
KeyChord.parse required a base key, so `shift shift` failed, and JetBrains
"Search Everywhere" (double-Shift) was unbindable.
Design decision: search-everywhere aliases clide's existing quick-open
finder (not a new overlay) — bound across all four presets per the user.
Changes:
- KeyChord: a bare modifier name (`shift`, `ctrl`, `cmd`, …) parses as a
modifier-free chord on that modifier's logical key, so parseSequence(
'shift shift') yields a two-chord double-tap. Adds KeyChord.bareModifier
and modifierForLogicalKey.
- ModifierTapTracker: headless, clock-injected double-tap detector. A bare
modifier never forms a single chord; an intervening key breaks the gesture.
- app.dart global handler feeds bare-modifier KeyDowns to the tracker and,
on a double-tap, resolves the 2-chord sequence via the new
KeymapService.resolveSequence. The existing single-chord path is untouched
(zero behavioural risk to normal keys).
- Presets: default/vim/vscode/jetbrains add `shift shift` → quickOpen.open.
jetbrains header updated (the gesture is now expressible).
Tests: bare-modifier parse/equality/round-trip; tracker window/reset/
different-modifier/consume; each shipped preset resolves double-Shift to
QuickOpenIntent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raise the declared minimums in pubspec.yaml to what our deps already
require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist
0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is
the binding floor. Pin the exact build toolchain in .fvmrc (Flutter
3.44.1).
Moving to the Dart 3.9 language level switches `dart format` to the new
"tall" style and enables two new lints. This commit is the resulting
mechanical churn, isolated from any behaviour change:
- whole-tree `dart format` reformat (tall style)
- `dart fix` for unnecessary_underscores + use_null_aware_elements
No runtime behaviour change; `make test` green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the 49 hand-maintained named consts with one generated
label→codepoint map (phosphor_glyphs.g.dart, 1512 glyphs from the glyph
table via tool/gen_phosphor_glyphs.dart). Feature code now references
glyphs by their exact kebab-case name — PhosphorIcons.byName('folder') —
with no raw codepoints; this also lets a Lua extension name an icon
without crossing the FFI boundary with a codepoint.
byName is total: an unknown name degrades to the `placeholder` box so the
bug is visible (it's a real error), while phosphor_glyphs_test asserts
every byName('...') literal in lib/ resolves — recovering the typo check a
const gave. Migrated the 89 call sites. Adds EmptyIconPainter for an
intentional blank that still reserves the icon box; ClideFilterBox gains
showIcon to keep the slot aligned when blank.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The composer (and any bottom-most pane content) ran flush into the
window's bottom resize-drag strip when the status bar was hidden, since
the bar normally covers that edge. Reserve ClideResizeBorder.edgeThickness
as a bottom inset in RootLayout whenever the status bar is not visible, so
the interaction zone bottom-anchors consistently either way.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two fixes to the just-shipped collapse toggles:
- They lived inside the centre StatusbarHost, so they shifted when a pane
collapsed and the bar resized. Move them to the OUTER status-bar row ends
(screen edges) so they stay put. StatusbarHost reverts to its prior layout.
- The toggle was a const widget that read isCollapsed in its own build; Flutter
skips identical const children on rebuild, so the chevron never flipped. Pass
collapsed/visible in from the parent's arrangement listener so it rebuilds and
the chevron tracks the action direction. A 'flips live' test now guards it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixed ~24px caret-line cells bookend the status bar (left=sidebar, right=context);
chevron flips per arrangement.isCollapsed (inward=collapse, outward=expand) and
fires the existing sidebar.collapse / context.collapse commands — the mouse
affordance for the already keyboard/CLI-addressable action (D-6). Visual only;
no new collapse logic. Status items shift 28px inward for the reserved cells.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A custom in-window menu bar in the hat (no native menu, D-7), built from
the command registry so it stays in sync and satisfies D-6 parity.
- Menu model + hybrid resolver (menu_model.dart): a curated File/View/Help
tree where a MenuAutoFill node sweeps in unplaced view.* commands; titles
+ keybindings come from the registry/keymap; unregistered or
enabledWhen-false items render disabled (greyed), never hidden.
- Widgets: MenuBar row in the hat (chrome tokens), anchored MenuDropdown
overlay (dropdown tokens), two-column MenuItemRow with inline keybinding.
- Full keyboard: Alt+mnemonic opens (hook in _RootShell._onKey), arrows
navigate, Enter activates, Esc closes, Left/Right switch menus.
- Commands: file.openFolder / file.newWindow / file.closeWorkspace /
help.about, registered by MenuBarExtension(services:). File logic lifted
out of the project switcher into FileActions (one source of truth; the
switcher now dispatches the commands). Ctrl+O / Ctrl+Shift+N are now real
keybindings in default.yaml.
- Help → About: version/commit/date/repo from build-info + the bundled
dependency licenses parsed from assets/licenses.yaml.
Edit/Selection menus are deferred to T-271/T-272 (need focused-surface
command routing).
Tests: resolver + controller + licenses parse (pure); menu-bar widget
(open/close/execute/disabled/Esc/arrow/Enter/Left-Right); FileActions +
Open dialog; app-level Alt+F, non-repo dialog, and closeWorkspace.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The D-87 bottom dock, end to end. New Slots.dock in the classic preset
(hidden by default); RootLayout renders it full-width above the status bar
when open, capped at half the window so Claude stays largest (the D-47
amendment). LogRing now lives on KernelServices (boot tees the kernel logger
into it; main.dart also tees the IPC/MCP logger), so the dock shows logs from
every subsystem.
OutputExtension contributes the Output tab, the merged health/toggle
status-bar widget (green check when clean, warn/error counts otherwise) that
replaces the old ipc-status item, and the dock.toggle command (Ctrl+J).
Problems moves out of the sidebar into the dock. open/height persist per
workspace via the default-layout extension.
Drag-resize of the dock height is deferred (DragResizeHandle needs a dock
sign case); height is the persisted default for now. Boot verified via
testmode; full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A live test of ui open diff surfaced that the workspace slot hardcoded
the Claude pane (primary = claude ?? active), so activating diff.view
flipped the registry but never rendered. Generalise _WorkspaceSlot: when
a non-Claude, non-editor workspace tab is the active one, reveal it in
the split region above Claude with a close affordance back to full-Claude
— so clide ui open diff actually shows the diff alongside the
conversation. Fixes the reveal for terminal/team-chat tabs too.
Closes T-233.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Non-modal operation-feedback toasts, bottom-right: a ClideToast card per
severity (success/warning/error/info), auto-dismiss (errors linger), queue
with a visible cap, slide+fade in, manual dismiss, live-region a11y.
ToastService is a MessageBus consumer — components raise a toast by publishing
to the 'toast' channel (publishToast helper), so emitters stay decoupled from
the UI. GitController's push/pull are the first emitters. ToastOverlay mounts
in the app-root Stack.
Also adds comprehensive GitController coverage: importing it for the toast
emitter test first pulled the whole file into the coverage denominator, so the
controller is now tested end to end (status/stage/commit/stash/push/pull).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T-122: WelcomeView "hangs when recents are non-empty" was not a render/marquee
bug — SettingsStore.set does real file I/O, and awaiting settings.set +
loadRecents inside a testWidgets body runs it in fake-async, trapping the
completion so the await never returns. Fix: seed via tester.runAsync. Un-skip
the welcome recents test; add render/sticky/open-recent coverage.
Coverage: new test/app_test.dart covers the app shell (RootLayout, slots,
rails, spines, editor split, hat bar, intents, keymap, project switcher +
dialogs); welcome recents + events/types fill the rest. Total 92.04% -> 95.13%.
Also fixes a real bug found en route: the recent-project row (welcome +
switcher) overflowed instead of ellipsizing a long path (T-122).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that the editor split actually opens (T-197), it exposed latent
issues, plus a coincidental Claude-session crash in the same log:
- T-203: the _EditorDragHandle's slider Semantics had value +
onIncrease/onDecrease but no increased/decreasedValue, so Flutter
asserted on every semantics flush — add them. And opening the split
reparented the Claude pane (direct child → Column/Expanded), tearing
down its SelectableRegion mid selection-update ('selectable not in
this registrar' / 'inactive element'); a stable GlobalKey on the
workspace primary makes Flutter move the element instead.
- T-202: rate_limit_event.resetsAt arrives as a unix-epoch number but
was cast `as String?`, throwing in the stream-json line parser. Accept
a num (epoch) or an ISO string.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The find-in-files UI on top of the search.grep engine. A
FindInFilesController drives search.grep, accumulates streamed
search.match events (scoped to the active searchId, stale ids
ignored) grouped by file, and opens a match in the editor at its line.
The SearchPanelView contributes a sidebar tab: a debounced query box,
regex + case toggles, include/exclude glob fields, and a grouped
results list with the matched span highlighted.
findInFiles.open (Ctrl/Cmd+Shift+F) reveals and activates the search
tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A file picker overlay over the whole workspace, distinct from the
command palette. QuickOpenController holds the file list + a
subsequence fuzzy filter; the overlay loads the list via files.walk on
open, shows RecentFilesService entries on an empty query, and opens the
selection through a shared openWorkspaceFile helper (.md → markdown
reader bus, else editor.open) that the files panel now also routes
through, so recents stay in sync from every open site.
Bound to ctrl+p / meta+p with `when: !palette.open` so it never
collides with the palette's ctrl+p navigation; in-overlay arrows/enter/
escape reuse the palette's keymap-driven model via quickOpen.* intents.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
StatusbarHost laid every item at intrinsic width, so once the focused-pane
context line grew long (a model/mode/context/skills summary) the row's
content exceeded the bar width and overflowed instead of letting the slot
shrink. Add an opt-in flex factor to StatusItemContribution; the host wraps
flex>0 items in Flexible(loose) so they yield width when the bar is tight,
and the marquee then receives a bounded viewport and scrolls. Drops the
fixed maxWidth cap on the pane-context item.
T-160.
Co-Authored-By: Claude <noreply@anthropic.com>
test / unit + widget + golden + a11y (push) Failing after 32s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
User asked for the rest of the pubspec-derived strings to share the
same path the version went down. gen-build-info now also writes
`clideName`, `clideTagline`, `clideRepository` to
lib/src/build_info.g.dart from pubspec.yaml. Added a `tagline:`
field to pubspec for the short user-facing line (the welcome
subtitle, future web meta) — pubspec stays the single source of
truth for every name/tagline/version/repository string the app
shows.
Consumers swept:
* welcome banner ('clide' / 'IDE for Claude Code CLI') and status
line version label read from the constants.
* app.dart WidgetsApp title + project-switcher label use clideName.
* clide_column_hat uses clideName for the empty-projects fallback.
Co-Authored-By: Claude <noreply@anthropic.com>
Drag handles for sidebar / context / editor split were pure
pointer-Listeners — no Tab focus, no arrow-key adjust, no Semantics.
Each now wraps in a FocusableActionDetector with arrow shortcuts (10
px fine / 50 px coarse for the column handles, 2% / 10% for the
editor split) and a slider Semantics node that announces the current
size. The CLI verb half is split out as T-119 and waits on T-99.
Co-Authored-By: Claude <noreply@anthropic.com>
test / unit + widget + golden + a11y (push) Failing after 30s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m1s
Workspace text-zoom (Ctrl +/-/0) was local state on _RootShellState,
reachable only via the keymap intent path. Lifted to a kernel TextZoom
ChangeNotifier so the new `view.zoomIn/Out/Reset` palette commands
mutate the same number the keymap does — closing T-114's "discoverable
in the palette" item.
Co-Authored-By: Claude <noreply@anthropic.com>
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m2s
Hard-coded Windows close-button red (#E81123), white close glyph, and
the palette's 0x40000000 drop shadow were the three colour sites the
UX consultant flagged as not adapting per theme. Now they're
`windowControl.closeHoverBackground` / `closeHoverForeground` /
`shadow.ambient`. Paper themes override the shadow to a softer ink so
it doesn't read as a CRT halo on cream.
Co-Authored-By: Claude <noreply@anthropic.com>
Each SlotHost now owns a FocusScopeNode and registers it with
FocusTracker on mount. The render is wrapped in
FocusScope + FocusTraversalGroup so Tab stays within a panel and
slot-level focus is observable.
When a slot's scope gains focus, SlotHost pushes
(slot, activeContributionId) to FocusTracker — this collapses the
parallel-tracker model the consultant flagged. FocusTracker keeps
its setActive surface for explicit callers (palette, etc.) but
slot-scoped tab activation feeds it automatically.
Two new intents, two new bindings:
FocusNextPanelIntent → F6
FocusPreviousPanelIntent → Shift+F6
(VS Code convention; preset YAML.)
The cycle skips slots without a registered scope, so a layout that
hides the context panel doesn't strand focus on a missing target.
Fewer than two registered → no-op.
SlotHost split into a stateful outer (scope + registry) and a
stateless `_SlotBody` (the existing slot-specific rendering),
keeping the build straightforward.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 1m5s
Plug widgets into the keymap layer landed in T-117.
ClideTappable:
- Wrap in `Actions(ActivateIntent → onTap)` outside a `Focus` so
dispatch from the focused context walks up and hits the action.
- Add a focus ring via `tokens.globalFocus` (DecoratedBox foreground
overlay, transparent border when unfocused, no layout shift).
- Disabled (`onTap == null`) skips focus traversal and shows the
forbidden cursor.
ClidePalette:
- Register Actions for the four palette intents
(selectNext / selectPrev / accept / dismiss).
- Publish `palette.open` scope flag via `KeymapService.setScopeFlag`
so when-clauses can scope future bindings to "palette only".
- Highlight the selected row with `listItemSelectedBackground`;
scroll it into view on nav.
- `PaletteController` grows `selectedIndex` + `selectNext` /
`selectPrevious` / `acceptSelected`; index resets on open /
filter change.
Intents.dart drops the `ClideIntent` base — `ActivateIntent` and
`DismissIntent` come from Flutter; clide owns the palette and text-
scale and command-bridge subclasses. `parseIntentId('activate')` →
Flutter's class; same for dismiss. Widget code uses the canonical
Flutter Intent types where they fit.
App root grows a PaletteOpenIntent action that calls
`services.palette.open()`, completing the ctrl/cmd+shift+p path
end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Build the upstream of every keyboard-driven feature: widgets bind
to typed Intents, the keymap resolves chord+context to an Intent,
and Flutter's Actions dispatches. The widget never touches a key.
Layers (low → high precedence):
1. preset YAML in assets/keymaps/<preset>.yaml
2. extension-registered command bindings (via
KeymapService.registerCommandBinding from ExtensionManager)
3. user file at <appDir>/keybindings.yaml
4. settings JSON overlay at app.keymap.overrides
The when-clause grammar is a tiny recursive-descent parser over
boolean expressions on a named context bag — VS-Code style
`palette.open && !textInputFocused`. Producing services publish
scope flags via setScopeFlag.
Keys reference LogicalKeyboardKey.keyId (stable across keyboard
layouts), not the locale-aware keyLabel the consultant flagged.
Ships:
- lib/kernel/src/keymap/{key_chord, when_clause, intents, keymap,
keymap_service}.dart
- assets/keymaps/default.yaml (the baseline preset)
- 90+ unit tests covering parser precedence, layering precedence,
scope evaluation, register/unregister, settings overlay,
malformed-input tolerance
- app.dart root handler routes through KeymapService → Actions
- ExtensionManager mirrors every legacy defaultBinding into the
keymap as a contribution layer
KeybindingResolver kept temporarily as a back-compat shim for
callers we haven't migrated yet; safe to delete once the last
caller goes through Actions.
Closes T-110 (consultant: scoped Shortcuts/Actions; off keyLabel).
Annotates T-23 with what's left for T-100. Unblocks T-64 / T-65 /
T-66 (preset data tickets).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Unused _resolvePtycPath (bin/clide.dart), _TrafficDot (app.dart
and clide_column_hat.dart), stale @override on _spawned field
(claude_pane.dart), duplicate import and unused local in
test_app.dart, unnecessary non-null assertions in project.dart.
Co-Authored-By: Claude <noreply@anthropic.com>
test / unit + widget + golden + a11y (push) Failing after 30s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
Dropdown width 380 → 480 to prevent RenderFlex overflow on recent
project rows. "Close Workspace" → "Close Project". Keyboard shortcuts
show ⌘ on macOS instead of Ctrl.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
test / unit + widget + golden + a11y (push) Failing after 29s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
The project switcher dropdown's "Open Local Project" action now uses
the native directory picker (same as the welcome screen). Shows "No
git repo found" dialog when the selected directory is not a git
repository.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Register files.read command for reading file content by relative path.
Remove top padding on context panel container and mainAxisSize.min
from ClidePaneChrome so the header sits flush and the pane fills
available height. Context panel max raised from 420px to 1000px.
Co-Authored-By: Claude <noreply@anthropic.com>
Ctrl+Plus/Minus/0 text zoom (5% steps, 60%–200%) via MediaQuery
textScaler. App-wide clideLineHeight (1.25) applied in DefaultTextStyle.
Context panel drag resize was inverted — dragging left shrank the
panel instead of growing it. Negate delta for right-anchored slots.
Default sidebar widened from 240px to 400px, context panel from 280px
to 420px (both at their previous maximums).
Co-Authored-By: Claude <noreply@anthropic.com>
ToolCheck kernel service checks ptyc/pql/tmux/git availability
once at boot, shared by status bar and welcome screen. Welcome
status line shows "application ok" in green or lists missing
tools in amber. Logo doubled to 144px. DialogHost moved inside
Expanded to fix blank workspace. Welcome maxWidth 850px, path
overflow fixed with Flexible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hat bar moved above the Stack so it stays visible when the welcome
overlay shows. Recent project path row uses Flexible with ellipsis
to prevent horizontal overflow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Click "clide > project" label to open a dropdown with:
- Search/filter box for recent projects
- Recent projects list with path, branch, and time ago
- Open Local Project action (path dialog)
- New Window (spawns detached process)
- Close Workspace (returns to welcome screen)
Dropdown uses DialogRouter, dismissible via Escape or backdrop
click. Reuses ProjectManager.recents and ClideFilterBox.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flutter's globalPosition is window-relative, not screen-absolute.
Now using gdk_device_get_position() on the native side to get
the actual screen coordinates at the moment startDrag/startResize
is called. Dart no longer passes coordinates — the native handler
queries the pointer directly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace XWayland fallback with proper xdg-decoration protocol
negotiation. On Wayland: binds zxdg_decoration_manager_v1 from
the compositor registry, requests CLIENT_SIDE mode on the toplevel
surface — KWin respects this and drops server-side decorations.
Falls back to gdk_window_set_decorations(0) + set_decorated(FALSE)
on X11. GDK_BACKEND=x11 removed from Makefile.
Also fixes mouse-jumping glitch: startDrag and startResize now
pass screen coordinates from Flutter's globalPosition through the
method channel to gtk_window_begin_move/resize_drag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without OS decorations there are no resize handles. Added 6px
edge and 12px corner invisible hit zones around the window
perimeter via ClideResizeBorder. Each zone changes the cursor
and calls gtk_window_begin_resize_drag via the clide/window
method channel with a GdkWindowEdge direction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New tokens: chromeBackground, chromeForeground, chromeBorder —
shared root for hat bar, sidebar, context panel, status bar,
spines, and drag handles. All resolve to bgSunken/textDim/border
in the palette by default. Themes can override individually to
diverge surfaces. Replaces direct sidebarBackground/statusBar
references in chrome contexts. Updated theme-ui skill.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sidebar pads left/top only (not right — that's the divider side).
Context panel pads right/top only (not left — divider side). The
divider handle's transparent pixels now show the center column's
background correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The 1px visible line in the 5px drag handle now sits on the
innermost pixel (touching the center column) instead of centered.
Side panels (sidebar and context) get 2px padding on all sides
except the bottom, creating a balanced inset that matches the
divider's visual weight.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hat bar is now a single continuous row above the three columns
instead of per-column segments. Left: macOS traffic lights or
empty. Center: project > branch. Right: minimize/maximize/close
on Linux or empty. Column separators keep 4px hit area but show
a 1px centered line.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hide GTK title bar via gtk_window_set_decorated(FALSE). Add
MethodChannel('clide/window') for drag/minimize/maximize/close
wired to GTK window functions. Dart WindowControls service wraps
the channel. Three per-column hats in RootLayout: left (macOS
traffic lights or plain drag), center (project > branch label),
right (minimize/maximize/close glyph buttons on Linux). Resolves
Q-006.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single Flutter package at the repo root. All code, tests, assets,
and platform directories moved from app/ to root. Package renamed
from clide_app to clide — all imports rewritten. Merged pubspec
combines core (ffi) and app (flutter, yaml, xterm) dependencies.
Makefile simplified: no APP_PRESENT conditionals, no cd, no daemon
lifecycle. 317 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>