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 first T-197 fix flipped the wrong lever: it called
activateTab(Slots.workspace, 'editor.active'), but _WorkspaceSlot
renders its editor split off arrangement.editorOpen — not the active
tab — so clicking a file still showed nothing. Call arrangement
.openEditor() on editor.opened / active-changed(non-null), and
closeEditor() on active-changed(null) so the split collapses when the
last buffer closes. Test now asserts arrangement.editorOpen, the lever
the UI actually reads.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pql sidebar panel and the find-in-files tab were duplicate search
surfaces. Consolidate into one Search tab with a mode switch: Find
(content grep), Vault (pql ranked search), Query (PQL DSL), Markdown
(the synced markdown-file listing, keeping focus-highlight + live
refresh). SearchPanelView holds both FindInFilesController and
PqlController; the pql body + result rows move into a reusable
PqlSearchBody. The standalone builtin.pql sidebar tab is removed (one
fewer tab — eases the rail); the pql extension keeps the Backlinks
context panel. No D-79 conflict — grep vs ranked search remain distinct
backends, this is UI consolidation.
Adds the pql builtin's first widget/controller tests (it was untested,
so folding it into the tested Search panel required covering the
Vault/Query/Markdown modes + PqlController).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rail was a fixed Row(center, max) — one button per tab — so adding
the Search tab pushed it 54px past its width and threw a RenderFlex
overflow. Center the icons when they fit and scroll horizontally when
they don't (LayoutBuilder + SingleChildScrollView + a minWidth floor),
so the rail stays correct at any tab count. (T-200)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the tickets detail in line with markdown/decisions (D-81). The
controller loads on 'load' (the channel the retained ReaderNav emits),
the extension reveals the static tickets.detail tab on selection instead
of the per-click uncontribute/contribute churn (the T-188 anti-pattern),
and the view grabs nav.current on mount and wraps in ClidePaneChrome
with a ReaderActionBar — pin toggle left, back/forward + jump-to-pin
right, no edit pencil (tickets are pql records, not files). The
controller drops its now-unused panels dependency.
Also adds the tickets builtin's first tests — the sidebar list
(load/sections/filter/select/empty/error/refresh) and the detail reader
(load, nav, pin, parents/decisions/status) — covering a pre-existing gap
exposed by bringing these files under test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per user feedback on the reader action bar: use the push-pin glyph (not
the chain/link), make the pin button toggle the pinned state (tap to pin
current, tap again to unpin) via ReaderNav.togglePin, and split the
layout so the pin/unpin toggle sits on the left while jump-to-pin joins
the navigator (back/forward) on the right — left toggles, right
navigates. The action button gains an active (accent) state for the
pinned indicator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Documents why the markdown/decisions readers load from a retained
per-reader nav-history (grab-current-on-mount + single 'load' path)
rather than per-view state (dies with the widget — the T-196 bug) or
MessageBus retention (wrong layer). The user chose the nav-history
helper over a bus fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The reader opened repo-local .claude markdown but rejected user-scope
files under ~/.claude with "path outside workspace" — that dir is
global, outside the repo, and files.read was repo-confined (T-102).
Per D-76 the Claude config surface is clide-managed, so files.read now
resolves a path under an allow-list: the workspace root plus trusted
extra read roots (FilesService.extraReadRoots), wired in main.dart to
~/.claude when present. Reads widen; writes stay repo-confined, and the
symlink re-check still refuses a config-root symlink that escapes. Off-
root paths and `..` traversal are rejected as before.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolveUnderRoot joined an absolute input onto the workspace root
(/repo + /repo/x → /repo/repo/x), so files.read 404'd on a file that
exists. The Claude Config tab hands the reader a skill's absolute
SKILL.md path, which hit this. Normalize an absolute input as-is; the
existing containment check still rejects absolute paths outside the
root, so the T-102 boundary is preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>