Commit Graph
90 Commits
Author SHA1 Message Date
Jeroen SchweitzerandClaude Opus 4.6 a6eca2561b remove dissolved daemon, retire ptyc, fix golden cross-platform
Complete three overdue cleanups discovered during macOS health check:

D-56 daemon dissolution: delete bin/clide.dart, DaemonServer,
and orphaned tests (test/cli/, subprocess_test, in_process_test).
Update stale "clide --daemon" references in i18n catalogs, error
messages, editor_commands, CI scripts, and decision records.

ptyc retirement: delete ptyc/ source tree, PtySession, scm_rights.
Remove from Toolchain resolution, ToolCheck gate, backend
serialization, testmode harness, Makefile, CI, and sandbox
entitlements. PTY spawning uses NativePty (Dart FFI forkpty) since
the terminal was absorbed in-tree. D-5 amended.

Golden tests: wire the existing but never-applied clideGoldenConfig
via flutter_test_config.dart. Disable CI goldens (Skia anti-aliasing
differs between macOS/Linux even with Ahem). Keep platform-keyed
goldens only — goldens/linux/ and goldens/macos/ each run on their
own OS.

Test suite: 826 pass, 0 fail on macOS (was 829 pass, 11 fail).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 18:40:01 +02:00
jpmschweitzerandClaude 6b7290dc42 fix dead-wired TerminalView.onTapUp callback (T-93)
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 59s
`TerminalView.onTapUp` was documented as "Callback for when the
user taps on the terminal" but was wired to a code path nothing
ever invoked:

- `TerminalView.build` passed it via `onTapUp:` to
  `TerminalGestureHandler`,
- which forwarded via `onTapUp:` to `TerminalGestureDetector`,
- whose `onTapUp` field was declared and accepted but never called
  by `_handleTapUp` (which only fires `onSingleTapUp`).

Net: every caller that registered an `onTapUp` callback on
`TerminalView` got silent failure. zero in-tree callers depended
on it (clide_pty_view.dart is the only TerminalView callsite and
doesn't pass any tap callback), but the public API said one thing
and did another.

Fix: wire `_onTapUp` (the cell-resolving state-method) through the
detector's working `onSingleTapUp` slot. The user-facing semantics
("fires on confirmed single tap with the resolved cell offset")
match the only sane interpretation of the docstring, and don't
overlap with the existing `onSecondaryTap*` (which were already
correctly wired through TapGestureRecognizer's secondary callbacks).

Also drops the dead surface that surfaced the bug:
- `TerminalGestureHandler.onTapUp` parameter + field — no caller
  passes it after the fix; was only used to forward into the dead
  detector field.
- `TerminalGestureDetector.onTapUp` parameter + field — never
  invoked by `_handleTapUp`. Pure dead code.

Tests: extends `terminal_view_test.dart` with a primary-tap
regression case + paired tests for selection-clearing and
secondary-tap callback routing. The double-tap recognizer's
300 ms timer is flushed via `pump(const Duration(seconds: 1))`
(pumpAndSettle waits for animations, not arbitrary timers).

Coverage delta:
- terminal_view.dart: 151/188 → 180/188 (95.7%; remaining gaps
  are IME `_onComposing`/`_onEditableRect`/`_onKeyboardShow`
  body branches that need deeper IME mocking).
- gesture_handler.dart: 18/60 → 39/59.
- gesture_detector.dart: 30/50 → 42/50.
- Total project: 65.76% → 66.97%; coverage_floor 65 → 66.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-07 08:49:09 +02:00
jpmschweitzerandClaude 4642a2f25b test sweep: cover Terminal orchestrator (T-91)
Adds test/terminal/terminal_test.dart — 54 unit tests covering the
`Terminal` class as a pure-Dart orchestrator: construction +
TerminalState defaults, the Observable mixin, write/writeChar,
keyInput / charInput / textInput / paste (with bracketed-paste +
ctrl/alt encodings, including macOS reservation), mouseInput
gating, resize (clamping + onResize callback + alt-buffer
scrollback clear), buffer switching (use{Alt,Main}Buffer +
clearAltBuffer), every SBC handler (bell / backspace / lineFeed /
CR / SO / SI / unknown), tab-stop manipulation (tab jump +
saturation, clearTabStopUnderCursor, clearAllTabStops, setTapStop),
every ANSI escape handler (save/restore cursor, index, nextLine,
reverseIndex, designateCharset), CSI cursor + erase + line/char
insert/delete + scroll + repeatPreviousCharacter (incl. no-op when
no preceding char), device-attribute and status reports, every
mode setter mirroring into its getter, every SGR set/unset attr +
colour setter, OSC handlers (setTitle / setIconName / unknownOSC),
and all the documented no-op fallbacks (unknownSBC, unkownEscape,
unknownCSI, setUnknownMode, setUnknownDecMode, setColumnMode,
unsupportedStyle).

Also fixes a real production bug surfaced while writing tests:
`BufferLine.eraseRange(0, 0, ...)` panicked with a `RangeError`
because the right-side wide-char guard read `_data[-1]` via
`getWidth(end - 1)` when `end == 0`. The left guard already had a
`start > 0` check; the right guard was missing the symmetric
`end > 0`. Real trigger path: `Terminal.eraseDisplayAbove`
(`ESC[1J`) with the cursor at column 0 — common after `ESC[H\x1b[1J`
home-then-erase-above sequences that many TUIs emit on redraw.
Regression test added in line_test.dart.

Coverage delta:
- terminal.dart: 0/283 → 291/291 (file grew by 8 LF for the
  fix's comment lines).
- base/observable.dart: 0/7 → 7/7 (covered transitively via
  Terminal's listener tests).
- Total project: 56.40% → 59.82%; coverage_floor bumped 56 → 59.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-07 08:21:18 +02:00
jpmschweitzerandClaude 6caa82597e fix dangling tail anchors in reflow on partially-filled lines (T-92)
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
test / dart doc (lib API) (push) Failing after 1m0s
`_LineReflow._addPart`'s post-loop block reparents anchors past the
source line's trimmed content onto whatever `_builder._result` was
active at that moment. When no further content lands in the builder
(non-wrapped lines, or the last logical line of a wrapped run),
`finish()` was emitting only when `_builder.isNotEmpty` — leaving
the empty result line with the reparented anchor unappended. The
anchor then pointed to a `BufferLine` that the reflow output never
included, `lines.replaceWith(reflowResult)` discarded it, and
`CellAnchor.attached` returned false. The selection controller's
`extent.attached` null-check then dropped the selection silently
on resize.

The fix adds a `_LineBuilder.hasAnchors` getter and uses it in
`finish()` so the builder line is also emitted when it's carrying
an anchor — even when otherwise empty. Trade-off: an extra trailing
line in the reflow output when (and only when) a tail anchor would
have dangled. `Buffer.resize` already pads the result to `newHeight`
afterward, so for the common case (resize fits inside view height)
the total ring length is unchanged; only when the result already
meets / exceeds `newHeight` does the buffer grow by one. Acceptable
in exchange for selections surviving a width change.

User-visible trigger paths:
- `SelectAllTextIntent` (Ctrl+A) creates an end anchor at
  `x = viewWidth` on the last buffer line — exactly the past-
  trimmed-length position. Resizing narrower while the selection
  was active dropped it.
- Mouse drag selections past the end of a partially-filled line
  hit the same shape.

Tests:
- The pre-existing `reflow anchors on the source line tail (past
  trimmedLength) get reparented` test was originally written to
  document the buggy behaviour ("anchor moves off the source onto
  a dangling builder line"). Updated to assert the post-fix
  contract: `out.contains(tail.line)` is true.
- New `SelectAllTextIntent-shaped end anchor survives shrink`
  regression test that mirrors the actual production trigger
  (anchor at `x = viewWidth` on a partially-filled line, narrower
  reflow).

reflow.dart 71/71 → 72/72 (the new getter is a one-liner). Project
coverage 54.62% unchanged within rounding.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-07 07:58:44 +02:00
jpmschweitzerandClaude c744c8ce4b test sweep: cover BufferLine + CellAnchor (T-91)
Adds test/terminal/buffer/line_test.dart — 51 unit tests covering
BufferLine and CellAnchor, hitting every reachable line in
lib/src/terminal/src/core/buffer/line.dart (192 / 192).

Coverage delta:
- line.dart: 0 / 194 → 192 / 192 (file shrank by two lines after
  the prior commit's iteration fix folded two for-loop heads into
  for-each-toList).
- Total project: 36.39% → 39.12%.
- pubspec.yaml `coverage_floor:` bumped 36 → 39 in lockstep.

Highlights:
- All packed-cell encodings (foreground/background/attrs/content
  channels, codepoint+width packing, CellData round-trips).
- `eraseRange` wide-char neighbor extension on both ends.
- `removeCells` / `insertCells` shift logic, anchor reposition, and
  the wide-tail-erase branch (insertCells case where the post-shift
  last cell carries a wide marker).
- `resize` exercising the [64, 256) capacity-doubling branch and
  the >=256 +32 branch separately.
- `getTrimmedLength` cols-clamp behaviour for null/over-capacity.
- `getText` skip-trailing-wide-char branch.
- `CellAnchor` lifecycle: detached construction, `reposition`,
  `reparent` (both detached→attached and between owners), `dispose`,
  attached y/offset via a real IndexAwareCircularBuffer.

Also cleans up five `unrelated_type_equality_checks` analyze infos
in test/terminal/buffer/range_test.dart by typing the RHS as Object
when intentionally probing the type-mismatch branch of operator==.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 23:03:21 +02:00
jpmschweitzerandClaude 1151a0ce80 install ratcheting line-coverage gate at 34% floor (T-90)
First child of T-89. Codifies "don't make coverage worse" as a
durable pre-push contract before any test-writing children land.

- pubspec.yaml: new `coverage_floor: 34` key. Single source of
  truth for the floor; ratchets up only.
- ci/coverage_gate.sh: parses coverage/lcov.info (LH/LF), reads
  the floor from pubspec.yaml, exits non-zero if integer-truncated
  measured % drops below it. Self-contained awk parser — no `lcov`
  CLI dependency.
- ci/test.sh: flutter test now runs with --coverage, so the gate
  reads fresh data without an extra test invocation. Wall time
  delta is small and stays inside the < 90 s pre-push budget
  (D-29).
- Makefile: new `coverage-gate` target wires the script in;
  `push-check` adds it as a dependency. The .githooks/pre-push
  hook (already wired) picks this up automatically.
- .gitignore: ignore /coverage/ wholesale; the floor lives in
  pubspec.yaml, nothing under coverage/ is committed.

Decision recorded as D-66 (decisions/testing.md). End target is
95%; reaching it is tracked as the rest of T-89's children.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 22:44:43 +02:00
jpmschweitzerandClaude a67a768592 restore semantic bold rendering in terminal panes (T-73)
Bold attributes from terminal escapes now render in a real bold
weight instead of being silently flattened.

- pubspec.yaml: register JetBrainsMono Bold + BoldItalic at
  weight 700 under family JetBrainsMono. Files already shipped on
  disk; only the registration was missing.
- assets/licenses.yaml: bump JetBrainsMono weights_bundled to
  [Regular, Italic, Bold, BoldItalic] per D-42 (the entry must
  match what is actually wired into the family).
- lib/src/terminal/src/ui/painter.dart: revert the `bold: false`
  override and drop the workaround comment. Bold now flows from
  CellFlags.bold to TextStyle.fontWeight.
- test/terminal/painter_bold_metrics_test.dart: load Regular and
  Bold via FontLoader and assert paragraph maxIntrinsicWidth is
  identical (cell-grid drift = 0). JetBrainsMono Bold's monospace
  by spec; this test is the canary for the day someone swaps the
  font.
- test/goldens/goldens/{ci,linux}/clide_button.png: regenerate.
  ClideButton's label renders slightly heavier on the bold variant
  (expected — 0.28% pixel diff before regen).

Earlier perception of over-bolding in the Claude pane was
synthetic-bold smearing (Flutter overpaints when no Bold.ttf is
registered for the family), not legitimate bold rendering. Visual
A/B confirms a real Bold face renders crisp emphasis without the
smear, so no per-pane renderer config is needed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 22:29:23 +02:00
jpmschweitzerandClaude ad807fb0df fail dart doc CI on validate-links warnings (T-17)
test / unit + widget + golden + a11y (push) Failing after 41s
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 1m22s
Wraps the `dart doc --validate-links` step so any warning fails the
job, not just hard errors. The previous step exited 0 even with
broken doc refs and dangling README links — exactly the
informational-mode drift that lets a clean board rot.

Updates the CHANGELOG entry to describe the gate accurately (the
earlier wording overstated `--validate-links`, which only prints).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 17:58:27 +02:00
jpmschweitzerandClaude 9945290bdd wire dart doc into staged CI workflow (T-17)
Adds a docs job to .gitea/workflows/test.yml that runs
`dart doc --validate-links` and uploads doc/api/ as an artefact.
Runs in parallel with unit; documents the public lib/ surface and
fails the build on broken references. Stays inert with the rest of
the workflow until Gitea Actions activates per D-32.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 17:47:52 +02:00
jpmschweitzerandClaude 6601056972 finish T-87: cold-start reap, kill-all-sessions, helper tests
Three remaining acceptance criteria for T-87:

1. Cold-start reap. The Claude extension's activate() now kills
   every leftover secondary tmux session for the current repo
   before any new spawn. activate runs before any UI mounts, so
   _nextSecondary's starting value of 1 is correct even when a
   previous run died abruptly (kill -9, OOM, force-quit). The
   deactivate() hook also calls reapSecondaries as a courtesy on
   explicit extension teardown — but Flutter's deactivate doesn't
   fire on app quit, so activate is the load-bearing path.

2. claude.kill-all-sessions actually kills server-side. The
   command previously called pane.close on every claude pane,
   which only kills the tmux client. It now also calls
   tmux.killAllForRepo to kill the sessions on the clide socket.

3. Tests. test/builtin/claude/tmux_session_test.dart covers
   killSession, listClideSessions, reapSecondaries, and
   killAllForRepo via the TmuxRunner override — no real shell-out
   in tests.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 16:48:06 +02:00
jpmschweitzerandClaude 1c424f26e7 kill secondary Claude tmux sessions on tab close (T-87)
Adds lib/builtin/claude/src/tmux_session.dart with helpers for the
clide-socket tmux server: killSession, listClideSessions,
reapSecondaries, killAllForRepo. The runner is overrideable via a
TmuxRunner typedef so tests don't shell out for real.

Wires ClaudePane.dispose() to call killSession(sessionName) for
secondary panes. Primary panes are left alone — D-41 keeps the
primary's tmux session alive across clide restarts so the next
launch re-attaches via `tmux new-session -A`.

Imports the helpers in the Claude extension as groundwork for the
app-shutdown reap and the existing claude.kill-all-sessions
command — wiring those uses lands separately.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 16:41:33 +02:00
jpmschweitzerandClaude e329f1cc6f codify UI spacing constants (T-86)
Adds lib/widgets/src/spacing.dart with three categories of named
constants — insets (clideInsetHairline / Tight / Icon / Standard /
Text), gaps (clideGapTight / Standard / Section / SectionLarge /
Major / Column), and sizes (clideIconMicro / Caption / Standard /
HitTarget / Emphatic, clideControlHeight).

Migrates MultitabPane to consume the constants and updates the
ui-design geometry reference to point at them. Inline pixel
literals in widget code were drifting (12 here, 6 there, 28
elsewhere) — pulling them through named symbols makes the
"uniform inner spacing" rule enforceable instead of eyeballed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 15:45:07 +02:00
jpmschweitzerandClaude 29ac9b3caf consolidate theme-ui into ui-design skill (T-85)
Replaces the single-file theme-ui skill with a routed ui-design skill
backed by four references:

- theme.md      — token system, identity rule, palette layers, type
- surface.md    — token selection per surface (chrome, panels, tabs,
                  buttons, status, overlays)
- geometry.md   — control spacing/alignment principles distilled from
                  the MultitabPane work: uniform inner spacing rule,
                  no double-edge padding, two-column control pattern,
                  perceived mass over measured pixels
- icons.md      — Phosphor icons + clide-owned painters

SKILL.md routes to the right reference and holds the universal rules.
The trigger description widens to cover spacing/alignment questions
in addition to token selection.

geometry.md references T-86 (codify spacing constants); the doc uses
literal pixel values until those constants land.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 15:38:19 +02:00
jpmschweitzerandClaude aa742eed79 MultitabPane: drag-to-reorder gesture wiring (T-84)
Each tab is wrapped in a Draggable (when allowReorder is true and the
entry itself is reorderable) and a DragTarget (always — the controller's
barrier logic decides whether the move actually happens). Drops insert
the dragged entry at the target tab's index. A 2px leading insertion
indicator highlights the active drop target.

The widget harness now wraps children in an Overlay so Draggable's
feedback can mount without each test re-wrapping. Sized by the test
view's bounds to avoid disturbing existing tests that query
find.byType(SizedBox).first.

Four widget tests cover the gesture path: drop reorders, pinned
barrier blocks, pinned tabs aren't draggable, and allowReorder=false
disables drag entirely.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 14:17:16 +02:00
jpmschweitzerandClaude 1caaf5f4dd implement MultitabPane widget + controller (T-83)
MultitabController<T> is a Flutter-free ChangeNotifier owning the
tab list, active selection, and reorder/close invariants:
- pinned (non-reorderable) entries form barriers that other tabs
  cannot cross
- non-closeable entries silently no-op on remove() so hosts don't
  need to gate the call site
- closing the active tab falls right, then left, then to null
- duplicate ids are rejected

MultitabPane<T> is the widget shell: a horizontal tab strip
followed by the active entry's body. Active tab gets the
panelHeader background and a panelActiveBorder top accent;
inactive tabs blend into the tab bar. Close × is hidden until
hover. Add button only renders when onAddRequested is wired.

Hosts route the user's add/close intent through callbacks so the
widget stays domain-free — for the Claude pane, add will spawn a
new tmux session and close will kill one. Drag-to-reorder is
controller-side only for now (the gesture wiring lands with T-24).

19 controller tests + 9 widget tests.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 12:12:42 +02:00
jpmschweitzerandClaude 2d64740007 welcome screen: add Tips card below START / RECENT row
Six common keybindings (Quick open, Command palette, Toggle
sidebar, Toggle context, Switch theme, New Claude session) shown
as a 3x2 grid card spanning the same 850px content column as the
two action columns above. LayoutBuilder gates the card on viewport
height (>640px) so on shorter windows the centered START / RECENT
columns stay the focus and the tips drop out cleanly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 11:10:17 +02:00
jpmschweitzerandClaude 5fb3df84ed map POSIX errno to actionable IPC error kinds (T-79)
pane.spawn (via PtyException.errno) and editor.open (via
FileSystemException.osError.errorCode) now route ENOENT to
not_found, EACCES/EPERM to user_error with a permissions hint,
EISDIR/ENOTDIR/EEXIST to distinct user-error/conflict, and
EMFILE/ENFILE to tool_error with a "fd limit hit" hint. The
mapping lives in lib/src/ipc/errno_mapping.dart so other handlers
can adopt the same surface as they pick up errno-bearing failures.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-06 09:56:04 +02:00
jpmschweitzerandClaude 41fd586ca0 IPC server: per-request timeout, error logging, stale-socket guard (T-77)
Three hardening fixes:
- 60s per-request timeout (configurable via DaemonServer constructor)
  prevents a misbehaving handler from blocking the connection's
  read pipeline indefinitely. On timeout the client gets a clean
  tool_error response.
- broadcast() and the per-request response writeln are wrapped in
  try/catch with stderr logging. Previously write failures silently
  dropped clients with no diagnostic; events going missing was
  invisible.
- start() probes for a live daemon before unlinking a stale socket.
  If something answers within 200ms, refuse to start. Previously
  two daemons racing to bind would let the second rip the first's
  live socket out.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-05 15:12:14 +02:00
jpmschweitzerandClaude 1787147e82 PTY: fix resource leaks and reader-isolate races (T-76)
NativePty.close() now awaits the reader-isolate spawn, kills the
child first to drive EOF on the master fd, awaits the isolate's
EOF acknowledgement, and only then closes the fd. Previously the
fd-close racing with the polling isolate left a window where the
fd number could be reused and the isolate would briefly target the
wrong file.

Both NativePty and PtySession now surface reader-isolate spawn
errors via the output stream's addError instead of silently
swallowing them.

PtySession.spawn closes the master fd on any post-receive failure,
closes parentSock in finally (was leaking on every spawn), and
kills the ptyc process if recvFd fails.

PtySession._recvFdAsync uses try/finally to close the ReceivePort
and kill the spawn isolate even when Isolate.spawn itself throws.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-05 15:10:43 +02:00
jpmschweitzerandClaude 0d333e8470 PTY: surface errno from forkpty/write/ioctl failures (T-75)
forkpty failures throw PtyException with the captured errno
(previously a generic StateError). The spawned child's chdir/execve
failures write a diagnostic line to its slave PTY before _exit, so
the parent's reader sees "exec failed: <path>" instead of an
indistinguishable EOF.

NativePty.write and PtySession.write loop on short writes and throw
PtyException on hard errors (with errno). NativePty.resize sets
_dead on EBADF so subsequent calls short-circuit cleanly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-05 15:07:25 +02:00
jpmschweitzerandClaude 816e60d028 reject path traversal in files.read and files.ls (T-78)
Both handlers concatenated the request path onto the workspace root
without validating containment, letting `path: "../../../etc/passwd"`
escape the workspace. resolveUnderRoot normalizes the path and
checks containment under root.absolute.path before any filesystem
access.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-05 15:02:30 +02:00
jpmschweitzerandClaude 4ff004790d suppress bold rendering in terminal to keep cell grid stable
Flutter falls back to synthetic bold when JetBrainsMono-Bold isn't
registered, and synthetic bold drifts glyph advance widths enough
to break the monospace cell grid (cursor block lands between
characters, prompts wrap mid-word). Color is enough to convey
emphasis in TUIs; semantic italic and underline still render.

Also drop the temporary `tmux -L clide kill-server` from the
install target — the rapid-iteration loop is no longer needed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-05 14:51:04 +02:00
jpmschweitzerandClaude c378503e26 spawn Claude in fullscreen mode and route mouse-wheel to PgUp/PgDown
Spawn `claude` directly as the tmux command with
CLAUDE_CODE_NO_FLICKER=1 so Claude Code runs in its fullscreen TUI
mode (input box pinned at bottom, owns its own scrollback). Mouse
wheel events are converted to PgUp/PgDown key input — universal
scroll signal that Claude, less, vim normal mode all respect, and
sidesteps the mouse-mode-but-no-scroll dead end where TUI apps
capture mouse without binding the wheel.

Drops the 1000-row tmux canvas + SingleChildScrollView experiment
in favor of viewport-sized tmux and Claude's native bottom-pinning.

Makefile install target now kills the clide tmux server so the
new config takes effect immediately. Marked TEMP — to be removed
once we no longer need the rapid-iteration loop.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-05 13:08:47 +02:00
jpmschweitzerandClaude 9c1597a450 inline terminal emulator, drop xterm.dart dependency
Replace the xterm pub.dev package with owned code under
lib/src/terminal/. Based on xterm.dart v4.0.0 by xuty (MIT).
Quiver LRU replaced with hand-rolled LinkedHashMap cache.
Scrollable removed from TerminalView — scroll events are forwarded
via Listener.onPointerSignal instead. zmodem, debugger, and
suggestion modules stripped as unused.

Also: bundle clide.tmux.conf (no status bar, 50k scrollback,
mouse on, zero escape delay, isolated -L clide socket), bump PTY
read buffer to 64KB, add 2px terminal padding, drop bold
JetBrains Mono registration.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-05 08:50:38 +02:00
jpmschweitzerandClaude 5301c4753c release v2.0.0
test / unit + widget + golden + a11y (push) Failing after 31s
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
Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 22:06:31 +02:00
jpmschweitzerandClaude b45699ccd1 fix test suite — green on make test
tabsFor() sorts by contribution priority when no user order is
set. Test expectations updated for sidebar defaultSize 400 and
decision ID D-1 (no zero-padding). PTY tests tagged forkpty and
run via dart test (forkpty output unreliable inside flutter test
runner). CI script adds --no-fatal-infos and --exclude-tags.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-03 21:52:39 +02:00
Jeroen Schweitzer c436e72c5c Merge remote-tracking branch 'gitea/main'
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
# Conflicts:
#	.pql/pql-plan.json
2026-05-03 21:24:52 +02:00
jpmschweitzerandClaude 7911075107 move canonical upstream to GitHub
Gitea remote kept as secondary (`gitea`). Updated repository URLs
in pubspec.yaml, licenses.yaml, and initial-plan.md. README rewritten
to reflect the current single-process Flutter architecture.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-26 20:07:54 +02:00
Jeroen SchweitzerandClaude Opus 4.6 5515d88407 lazy backend boot and rename workspace to project
Backend isolate now boots in two phases: resolve toolchain on spawn
(binary availability only), initialize services on project.open. The
dispatcher stays empty until a project activates — IPC requests before
that return "No project active".

Scheduler ticker only runs while a project is open. Fires an immediate
first cycle on ProjectOpened so sidebar panels refresh without waiting
for the next interval. Stops on ProjectClosed.

Renamed workspace → project throughout backend messages
(project.validate, project.open, project.ready), callbacks
(onProjectOpen, onValidateProject), and methods
(openProject, validateProject, resolveProject).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 14:16:46 +02:00
Jeroen SchweitzerandClaude Opus 4.6 a89910dc36 move daemon services to backend isolate
The merged UI/platform thread on macOS (Flutter 3.41) freezes on any
synchronous work — file I/O, Process.run, even isolate spawning during
early frames. No timing workaround (Timer, addPostFrameCallback,
Future.delayed) was reliable.

Fix: spawn a backend isolate that owns the DaemonDispatcher, GitClient,
PqlClient, FilesService, EditorRegistry, and Toolchain resolution. The
main isolate stays free for rendering. Communication uses SendPort with
the existing IPC message protocol (IpcRequest/IpcResponse/IpcEvent) —
zero new serialization.

New files:
  backend.dart       — spawns isolate, manages SendPort/ReceivePort
  backend_entry.dart — isolate entry point, boots all services
  isolate_client.dart — replaces InProcessClient for production

Toolchain now exposes resolveToolchainPaths() as a top-level function
with self-contained PATH expansion (no module-level state that would
prevent isolate message passing).

ClideTestApp gains boot-sequence tests: compute(), Isolate.run(),
sequential Process.run, and the full resolve+exec chain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-26 13:53:33 +02:00
jpmschweitzerandClaude 9e340197d7 expand ClideTestApp with IPC, extension, and theme tests
Six improvements to the testmode harness:

1. Shell passthrough tests use the resolved shell instead of
   hardcoded /bin/zsh.
2. Exit code reflects test results (non-zero on any failure).
3. JSON summary line for machine-readable parsing.
4. IPC round-trip smoke (ping, version, unknown-cmd, encode/decode).
5. Extension lifecycle smoke (register + activate files, diff,
   git, terminal; theme loading for all four bundled themes).
6. Test categories via CLIDE_TESTMODE dart-define (toolchain, ipc,
   extensions, all). Makefile exposes TESTMODE_CATEGORY variable.

Also switches main.dart from bool.fromEnvironment to
String.fromEnvironment so category values other than "true" work.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-26 13:45:43 +02:00
Jeroen SchweitzerandClaude Opus 4.6 73e80a55a6 add Toolchain, GitClient, native directory picker
Toolchain centralizes binary resolution — replaces five ad-hoc
mechanisms (expandedPath, _resolveGit, _resolve, _resolvePtyc,
_existsOnPath). Resolves via Future.delayed after runApp to avoid
blocking the merged UI/platform thread on macOS.

GitClient wraps all git operations with a typed API. Every subprocess
call goes through _run() using toolchain.git + toolchain.gitEnv.
Replaces free functions in operations.dart.

Native directory picker: NSOpenPanel on macOS (method channel in
AppDelegate), GtkFileChooserDialog on Linux. Falls back to text-input
dialog on web or MissingPluginException. Shows "No git repo found"
dialog when the selected directory is not a git repository.

PqlClient and pane commands updated to use Toolchain. ToolCheck
replaced by Toolchain.missing/allOk. All IPC handlers now catch
GitException to prevent unhandled exceptions on the merged thread.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 13:18:39 +02:00
Jeroen SchweitzerandClaude Opus 4.6 8edcc78bfe add macOS desktop target with sandbox entitlements
OS-detecting Makefile (make run works on macOS/Linux/Windows), 1280x720
default window in MainMenu.xib, squared app icons, sandbox with scoped
SBPL exceptions for subprocess execution, _DARWIN_C_SOURCE for ptyc
compilation, expanded PATH merging Homebrew and ~/.local/bin for GUI
apps, native traffic dots skipped on macOS (titlebar owns them).

DebugProfile.entitlements is gitignored (machine-specific SBPL paths);
a template with __HOMEDIR__/__PROJECTS__/__SHELL__ placeholders is
committed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 13:18:39 +02:00
jpmschweitzerandClaude 41a94b043d update docs for --with-context consolidation
test / unit + widget + golden + a11y (push) Failing after 26s
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / integration_test (xvfb) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
Pql skill: ticket show flags updated to reflect --with-context
replacing --with-decision and --with-children.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-24 11:29:24 +02:00
jpmschweitzerandClaude 77c2f25eaf update skills and changelog for scheduler, accordion, and refresh
test / unit + widget + golden + a11y (push) Failing after 35s
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
Theme-ui skill: document Phosphor Icons section with codepoint CSV
reference, icon lookup workflow, and bold/fill weight usage.

Pql skill: update ticket status command to reflect batch ID support
and removed state machine enforcement.

Changelog: scheduler service, auto-refresh, ClideAccordion, status
buttons, per-status ticket sections, codepoint CSV.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-24 10:50:53 +02:00
jpmschweitzerandClaude 226520cfce add ranked search to pql sidebar with DSL toggle
test / unit + widget + golden + a11y (push) Failing after 33s
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
Search tab defaults to ranked text search via pql search — debounced
300ms, scored results with visual score bar.  DSL toggle switches to
raw PQL query mode for SQL-like queries.  Clicking a search result
opens it in the markdown context viewer.

Adds pql.search IPC command and PqlClient.search method.  Controller
gains SearchMode enum and search() method alongside existing runQuery.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-23 22:22:16 +02:00
jpmschweitzerandClaude 2f7f797288 update changelog for pql restructure, tree-sitter fix, files.read
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
Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-23 21:26:03 +02:00
jpmschweitzerandClaude 2f6c11e9a6 update changelog for markdown, zoom, focus, and layout changes
test / unit + widget + golden + a11y (push) Failing after 37s
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
Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-23 18:53:58 +02:00
jpmschweitzerandClaude Opus 4.6 2235cc4c19 drop leading zeros from all D/Q/R/T IDs
pql 1.2.0 changed its record format to drop leading zeros. All
262 references across 18 files renumbered (D-001→D-1, T-043→T-43,
etc.). pql-plan.json export updated as the git source of truth
for the planning database. DB rebuilt via pql plan import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 12:16:52 +02:00
jpmschweitzerandClaude Opus 4.6 eb8c9ecf5d add ClideFilterBox widget to all sidebar panes
Shared filter box with search icon, debounced input, and clear
button. Applied to all six left-panel panes for consistent
filtering: Files (flat path results when filtering), Git (filter
staged/unstaged/untracked by path), Decisions (by ID/title/domain),
Tickets (by ID/title/status), Problems (by source/message), and
pql Query (replaces custom _QueryInput).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 07:56:39 +02:00
jpmschweitzerandClaude Opus 4.6 1fdd35706e fold project.yaml into pubspec.yaml
pubspec.yaml is now the single source of truth for version and
project metadata. Makefile reads version from pubspec.yaml. All
references to project.yaml across CLAUDE.md, CHANGELOG.md,
decisions, and skills updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 00:45:36 +02:00
jpmschweitzerandClaude Opus 4.6 16ecf51091 redesign welcome as full-screen overlay with recent projects
Welcome screen is now a full-window overlay that covers the IDE
when no project is open. Two-column layout: START actions (open
folder, clone from git, start Claude session) with keyboard
shortcut hints, and RECENT projects list showing name, path,
branch, and relative timestamp. Status line shows version, daemon
connection state, and active theme.

ProjectManager tracks up to 10 recent projects with path, branch
name, and last-opened time, persisted as JSON in app.recentProjects.
Boot chain: try last project → try cwd → show welcome. Clicking a
recent project opens it directly into Claude.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 23:35:21 +02:00
jpmschweitzerandClaude Opus 4.6 eedeaec05a persist last-opened project and wire welcome open dialog
ProjectManager.open() now saves app.lastProject to user settings.
Boot calls openLast() instead of opening cwd — first launch shows
the welcome screen, subsequent launches restore the last project
directly into Claude. The welcome "Open project" button shows a
modal path-entry dialog that validates against git and activates
the Claude tab on success. Removed the dead "New Claude session"
button since secondary sessions are spawned via command palette.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 23:29:40 +02:00
jpmschweitzerandClaude Opus 4.6 8a94fd07a7 wire decisions, tickets, markdown viewer, and graph extensions
Four stub extensions now contribute real tabs with views:

- Decisions panel (sidebar, priority -20): lists confirmed D-records
  from pql decisions list with ID, title, and domain.
- Tickets panel (sidebar, priority -10): lists tickets with status
  dot color-coded by state (done=green, in_progress=blue,
  cancelled=red).
- Markdown viewer (context, priority -100): shows raw content of
  the active .md file, listens for editor.buffer_activated events.
- Graph view (context, priority -80): lists files with inbound and
  outbound link counts from pql search --connections.

All four fetch data via the daemon's pql.exec IPC surface.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 23:17:03 +02:00
jpmschweitzerandClaude Opus 4.6 cab92b0c6e add Phosphor Icons font and wire into icon rails
Phosphor Icons v2.0.8 (MIT) — regular, bold, and fill weights
bundled under assets/fonts/phosphor/. PhosphorIconPainter bridges
the font glyphs into the existing ClideIconPainter interface via
Paragraph API so icon rails, pane headers, and any widget using
ClideIcon can consume them. Sidebar and context panel icon rails
now use Phosphor glyphs instead of hand-painted CustomPaint icons.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 23:10:40 +02:00
jpmschweitzerandClaude Opus 4.6 82c63da3ad persist layout state across sessions
Collapse state, panel sizes, active sidebar/context tabs, and
editor split ratio save to .clide/settings.yaml on every layout
change and restore on boot. Guards against missing projectDir
so tests and no-project scenarios degrade gracefully.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 23:02:48 +02:00
jpmschweitzerandClaude Opus 4.6 43901bc387 implement editor-above-Claude split in workspace
Editor opens above Claude via Ctrl+E with a draggable divider at
35% height (clamped 15-70%). Ctrl+W or Escape closes it. The
workspace no longer renders a tab bar — Claude is the always-visible
primary surface per D-047/D-048; the editor is a split overlay
per D-049, not a tab. The _WorkspaceSlot separates editor from
primary content tabs and renders them as a vertical split when the
editor is open.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 23:01:02 +02:00
jpmschweitzerandClaude Opus 4.6 11eb91effe implement collapse spine, focus mode, keyboard shortcuts, context rail
Collapsed side panels render as a 12px vertical spine with rotated
label, hover highlight, and badge dot (D-051). Focus mode snapshots
and restores the full layout arrangement (D-052). Fourteen new
commands in the default-layout extension: collapse toggles
(Ctrl+Shift+1/3), panel focus (Ctrl+1/2/3), focus mode (Ctrl+.),
sidebar section switching (Alt+1-5), Escape dismiss (D-054). Right
panel gets a bottom icon rail matching the sidebar pattern (D-047).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-22 22:47:45 +02:00
jpmschweitzerandClaude 02814b35f3 add POLICY.md and set line length to 160
POLICY.md codifies runtime behavior rules (no network on default
launch path), dependency vetting, vendored binary rebuild process,
telemetry prohibition, and licensing requirements. Binding on all
contributors including agents.

Line length 160 in .editorconfig (all files) and
analysis_options.yaml (dart formatter).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-22 22:20:42 +02:00
jpmschweitzerandClaude ce8529e424 add tree-sitter syntax highlighting via dart:ffi
Vendor libtree-sitter.so (v0.26.8, wasmtime 44.0 statically linked)
and call its C API through dart:ffi. Grammar WASM files are loaded by
tree-sitter's embedded wasmtime engine via ts_wasm_store_load_language.

48 grammars compiled with tree-sitter build --wasm, 48 highlight
queries (42 from upstream repos, 6 written in-house for comment,
erlang, gdscript, gitignore, hcl, hlsl, swift). SQLite grammar
replaces full SQL (974K-line parser exhausted RAM at compile time).

Removes wasm_run and wasm_run_flutter — they downloaded a 22 MB
binary from GitHub at first launch, violating the no-network-on-
default-launch-path policy.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-22 22:08:00 +02:00