Skip to content

feat(web): playground composer for typed messages + Save flow (Plan C)#13

Merged
gaurav0107 merged 5 commits into
mainfrom
feat/playground-messages-web
Jun 7, 2026
Merged

feat(web): playground composer for typed messages + Save flow (Plan C)#13
gaurav0107 merged 5 commits into
mainfrom
feat/playground-messages-web

Conversation

@gaurav0107

Copy link
Copy Markdown
Collaborator

Summary

Plan C, the user-visible half of the playground messages redesign.
The composer now edits a list of typed messages (system + human),
detects `{{ var }}` substitutions across all messages and dedupes
them into the Inputs panel, and saves the result as a versioned
prompt.

Companion to PRs #11 (Plan A — schema) and #12 (Plan B — api).

What changed

  • PromptOption type carries template_messages: Message[] alongside the legacy template string. The page mapper falls back to wrapping legacy template as a single human message during a deploy gap.
  • extractVariablesFromMessages dedupes {{ var }} matches across the union, preserves first-seen order.
  • <MessageEditor> — per-message card with role pill + dropdown, content textarea (auto-grows on newlines), reorder ↑/↓, delete ×. Uses design tokens (no hardcoded hex / radii).
  • Composer rewritemessages: Message[] state replaces rawMode/rawTemplate. The prompt panel renders one editor per turn plus an "+ Add message" button. Run sends raw_messages (or prompt_version_id when a saved version is loaded).
  • Save flow<SavePromptForm> (inline name+slug, slug auto-derived). Two paths: existing prompt → POST /v1/prompts/{id}/versions (api short-circuits identical bodies at HTTP 200); new prompt → POST /v1/prompts then v1, switch to loaded state. An Unload button detaches a loaded version without resetting the composer.

Files

  • web/src/components/playground/MessageEditor.tsx — per-message card
  • web/src/components/playground/SavePromptForm.tsx — inline name+slug
  • web/src/components/PlaygroundClient.tsx — composer rewrite + save wiring
  • web/src/app/playground/page.tsxPromptOption mapper carries template_messages

Test plan

  • `pnpm typecheck` clean
  • `pnpm lint` clean
  • `pnpm build` clean (Next.js production build)
  • CI green
  • Manual UI walkthrough (post-merge or pre-merge): edit, reorder, delete, +Add message, Run, Save (loaded → v2), Save (new → inline form), Save (no-op short circuit returns 200)
  • Merge

Out of scope

Compare mode (two prompt panels) — deferred per spec decision 4. Single prompt only. The existing `mode` toggle and `modelB` machinery is left in place for that follow-up.

Companion docs

  • Spec: `docs/superpowers/specs/2026-06-07-playground-messages-redesign-design.md`
  • Plan: `docs/superpowers/plans/2026-06-07-playground-C-web.md` (gitignored)

🤖 Generated with Claude Code

…ages

Adds the structured Message[] field alongside the legacy single-string
template so the composer (next task) can read messages directly. The
page mapper falls back to wrapping the legacy template as a single
human message when template_messages is absent (defensive - Plan B's
api always sends both, but old api versions during a deploy gap may
not).

No behavior change yet; the composer still reads template until Task
4 rewrites it.

Signed-off-by: gaurav0107 <gauravdubey0107@gmail.com>
Sister to extractVariables(template). Used by the composer (next
task) to keep the Inputs panel in sync as the user edits System and
Human bodies. Preserves first-seen order so the Inputs panel doesn't
reshuffle while the user is mid-edit.

Test file deferred until a JS test runner is configured for the
web workspace.

Signed-off-by: gaurav0107 <gauravdubey0107@gmail.com>
Self-contained, callback-driven. Renders the role pill (system/human
dropdown), content textarea (auto-grows on newlines), and the reorder
+ delete affordances. Uses the design system tokens (--surface,
--border, --r-3, .btn .btn-ghost .btn-sm) per DESIGN.md - no
hardcoded hex values or one-off radii.

Used by the composer rewrite in the next task. canDelete prop lets
the caller block the last delete so the composer can never end up
with zero messages.

Signed-off-by: gaurav0107 <gauravdubey0107@gmail.com>
Replaces the rawMode/rawTemplate dichotomy with a single messages:
Message[] state. The prompt panel renders one MessageEditor per turn
(reorder, delete, role pill, content textarea) plus an + Add message
button. Variable detection runs across all messages and dedupes the
union into the Inputs panel.

The Run payload sends raw_messages when no version is loaded, or
prompt_version_id when a saved version is loaded - Plan B's api
accepts both shapes during the back-compat window. Loading a saved
version replaces the editable messages and pins loadedVersionId so
the run goes through the catalog path; an Unload button detaches
without resetting the composer.

Save is wired in the next task. Typecheck + lint clean.

Signed-off-by: gaurav0107 <gauravdubey0107@gmail.com>
Two paths:

- Loaded version: POST /v1/prompts/{id}/versions with the current
  messages. Plan B's api short-circuits identical messages and returns
  the existing row (HTTP 200) - re-saving an unchanged composer is a
  cheap no-op. The composer pins loadedVersionId to the response id so
  subsequent saves land as v2/v3/...

- No version loaded: open the inline name+slug form (slug auto-derived
  from name with override). On submit: create the prompt, post v1, and
  switch the composer to the loaded state.

The Save button sits in the prompt panel header next to Load/Version
pickers. Errors render inline above the +Add message button. The
canSave gate requires non-empty messages and no in-flight save or run.

The proxy routes /api/prompts and /api/prompts/{id}/versions already
exist - no new server code needed.

Signed-off-by: gaurav0107 <gauravdubey0107@gmail.com>
@gaurav0107 gaurav0107 merged commit 1ec6503 into main Jun 7, 2026
3 checks 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