enhance: uiux flow#4
Merged
Merged
Conversation
…ructured logging
End-to-end UX overhaul of the workspace lifecycle, plus the source-loader
refactor that came out of dottedsign real-case testing.
## Workspaces
- New `/scaffold` body shape: `{ name, manifest }`. Server resolves
`<workspacesRoot>/<name>/` (default `~/.braid/workspaces/`) so the UI
never picks paths. Strict create-only; name collision returns 400.
- Boot-time `discoverCanonicalWorkspaces` registers any subdir with a
PRODUCT.md, so CLI-created or hand-copied workspaces show up in Studio
without an explicit register step.
- `DELETE ?purge=true` unregisters + rm -rf the canonical folder.
Arbitrary-path workspaces refuse purge with a clear error.
- `DELETE /sources/:id` now also rm's the resolved source path when it
lives inside the workspace, matching the loader's "owns destination"
contract.
- Wizard collapsed to create-only: removed "Open existing" branch,
`RegisterWorkspaceDialog`, and the Sidebar "Register from Path" menu.
Single + button opens wizard; existing workspaces open via Sidebar.
## Source loader: unified incremental sync interface
- `SyncReport` (in core) gains optional `added/updated/removed/unchanged`
counts so the UI can render `+a ~u -r` uniformly across loaders.
- GitLoader populates counts via `git diff --name-status before..after`.
- GoogleDriveLoader rewritten:
- Per-doc layout `<source>/<Doc Title>/index.md` (drops Drive folder
hierarchy mirror).
- Inline base64 images extracted to sibling files; markdown rewritten
to local refs.
- `.braid-manifest.json` diff-based incremental sync (add, update,
remove via Drive modifiedTime).
- Skip non-Doc Drive types and `Copy of …` / `…的副本` duplicates.
- `include` / `exclude` regex filters on Drive title.
## Source paths
- Path is now fully derived: `./intents/<id>/` or `./codebases/<id>/`.
UI only asks for `name`; path Input gone.
- `rolePathSegment(role)` exported so AddSourceDialog and the wizard
share the convention.
## Structured logging
- `@braidhq/core` exports `createLogger(ns)`. Pino + pino-pretty default
in dev, JSON in prod (`NODE_ENV=production`), `BRAID_LOG_LEVEL` and
`BRAID_LOG_PRETTY` overrides.
- Server entrypoint loads `.env` from repo root via Node's native
`process.loadEnvFile`.
## OAuth in wizard
- gdrive sources can `Connect Google` inside the wizard before scaffold,
since workspaceId == typed name so the secret store key is known in
advance.
- `useGoogleOAuth(workspaceId, sourceId, { onConnected })` hook shared
by wizard's `GdriveOauthBlock` and `AddSourceDialog`.
## Wizard progress
- Outside-click / Escape blocked while scaffold is pending (was killing
long-running gdrive ingests).
- ProgressStep subscribes to `source.synced` SSE events and flips a
per-source ✓ as each loader finishes.
## Refactor structure
- Extracted helpers to their proper layer:
- `infrastructure/fs/paths.ts`: `isUnder`, `pathExists`
- `infrastructure/fs/WorkspaceDiscovery.ts`
- `source-loader-gdrive/Manifest.ts`: read/write + types
- `studio/lib/useGoogleOAuth.ts`: popup + postMessage flow
- Root scripts: `dev:web` (server + studio), `dev:desktop` (tauri).
## Tests
- gdrive: 13 (regex, flat layout, image extraction, incremental sync)
- git: 4 (+ added / removed counts)
- server: 139 (+ workspace discovery, purge, scaffold strict)
- studio: 34 (sourceDraft path derivation)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…are sidebar End-to-end overhaul of the skill-running UX driven by dottedsign feedback. ## UX - Merge Skills + Runs into a single Actions tab. Sidebar shows available actions on top and recent conversations underneath, separated by a border. Run history opens via row click, no separate Continue Conversation button. - Replace single-line `<Input>` with `<Textarea>`. Enter sends, Shift+Enter inserts newline. - Recent timestamps render as ISO 8601 (`YYYY-MM-DDTHH:mm`). - Conversation header has a fixed height so the "New conversation" button appearing no longer bumps row height. ## Skill taxonomy - New `SkillCategory = ask | build | generate`, optional on `BraidSkillExtension`. Skills without a category land in a "Custom" bucket. - New `BraidSkillExtension.order` (positive integer) for the Build group's intra-group ordering. Built-ins use sparse numbering (100 / 200 / 300) so plugins can slot between by picking e.g. 150 without renumbering. The UI displays sequential rank, not the raw order. - New `SkillManifest.pluginId` (optional) carries the plugin id of the contributor. `FsSkillRegistry` fills this from the existing `ref.contributedBy`. The sidebar badge shows the plugin id (e.g. `redoc-ddd`) instead of a generic `plugin` label. ## braid-model New built-in skill at `packages/core/skills/braid-model/SKILL.md`. Closes the OSS-PROPOSAL §710 gap (extract / clarify / model / generate-doc / ask baseline). Two modes: build + validate (default) and validate only. Adapted from redoc-model but rewritten against Braid's API and ontology contract (no hardcoded DDD types). ## Files - `packages/schema/src/skill.ts`: add SkillCategory, BraidSkillExtension.category + order, SkillManifest.pluginId - `packages/server/src/infrastructure/fs/FsSkillRegistry.ts`: thread pluginId from PluginSkillRef - `packages/studio/src/pages/Actions.tsx`: new; replaces Skills.tsx + Runs.tsx - `packages/studio/src/components/ui/textarea.tsx`: new - `packages/studio/src/components/CommandPalette.tsx`: TabKey + shortcut updates - `packages/studio/src/App.tsx`: remove RunsPage + continuation state ## Tests - schema/test/skill.test.ts: +4 tests for pluginId, category, order, defaults - studio/test/pages/Actions.test.ts: new; 12 tests covering bucketByGroup (incl. sparse-numbering plugin insertion), originLabel, formatTimestamp, groupBySession - server FsSkillRegistry.test.ts: assert pluginId plumbing - server composeFs.test.ts: include braid-model in the builtin list Net: +3 schema, +12 studio. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lakiness The Kuzu NAPI binding occasionally crashes the vitest worker fork on Node 22 with a bare "Worker exited unexpectedly" from tinypool. Each individual test is deterministic; only the worker process lifecycle is flaky. Reproduced both locally (macOS Node 22.21) and in CI (Node 22 job fails, Node 20 passes consistently). Vitest's built-in `retry` happens within the same worker, so it can't recover from a worker crash. Wrap `vitest run` in a Node retry loop that spawns a fresh fork each attempt; in observed runs the suite passes within five attempts. A genuine assertion failure still fails five-in-a-row because that path is unrelated to the native shutdown crash. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`process` must be imported from `node:process` (no global), and `node:path` sorts before `node:url`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…vicon polish ## Theme - New `lib/theme.ts` exporting `useTheme()` and the `Theme = 'light' | 'dark'` type. Two states only: a `system` mode was considered but cut because it adds a third state without a third visual. - `index.html` runs an inline script in <head> that reads `localStorage.braid-theme` and applies the `dark` class before React mounts. First-time visitors fall back to `prefers-color-scheme`. The script mirrors the resolution logic in `theme.ts` to avoid a flash-of-wrong-theme on the first paint. - `<ThemeToggle />` in the header. Icon shows the current state (Sun / Moon); the tooltip describes what clicking does. ## Color tokens - Both themes pulled away from the absolute extremes. Light page background now `oklch(0.97 0.003 260)` (was pure white); dark page background `oklch(0.2 0.005 260)` (was near-black). All neutrals carry a small cool-blue tint (chroma 0.003-0.005 at hue 260), matching Linear's chrome. - Surface layering: card / popover are the brightest plane, page background sits below, sidebar slightly recessed. Same hierarchy in both themes so depth cues are consistent. - Primary purple chroma toned down from 0.17 / 0.18 to 0.12 / 0.13. Same hue (Linear's 274), same brand feel, less shouty CTA. ## Icon - Favicon and Sidebar logo now share a single source `src/assets/braid-logo.svg`. Removed the duplicated `public/` variant; Vite resolves the asset path during build. - SVG viewBox tightened from `0 0 512 512` to `86 86 340 340`. The artwork keeps the same proportions but fills ~80 % of the canvas, leaving ~10 % padding so a 16 px favicon doesn't crowd the tab edge. - Sidebar logo bumped from `size-4` to `size-5` and the brand text gains an italic subtitle "braiding intent & code", echoing what the three strands of the logo represent. Header height stays `h-11` to line up with the App header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`npx vitest` cannot find the binary under pnpm's strict hoisting layout (the actual file lives deep in the pnpm store). Each retry attempt was exiting in microseconds without running anything, so the wrapper burned through all 5 attempts instantly and CI failed. Switch to the package-local `node_modules/.bin/vitest` symlink directly. Same target `pnpm exec vitest` would resolve to, no recursive pnpm invocation. Local stress test now hits 1-3 retries per run and always passes within the 5-attempt budget. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Kuzu's prebuilt NAPI binary hangs vitest's worker fork at startup on Node 22 under the free Ubuntu GitHub runner. Each attempt hangs ~70s producing "Worker exited unexpectedly" with `tests 0ms` (no user code runs). Five-attempt retry wrapper inflates job duration to ~6 minutes without recovering. Local macOS Node 22 is merely flaky and the retry wrapper handles it. The CI hang is a Linux x64 + Node 22 + CI resource budget interaction that the retry cannot fix. Excluding storage-kuzu from the Node 22 matrix keeps Node 20 running the full suite (and storage-kuzu is the only Node 22-incompatible package). Node 22 still exercises every other workspace package. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Sidebar shape Three-section vertical layout: - Header (h-11): logo + brand + tagline + collapse toggle - Body (flex-1): workspaces list, scrolls - Utility row: server URL + theme toggle, anchored at the bottom The empty middle that used to follow short workspace lists is now visually balanced by content at the top and bottom of the sidebar. A future user / account avatar slots into the empty flex-spacer on the left of the utility row when login lands. ## Collapse The sidebar collapses from 240 px to 48 px on a chevron click or via the Cmd+\\ / Ctrl+\\ chord (Linear / VS Code convention). State persists to localStorage as `braid-sidebar-collapsed`. Collapsed mode shows: logo, workspace folder icons (tooltip on hover reveals the full id), utility icons stacked vertically (server, theme, expand). All actions remain reachable; nothing hides behind a menu. `ListRow` gained an optional `title` prop so the workspace icons can carry a hover tooltip when their text is hidden. ## App header The theme toggle and server URL button moved into the sidebar utility row, leaving the App header with just the workspace context button and the Cmd+K hint. ## Side cleanup Removed the per-skill em-dash prohibition note from the four built-in SKILL.md files; that rule belongs in the project-level guidance, not duplicated across every skill. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Schema: previewProposal pure helper (snapshot + diff) with lenient
apply, structural metadata equality, undefined-aware patch
* Schema: SkillFrontmatter.braid.summary one-liner field; all 5 builtin
skills backfilled
* Core: newProposalId / newClarifyTicketId take a Clock and emit
p-YYYY-MM-DD-{short} / ct-... ids matching the documented format
* Studio: ProposalPreview with Graph/Table/List tri-view; new
GraphSurface composes Canvas + Table behind shared selection +
focus-mode state; PageActions portal slot in the App tab bar
* Studio: NodeDetailPanel extracted as the single right-aside detail
for both views (replaces Sheet + table's JSON dump)
* Studio: type-filter chips become strict whitelist with All / Clear /
Reset; Focus toggle dims or hides non-neighbourhood; minimap
per-node colour from ontology palette; auto-fit on visible-set
change
* Studio: Actions Recent + SkillCard use summary, no truncate
* Refactor: useControllableState / useNodeNeighbors / optional() lib
helpers; useFilterSeed / useFitOnLayoutChange / useGraphShortcuts
hooks; styleTokens + GraphToolbar atoms shared between pages
* Refactor: PageActions portal uses memoised context (no forceRender);
selectedNode/Edge collapsed into selectNode/Edge/clearSelection
helpers; withAlpha exported from palette (drops inline dim() copy)
400 tests pass (studio 55 / core 145 / schema 200).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
UX iteration across the Studio app, plus a few schema / core upgrades that surfaced along the way.
Studio
GraphSurface— selection + focus mode persist across views; right-handNodeDetailPanelis one component for bothpreviewProposaldiff; type-coloured chips, ring overlays, dashed-removed edges, change-badged tablesAll/Clear/Reset;Focustoggle dims or hides non-neighbourhood; minimap colours per ontology palette; auto-fit on visible-set changeSchema / core
SkillFrontmatter.braid.summaryone-liner field; 5 builtin skills backfilledpreviewProposalpure helper with lenient apply + structural metadata equality + undefined-aware patchnewProposalId/newClarifyTicketIdemitp-YYYY-MM-DD-{short}/ct-...matching the documented formatInfra
storage-kuzuNode 22 flakiness worked around (retry script + matrix skip)