The pre-push coverage gate was failing at 94.84% — below the 95% floor
even before the T-511 feature landed (the feature files sit at 97%; the
tree without them was at 94.80%). The bulk of the debt was the pql
backlinks panel: controller and view carried ~100 lines with zero
tests. Covers controller fetch/error/event-refresh/dispose and view
empty/error/group/row-navigation states, plus the T-511 stragglers a
formatter-conflicted edit dropped (presetLookupRoot) and the
env.path production default seams. Gate now passes at 95.26%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 23dd7fd1 behavior shipped without a test delta; the builtin-roster
lock test and the scaffold assertion now cover it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three holes from the T-511 adversarial review pass:
- An entry containing the PATH separator smuggled extra tokens into
the joined PATH — a stray trailing ':' yields an EMPTY token, which
POSIX shells resolve as CWD (the dot-in-PATH hazard). The CLI verb
and the settings control now reject such entries, and applyPathPreset
skips malformed stored values that predate the check.
- The gitdir pointer a worktree resolution follows is repo-controlled
text; the resolved main root is now validated (must hold a real
.git directory) before its preset key is trusted, so a crafted
pointer can't alias an arbitrary path's preset.
- A pane spawned with a cwd below the workspace root hashed the
subdirectory and silently missed the workspace preset; the lookup
now keys any in-workspace cwd to the workspace root
(presetLookupRoot).
Also: the Add button pairs buttonBackground with its own
buttonHoverBackground token instead of borrowing the list-item hover
token, and the hosted-Claude leg gains an end-to-end orchestrator test
(preset lookup → spawn env).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The emitter wrote block-map keys raw while only values went through
quoting. A workspace-hash key segment that happens to be all digits
with a leading zero (or digits-e-digits) reloaded as an int/float —
leading zero dropped, or collapsed to Infinity — orphaning the stored
value. Hit ~1 in 1200 repos, deterministically and permanently: the
PATH preset (D-106) and the pre-existing Claude account binding for
such a repo vanished on every restart with no diagnostics. Same defect
class as the T-376 value-corruption fixes in this file; found by the
T-511 review pass, confirmed against the live store.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The UI half of D-106 (D-6 parity with `clide env path`): an ordered
preset editor — add/remove/reorder, a missing-dir warning, a worktree
note naming the shared main repo, and capture-from-login-shell
suggestions adopted with one click. Writes land on the same user-scope
key the CLI verbs use and publish on the same channel, so both
surfaces stay live off the store notifier.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implements D-106. The T-439 login-shell probe is a global heuristic
with a known hole — login-but-non-interactive shells skip ~/.bashrc,
so interactive-only additions (brew shellenv) never reach the agent's
Bash tool or terminal panes on a desktop launch. The preset is the
explicit per-repo layer on top: user-scope storage keyed by repo
identity (a linked worktree resolves through its gitdir pointer to the
main repo, so worktrees share the preset), prepended at spawn via the
PaneRegistry pathForSpawn hook and agentEnvDelta prependDirs — which
now exports PATH even when clide is already resolvable, closing the
gap where the hosted session inherited the sparse GUI PATH untouched.
CLI half: `clide env path list|set|add|remove|clear|capture` over an
injected Flutter-free store port; capture diffs the login-shell PATH
against the process PATH to suggest the dirs a desktop launch dropped.
Binary resolution (toolchain, supporter pins, bundled pql/git) stays
preset-blind per the D-92/T-98 fence.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Restores the original intent that T-226's refinement wording lost: the
shift modifier is the explicit opt-in for bypassPermissions, not a
separate confirm flow. Plain Ctrl/Cmd+M keeps cycling the safe trio;
Ctrl/Cmd+Shift+M cycles the full list. The composer menu's bypass row
was permanently disabled, deferring to "the cockpit's confirmed path" —
but that roster is ghost-fed (T-396), so bypass was unreachable from
the primary session's UI entirely. The row now no-ops on a plain click
(menu stays open) and selects on shift-click, with a hint naming the
gesture. Roster badge and /permissions paths unchanged.
lib/test_app.dart is a format-only follow-up to the previous commit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Makes the canvas foundation (parser/renderer/view) reachable. The
extension owns an app-scoped MultitabController (the diff/T-233
pattern) so open documents survive the pane being rebuilt; each
document is a real sub-tab per the refinement decision, kept alive
across switches. Routing goes through the existing seams instead of
the dead TabContribution.fileGlobs field: openWorkspaceFile gains a
.canvas branch mirroring .md, and ui.open gains a canvas reader for
D-6 parity (clide ui open canvas <path>). Also corrects the
json_canvas doc header that claimed SVG-lowering — the interactive
pane paints the model directly (D-103 live-widget exception).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds hitTestCanvasNode (topmost node under a point, cards over the group
frames behind them, via the same viewport the painter draws with) and the
CanvasView widget: scroll-zoom, drag-pan, and click-to-select with a focus
ring. Node drag/resize and edit affordances follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Paints a CanvasDoc — group frames behind, edges with arrowheads, then
node cards (text / file / link) — fitted by a shared pan/zoom CanvasViewport
so a later hit-test lands on what's drawn. Node colours come from the file
(Obsidian presets 1..6 or hex), not clide tokens, since a .canvas is
arbitrary content; only the chrome is themed. Read-only for now; selection,
drag, and edit affordances follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parses the Obsidian .canvas format into a typed CanvasDoc — text / file /
link / group nodes and edges (sides, end caps, colour, label) — and
serialises back with round-trip fidelity. Unknown node types and entries
missing required fields are skipped rather than fatal; end caps omit their
spec defaults on write. Pure Dart, no I/O; runs under dart test. The
foundation for the interactive canvas pane.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Switches the controller from pql.outlinks to pql.meta (outlinks + tags in
one call per file) and strips #heading fragments from link targets, so a
heading link now connects the two notes. Adds a filter bar above the graph:
a path glob that re-queries pql on submit, a depth-from-active selector for
the local graph around the open note, and tri-state tag pills
(neutral / include / exclude). The pane draws the filtered visibleGraph.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the pure filtering primitives the graph pane composes: VaultGraph
nodesWithin (depth-bounded BFS over undirected edges = the local graph
around a note) and subgraph (retain a node set + the edges between them),
plus GraphFilter, which combines depth-from-active with tag include/exclude
over a caller-supplied tag map. Flutter-free; runs under dart test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Folds a user zoom (scroll wheel, clamped) and pan (drag) transform into
GraphViewport so the painter and hit-testing move in lockstep — hover and
click keep landing on what's drawn. Zoom scales about the canvas centre; a
fresh graph re-fits and drops the transform.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires GraphPanel into GraphExtension as the graph.view context-panel tab
(its icon-rail entry and tab id were already scaffolded) and ships the
builtin.graph i18n catalogs. The graph is now reachable: notes as nodes,
wikilinks as edges, hover-highlight and click-to-open. It activates after
its pql dependency, which supplies the link data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wraps GraphController + GraphView into the panel widget: loads the vault
graph on mount, shows a spinner / empty / error state until it is ready,
then draws the graph and opens a note on node click via editor.open. A
debounced refresh keeps the current graph on screen instead of flashing
back to the spinner.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lists every markdown note (nodes) and fetches each one's outlinks
(edges) via the pql.files / pql.outlinks IPC commands, assembling the
file->outlinks map that VaultGraph consumes. Coalesces file-change
bursts into one debounced reload. 1+N calls, since pql exposes no
bulk-outlinks query; acceptable for a spinner-backed, explicitly-opened
view.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GraphView lays a VaultGraph out with the force solver, paints it, and wires
hover (light the hovered node's neighbourhood, dim the rest) + click (onOpen
with the node's vault path). A shared GraphViewport keeps hit-testing aligned
with paint. Solver + model + painter + interactive view now stand; the pql
link-data wiring, filter, and MultitabPane/slot registration are the
remaining "integrate into the app" work.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GraphPainter draws a solver-positioned VaultGraph — edges as lines, nodes as
labelled dots — fitting the layout space into the canvas (aspect-preserving,
centered), with a hovered node's neighbourhood lit and the rest dimmed.
Paints through SurfaceTokens (the graph is IDE chrome here, D-7).
Pixel-tested. Solver + model + painter now stand; the interactive pane (pql
wiring, hover/click, filter, tab/slot) is next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VaultGraph.fromOutlinks builds the graph from pql's per-file outlinks: every
file a node (labelled by basename), each outlink to a known file an edge;
self/dangling links dropped, parallels de-duped. neighborhood() drives the
hover-highlight; edgePairs feeds the ForceLayout solver. Pure Dart,
dart-tested (6 cases). The graph pane's data layer, on top of the solver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The three worst-covered files were genuinely untested, not edge cases:
os.dart 27%→~85% (inject the process runner so openURL/reveal don't spawn a
real browser), clide_code_block 39%→~90% (expose the byte→char span mapper
as a top-level fn + render tests), deeplink 29%→~75% (the confirm-opens and
not-activated paths). Buys buffer above the 95% floor so a feature batch
doesn't immediately trip the gate. 95.01% → 95.22%.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The drawing-card feature batch (icon/image/compare/graph/d2 cards, --stdin,
tool resolution) added widget + wiring code that dipped total coverage under
the 95% floor — surfaced by `make release` (push-check skips coverage). Cover
the gaps: ProblemsController.refresh, every SVG shape-type bbox + the style
vocabulary, _spawnD2 via a real /bin/cat, the icon-show bus path + error
branches, and quad/arc/close marker paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
draw is single-token (no verb), so `clide draw --file x` mis-parsed as cmd
"draw.--file" — the dispatcher registered draw but the argv parser never
learned it was umbrella (unlike icon/image which use subsystem.verb). Found
driving the live install; the handler unit tests bypassed the argv path so
they missed it. Added draw to _umbrellaCommands + a parse regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fruchterman-Reingold (clide-owned, no layout package): nodes repel, edges
attract, cooled over iterations into a readable layout. Deterministic — a
fixed circular seed, no RNG — so the graph view is stable across rebuilds
and the solver is unit-tested (6 cases). Flutter-free; the interactive graph
pane (render, pan/zoom, hover, filter, pql wiring) builds on this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
graphTemplateHandler lowers a {nodes,edges} payload to SVG: a deterministic
circular layout of labelled <circle> nodes with <line> edges, self-contained
(own light backdrop + content colors, like a d2 card) and painted by the
shared renderer. Display-only (D-78); decoupled from the interactive graph
pane (T-323) since a static card only needs a layout->SVG. Honest error on
a duplicate id or an edge to an unknown node.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The C client slurps stdin when it sees --stdin, strips the flag, and ships
the payload alongside the argv; the Dart unwrap folds it into the request
as a `stdin` arg (undeclared keys pass the schema untouched). icon.show and
image.show now read that payload as the peer of --file (stdin wins). Bounded
slurp + the envelope's existing size guard keep a huge payload from
corrupting the wire. C client builds clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The drawing card now loads any <image> hrefs its SVG references into
ui.Images before painting (loadDrawingImages, decoder injectable for
tests) — the renderer and the lightbox both paint through the resolver, so
a compare card shows real images and zooms. main registers the `compare`
template with a workspace path resolver. A drawing with no images is
unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
compareTemplateHandler lowers an `images` array to an SVG of side-by-side
<image> cells, each with the per-object data-label/description caption and
data-lightbox; paths resolve to absolute up front (injected, honest error
on a miss). _paintImage now aspect-fits (contain, centered) so cells of
differing-shape images don't distort. Flutter-free handler + painter pixel
tests. Card image-loading + registration next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The icon card renders each glyph at a hero size plus a continuous sample
strip (10–48px) so legibility is judged at the sizes the app uses, with the
entry's optional label + description and a per-entry or card-level color.
Wired end to end: icon.show publishes on the `icon` bus, the Claude
extension injects an IconMessage, and conversation_view paints the card
(display-only, D-78). en+nl catalogs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
icon.show resolves Phosphor glyphs by name (injected resolver) or a 0xNNNN
codepoint, reads a --file JSON array of {icon,label,description,color}
entries, validates colors via parseSvgColor (hex or CSS name), and
publishes on the `icon` bus channel. Honest userError on an unknown glyph,
a bad color, or a malformed payload. A trailing stringList positional is
now variadic so `icon show gear folder gauge` collects every token.
Flutter-free, dart-tested. Card widget + wiring next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`clide image show --file meta.json` reads a {path,label,description,caption}
payload so an image can carry a title and a longer description, not just a
one-line caption. ImageMessage + the image card render the richer metadata;
the bare `image show <path> [--caption]` form is unchanged. Honest userError
on a malformed/missing payload. Text annotation only (option a) — visual
marker overlays stay a follow-up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rendered diagram leads; the d2 source carries through the draw bus and
folds into a collapsed ClideCollapserCard beneath the card (D-78 display-
only). en+nl catalogs. Also switches the d2 test to a null-aware element.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The d2 drawing template compiles a diagram's source to SVG through the d2
binary (resolved via the D-104 path layer), then paints it with the same
renderer the svg card uses. `clide draw --file x.d2` infers the type from
the extension; `.svg` files render directly. Template handlers now return
a DrawResult so a compile failure or an unresolved d2 surface as an honest
userError with an install hint, not a generic "no SVG". Real d2 0.7.1
verified end to end.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ToolsSettingsExtension adds a Tools settings category — a path field per
tool (app.tools.<name>) plus a Re-detect action (tools.detect) — and keeps
the live resolver in sync as paths are edited (supporterBinariesFrom). en
+ nl catalogs. Completes the D-104 UI surface.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Store each tool's override under its own key (app.tools.<name>) instead of
one map, so the settings panel can bind a plain text field per tool.
loadSupporterBinaries reads the per-tool keys plus a first-run marker and
keeps an explicit path; redetectSupporterBinaries backs the Re-detect
action. The resolver itself is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Problems panel flags a configured tool path (app.tools) that no longer
points at a file — a real misconfig, echoing D-58's soft floor. A merely-
unfound optional tool isn't flagged (its use-time userError covers that).
Extracted to a pure supporterToolProblems(), flutter-tested (3 cases).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
loadSupporterBinaries reads the persisted tool→path override map; on first
run (key absent) it probes the known tools once and persists the result —
pinned thereafter, not re-detected each launch (D-104). Injected read/write
so it stays Flutter-free and dart-test covered (3 new cases). Boot wiring
next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SupporterBinaries resolves claude/d2/etc. to absolute paths (D-104): an
explicit override map first, then the login-shell/process PATH, then the
well-known bin dirs incl. Homebrew-on-Linux — the dir the standard PATH
expansion omits and a .bashrc-only brew shellenv misses (the D-104 gap).
detect() probes those to seed the override map (pinned, not re-probed each
launch); isStalePin flags a moved tool. Pure Dart, dart test (11 cases).
Settings storage + first-run detect wire on next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A data-lightbox element gets a tap target (click cursor) over its region;
DrawingCard exposes an onLightbox callback, which the conversation wires to
open the whole drawing in a ClideLightbox (zoomable). Completes the
per-object overlay — captions + lightbox — and T-318's defined scope.
Widget-tested (the tap fires the callback).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DrawingCard stacks a caption layer over the SVG: each data-label /
data-description annotation renders a themed caption just below its
element, mapped to pixel space via a shared svgViewportFit (refactored out
of the painter so the overlay and the paint use identical viewBox-fit
math). Display-only (IgnorePointer). Widget-tested. data-lightbox is
captured in the model; the tap-to-zoom interaction is the last follow-on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The builder captures data-label / data-description / data-lightbox on SVG
elements into SvgDocument.annotations, each with the element's AABB in
viewBox coordinates (accumulated transform applied — rect/ellipse/image/
line/poly/path bounded; groups skipped, text degenerates to its anchor
point). Feeds the Flutter caption/lightbox overlay next. Flutter-free,
covered by dart test (6 new cases).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
draw_commands.dart re-exports DrawingRegistry, so the direct import is
unnecessary; cleared the analyzer warning.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The last wire: a DrawingMessage conversation item + the Claude-extension
subscriber on the `draw` channel (buildSvgDocument → inject), the
conversation renderer (DrawingCard), the fold/summary switches, the i18n
`drawing` label, and registering `clide draw` at boot (empty template
registry for now — primitive SVG works; d2/icon/compare/image handlers
plug in as they land). `clide draw --file doc.json` now renders a card in
the conversation. Render widget-tested; analyze + format clean across the
repo. Templates + per-object overlay remain; the CHANGELOG entry lands
with the user-complete feature.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirrors image.show: a Flutter-free `draw` handler reads + JSON-parses the
document (injected reader), lowers it to SVG via the template registry,
and publishes {svg, label, description} on the `draw` MessageBus channel
for the Claude extension to inject. Honest errors — notFound (missing
file), userError (bad JSON / unknown template / no --file), toolError (no
live UI). Covered by dart test (7 cases). The extension subscription that
injects the card remains to wire.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Display-only card (D-78): renders an SvgDocument in a framed, aspect-
ratio-sized region with an optional clide-themed label/description caption
beneath. The SVG is content (its own palette); the frame + captions are
chrome (SurfaceTokens). Widget-tested via the kernel fixture. Per-object
data-* overlay and lightbox remain follow-ons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolveDrawingSvg lowers a DrawingCardDoc to an SVG string: primitive docs
use inline svg or read svgPath (via an injected reader); template docs use
a registered DrawingRegistry handler, so the d2/icon/compare/image children
plug in. Honest DrawErr on no source / unknown template / unreadable path /
empty output. Flutter-free, covered by dart test (7 cases). Not yet wired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DrawingCardDoc + parseDrawingCardDoc read the `clide draw` JSON: card
label/description, primitive svg/svgPath, or a template name plus its
fields (for a handler to lower to SVG). Tolerant — a non-object payload
is null, blank strings are absent, never throws. Flutter-free, covered by
dart test (10 cases). First slice of the drawing-card core (D-103); not
yet wired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
paintSvg/SvgScenePainter/SvgView take an optional SvgImageResolver
(href → decoded ui.Image); image nodes draw into their dest rect. The
caller owns loading (file/asset/network), so the painter stays pure
rendering. Threaded marker + resolver through a small paint context.
Pixel-probe tested (image draws via the resolver; nothing without one).
The clide-owned SVG renderer is now feature-complete for T-320.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Collect <marker> defs (refX/refY, orient=auto, markerUnits) and the path
marker-start/mid/end refs in the builder, then paint them at the path
endpoints rotated to the path tangent (orient=auto). The viewBox→viewport
scale is approximated 1:1 (holds for d2's markers). d2 edges now render
with arrowheads. Covered by dart test (builder) + a flutter-test pixel
probe (the green arrowhead draws). image painting remains the last T-320
follow-on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
paintSvg walks the typed SvgDocument and draws it: shapes → ui.Path
(rect/rrect/ellipse/line/poly/path incl. arcs), text → TextPainter
(anchor + baseline), per-node Affine transforms, group opacity via
saveLayer, and viewBox fit (xMidYMid meet). Exposes SvgScenePainter and
the SvgView widget. image-href (async) and markers/arrowheads are deferred
follow-ons. Pixel-probe tested under flutter test (8 cases, incl.
rendering the real d2 fixture). Completes the clide-owned SVG renderer
core (D-103); not yet wired into a card.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>