Skip to content

feat: commit UI#5268

Merged
mchalapuk merged 45 commits into
mainfrom
feat/commit-ui
Jun 12, 2026
Merged

feat: commit UI#5268
mchalapuk merged 45 commits into
mainfrom
feat/commit-ui

Conversation

@mchalapuk

@mchalapuk mchalapuk commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Introduces server-side draft staging (workflow_staging) and a stageCommitPublish editing loop for multi-draft apps.

  • UI: Canvas/console edits stage to workflow_staging; Commit (orange) materializes the draft version row; Publish (blue) promotes to live. Orange/blue indicators show uncommitted vs ready-to-publish state.
  • CLI and agent tools: Never operate on staging; they commit directly to the draft version row.
  • Agent: Always operates on staging; never commits.

Closes #5328
Resolves #5355

Changes

Backend

  • New workflow_staging table and model.
  • Staging RPCs: StageCanvasRepositoryFile, DiscardCanvasStaging, CommitCanvasStaging.
  • Effective spec reads via repository file GET with ?stage=true (staging overlay → committed draft row).
  • Auth: staging/discard/commit-staging/auto-layout and direct repository commits map to update_version (agent-allowed).

UI

  • Spec autosave stages canvas.yaml / console.yaml instead of writing the version row directly.
  • Header: Reset (uncommitted), Commit, Publish; draft change dots (orange/blue).
  • Files tab wired through repository file staging.
  • DescribeCanvas kept for metadata/runtime status; draft spec from effective YAML.

CLI

  • No changes in commands.
  • Never operates on staging. superplane apps canvas get --draft-id reads only committed changes. superplane apps canvas update --draft-id always commits.

Agents

  • update_draft commits directly to the UI staging area of the agent's private draft version row via StageCanvasRepositoryFiles. Agent never commits or publishes.

Test Plan

UI — staging loop

  • Open a draft, add/move a node → orange uncommitted indicator; live unchanged.
  • Commit → staging cleared; draft version has the edit; blue “ready to publish” when appropriate.
  • Publish → node appears on live canvas; runs still work.
  • Reset with uncommitted staging → reverts to last committed draft; confirm dialog.
  • Switch between two drafts → staged edits isolated per draft.
  • Files tab: canvas.yaml / console.yaml match export modal; edit → stage → commit

CLI

  • superplane apps canvas get <app> --draft-id <id> -o yaml → committed draft only
  • superplane apps canvas update --draft-id <id> -f canvas.yaml → commits

Agents

  • Builder agent commits to its private draft (staging)
  • After agent edit, UI shows draft in versions list; user then Commits

@superplanehq-integration

Copy link
Copy Markdown

👋 Commands for maintainers:

  • /sp start - Start an ephemeral machine (takes ~30s)
  • /sp stop - Stop a running machine (auto-executed on pr close)

mchalapuk and others added 7 commits June 11, 2026 14:21
Introduce the DB-native uncommitted edit layer keyed by draft
workflow_versions.id. Each row stores staged YAML for a path on a draft;
rows cascade away when the draft version is deleted.

Add InTransaction CRUD: upsert path content, mark path deleted (keeps the
row so effective read can tell unstaged from staged removal), list,
discard (hard-delete to revert staging), and has-staging checks. Document
why staged deletion is soft while discard is hard.

Includes migration, regenerated structure.sql, model tests for upsert/
discard/isolation, and cascade-on-draft-delete.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Replace direct spec publish with a git-like stage → commit → publish flow
for canvas, console, and repository files. Backend adds CommitCanvasStaging
and DiscardCanvasStaging (spec into the version row, arbitrary paths into
git), wires authorization, and removes CommitCanvasRepositoryFiles.

Frontend adds instant local staging indicators (orange uncommitted, blue
committed vs live), draft branch status badges, and tab/header dots.
Files edits route through staging with Reset/Commit; fix first-keystroke
loss in FileMonacoEditor. Publishable state diffs committed YAML baselines
against live so ready-to-publish badges and blue dots stay accurate after
commit.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Add canvas_staging_commit_publish_test for the stage→commit→publish loop
and per-draft staging isolation. Extend shared canvas steps with staging,
publish, and draft assertion helpers. Fix agent preamble expectations in
service_test for operator-mode session context.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Rewire the superplane_canvas custom tool's update_draft action to stage
spec-file edits onto the agent's private draft (then auto-layout the staged
canvas) rather than committing them directly. This mirrors the human
--stage-only CLI flow and respects the agent permission boundary, where
commit and publish stay user-driven. Usage limits are now enforced at commit
time, so update_draft no longer fails on node limits at stage time; the tool
test is updated to assert successful staging accordingly.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Draft staging changes flashed orange then reverted to blue on refresh,
and the "UNCOMMITTED CHANGES" badge stuck after committing console edits.
Both came from the frontend cache treating staged and committed content
as interchangeable.

- Separate staged reads from committed reads with dedicated query keys
  (versionStagedDetail, consoleStaged) so a committed (stage=false) fetch
  can no longer overwrite the editor's pending edits. Fixes the
  orange-flash-on-refresh for the Canvas and Console tabs.
- Only switch the Files tab to local staging detection once an in-session
  edit is actually observed, so a fresh mount no longer hides persisted
  server-side staging.
- Compare console panels through a key-order-insensitive snapshot. The
  backend serializes panel content with alphabetical map keys while the
  editor keeps insertion order; a plain JSON.stringify reported these
  identical consoles as different, leaving the badge orange after commit
  until a full refresh.
- Guard async console save callbacks with a mutation generation so a
  stale in-flight autosave can't write staged data back into the cache
  after a commit/reset.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
The console tab's diff surfaces only reflected committed changes, and a
draft whose only change was the console lost its "READY TO PUBLISH" badge
once it was no longer the active draft.

- Drive the console X-ray, diff summary, and "Show diff" modal from the
  effective draft console (committed + uncommitted staged edits) instead
  of the committed-only console, matching the canvas tab. Publishable
  indicators still diff the committed console against live, so publish
  semantics are unchanged.
- Factor per-draft console changes into the draft branch publishable
  status: diff each inactive draft's committed console against live in
  addition to the existing graph diff, so console-only drafts keep their
  "READY TO PUBLISH" badge when inactive.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
While a commit is in flight the button no longer swaps to "Committing…"
(which changed its width). The progress spinner now renders to the left
of the Reset button, and both Reset and Commit are disabled until the
commit response returns. The Reset button label is shortened to "Reset",
with "Reset to last commit" moved into a hover tooltip (2s delay).

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
mchalapuk and others added 10 commits June 11, 2026 14:52
This branch's Canvas model has no IsTemplate field (templates were removed
from the database), so the staging precondition check no longer compiles.
Remove the guard to keep StageRepositorySpecFileOperations building.

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Restore the CommitCanvasRepositoryFiles RPC alongside the staging flow so
the CLI (`canvas update`, `console set`) writes the draft version row
directly instead of going through stage -> commit. The direct commit now
discards any existing workflow_staging rows for the target version in the
same transaction as the version-row write, so a CLI/API commit always
supersedes pending staging atomically.

- proto: re-add CommitCanvasRepositoryFiles RPC + request/response messages
- backend: restore handler, service method, and authorization entry; thread
  autoLayout + discardStaging through ApplyRepositorySpecFileOperations into
  UpdateCanvasVersionWithUsage and UpdateConsole
- cli: restore CommitRepositoryFiles/CommitRepositorySpecFile and point
  `canvas update` / `console set` back at the direct-commit path
- tests: restore commit_canvas_repository_files_test.go and cover in-tx
  staging discard

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Canvas edits now stage to workflow_staging before commit, so tests that
publish draft changes must call CommitAndPublish explicitly. Add shared
helpers to read effective draft spec from staged YAML and update autosave
assertions that previously read only the committed version row.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Clarify that canvas edits stage to workflow_staging, agents stage only,
and E2E tests must commit before publish when promoting draft changes.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Extract draft staging indicators, graph diff, and commit/reset actions
from AppPage into dedicated hooks, fix files editor exhaustive-deps, and
keep index.tsx within eslint line/complexity budgets.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Propose Change stays disabled until the committed draft differs from live;
canvas edits only stage until Commit, so change-request tests must commit
before waiting for the propose action.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Move echo-release guards and draft staging composition out of AppPage so
Prettier formatting no longer pushes index.tsx over the max-lines budget.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
@mchalapuk mchalapuk added the feature Completely new feature/functionality label Jun 11, 2026
Comment thread docs/contributing/agent-tools.md Outdated
Comment thread docs/contributing/agent-tools.md Outdated
Comment thread pkg/authorization/interceptor_test.go Outdated
mchalapuk and others added 7 commits June 12, 2026 01:36
…ions

Extract helpers in useDraftStagingIndicators and the loop mapper so
check.lint.ui stays within the ESLint complexity budget.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Agents commit draft edits directly (like the CLI), not via workflow_staging.
Map staging, discard, commit-staging, and auto-layout RPCs to update_version
so authorization matches the draft-version permission model.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Clarify the UI stage→commit loop vs CLI/agent direct commits, and note
that uncommitted staging must be committed before change requests.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Document access/read_runtime actions and note that update_draft uses
CommitCanvasRepositoryFiles instead of workflow_staging.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
… budget

Split committed-content state and staging sync effects out of useEditor
so the hook stays within the max-statements lint cap after the draft
staging work.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
The Files tab Diff was unreliable. This addresses four issues:

- Empty diffs: the diff used loadedContentByPath (staged content, since
  draft reads use stage=true) as the baseline, so after autosave the
  baseline equaled the edit. Use committedContentByPath (stage=false) as
  the baseline instead.
- Stale Diff button: reverting a file to its original left a phantom
  pending change because pending detection preferred the staged
  loadedContentByPath over the committed baseline. Prefer committed.
- Missing spec and post-refresh changes: canvas.yaml/console.yaml edits
  autosave into the staging layer (not pendingChanges), and repository
  staging outlives a page refresh while in-memory pendingChanges do not.
  Surface all server-staged paths not covered by a pending change via a
  committed-vs-effective (stage=false vs stage=true) read.
- Noisy spec diffs: committed YAML is server-serialized and staged YAML
  is client-serialized, so canvas.yaml showed spurious key-ordering,
  quote-style, and isCollapsed:false differences. Normalize both sides to
  a canonical form before diffing so only real edits appear.

Add tests for the spec YAML normalization.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>

@forestileao forestileao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things I want to flag before this goes in, mostly around the commit/staging rules only being enforced in the UI, plus a couple of races in the file-staging hooks. Details inline.

Comment thread docs/contributing/canvas-change-requests.md
Comment thread pkg/public/repository_file_download.go
Comment thread pkg/grpc/actions/canvases/commit_canvas_staging.go
Comment thread web_src/src/pages/app/useDraftStagingActions.ts Outdated
Comment thread web_src/src/pages/app/files/useRepositoryFileStaging.ts
Comment thread web_src/src/pages/app/useDraftStagingIndicators.ts
Route the Files tab diff, committed-baseline lookups, and committed file
selection reads through React Query so already-fetched content is reused
instead of refetched.

- Add a cached `repositoryFileContent` query (prefix-extending
  `repositoryFile`, so existing invalidations clear it) and a
  `fetchRepositoryFileContentCached` helper. Committed (stage=false)
  content is cached; staged (stage=true) content always refetches so the
  diff stays correct.
- Have `useStagedFileDiffs` and `useEditorLifecycle` read through the
  cached helper, deduping the committed read and reusing diff content
  across re-opens.
- Make committed baselines reuse the canonical `versionDetail`/`console`
  caches, collapsing the duplicate committed `console.yaml` read shared
  with the draft console query into a single request.

Signed-off-by: <Your Name> <your@email>

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	web_src/src/pages/app/index.tsx
#	web_src/src/ui/CanvasPage/components/CanvasModeToggle.spec.tsx
#	web_src/src/ui/CanvasPage/components/CanvasModeToggle.tsx
Comment thread db/migrations/20260610003157_workflow-staging.up.sql Outdated
Comment thread protos/canvases.proto Outdated
mchalapuk and others added 10 commits June 12, 2026 22:23
Merging main shifted three per-rule lint counts one over budget. Resolve
without raising the baseline, via behavior-preserving refactors:

- usePageTitle: derive the title with useMemo and depend on the stable
  string, removing the exhaustive-deps eslint-disable-next-line.
- TextFieldRenderer: hoist the static editor options to a module-level
  CODE_EDITOR_OPTIONS const so CodeTextFieldRenderer drops under the
  max-lines-per-function limit.
- parseDefaultValues: collapse the number case into a single ternary to
  stay under max-statements.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
The agent no longer shells out to the CLI, so its tools now read and write
through the same staging layer as the UI editor instead of committing
directly to the draft version row.

- read: serve effective staged draft content (staged edits when present,
  otherwise the committed draft) and derive the summary from that YAML
- update_draft: validate up front, then save edits as pending staged
  changes and run auto-layout via the staging path instead of committing
- add ParseAndValidateCanvasYAML / ValidateConsoleYAML helpers and a
  staged-canvas summary, since staging never materializes the version row
- DraftActionsWidget: commit staged edits before publishing so the agent's
  staged changes are included (publish ignores uncommitted staging)
- update agent preamble and contributing docs to match the staging flow

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Gate staged reads behind the draft owner, revert git on failed spec
commits, flush file staging before Commit, and add debounce generation
guards. Rename workflow_staging to workflow_staged_files and
StagingState to StagingSummary.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>
Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Pick up the workflow_staging -> workflow_staged_files constraint renames
(primary key, unique, and foreign keys) after recreating the dev/test
databases.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Merge the near-identical TYPE_ACTION and TYPE_TRIGGER branches in
getNodeEditData (src/pages/app/index.tsx), which differed only in the
metadata map they read from. This drops the function below the
max-statements limit (taking the over-budget count from 73 back to 72)
and trims the file from 4970 to 4966 lines (under the 4968 max-lines
maximum), resolving both budget regressions flagged by
make check.lint.ui.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
Apply gofmt struct-field alignment in describe_canvas_version.go and
Prettier formatting in src/pages/app/index.tsx so make check.format.go
and make check.format.js pass.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
The original 20260610003157 migration already creates the table as
workflow_staged_files, so the later rename migration's guarded DO block
was always a no-op. The workflow_staging table was never deployed, so
drop the rename migration and point structure.sql at the new latest
migration version.

Signed-off-by: Maciej Chałapuk <maciej@chalapuk.pl>
Co-authored-by: Cursor <cursoragent@cursor.com>
@mchalapuk mchalapuk merged commit 6002307 into main Jun 12, 2026
6 checks passed
@mchalapuk mchalapuk deleted the feat/commit-ui branch June 12, 2026 23:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Completely new feature/functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adding a draft with CLI doesn't immediately show it in the UI Git SoT Phase #4: Commit UI

3 participants