Skip to content

Redesign Loops & Skills: library model, semantic run history, inline script editing#354

Closed
brsbl wants to merge 40 commits into
mainfrom
bb/redesign-thread-p-thr_xnwqkhbpc4
Closed

Redesign Loops & Skills: library model, semantic run history, inline script editing#354
brsbl wants to merge 40 commits into
mainfrom
bb/redesign-thread-p-thr_xnwqkhbpc4

Conversation

@brsbl

@brsbl brsbl commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

What

Redesigns the Loops and Skills pages and the Loop detail page, and folds in two enabling pieces that were spun out as their own spikes.

Skills — a browse/manage library

  • One list of every skill across providers (bb / Claude / Codex), searchable and grouped.
  • Create is a split button (New bb skill ▾): the left half makes a blank skill, the chevron opens example templates.
  • No persistent teaching panel — the page always ships the 2 default bb skills, so it's never empty; teaching lives in the create action, not stacked on a populated list.

Grounded in design research: VS Code Extensions / Raycast (one managed list + a single template-based create action) and NN/G (teaching belongs to genuinely-empty states; don't over-signal).

Loops overview — two states, not five

  • Rows answer only on/paused (quiet dot) and failing (the one loud signal: red CircleX + "Failed"). The full run taxonomy (ran/skipped/running) moved to the detail run history.
  • Search, loading skeletons, and the same split create button (New loop ▾); teaching cards stay in the genuine empty state.

Loop detail — calmer hierarchy, semantic run history

  • Next-run line + inline Edit / Pause / Delete (Delete behind a confirm).
  • Execution/Environment shown prompt-box style; readable permission labels.
  • Run history uses semantic status icons; a script run that spawned a thread shows its exit code and a "View spawned thread" link (script → agent escalation). Status-aware empty state + loading skeleton.
  • Inline script editing end-to-end (was Spike: edit script loops from the detail page #337): edit the script / interpreter / timeout from the page; the single-automation GET enriches with stored script content; the script write is atomic.

Composer hook (was #283)

useComposerArea extracts the shared prompt-composer assembly so the loop-edit form can render the real prompt box; migrated the 3 existing composer surfaces onto it, parity-first.

Folded-in PRs

Testing

  • turbo typecheck clean (app + server).
  • App suites pass (Skills / Loops / Loop-detail incl. script-edit + escalation; composer suites: SideChatTabContent, RootCompose*).
  • Server automations route tests pass (incl. inline-script update + policy gate).
  • Every state is exercised by Ladle stories under apps/app/src/views/*.stories.tsx.

🤖 Generated with Claude Code

brsbl and others added 30 commits June 20, 2026 03:36
Stage 1 of plans/loops-skills-redesign-build.md:
- Two-line loop rows: human cadence + last-run health (status + time) +
  next-run; drop Script/API badges (mode lives in detail), keep project pill.
- Body description; relabel chrome title, breadcrumb, and the sidebar entry
  (text + Repeat icon) to "Loops". Internal Automation types/routes unchanged.
- Update AutomationsView tests + story fixtures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Group the overview by project (folder icon + name header) instead of a
  project pill; enabled loops sort above paused within each project.
- One status signal: leading icon reflects the last run (success/failed/
  skipped/running/never); drop the enabled/paused dot. Paused is conveyed by
  the next-run label.
- Row layout: last-run date under the name (left); next-run + schedule stacked
  on the right. Add icons to the row context menu (Pause/Resume/Run/Delete).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The leading slot shows the last-run icon once a loop has run; before its first
run it falls back to the enabled-state dot (green when enabled/scheduled, muted
when paused) so a brand-new enabled loop doesn't look dead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Health rollup stat cards: success rate, last run, next run, avg duration
  (computed from runs + schedule).
- Restyle header: back link to Loops, name + Active/Paused status; drop the
  Script/API pills (mode lives in the config card). Config shown as a titled
  card; run history kept.
- Relabel detail strings automation→loop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add updateAutomation API client + useUpdateAutomation hook over the existing
  PATCH route.
- Edit button flips the config card to inline fields (name, cron, timezone,
  prompt for agent loops, auto-archive) using real Input/Switch; Save persists
  via PATCH, Cancel reverts. Script bodies aren't editable inline (response
  carries only scriptFile) — noted in the form.
- Update detail tests/stories for the new onSave/savePending props.

Stages 2–3 of plans/loops-skills-redesign-build.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Session S1 of the Loops and Skills Pages spec: the genuinely-new backend the
later Skills-page UI binds to. No automations API/DB/CLI or typeahead change.

Daemon:
- host.list_skills: skill-only discovery that emits each record's absolute
  filePath + originating rootKind, reusing the existing scan-root resolution and
  leaf helpers (typeahead scan path untouched).
- host.delete_skill: new confined FS write primitive (bb scopes only). Path is
  built host-side from (scope, name, cwd); name must be one safe segment; the
  realpath target must be the exact named direct child of the allowed bb root,
  refusing any symlink redirect. Non-retryable.

Server:
- GET /projects/:id/skills: queries both command-surface providers, maps
  (provider, rootKind) -> product scope/manageable, de-dupes provider-agnostic
  bb skills by filePath.
- DELETE /projects/:id/skills: re-validates the bb scope, resolves the workspace
  (never forwards a client filePath), calls the non-retryable daemon primitive.

Contract: skillSummarySchema + skill scope/provider enums + daemon command and
result schemas, registry, and online-rpc response wiring.

Tests: daemon discovery rootKind-per-root + delete confinement (traversal,
symlink escape, sibling-symlink redirect, non-skill, missing); server scope-map
+ de-dup + delete validation/409; contract round-trip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- /skills route + lazy SkillsView; chrome title "Skills"; useRouteState.isSkillsView.
- API client listProjectSkills/deleteProjectSkill over the cherry-picked
  GET/DELETE /projects/:id/skills; useProjectSkills/useDeleteSkill hooks.
- SkillsView: provider-grouped (real provider logos, bb-agnostic last),
  searchable, calm typeahead-style rows; scoped to the personal project.
- Sidebar: Skills entry (Zap) above Loops via ProjectListActionButtons.
- Tests for grouping + states. Verified live: GET /skills returns builtins.

Stages 4–5 of plans/loops-skills-redesign-build.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
--primary light oklch(0.4891)->0.27 (near-black), dark 0.7058->0.82 (more
decisive, still light-on-dark). Decision #3 from the redesign note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop the redundant in-body back link (the chrome breadcrumb already returns
  to Loops).
- Header actions: surface a persistent "View thread" (latest run's thread,
  when one exists) and fold Edit + Delete into a "..." overflow menu, leaving
  Run now + Pause/Resume as the primary actions.
- Run-history rows keep their per-run View thread link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New skill action opens the composer seeded with "Create a new bb skill that "
(creation always targets a bb skill — the only manageable scope); the spawned
thread authors the SKILL.md. Mirrors the Loops create flow. True inline-embedded
prompt box is deferred: NewThreadPromptBox needs RootComposeView's full
modeConfig, so it's a separate refactor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- View (all scopes): GET /projects/:id/skills/content resolves the skill by
  (scope,name) to its authoritative filePath and reads via the existing
  host.read_file daemon command (never a client path).
- Edit (bb skills only): new secure host.write_skill daemon command (bb-user/
  bb-project, path resolved host-side from (scope,name,cwd), realpath/symlink
  guards, edits existing SKILL.md only) + PATCH /skills/content route.
- Skill rows open a detail dialog showing the SKILL.md; bb skills get inline
  edit (Save) + Delete; other scopes are read-only.
- Contract/session registration + result fixture for host.write_skill.

Stage 7 of plans/loops-skills-redesign-build.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Order bb-agnostic skills above the provider groups.
- Each provider group is its own card with a collapsible header (chevron,
  aria-expanded) and a max-h-64 scroll area, so a large group (e.g. plugins)
  scrolls in place instead of running the page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each project group header is now a toggle (chevron + folder + name + count),
mirroring the Skills page; collapsing hides that project's loops.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Personal == projectless in bb (isProjectlessProjectId), and the sidebar renders
personal threads without a project header. Mirror that: render personal/
projectless loops as a flat list, and only group real projects under collapsible
folder headers. With no real projects it's just a clean flat list (no redundant
'Personal' header).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prompt-composer wiring — useThreadCreationOptions + usePromptDraftStorage
+ usePromptMentions + useCommandSuggestions + useUploadPromptAttachment +
buildProviderPromptActionProps, assembled into the execution / permission /
typeahead / attachments / composer configs — was hand-rolled in three places.
Extract it into one shared hook, apps/app/src/components/promptbox/
useComposerArea.ts, and migrate the three call sites onto it.

Parity-first migration (no behavior change to any surface), in order of
coupling:

1. SideChatTabContent (FollowUpPromptBox, read-only footer, submit creates the
   child thread) — lifted first as the most self-contained.
2. ThreadDetailPromptArea (FollowUpPromptBox, editable footer, submit steers /
   follows up).
3. RootComposeView (NewThreadPromptBox, submit creates a new thread) — most
   coupled; its project picker, branch/worktree selection, queued-message
   stack, and the box itself stay per-site. Command discovery follows the
   resolved environment selection, which the hook itself produces, so it is
   supplied as a resolver over the live selection value.

The hook owns only the shared assembly. Each site keeps its box choice, submit
handler, and chrome. Sites differ on a few explicit, typed parameters:
execution interactivity (read-only / provider-switchable), permission shaping
(editable / editable-gated / read-only), the attachment upload-error strategy
(collect failed names / first error), and the mention/command scoping.

The Loops/Skills inline composer that motivated this is a follow-up that will
consume the hook; it is intentionally not built here.

Validation: typecheck and the full @bb/app test suite pass (871 tests,
including the SideChatTabContent render test that exercises the real component
through the new hook); lint clean on the changed files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an "Open in editor" button to the skill detail dialog's view state for
every skill (next to Edit for bb skills, beside the Read-only label otherwise),
wired to the existing useLocalOpenTargets → openPathInPreferredFileTarget path.
Hidden in edit mode and when no local file-open target is available.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When an agent creates a loop from within a thread, append an informational
system/operation row ("automation-created") to that thread's timeline whose name
links to the loop's detail page. Mirrors the parent-change row: a non-turn
notice carrying a typed { automationId, projectId, automationName } payload, with
a new "automation" TimelineTitleLink kind resolved to the automation route.

- domain: automationCreatedOperationMetadataSchema (system/operation metadata)
- server: appendAutomationCreatedEvent, emitted from the create route only when
  createdByThreadId is set (best-effort; never fails the create)
- thread-view: parse + project the event into the new row variant + linked title
- server-contract: timelineAutomationCreatedSystemRow variant
- app: resolve the automation link + Repeat glyph
- tests at every layer; story in System.stories

Skills get no notice yet (no creation hook / deep-link route) — follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Skills are on-disk files mutated out-of-band (agents write SKILL.md; users edit
via "Open in editor"). Set staleTime: 0 + refetchOnMount: "always" on the list
and content queries so the page/detail re-read from disk on mount and focus
instead of serving the 2s-stale global cache. Daemon + server already read fresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align both pages to app conventions from a design consistency review:
- Collapse caret = single ChevronRight rotating 90° on expand (matches
  ProjectList), not a ChevronRight/ChevronDown swap.
- Skills search input matches the standard page-search pattern (icon left-2.5,
  base Input focus); skill-row focus ring + edit textarea use ring-ring /
  border-input instead of file-accent (reserve the accent for links/active).
- Width: both overviews use a shared max-w-5xl; detail pages stay max-w-3xl.
- Loops rows use sanctioned text tiers (muted/subtle-foreground) instead of
  opacity tints (text-muted-foreground/50–/80) that fell below the AA floor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the sidebar thread-search treatment instead of the generic page-search:
a filled pill (bg-muted, no border) with a leading search glyph, a bare
transparent input, and a focus-within ring — rather than a bordered Input with
an absolutely-positioned icon. Uses page-neutral tokens + the sanctioned
focus-within:ring in place of the sidebar's scoped tokens/arbitrary shadow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the persistent header "View thread" button; the run history already links
each agent run to the thread it created, which is the right place for it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lish

- Empty state teaches create-via-prompt: a marketing one-liner + clickable
  example cards (cross-provider skills; cheap-script-escalates-to-threads loops).
  Skills teach when there are no user-created bb skills.
- Fork-style context chip ("New bb skill" / "New loop") above the composer when
  creating via prompt, mirroring the thread-fork treatment.
- Design-review polish: single explainer per page (drop the redundant intro),
  no container/lightning-icon on the example cards, search reverted to the
  bordered look with the sidebar's subtle focus ring (no black ring), provider
  section max-height grows with the window, "New bb skill" label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rework the detail header and config so the loop name is the focal point:
- Header is the name + a single row: next-run timestamp and inline Edit /
  Pause·Resume / Delete (quiet ghost buttons; Delete keeps its confirm modal).
  Drop the four-stat health card (inferable from run history), the "Active"
  label, the Run now button, and the overflow menu; clean up the dead onRun /
  runAutomation plumbing.
- Configuration shows Execution like the prompt box (provider logo + model +
  a readable permission chip — "Read-only", not "readonly") and Environment
  with an icon, instead of a raw "Agent · codex/gpt-5 · readonly" string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Loops gets a search bar matching Skills (filters by name across groups, with a
  no-match state); drop the count beside the project folder icon.
- Both overviews use a loading skeleton instead of "Loading…" text.
- Skills: an "all your local skills, view/manage" explainer above the search
  (the bb-skills intro stays in the teaching below); section max-height grows
  with the window.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add SkillsView stories (overview, builtins-only, empty=the two default bb skills,
loading, error) and round out the Loops overview (running/skipped statuses) so
the real presentational components can be iterated across states in Ladle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make SCRIPT-mode loops editable end-to-end from the loop detail page,
replacing the "delete and recreate the loop to change its script" message.

The PATCH route + contract already accepted an `execution` update for the
script variant (inline `script`, interpreter, timeout) and wrote it via
`resolveStoredExecution`. What was missing to make this usable:

- Server: enrich only the single-automation GET with the stored inline
  script content so the edit form can pre-fill it (responses otherwise
  carry only `scriptFile`). List/overview stay lean — no per-row disk read.
- Storage: write inline scripts via a temp file + atomic `rename`, so an
  update that rewrites an existing script is atomic w.r.t. a concurrent run
  (a reader sees the full old or full new content, never a truncated file).
- Frontend: in edit mode, for script loops render an editable font-mono
  script textarea + interpreter select + timeout (seconds), wired into the
  existing PATCH flow alongside name/cron/timezone. Existing `env` is carried
  through untouched so it isn't silently dropped; Save is disabled on an
  empty script.

Tests: server PATCH round-trips the new script through disk and surfaces it
via GET; PATCH to a script execution is rejected (403) when script runs are
policy-disabled. Frontend test drives the edit form and asserts the saved
patch. Live-smoked against the dev server (create → edit → disk → GET).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
brsbl and others added 10 commits June 23, 2026 23:41
…poser-hook-usecomposerarea-thr_xuvnw67au8

# Conflicts:
#	apps/app/src/views/RootComposeView.tsx
- Skills is one browse/manage library; create is a split button (blank + example
  templates), teaching only in genuine empty states (grounded in VS Code/Raycast
  + NN/G empty-state research)
- Loops overview rows signal only on/paused + failing; the run taxonomy moved to
  the detail run history
- Loop detail run history uses semantic status icons; a script run that spawned a
  thread shows exit code + View spawned thread; loading skeleton + status-aware
  empty state
- Share create-example data via getCreateExamples + CreateWithTemplatesButton; add
  the Script-escalates-to-agent detail story

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the shared composer hook into this branch so the loop-edit form can render
the real prompt box. Resolved one import conflict in apps/server/src/routes/projects.ts
(kept both the skill-listing and daemon-file-response imports). RootComposeView
auto-merged (create-draft chip + useComposerArea migration); typecheck + composer
and redesign test suites pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings end-to-end script-loop editing from the detail page: the edit form gains
script/interpreter/timeout fields, the single-automation GET enriches with stored
script content, and the script write is atomic. Clean auto-merge with the
run-history rework (both touched AutomationDetailView). Typecheck (app + server)
clean; app suites (incl. script-edit) and the server automations route tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reconcile main's new provider-CLI-install feature + folder/PromptStackCard work
with the useComposerArea refactor across the composer surfaces:
- RootComposeView: keep the hook-based destructure; re-apply main's codex CLI
  install runner/banner; keep both the create-draft chip and folder-id reset.
- SideChat/ThreadDetailPromptArea: drop main's inline attachment/creation-options
  wiring (now owned by useComposerArea); gate the composer on thread.archivedAt.
- ProjectList: keep the Loops tooltip.
Typecheck (app + server) clean; 83 composer + redesign tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
main's cache-owner-registry guard flags raw queryClient.invalidateQueries outside
hooks/cache-owners/. The skills mutation hooks did raw invalidations, so:
- moved the skills query keys to query-keys.ts (projectSkillsQueryKey,
  skillContentQueryKey)
- added skills-cache-effects.ts owning the invalidations
- skills-queries.ts now delegates to it, like automation-queries does
- registered the owner's query-key imports in the guard

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…thr_xnwqkhbpc4

# Conflicts:
#	apps/host-daemon/src/command-dispatch.ts
…jigsc' into bb/redesign-thread-p-thr_xnwqkhbpc4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant