Skip to content

Prompt templates: reusable briefs for every loop type - #231

Merged
scgopi merged 6 commits into
mainfrom
feat/prompt-templates
Sep 1, 2026
Merged

Prompt templates: reusable briefs for every loop type#231
scgopi merged 6 commits into
mainfrom
feat/prompt-templates

Conversation

@scgopi

@scgopi scgopi commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Implements PROMPT_TEMPLATES.md (New Designs v4), checked against GraphCode New Loop v4.dc.html, README.md §3 and MAIN_LOOP.md, and folds in the fixes from a design review of the first draft.

What it adds

  • Templates button + ⌘T picker in the New loop dialog — search-first, replacing the dialog body, grouped This project above All projects.
  • Applied state that never gains a shape silently: a removable chip, a plain-words strip, a "from template" dot on every field it set (nothing locked), Undo the shape and ✕.
  • walks the unfilled tokens across brief, done check, metric and branch — the canvas prints "One token left to fill · ⇥ to jump to it", so the key had to actually do it.
  • Storage: one template is one markdown file. ~/.graphcode/templates is where the app writes; <project>/.graphcode/templates is also read and wins on a filename collision. Sharing is a git action — no sync layer, no .gitignore touching.
  • Save as template from the dialog, a loop's context menu, and a composite's menu. Values typed into {token} positions come back as tokens.
  • Follow vs snapshot: timed and composite loops re-read their template on the next run; main, goal and turn loops snapshot at creation. Detach sits beside the Follows chip on the card and in its context menu.
  • Template editor in Settings (the picker's "Manage…" destination) — name, shape, brief and settings, listing home and project templates, with the design's load-bearing usage line above Save.

Review fixes included

Data loss / correctness

Was Now
move wrote the copy then deleted "the original" at the same path when a project relocation fell back to home — destroying the only copy The move that isn't a move stops before writing
A save silently overwrote a different template that slugged onto the same filename The second takes the next free name; same guard on move
Clicking a picker row applied whatever the keyboard was on Applies the row that was clicked
"Save as Template…" from a card menu set state nothing presented The canvas and the overview host the sheet, with a quiet notice bar for the path
A following composite re-identified its whole sub-graph on every pilot, orphaning the previous pass's child sessions (node ids are zmx session names) and stranding their memory Compares what a human authored; replaces only on a real change, tearing down the outgoing children when it does

Fidelity to the spec

  • Asking whether a project can take a template no longer creates .graphcode/templates in the checkout — nothing lands there by default.
  • The use count counts uses, not saves.
  • A resolve that flips a follow to missing broadcasts, so the warning reaches the card; an unchanged resolve doesn't, so the timer-driven sweeps don't persist the graph every tick.
  • Templates carry an agent only when they name one, so a shared template doesn't pin an agent a teammate may not have installed.
  • The relocation notice reports where the file actually landed, not where it was asked to go.
  • A rename in the save sheet reaches the filename; a rename in the editor moves the file and keeps the id, so followers stay attached.
  • The directory watch re-arms, so a git pull that creates .graphcode/templates is picked up.
  • An unwritable project is dropped from the save sheet rather than an ineffectual .disabled on a Picker tag.
  • Tokens left in a done check, metric or branch block Start too; the save notice is dismissible; ✕ restores the pre-template draft after two applies.
  • The applied strip uses the canvas's own copy — one shared verb ("sets a done check and a worktree"), finishing with "Undo the shape to keep just the prompt."

The repo-wide cyclomatic_complexity relaxation the first draft added is dropped — the reducer is split into picker / apply / save switches instead, which is what the complexity was actually reporting.

The usage line

§ Follow vs snapshot calls this load-bearing rather than decorative, and it is: a committed edit to a project template changes what runs on a teammate's machine. TemplateUsage counts followers across every known project and at any depth inside composites, and the editor puts the line directly above Save. The other half is stated too — loops that snapshotted keep their brief — so "3 loops use this" is never read as "and the other four change too". Deleting a followed template says what actually happens rather than letting someone assume it stops a nightly run.

Note on the mock's field labels

The canvas's applied-state artboard labels the Goal fields "Prompt" and "Done when". README.md §3 specifies "What does done look like?" and "Done check", and PROMPT_TEMPLATES.md says the fields are "exactly as specced in README.md § 3" — so those are read as designer shorthand, not a rename, and the labels are unchanged. Worth a second opinion.

Verification

  • xcodebuild -workspace graphcode.xcworkspace -scheme graphcode buildsucceeds
  • test1356 tests in 147 suites pass (up from 1328/144 on the first draft; 28 new)
  • swiftlint lint0 errors, with the original .swiftlint.yml
  • swift format lint --strict — clean on every file this branch touches

Not verified visually — this environment has no GUI automation, so the SwiftUI layout of the picker, the applied strip and the editor wants a human eye before merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MJR8Wuwc4qnaSBUMcYf7Af

scgopi and others added 6 commits September 1, 2026 08:04
Implements PROMPT_TEMPLATES.md (New Designs v4): a Templates button and ⌘T
picker in the New loop dialog, an applied state that never gains a shape
silently, markdown template files in ~/.graphcode/templates (read also from
<project>/.graphcode/templates), Save as template, and follow-vs-snapshot —
timed and composite loops re-read their template on the next run; main, goal
and turn loops snapshot at creation.

Includes the fixes from the design review of the first draft:

Data loss and correctness
- `TemplateStorage.move` wrote the copy and then deleted "the original" at the
  same path when a project relocation fell back to home, destroying the only
  copy. The move that isn't a move now stops before writing.
- Saving no longer overwrites a different template that slugged onto the same
  filename; the second takes the next free name. Same guard on `move`.
- A picker row click applied whatever the keyboard was on rather than the row
  clicked.
- "Save as Template…" from a card's context menu set state that nothing
  presented — the only sheet host lived inside the New loop dialog. Both the
  canvas and the overview now host it, with a quiet notice bar for the path.
- A following composite re-identified its whole sub-graph on every pilot, so
  the previous pass's child sessions were orphaned (node ids are zmx session
  names) and their memory stranded. It now compares what a human authored and
  only replaces when the template actually changed, tearing down the outgoing
  children when it does.

Fidelity to the spec
- Asking whether a project can take a template no longer creates
  `.graphcode/templates` in the checkout; nothing lands there by default.
- The use count counts uses, not saves.
- A resolve that flips a follow to `missing` now broadcasts, so the warning
  reaches the card; an unchanged resolve does not, so the timer-driven sweeps
  don't persist the graph every tick.
- Templates carry an agent only when they name one, so "absent means the app's
  own default stands" is reachable and a shared template doesn't pin an agent
  a teammate may not have.
- The relocation notice reports where the file actually landed.
- A sheet rename reaches the filename (`PromptTemplate.renamed(to:)`).
- The directory watch re-arms, so a `git pull` that creates
  `.graphcode/templates` is picked up rather than never seen.
- An unwritable project is dropped from the save sheet rather than
  ineffectually `.disabled` on a Picker tag.
- Tokens left in a done check, metric or branch block Start too; the save
  notice can be dismissed; ✕ restores the pre-template draft after two applies.

Also: the repo-wide `cyclomatic_complexity` relaxation is dropped — the
reducer is split into picker/apply/save switches instead, which is what the
complexity was actually reporting.

17 new tests (1345 total, up from 1328), covering every fix above.
make check: swiftlint 0 errors, swift-format clean on all touched files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJR8Wuwc4qnaSBUMcYf7Af
…copy

Checked the implementation against `GraphCode New Loop v4.dc.html` (and
README.md § 3 / MAIN_LOOP.md) rather than PROMPT_TEMPLATES.md alone. The
markdown is terse where the canvas is exact, and three things were off.

- **`⇥` now walks the unfilled tokens.** The spec says tokens "are ⇥-navigable"
  and the canvas prints "One token left to fill · ⇥ to jump to it"; nothing
  implemented it, so the promise was going to be a lie. Tab cycles the fields
  still holding a `{token}` in form order — brief, done check, metric, branch —
  and is swallowed only while one is unfilled, so it stays the ordinary focus
  walk everywhere else. The focus request is per-field and consumed by whichever
  field answers it.
- **The applied strip says what the canvas says.** One shared verb ("sets a done
  check and a worktree", not "sets … and sets …"), and the sentence finishes
  with the design's own "Undo the shape to keep just the prompt."
- **`Detach` sits beside the Follows chip on the card**, which is where the
  canvas puts it — the fact and the way out of it belong together. It stays in
  the context menu too.

README § 3 confirms the Goal form's labels are "What does done look like?" and
"Done check"; the canvas's "Prompt" / "Done when" in the applied artboard is the
designer's shorthand, not a field rename, so those are left alone.

1348 tests (up from 1345). swiftlint 0 errors, swift-format clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJR8Wuwc4qnaSBUMcYf7Af
…d-bearing

Closes the last gap from the v4 design canvas. The ⌘T picker's "Manage…" footer
pointed at a Settings list that could only reveal and delete; a template's own
text could not be changed anywhere in the app.

- `TemplateEditorView` — name, shape, brief, and the shape's own settings (done
  check + metric, cadence, pause scope) plus the branch. Saving goes through the
  new `TemplateStorage.update(_:replacing:)`, which **keeps the id**: that is
  what makes every timed and composite loop following the template go on
  following it across an edit. A rename moves the file, and removes the old one
  only once the new one is written and only when it is genuinely a different
  file — the trap `move` had.

- `TemplateUsage` — the number behind the design's sentence, "3 scheduled loops
  use this — they'll pick up changes on their next run", counted across every
  known project and at any depth inside composites. It sits directly above Save,
  because the reach of an edit has to be read on the way to the button. The
  other half is stated too: loops that snapshotted keep their brief, so "3 loops
  use this" is never read as "and the other four change too". A project template
  adds the line about a committed edit reaching a teammate's machine.

- The Settings list now shows **project templates as well as home's**, marked
  "in project". A list that hid them made "project templates are read too"
  invisible in the one place templates are managed. Project paths and graphs come
  from `ProjectPersistence` — Settings has no store, and wiring the whole app's
  state in to count integers would be the larger change.

- Deleting a followed template now says what actually happens: the loops keep
  running on the brief they last read and warn on their cards. Deleting is not a
  way to stop a nightly run, and the dialog no longer lets anyone assume it is.

`TemplatesSettingsSection` moved to its own file with the editor;
`SettingsView.swift` is back under the file-length guideline.

1356 tests (up from 1348). swiftlint 0 errors, swift-format clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJR8Wuwc4qnaSBUMcYf7Af
# Conflicts:
#	GraphcodeKit/Sources/Domain/LoopNode.swift
#	GraphcodeKit/Sources/GraphStore.swift
#	GraphcodeKit/Sources/ProjectRegistry.swift
#	graphcode/Sources/Features/Project/ProjectCanvasCards.swift
`swift build` compiles GraphcodeKit for CI on Linux, where
`DispatchSource.makeFileSystemObjectSource` and `O_EVTONLY` do not exist. The
watch is a UI affordance — it keeps the ⌘T picker's library live — and the only
thing that reads templates on Linux is the daemon's by-id resolve, which
re-reads the directory on every call. So the watcher is Darwin-only and the
Linux stream finishes immediately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJR8Wuwc4qnaSBUMcYf7Af
The Linux CI job builds GraphcodeKit through SwiftPM, which does not enable
bare-slash-regex; Xcode does, by default, which is why this compiled locally
and failed in CI. `{token}` is now an NSRegularExpression — the same type the
file already uses to recover token values — behind a `tokenRanges(in:)` helper
both call sites share.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJR8Wuwc4qnaSBUMcYf7Af
@scgopi
scgopi merged commit 6f5731d into main Sep 1, 2026
1 check passed
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