Commit Graph
538 Commits
Author SHA1 Message Date
jpmschweitzerandClaude Opus 4.8 dab0b3194e feat(env): supporter-binary resolver — explicit override → PATH → well-known (T-495)
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>
2026-06-28 23:03:12 +02:00
jpmschweitzerandClaude Opus 4.8 b1ec4afe15 feat(draw): data-lightbox tap-to-zoom on the drawing card (T-318)
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>
2026-06-28 22:40:26 +02:00
jpmschweitzerandClaude Opus 4.8 3e43746c17 feat(draw): per-object data-* caption overlay on the drawing card (T-318)
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>
2026-06-28 22:35:12 +02:00
jpmschweitzerandClaude Opus 4.8 78b0a7b8a4 feat(svg): extract per-object data-* annotations for the drawing-card overlay (T-318)
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>
2026-06-28 22:31:04 +02:00
jpmschweitzerandClaude Opus 4.8 7acea8baa0 feat(draw): wire the drawing card into the conversation — end to end (T-318)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 14bbd1fd2d feat(draw): clide draw command — drive a drawing card from the CLI (T-318)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 ed5cd6e312 feat(draw): DrawingCard widget — SVG region + themed caption (T-318)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 1fdba4aba5 feat(draw): template dispatch — lower a drawing-card doc to SVG (T-318)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 1a0a69df5f feat(draw): drawing-card document envelope model + parser (T-318)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 406da1e0c5 feat(svg): paint <image> via an injected resolver — completes the renderer (T-320)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 718d2b39c2 feat(svg): marker/arrowhead rendering for the drawing-card renderer (T-320)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 91607d54d1 feat(svg): CustomPaint renderer for the drawing-card SVG engine (T-320)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 1a3486708c feat(svg): typed scene model + document builder for the drawing card (T-320)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 ac387796c8 feat(svg): SVG colour + transform parsers for the drawing card (T-320)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 fb96e5238f feat(svg): XML tokenizer + inline-style normalizer for the drawing card (T-320)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 9cc3d68862 feat(svg): SVG path-data parser for the drawing-card renderer (T-320)
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>
2026-06-28 22:18:53 +02:00
jpmschweitzerandClaude Opus 4.8 066197c5e1 fix(i18n): stop warning on missing tool.name.* keys for proper-name tools (T-493)
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>
2026-06-28 14:58:12 +02:00
jpmschweitzerandClaude Opus 4.8 7c7a91545e feat(menubar): manual "Check for updates" in the About box (T-47 P1)
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>
2026-06-28 12:17:29 +02:00
jpmschweitzerandClaude Opus 4.8 095c45a023 feat(markdown): live-sync read-mirror of the open editor buffer (T-36)
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>
2026-06-28 11:08:31 +02:00
jpmschweitzerandClaude Opus 4.8 84f4b20cb8 feat(claude): nudge fresh sessions to load the pql + clide skills (T-490)
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>
2026-06-28 10:53:08 +02:00
jpmschweitzerandClaude Opus 4.8 a46677facf feat(welcome): initialize a non-repo folder as a project (T-489)
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>
2026-06-28 08:42:02 +02:00
jpmschweitzerandClaude Opus 4.8 25430686cc feat(welcome): New project flow + per-repo account roadblock (T-488)
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>
2026-06-28 08:35:30 +02:00
jpmschweitzerandClaude Opus 4.8 adaf8966dc feat(cli): clide project new — create + git-init a new project (T-487)
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>
2026-06-28 08:23:18 +02:00
jpmschweitzerandClaude Opus 4.8 b9fc720f97 feat(git): GitClient.init for the new-project flow (T-487)
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>
2026-06-28 08:19:10 +02:00
jpmschweitzerandClaude Opus 4.8 996b10d3ec feat(claude): account login pane — host claude login in a modal terminal (T-485)
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>
2026-06-28 00:52:41 +02:00
jpmschweitzerandClaude Opus 4.8 222d4ae5e3 feat(claude): per-repo account badge in the Claude pane chrome (T-481)
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>
2026-06-28 00:43:40 +02:00
jpmschweitzerandClaude Opus 4.8 5c31190c4f feat(claude): Settings → Claude accounts registry list (T-482 part 2)
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>
2026-06-28 00:35:47 +02:00
jpmschweitzerandClaude Opus 4.8 c3efcf3855 feat(claude): Settings → Claude per-workspace account picker (T-482 part 1)
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>
2026-06-27 23:46:43 +02:00
jpmschweitzerandClaude Opus 4.8 e86543d476 fix(ipc): write the /ide discovery lock into bound-account config dirs (T-479)
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>
2026-06-27 21:25:19 +02:00
jpmschweitzerandClaude Opus 4.8 34b9690005 feat(claude): respawn panes on account set/unset + safe --purge (T-480 part 2)
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>
2026-06-27 21:18:49 +02:00
jpmschweitzerandClaude Opus 4.8 744b6cff43 feat(cli): clide claude account verbs — add/list/login/set/unset/remove (T-480 part 1)
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>
2026-06-27 17:30:01 +02:00
jpmschweitzerandClaude Opus 4.8 1fa7dee863 feat(claude): spawn hosted sessions under the bound account's CLAUDE_CONFIG_DIR (T-484)
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>
2026-06-27 17:00:06 +02:00
jpmschweitzerandClaude Opus 4.8 a8d321f344 feat(claude): per-repo account registry + storage foundation (T-483)
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>
2026-06-27 16:42:39 +02:00
jpmschweitzerandClaude Opus 4.8 679b5ba9d9 feat(cli): clide instances / instance verbs for instance discovery (T-247)
Closes the observability half of T-247: a way to find and identify running
clides. `clide instances` scans the runtime socket dir, probes each live
*.sock, and prints its identity (version/pid/workspace/socketPath) as jsonl;
dead sockets are skipped. `clide instance` reports the one you're connected
to. Combined with CLIDE_SOCK honoring (this same ticket), you can now list
instances and pin the CLI to a chosen one.

Server: a new `instance` dispatcher command (registered in buildDispatcher
with the live workspace/pid/socket) returns the identity map; added to the
argv translator's umbrella set so a bare `instance` token routes to it.
Client: a POSIX dir-scan in clide.c (Windows stub until it ships).

Tests: e2e `instances` lists the live test server with its identity; a
cc-free unit test covers the `instance` command shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:51:13 +02:00
jpmschweitzerandClaude Opus 4.8 ceab497ba8 fix(ipc): sweep dead orphan sockets from the runtime dir on startup (T-247)
The runtime socket dir accumulated orphaned *.sock nodes from crashed
instances — only the current workspace's own path was ever cleaned. Add a
best-effort startup sweep that probes every *.sock in the dir and unlinks
only the dead ones; live instances (something answers) and unresponsive
nodes (possibly hung) are left alone. Runs before bind, alongside the
existing per-workspace stale-unlink. Never blocks our own startup on a
sweep failure.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 08:15:05 +02:00
jpmschweitzerandClaude Opus 4.8 3cbe0e6ec6 refactor(claude): drop the redundant Team-tab account budget (T-158)
After live review: the shared account /usage budget read as redundant on
both the Activity and Team tabs. Usage is per-account and can't be split per
member, so one place is enough — it lives only on the Activity tab, next to
the refresh control that fetches it (T-415). Removes the Team-tab account
card, its usage prop wiring, the team.section.usage/team.usage.shared i18n
keys, and the now-obsolete team_tab test. The three-tab card facelift stays.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 07:56:09 +02:00
jpmschweitzerandClaude Opus 4.8 763d628fc9 feat(claude): shared account budget in Team tab + meta-sidebar card facelift (T-158)
Implements T-158 option A: the forwarded /usage budget (already parsed for
the Activity tab, T-415) now also renders in the Team tab as a single
ACCOUNT card, captioned "shared across the team". Usage is per-account —
every team session shares one ~/.claude login — so it's shown once, not
split per member (which would just repeat identical numbers).

Facelift: the Activity, Team, and Config tabs now render their sections as
elevated cards (panelHeader fill, dividerColor hairline, 6px radius) under
small-caps mono headers, matching the settings overlay's card design
(settings_category_view). Shared helpers — metaSectionHeader / metaCard /
metaCardRow — live in meta_sidebar/models.dart; the SESSION control strip
and the pinned Config SETTINGS block adopt the same card. The shared
ClideAccordion (also used by decisions/tickets) is left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 11:31:50 +02:00
jpmschweitzerandClaude Opus 4.8 b7db22924d feat(theme): canonical theme/namespace lists + ship Summer Night (T-371, T-478)
The bundled-theme list and the Tier-0 i18n namespace list were each
hand-maintained in 3+ places that had drifted: the testmode harness loaded
8 themes (catppuccin silently unvalidated) while the app and contrast gate
loaded 10, and the i18n gate checked 4 of the shipped namespaces. Export
one canonical const each — kBundledThemePaths and kTier0Namespaces — and
have the app, the testmode harness, and the a11y gates iterate them.

Drift-proof meta-assertions:
- contrast gate fails if any theme YAML on disk is absent from
  kBundledThemePaths (so a theme can't sit unvalidated).
- i18n gate derives its subjects from assets/i18n/en_us/ at test time and
  asserts en/nl key parity for every shipped catalog (26, was 4), plus that
  every kTier0Namespaces entry has a shipped catalog.

Surfaced summer-night.yaml: a legacy-ported palette on disk, unbundled and
never contrast-checked. Per the maintainer's call, ship it: flesh the
palette to full token + syntax coverage (honouring the v1.2 colours, clide
derivation pattern for the new keys) and add a structurally identical
summer-night-hc sibling that clears the strict extended gate. Both pass.

No licenses.yaml change: summer-night is clide's own (ported from legacy
clide v1.2 under legacy/, MIT); the only third-party palette, Catppuccin,
is already acknowledged; no fonts added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 15:31:16 +02:00
jpmschweitzerandClaude Opus 4.8 1e49f3e1d9 fix(editor): collapse the workspace split when the last buffer closes (T-459)
EditorRegistry.close() guarded its active-changed emit on `_activeId !=
null`, so closing the LAST buffer (active clears to null) emitted only
editor.closed — never the active-changed(id:null) the editor extension
listens for to call closeEditor(). editorOpen stayed true and the top
split sat orphaned over the Claude pane. Always emit active-changed when
the active buffer is removed, including the cleared-to-null case; the
slot renderer already collapses correctly once editorOpen flips false.

The existing extension test fabricated the null active-changed event, so
it passed despite the registry never emitting it — that gap is why the
bug shipped. Add a registry test that drives the real close() path, plus
a slot_host widget test asserting the split (drag handle) drops out and
the primary pane fills the column.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 17:49:12 +02:00
jpmschweitzerandClaude Opus 4.8 ea340ef141 fix(i18n): localize remaining Claude-pane strings (T-462)
Completes i18n coverage of the conversation surface — the strings the
T-464 migration left as "decorative" English:

- running-indicator verbs (Pondering/Conjuring/… → catalog, Dutch added)
- pane chrome title (primary / secondary N) and banner role
- tool-card titles (Edit → Bewerken) via a tool.name.* key set, with
  command/proper-name tools (Bash, Grep, Glob, LS) falling back to the
  raw name
- collapser step/edit/agent counters
- the folded-activity ticker (result/error/thinking/image + tool name)

English degrades to the placeholder where a key is absent, so untranslated
or MCP tool names render unchanged. en_us and nl_nl stay at key parity.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 00:24:34 +02:00
jpmschweitzerandClaude Opus 4.8 43c650de10 feat(i18n): UI language selector (Settings → Appearance → Language) (T-462)
Make the localization usable: an app.locale select (English / Nederlands) in
the Appearance category, applied live by root_shell — it parses app.locale and
calls i18n.setLocale on boot + on settings change (setLocale is a no-op when
unchanged). nl_NL registered in availableLocales. The ui-design skill now
documents the locale-dir config and the rule to design for string-length
variation (translations run ~20% longer; never hard-size to the English label).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 17:34:08 +02:00
jpmschweitzerandClaude Opus 4.8 1bdfd1be1c refactor(i18n): bundle catalogs under assets/i18n/<locale>/ (T-462)
Move the catalogs out of lib/kernel/src/i18n/catalog/ into assets/i18n/, and
switch the layout from a `{namespace}_{locale}.json` filename to a per-locale
DIRECTORY: assets/i18n/en_us/{namespace}.json. Adding a language is now just a
new folder (assets/i18n/nl_nl/, nl_be, en_eu, …) of the same namespace files —
no renames — which is cleaner to maintain and mirrors how an external
extension ships its own catalog. AssetCatalogLoader/FileCatalogLoader resolve
`<root>/<locale>/<namespace>.json`; pubspec bundles the locale folder.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:54:14 +02:00
jpmschweitzerandClaude Opus 4.8 37fa255a63 refactor(i18n): route command + settings manifest labels through the catalog (T-462)
Apply the manifest-i18n foundation across every extension: each
CommandContribution (45, command-palette + menu) gains titleKey + i18nNamespace,
and each SettingsCategory/section/field/option gains its key — with the English
text added to the owning extension's catalog. The settings renderer
(settings_category_view + settings_modal) threads the category's i18nNamespace
down and resolves every label/help/option through it; new catalogs created for
view, cli-install, keybindings-ui. No en_US behaviour change — the command
palette, menus, and settings panel now localize from the catalog (D-21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:20:19 +02:00
jpmschweitzerandClaude Opus 4.8 198c8b18b5 feat(i18n): i18n key fields on command + settings contributions (T-462)
Lets manifest labels (command-palette/menu titles, settings labels) localize,
not just displayed widget strings. Adds optional titleKey/i18nNamespace to
CommandContribution and labelKey/helpKey/titleKey + a category i18nNamespace to
the settings schema. The command palette and menu bar now resolve titles via a
shared localizedCommandTitle helper — and the palette's fuzzy search matches
the localized title too (PaletteController.titleResolver). No behaviour change
until the per-extension keys + catalog entries land (placeholder == English).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 09:44:35 +02:00
jpmschweitzerandClaude Opus 4.8 33a8a74240 refactor(i18n): route framework + shared chrome through a 'core' catalog (T-469)
Framework strings outside any extension — widget primitives (collapser, toast,
lightbox, multitab, ex-line, spine, pane chrome), the shared reader chrome, the
markdown 'Open in editor' tooltip, and the drag-resize handle a11y labels — now
resolve under a new 'core' namespace (preloaded at boot). Settles the T-469
namespace question: framework chrome gets one 'core' catalog.

Makes ClideSettings.i18n.string/.interpolated null-safe (ClideKernel.maybeOf):
primitives render kernel-less in isolated tests, returning the placeholder —
matching the D-101 fallback contract for fonts. The markdown tooltip threads
via the ClideMarkdownHooks carrier like mono/ui; drag_resize reads the kernel
i18n directly to avoid a kernel→widgets layering inversion. No en_US change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 23:26:02 +02:00
jpmschweitzerandClaude Opus 4.8 c5500dc235 refactor(i18n): route remaining extension labels through the catalog (T-468)
Migrate the user-facing strings in menubar, output, search, deeplink, editor,
files, terminal, markdown, and vim to ClideSettings.i18n.string/.interpolated;
extend the existing catalogs and create the missing ones (deeplink, output,
search, markdown, menubar, vim). vim mode labels now resolve through
builtin.vim (default still `-- NORMAL --`, now localizable). A dock-status test
seeds builtin.output so the widget's own i18n lookups resolve instead of
warning into the logRing it counts. No en_US behaviour change (D-21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:16:20 +02:00
jpmschweitzerandClaude Opus 4.8 17d22b9571 refactor(i18n): route planning-pane labels through the catalog (T-467)
Migrate the user-facing strings in builtin.tickets, builtin.pql,
builtin.decisions, and builtin.problems (list/detail views, section headers,
filters, empty/loading/error states, type chips) to ClideSettings.i18n; extend
each extension's catalog. The detail/links tabs that had a bare title now wire
titleKey + i18nNamespace so the tab title resolves too. Context threaded into
the static label helpers. No en_US behaviour change (D-21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:14:01 +02:00
jpmschweitzerandClaude Opus 4.8 f33cd603be refactor(i18n): route git + diff panel labels through the catalog (T-466)
Migrate the user-facing strings in builtin.git (panel, status groups, commit
bar, branch picker, discard dialog, row a11y) and builtin.diff (toolbar,
empty states, file meta) to ClideSettings.i18n.string/.interpolated; extend
both catalogs. _stateLabel threaded a BuildContext from its caller. No en_US
behaviour change (D-21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:13:35 +02:00
jpmschweitzerandClaude Opus 4.8 16fe71cb3f refactor(i18n): route claude pane labels through the catalog (T-464)
Migrate the hardcoded user-facing strings across builtin.claude — composer,
conversation cards/segments, permission + AskUserQuestion prompts, task dock,
meta-sidebar (activity/config/team/roster), session/model pickers — to
ClideSettings.i18n.string/.interpolated, English kept as the placeholder;
builtin.claude_en_us.json extended to cover them. Context threaded into the
context-free render helpers (toolInputBody chain, _ConversationTurn) the same
way the mono family already is. No en_US behaviour change (D-21).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:19:19 +02:00
jpmschweitzerandClaude Opus 4.8 93aa9bc332 fix(i18n): re-export I18nReplacer with the ClideSettings facade (T-462)
ClideSettings.i18n.interpolated takes I18nReplacer, but the type didn't travel
with the facade — every templated-lookup call site had to separately import it
from the kernel. Re-export it from clide_settings so it ships via the widgets
barrel alongside ClideSettings; the interpolated API is now self-contained.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:14:20 +02:00