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>
Decides FR-1: a machine-local, user-scope list of dirs prepended to the
PATH of every spawned shell, keyed by repo identity so linked worktrees
(.worktrees/) share their main repo's preset. Prepend-over-login-shell
semantics, D-6 CLI parity (env path verbs), and a security fence: the
preset never feeds clide's own binary resolution (T-98 lesson).
Also corrects D-104's cross-references: the bundled-pql decision is
D-92, not D-58 (D-58 is format engines) — the anchor was dead.
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>
builtin.canvas graduated from a Tier-0 stub to a real extension with an
activate/deactivate lifecycle, so the real-boot harness should exercise
it like its siblings. 26/26 pass.
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>
Batch selection via /whats-next confirmed finishing T-322 as the next
work. Refinement pass resolved the open integration questions:
workspace slot + real MultitabPane tabs (user-confirmed), file-open
routing via the openWorkspaceFile() .md-branch mirror plus a ui_command
_readers entry for D-6 CLI parity (fileGlobs confirmed dead — not
wired), and a stale SVG-lowering doc header in json_canvas.dart to fix
alongside the pane-shell work.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The .canvas rendering + interaction foundation is built and tested (parser,
CustomPaint renderer, interactive CanvasView — 21 tests). Records the
remaining reachable-integration phase: pane shell, CanvasExtension
registration, and .canvas file-open routing (TabContribution.fileGlobs is
declared but unconsumed, so routing must be built).
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
Records that the model/effort trust problem spans more than the
local-command-stdout render defect: control state reflection, point-of-
interaction acknowledgement, and active-value drift are all in scope.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T-509 (child of the UI/UX epic T-276): investigate + improve the model
switch and effort toggle so a change is unmistakably confirmed. T-322
(canvas pane) moved to in_progress with its parser + renderer foundations
recorded.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T-509 (child of the UI/UX epic T-276): investigate + improve the model
switch and effort toggle so a change is unmistakably confirmed. T-322
(canvas pane) moved to in_progress with its parser + renderer foundations
recorded.
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>
The Tier-5 graph pane is fully delivered: force-directed layout, hover
highlight, click-to-open, pan/zoom, and path-glob / tag / depth filters.
Real-boot verified via testmode; 56 graph tests green.
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>
Adds PqlExtension + GraphExtension to the extensions-category boot test so
the graph view's dependency-ordered activation and real i18n-catalog load
are validated in a full app boot, not just the widget fixture.
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>
The 2026-07-02 skill review found the repo bundling skills that don't
belong to it: d2-diagram and frame0-wireframe are repo-agnostic and
carried settled-reach game content verbatim (agent personas, palette,
templates) presented as clide conventions; skill-create was a
byte-identical copy of a generic skill; clean-house was double-installed
(the pql-managed user-scope copy is canonical); the generic commit skill
collided with git-commit on its trigger word while contradicting three
repo safety rules; penpot-login was fully broken and published personal
infrastructure in a public repo.
d2-diagram, frame0-wireframe, and skill-create now live de-contaminated
at user scope (~/.claude/skills/). The commit/penpot-login/clean-house/
skill-create deletions landed with the previous commit (pre-staged by
git rm); this one removes the centralized pair and rewrites the README
index to the five clide-specific skills that remain.
Co-Authored-By: Claude <noreply@anthropic.com>
The 2026-07-02 skill review (adversarially verified) found the bundled
skills teaching pre-refactor reality: whats-next pointed refinement
agents at the pre-governance/ decision paths, claimed gh doesn't exist,
and taught the hand-staged .pql/changelog flow the pre-commit hook
replaced; git-commit mandated the heredoc-in-substitution commit form
the repo moved away from and referenced sidecar-era paths; the clide
skill omitted the dock slot (D-87); testmode omitted the terminal
category; ui-design's glyph table taught the static-const icon API
removed in T-314, and its generator scanned for a pattern that no
longer matches anything.
All fixed against current repo state. The glyph table is regenerated
3-column (name-keyed, byName-first); tool/gen_phosphor_glyphs.dart
round-trips it byte-identically. git-commit also gains the
session-vs-pre-existing triage step (backported from the settled-reach
variant) since this worktree hosts concurrent Claude sessions.
Co-Authored-By: Claude <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>
main resolves the override map (and first-run auto-detects, using the
primed login-shell PATH) right after bootSettings.load(), exposing the
process-wide activeSupporterBinaries that tool consumers read — the d2
template (T-494) will resolve its binary through it. Analyze clean.
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>
Distills CLAUDE.md guardrails and .claude/skills/ into Vibe's
instruction hierarchy, preserving Claude Code punch.
Includes full pql skill integration for vault queries and
project planning workflow.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
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>
The builder ties the five parsers together: parseXml → inlineStyles →
a typed SvgNode tree (group/rect/ellipse/line/poly/path/text/image) with
viewBox, per-node Affine transforms, and inheritance-flattened SvgStyle
(fill/stroke/font resolved to ARGB; opacity per-node). Tolerant — a
non-svg root yields an empty doc; defs/marker deferred. An end-to-end test
runs the whole pipeline against a real d2-rendered SVG fixture. Flutter-
free, covered by dart test (17 cases). No user-visible behaviour yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parseSvgColor → packed ARGB (#rgb/#rgba/#rrggbb/#rrggbbaa, rgb()/rgba()
integer or %, common names, none/transparent → 0x0); null on unrecognised
so the caller can inherit. parseTransform → a composed 2-D Affine
(translate/scale/rotate[/about-point]/matrix/skew), applied left-to-right.
Both tolerant and Flutter-free, covered by dart test (22 cases). These feed
the typed node model next. No user-visible behaviour yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zero-dependency, tolerant XML reader (elements/attrs/text/comments/prolog/
CDATA/entities; <style> read as raw text) producing a generic element tree.
The normalizer folds d2/graphviz's class-based <style> rules into inline
presentation attributes — cascade presentation-attr < tag < class < style —
then drops <style>/class/style, so the painter only ever sees inline attrs
(D-103). Flutter-free, covered by dart test (26 cases). No user-visible
behaviour yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parse the `d` attribute into an absolute-coordinate SvgPathSeg list,
lowering SVG shorthands to a small command set the painter can replay:
relative→absolute, H/V→lineTo, S→cubic and T→quad with control
reflection, arcs kept as arcTo. Tolerant — malformed tail returns the
understood prefix and never throws, so a bad diagram can't crash the
conversation. Flutter-free, covered by dart test. First slice of the SVG
engine (D-103); no user-visible behaviour yet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pin the bounded SVG subset from a real d2 sample + our templates (markers
for arrowheads in; mask deferred to v1-ignore; foreignObject/filters/
gradients out, so mermaid isn't a launch target). Class styling resolves
via an inline-normalize preprocess (flatten <style> classes to inline
attrs) so the painter stays a pure attribute renderer. Tool-PATH for the
d2 binary now resolves via D-104.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pre-commit changelog export left ticket_idmap unstaged; commit it so
the T-495 label↔record_id mapping survives a rebuild.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
clide resolves external supporter binaries (claude, d2, future tools)
via an explicit user-scope tools: map (name → absolute path), consulted
first. First run auto-detects each tool once and writes the discovered
absolute paths into the map — a one-time populate, pinned thereafter, not
a per-launch heuristic. Escapes the login-shell-probe brittleness (T-439
misses brew when shellenv lives only in ~/.bashrc) while staying zero-
config for standard installs. User-scope only (D-93); generalizes D-58's
CLIDE_PQL_BIN override. Filed T-495; gates the d2 template (T-494).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pre-commit changelog export didn't re-stage the dependency edits from
this turn (blocker flip T-320↔T-318, template blockers, T-494). Stage
them explicitly so the re-sequencing persists.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seven Frame0 wireframes for the conversation drawing-card family — image
annotation, icon glyph, svg, d2, compare, graph, and the core dispatch
shell — plus the draft JSON schema refined from them. Schema follows the
SVG-substrate model (D-103): document envelope, bounded SVG subset, a
data-* Flutter overlay for captions/lightbox, templates lower to SVG.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The unified drawing card's primitive layer is SVG, not a bespoke
{type:"rect"} JSON vocabulary; the clide-owned CustomPaint SVG renderer
(T-320) is the engine the rest builds on. Templates lower to SVG; a thin
Flutter overlay carries label/description captions + lightbox, anchored
via data-* attributes; the graph template stays a live widget. Inverts
the former T-320 → T-318 dependency. Raised by the user: "if we are close
to svg, are we not better off extending svg instead?"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D-59 requires tracking dugite-native (bundled git) releases for security
updates, but it lived only in the D-record prose with no mechanism (clean-house
flagged RULE-SUNSET-WITHOUT-TICKET). Add ci/check_dugite_version.sh + a
`make dugite-check` target: compares the Makefile DUGITE_VERSION pin against the
latest desktop/dugite-native release and loudly flags CVE / security mentions in
the notes. Informational (not a push gate) — the bump itself is manual per D-63
and automated by T-25.
The script header is the maintenance home (dugite is fetched, not built, and
native/dugite/ is gitignored, so there's no BUILD.md to record it): cadence
(quarterly / on a git CVE), advisory subscriptions, and the bump procedure.
D-59's cost line + a Makefile comment now point at the mechanism. T-88 stays
open as the recurring calendar; first check (2026-06-28) shows v2.53.0-3 current.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tool display names are open-ended (Bash, Grep, Glob, ScheduleWakeup, MCP tools,
…) — they intentionally have no catalog key and fall back to the raw name, so a
miss isn't a gap to fix. The conversation pane was logging an i18n "missing key"
warning for each, cluttering the output dock.
Adds a `warnIfMissing` flag to I18nService.string (default true, so fixed UI
strings still warn on a real gap) threaded through the ClideSettings facade;
_toolNameLabel passes false. Display is unchanged — the placeholder already
rendered the raw name.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The injectable-fetch tests skip the real HTTP path; exercise githubGet's
200-body and non-200-throws branches against a localhost HttpServer so no test
hits the network and the gate stays green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Help → About gains a "Check for updates" button that fetches the latest GitHub
release, semver-compares it to clideVersion, and shows the result inline:
up-to-date, available (with a tappable link to the release notes), or a clear
error. clide's first and only outbound HTTP call — a plain GET with no user
data, run ONLY on this explicit tap, never on a launch path or a timer. So it's
D-64-clean with no amendment; a background/periodic poll stays deferred (would
need the narrow opt-in amendment first).
The fetch is injectable so no test touches the network. compareSemver handles
2.3.10 > 2.3.9 and ranks pre-releases below their release. Closes T-492 (P1);
the release-channel CI for downloadable signed packages is T-491, and download/
apply (P2/P3) depend on it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
D-50 behavior 4: opening a renderable .md in the editor now auto-reveals the
context-panel reader, which mirrors the buffer read-only and re-renders as the
user types — rather than a one-shot disk read.
The extension reveals the tab on editor.opened (renderable only — D-50 behavior
5 leaves non-.md files alone). The viewer owns the mirror: on mount it picks up
the active buffer (editor.read, no id), enters mirror mode on editor.opened /
active-changed for a renderable file, re-reads the in-memory buffer on
editor.edited, and drops the mirror (with its edit affordance back) on a disk
load or a switch to a non-renderable buffer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A fresh Claude session now gets a one-line preamble (clideSkillsNote) telling it
to reach for the bundled pql + clide skills from its first turn rather than
rediscovering the workflows. Layered onto clideContextNote in the single
--append-system-prompt.
Gated on a NEW session (!spec.resume && !spec.isFork): new tabs and the
post-/clear respawn spawn with resume:false and get it; the account-change
respawn (T-480) and real resumes carry prior context (resume:true), and forks
inherit their source — none are re-nagged. Both skills already ship (pql
user-scope, clide repo-scope), so no precursor was needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Closes the new-project story (T-486). The dead-end "not a git repo" dialog now
offers to initialize the folder: project.init runs git init + a non-clobbering
scaffold, then opens + announces on projectCreatedChannel so the account
roadblock fires — the same path a brand-new project takes. Adds initExistingProject
+ the `clide project init [--dir]` verb (default: the current workspace).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The UI half of the new-project flow (story T-486). A "New project…" welcome
action opens a dialog (location + name) that dispatches project.new (T-487),
opens the result, and announces it on projectCreatedChannel. The Claude
extension consumes that and shows the account roadblock — the embedded
per-workspace picker + accounts list, so a fresh project gets bound to an
account (or Default) right at birth.
The two halves stay decoupled: the welcome builtin only publishes the event
(no claude import); the claude builtin owns the account dialog. Only freshly-
created projects announce, so existing opens are never prompted.
Closes T-488. The non-repo "initialize as a project" path (T-489) is next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The backend half of the new-project flow (story T-486). createNewProject
validates the name, makes <parent>/<name>/, runs git init (injected from the
toolchain in main.dart so the handler stays Flutter-free), and writes a minimal
scaffold (.gitignore + a CLAUDE.md stub). The project.new verb wraps it; --dir
defaults to the current workspace's parent so a new project lands beside it.
Create-only by design — opening the new workspace and the account roadblock are
the UI flow's job (T-488). Closes T-487.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
clide treats a git repo as the workspace, so creating a new project starts with
`git init`. Add GitClient.init — `git init -b <main>` in workDir, deterministic
default branch, idempotent on an existing repo. The first primitive of the
new-project flow (T-486); the create-dir + scaffold service and the
`clide project new` verb build on it next.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the design pivot: the account choice is only a real decision at project
birth, so it belongs as a roadblock in the new-project / init-in-a-new-dir flow
(evolving the dead-end _NotARepoDialog), not a welcome-screen list. Opening an
existing project never prompts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two more real branches the account feature left uncovered: removing an
unregistered account (the no-such-account error) and the registry list
rebuilding when a CLI-side registration notifies the shared settings store.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the missing-line coverage the epic's new code left uncovered: Account /
DetectedAccount value equality, the set/unset no-workspace error branches, the
login dialog's escape-to-close, and the settings controls' live-update /
no-workspace / duplicate-add / re-login paths.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Account section (T-482) adds custom-kind controls to the Claude settings
category, so the T-457 test can no longer assert that *every* field in the
category is a select. Scope the kind/options check to the new-session-default
fields (model/effort/permission) it was actually about.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the `login` verb + the UI add/re-login affordances. The
accountActionChannel 'login' action opens ClaudeLoginDialog — a modal hosting a
TerminalPane that runs `CLAUDE_CONFIG_DIR=<dir> claude login`, so the CLI drives
the OAuth browser flow and credentials land in that account's config dir (D-64:
one CLI-initiated browser flow, on explicit action).
TerminalPane gains optional argv/env/cwdOverride (default stays the login
shell), and its pane.spawn carries env when set. The login pane reuses the
TerminalPane *widget* — a code import, not a runtime dependency, since it spawns
through the always-present pane.spawn IPC rather than the terminal extension.
Tests cover the parameterized spawn args and the dialog's host wiring (title,
CLAUDE_CONFIG_DIR, close). Closes T-485.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The always-visible affordance for the multi-account epic: a compact badge in
the pane header showing which account this workspace is bound to (or "default"),
colour-tinted per account so two windows are distinguishable at a glance. Tapping
opens a picker of the registered accounts + Default; selecting binds/unbinds via
the shared bindWorkspaceAccount helper (respawn + lock-sync follow on the bus).
Hidden when no accounts are registered, so it adds no chrome for users not using
the feature.
accountAccent derives the tint by hashing the name into a fixed set of theme
tokens — never an arbitrary colour, so the palette stays theme-owned.
The welcome-view accounts section (the other half of T-481) is split to T-486:
the welcome builtin is intentionally decoupled from feature builtins, so it needs
a welcome-section contribution point rather than importing claude directly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the account settings surface with the global Accounts list under the
Claude category: each registered account shows a sign-in dot, name, and config
dir, with re-login and remove affordances plus an inline add-account field.
- Sign-in status: accountIsSignedIn, a read-only probe (a .credentials.json, or
an oauthAccount marker in .claude.json; under-reports on macOS keychain).
- Add: registers ~/.claude-<name> and publishes a login action (T-485 opens the
pane).
- Remove: registry-remove, guarded while a workspace is bound — matching the CLI
`account remove`. --purge dir deletion stays on the CLI flag.
All management routes through the AccountRegistry + accountActionChannel, and
the list rebuilds live off the settings notifier. en/nl strings + widget/probe
tests added. Closes T-482.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A custom settings control under the Claude category: a dropdown of the
registered accounts plus Default. Picking one binds (or unbinds) this
workspace via the AccountRegistry and publishes set/unset on
accountActionChannel — the same channel the CLI verbs use, so the session
respawns onto the account (T-480) and the IDE lock re-syncs (T-479). It reads
live off the shared settings notifier, so a CLI `account set` updates the
dropdown too. Empty states cover no-workspace and no-accounts, each pointing at
the CLI.
Registry writes set the in-memory binding synchronously then flush, so the
control publishes the bus event before the disk write completes.
Part 2 (the global Accounts registry CRUD list + sign-in probe) is still open
on T-482. en/nl strings added; widget test covers the states + bind/unbind.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A `claude` started with CLAUDE_CONFIG_DIR=<dir> looks for clide's /ide
discovery lock under <dir>/ide, not ~/.claude/ide — so today a per-repo
account session (T-476) can't reach clide's IDE bridge.
McpServer now reconciles a SET of lock paths instead of one: the default
~/.claude/ide always, plus the bound account's <dir>/ide when this workspace
is bound (boundConfigDir, injected from main.dart via the AccountRegistry).
syncDiscoveryLocks() writes/removes locks to match the active set; it runs on
start, once post-boot when the registry becomes resolvable, and on every
accountActionChannel event, so binding/unbinding adds/removes the account lock
at runtime. stop() sweeps every path written — no orphans. Lock content +
0600 are identical across dirs.
Acceptance 1-4 and 6 are covered by mcp_server_test (multi-dir write, no-binding
regression, dynamic add/remove, full cleanup); #5 (a live claude resolving the
bridge end-to-end) is manual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The extension consumer for the per-repo account verbs, making set/unset
fully functional. The Claude extension subscribes to accountActionChannel:
- set / unset → ClaudeSessionOrchestrator.respawnForWorkspace(cwd): closes the
workspace's solo sessions (awaiting real process death, T-437) and re-spawns
each on the same id with --resume, so the conversation continues under the
newly-bound CLAUDE_CONFIG_DIR (resolved at spawn by agentBootstrap). Team and
forked sessions are skipped — re-joining the broker / re-forking on an account
swap is out of scope; they adopt the account on their next natural spawn.
- remove --purge → deletes the config dir behind isPurgeableAccountDir, a strict
guard that only ever removes a ~/.claude-* directory that is a direct child of
$HOME. The purge payload now carries the dir (the account is gone from the
registry by publish time).
login still only publishes its action — spawning the `claude login` terminal
pane needs argv+env terminal-pane support and is split to T-485.
Covered: respawnForWorkspace (respawn solo, skip fork/other-repo) and the purge
guard's accept/reject matrix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The CLI half of the multi-account feature (epic T-476; D-6 parity). A new
`claude.account` dispatcher command multiplexes the six sub-verbs over an
injected, Flutter-free AccountStore port (runs under `dart test`):
- add <name> [--dir] register (default ~/.claude-<name>); idempotent, clear
conflict error
- list {accounts, boundAccount (this workspace), detected}
- set <name> bind this workspace (persists)
- unset clear this workspace's binding
- remove <name> [--purge] registry-remove; refuses while any workspace is
bound
- login <name> (publishes the login action)
Registry reads/writes go through the user-scope SettingsStore; side-effects
that only the UI layer can do — respawn on set/unset, the `claude login`
terminal pane, and the --purge rm — are published on accountActionChannel for
the Claude extension to consume (that consumer is T-480 part 2). main.dart
adapts the real AccountRegistry to the port and registers the command alongside
image.show / status.
Adds SettingsStore.keysAt (binding enumeration) and AccountRegistry.boundName /
boundAccountNames. No changelog yet — set/unset don't auto-respawn until part 2,
so the feature isn't user-complete. Verb behaviour + payloads + the in-use
guard are unit-tested against a fake store.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The load-bearing piece of the multi-account epic (T-476): a bound workspace's
hosted claude now spawns with CLAUDE_CONFIG_DIR set to that account's dir, so
it runs under the bound account end-to-end. Every hosted session (primary /
secondary / fork / teammate) inherits it — the orchestrator already routes all
spawns through agentBootstrap.
- New pure resolver claudeConfigDirForWorkspace(cwd, boundConfigDir, env):
bound account dir > parent CLAUDE_CONFIG_DIR (respect the launcher) > null
(Claude defaults to ~/.claude). The registry is injected as a plain lookup
so agent_bootstrap stays Flutter-free (its tests run under `dart test`).
- agentBootstrap merges CLAUDE_CONFIG_DIR BEFORE base, so an explicit
SpawnSpec.env override still wins (override > binding > parent > unset); the
key is omitted entirely when the resolver returns null.
- Orchestrator carries an optional AccountRegistry; the claude extension builds
it from ctx.settings. Null in tests → no injection (unchanged behaviour).
No way to SET a binding yet (that's the CLI T-480 / settings UI T-482), so no
changelog entry — the mechanism is in place, the surface lands next. Unit tests
cover the resolver's four states and the envDelta precedence.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Foundation for the multi-account epic (T-476): a Flutter-free AccountRegistry
over the app-scope SettingsStore (per-user, never committed to a repo).
- `app.claude.accounts` — the {name, dir} account list (dir = CLAUDE_CONFIG_DIR).
- `app.claude.account.<hash>` — per-workspace binding to an account name; the
hash is the SAME FNV-1a 64-bit hex D-70 uses for the socket path, trailing
separators stripped so /repo and /repo/ map alike.
- accountForWorkspace(cwd) resolves an account or null (a binding to a removed
account degrades to null = Claude's default, never errors).
- probeExistingAccountDirs(home): read-only bootstrap probe for adoptable
~/.claude-* config dirs (welcome-view UX is T-481).
Keys are app.-prefixed because SettingsStore requires a scope prefix; app scope
already provides the per-user persistence T-483 wants (T-356 will consolidate
more state later but isn't a hard blocker). No spawn/UI/CLI — downstream
tickets. Unit tests cover CRUD, the disk round-trip, hash/trailing-slash
mapping, independent bindings, and probe filtering.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>