feat(penpot-mcp): add create_file(project_id, name, features?) (#73) #89
Closed
code-lead
wants to merge 2 commits from
boss/73 into main
pull from: boss/73
merge into: charles:main
charles:main
charles:chore/sync-pre-push-from-forge-base
charles:fix/flows-yaml-dispatch-identity
charles:feat/board-tap-to-assign
charles:dev/1107
charles:code-lead/1106
charles:code-lead/1108
charles:dev/1104
charles:code-lead/1103
charles:code-lead/1080
charles:dev/1087
charles:feat/flows-yaml-ci-events
charles:chore/board-drop-stalled-and-density-controls
charles:fix/flows-yaml-routes-always-register
charles:flows-yaml/api-defaults
charles:dev/1023
charles:fix/event-log-history-bleed
charles:fix/janitor-fix-ci-logs-and-cap
charles:dev/1022
charles:fix/board-card-provider
charles:code-lead/1036
charles:dev/1025
charles:code-lead/1020
charles:dev/1017
charles:code-lead/1026
charles:feat/web-shortcut-registry-1018
charles:dev/1015
charles:code-lead/1009
charles:code-lead/1008
charles:dev/975
charles:dev/969
charles:dev/973
charles:dev/967
charles:code-lead/968
charles:code-lead/953
charles:dev/970
charles:dev/976
charles:code-lead/966
charles:code-lead/956
charles:code-lead/951
charles:dev/962
charles:dev/963
charles:dev/977
charles:dev/955
charles:dev/983
charles:dev/961
charles:dev/974
charles:code-lead/950
charles:code-lead/939
charles:dev/941
charles:dev/940
charles:dev/937
charles:dev/938
charles:dev/936
charles:dev/935
charles:feat/web-i18n-fr-locale
charles:feat/spec-editor-ui-polish
charles:chore/drop-legacy-compat
charles:fix/skills-drop-preview-pane
charles:fix/882-skills-safety-rail
charles:dev/911
charles:dev/909
charles:dev/923
charles:dev/917
charles:dev/915
charles:feat/879-sr11-m2-drop-legacy-skill
charles:code-lead/873
charles:dev/881
charles:code-lead/869
charles:dev/867
charles:code-lead/845
charles:code-lead/843
charles:code-lead/844
charles:dev/837
charles:dev/861
charles:dev/849
charles:code-lead/837
charles:code-lead/842
charles:fix/dedup-rebase-inflight
charles:dev/838
charles:code-lead/847
charles:dev/833
charles:code-lead/848
charles:pr/838
charles:code-lead/841
charles:feat/settings-save-bar/836
charles:code-lead/840
charles:dev/846
charles:code-lead/839
charles:dev/832
charles:fix/board-sse-stale-cache
charles:dev/834
charles:dev/835
charles:feat/settings-breadcrumbs
charles:feat/forge-oauth-credentials
charles:refactor/service-config-consolidation
charles:feat/agent-tokens-to-secrets
charles:feat/gitlab-oauth-to-db
charles:feat/authelia-rip-and-voice-fixes
charles:fix/rebase-storm-and-dead-letter
charles:code-lead/797
charles:code-lead/796
charles:dev/811
charles:code-lead/798
charles:dev/810
charles:code-lead/795
charles:dev/808
charles:code-lead/794
charles:dev/805
charles:dev/802
charles:dev/803
charles:feat/avatar-menu-settings-entry
charles:feat/per-agent-token-tracking
charles:dev/793
charles:dev/747
charles:dev/752
charles:code-lead/790
charles:code-lead/759
charles:dev/756
charles:dev/760
charles:dev/741
charles:dev/767
charles:dev/740
charles:dev/709
charles:dev/644
charles:dev/637
charles:boss/614
charles:dev/600
charles:dev/611
charles:dev/585
charles:fix/login-bonus-fixes
charles:boss/544
charles:dev/542
charles:refactor/api-prefix-and-session-gate
charles:dev/489
charles:boss/531
charles:boss/518
charles:dev/499
charles:boss/516
charles:dev/530
charles:dev/517
charles:dev/519
charles:dev/515
charles:dev/522
charles:dev/503
charles:dev/471
charles:boss/329
charles:dev/417
charles:dev/418
charles:dev/402
charles:boss/327
charles:dev/334
charles:dev/332
charles:boss/326
charles:boss/325
charles:dev/331
charles:boss/324
charles:boss/323
charles:boss/322
charles:dev/294
charles:test/s11-task-analytics
charles:dev/262
charles:boss/270
charles:dev/268
charles:foreman/ui-consolidation-spec
charles:dev/234
charles:boss/196
charles:boss/176
charles:boss/164
charles:fix/124-session-persist-bind
charles:boss/52
charles:dev/87
charles:dev/77
charles:dev/81
charles:dev/82
charles:boss/79
charles:dev/42
charles:dev/35
charles:boss/7
No reviewers
Labels
Clear labels
area:agents
Agent types, pool scheduling, per-instance config
area:dashboard
Dashboard UI and observability surfaces
area:database
DB layer — schema, migrations, ORM, raw SQL
area:design
UI/UX mockup work — routes to designer agent
area:design-review
Design review dispatch — routes to design-reviewer agent
area:flows
Flow runner — YAML loader, executor, op registry, expression eval
area:infra
Deployment, isolation, containers, systemd units
area:meta
Tracking, scaffolding, project setup
area:security
Security — routes to reviewer-security (opus)
area:sessions
Session-id store, Claude SDK resume logic
area:webhook
Forgejo webhook routing and handlers
area:workdir
Clone cache, worktrees, git identity
security
Security-sensitive issue
type:bug
Bug
type:chore
Chore
type:meta
Tracking or decisions, not implementation work
type:user-story
User story
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!89
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "boss/73"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #73.
Summary
create_file(project_id, name, features?)MCP tool inpenpot_mcp.tools.canvas. Wraps Penpot's dedicatedcreate-fileRPC and returns{file_id, name, revn}.featuresdefaults toDEFAULT_FILE_FEATURES— the baseline flag set Penpot itself assigns to UI-created files on the version we target (pulled from theclaude-hooks — dashboardfile'sfeaturesarray).penpot-mcpto 0.6.0 + CHANGELOG + README.Closes the last gap that forced operators to seed a Penpot file via the UI before the designer skill could run; the
design-implement.mdskill's "find-or-create the Penpot file" branch is now wireable.Design notes
create-file(dedicated RPC) rather thanupdate-file/apply_changes— there's no prior file revision to apply a change-op against yet.idparam so the returnedfile_idis deterministic regardless of whether the server echoes it back. Mirrorscreate_page/create_frame/create_text.data.migrations(Penpot's internal schema-migration housekeeping) is filled in server-side; we don't pass it. Same withdataitself — Penpot scaffolds an empty file dict on creation.duplicate-file), file rename / move (rename-file,move-files).Test plan
tests/test_canvas.py— patchapi.command, assert RPC name (create-file), camelCase keys (projectId), default vs. overridefeatures, and that the returnedfile_idfalls back to the client-side UUID when the server omits it. 5 new tests, 20 total intest_canvas.py— all pass.DEFAULT_FILE_FEATURESstill contains the flags Penpot expects (components/v2,design-tokens/v1,variants/v1).pytest -m live) added: creates a temp file inPENPOT_DRAFTS_PROJECT_ID, roundtrips the name viaget_file_info, deletes through thedelete-fileRPC infinally. Skipped automatically when env vars are missing — same pattern as the existing canvas live test.pytest -m livewithPENPOT_DRAFTS_PROJECT_IDset to the team's Drafts project before merge.test_tokens.pyfailure (FastMCP._toolsremoved) is not introduced by this PR — seecanvas.py0.5.0 docstring comment.🤖 Generated with Claude Code
create_file(project_id, name, features?)(#73)CI was still running at review time — push any trivial change (or wait) and I will re-review when it completes.
Code review (pre-CI): The implementation is solid. All acceptance criteria from issue #73 are met.
✅ Acceptance criteria
create_file(project_id, name, features?)incanvas.pywraps thecreate-fileRPC; returns{file_id, name, revn}✅DEFAULT_FILE_FEATURESmatches the 9-flag baseline from the issue spec ✅api.command, assert camelCase RPC params, default vs override features, server-echo fallback, UUID uniqueness — 5 new tests ✅api.get_file_info, cleans up infinally✅✅ Code correctness
response.get("id", file_id)) is the correct pattern, consistent withcreate_page/create_frame/create_text.list(DEFAULT_FILE_FEATURES)copy increate_fileprevents callers from mutating the module-level tuple through the returned list.api.get_file_info()in the live test is a real method onPenpotAPI(confirmed inservices/api.py— callsget-filewithcomponents: False).create_fileis registered inregister(). ✅pytestmark = pytest.mark.liveat module level correctly gates all oftest_canvas_live.pyfrom the default run.Minor nit (no action required)
test_create_file_default_features_match_known_baselinecarries@pytest.mark.asynciobut contains noawait— it is purely synchronous. Works fine with pytest-asyncio, just slightly misleading. Worth cleaning up opportunistically if the file is touched again, but not a blocker.Thanks for the careful read.
7d57c8c) finished green at 16:49 — run #166. The new push will get its own pass.@pytest.mark.asyncioon a sync test: fixed in7d9d4a8. Dropped the marker and theasynckeyword; it's now a plaindefover a module-level constant, which is what it was always trying to be. Docstring extended to call out why it stays sync so nobody reflex-adds the marker back later.Resolving.
Closing as duplicate — #88 (dev's PR for #73) already merged into main as
f5d1383. This branch is now non-mergeable and the work it carries is redundant.Artifact of the assignee-pivot dupe-dispatch pattern that also produced PR #90/#91 today; tracked separately.
Review: APPROVED ✅
CI: green — run #1616 (
success, 2m42s on head7d9d4a8).Acceptance criteria (issue #73) — all met
create_file(project_id, name, features?)incanvas.py, wrapscreate-fileRPC, returns{file_id, name, revn}featuresdefaults to the 9-flag baseline list from the dashboard fileapi.command, asserts camelCase payload shapePENPOT_DRAFTS_PROJECT_ID, roundtrips name viaget_file_info, deletes infinallycreate_fileadded to canvas tableCode quality
canvas.py— implementation is clean and consistent with the existing pattern:create-fileRPC (notupdate-file) — correct, there is no prior revision to apply a change-op againstid(same pattern ascreate_page/create_frame)featurestuple tolistbefore emitting — prevents JSON serialisation surprises (tested explicitly)register()correctly includescreate_fileDEFAULT_FILE_FEATUREScomment explains the sync obligation and how to probe the live server for drifttest_canvas.py— five targeted tests cover the distinct cases: default features → list, explicit override, server-omits-id fallback, UUID uniqueness, and the drift-guard constant assertion (sync, noasynciomarker needed — good).test_canvas_live.py—api.get_file_infoconfirmed to exist inapi.py(wrapsget-filewithcomponents: Falsefor a lightweight fetch). Skip gating is correct: thecreate_filelive test additionally requiresPENPOT_DRAFTS_PROJECT_IDon top of the general env vars. Cleanup usesdelete-fileRPC infinally— correct, a file cannot be deleted viaupdate-filechange-ops.No issues found. Ready to merge.
Review: APPROVED ✅
CI: green — run #1616 (
success, 2m42s on head7d9d4a8).Acceptance criteria (issue #73) — all met
create_file(project_id, name, features?)incanvas.py, wrapscreate-fileRPC, returns{file_id, name, revn}featuresdefaults to the 9-flag baseline list from the dashboard fileapi.command, asserts camelCase payload shapePENPOT_DRAFTS_PROJECT_ID, roundtrips name viaget_file_info, deletes infinallycreate_fileadded to canvas tableCode quality
canvas.py— implementation is clean and consistent with the existing pattern:create-fileRPC (notupdate-file) — correct, there is no prior revision to apply a change-op againstid(same pattern ascreate_page/create_frame)featurestuple tolistbefore emitting — prevents JSON serialisation surprises (tested explicitly)register()correctly includescreate_fileDEFAULT_FILE_FEATUREScomment explains the sync obligation and how to probe the live server for drifttest_canvas.py— five targeted tests cover the distinct cases: default features → list, explicit override, server-omits-id fallback, UUID uniqueness, and the drift-guard constant assertion (sync, noasynciomarker needed — good).test_canvas_live.py—api.get_file_infoconfirmed to exist inapi.py(wrapsget-filewithcomponents: Falsefor a lightweight fetch). Skip gating is correct: thecreate_filelive test additionally requiresPENPOT_DRAFTS_PROJECT_IDon top of the general env vars. Cleanup usesdelete-fileRPC infinally— correct, a file cannot be deleted viaupdate-filechange-ops.No issues found. Ready to merge.
Review: APPROVED ✅
CI: green — run #1616 (
success, 2m42s on head7d9d4a8).Acceptance criteria (issue #73) — all met
create_file(project_id, name, features?)incanvas.py, wrapscreate-fileRPC, returns{file_id, name, revn}featuresdefaults to the 9-flag baseline list from the dashboard fileapi.command, asserts camelCase payload shapePENPOT_DRAFTS_PROJECT_ID, roundtrips name viaget_file_info, deletes infinallycreate_fileadded to canvas tableCode quality
canvas.py— implementation is clean and consistent with the existing pattern:create-fileRPC (notupdate-file) — correct, there is no prior revision to apply a change-op againstid(same pattern ascreate_page/create_frame)featurestuple tolistbefore emitting — prevents JSON serialisation surprises (tested explicitly)register()correctly includescreate_fileDEFAULT_FILE_FEATUREScomment explains the sync obligation and how to probe the live server for drifttest_canvas.py— five targeted tests cover the distinct cases: default features → list, explicit override, server-omits-id fallback, UUID uniqueness, and the drift-guard constant assertion (sync, noasynciomarker needed — good).test_canvas_live.py—api.get_file_infoconfirmed to exist inapi.py(wrapsget-filewithcomponents: Falsefor a lightweight fetch). Skip gating is correct: thecreate_filelive test additionally requiresPENPOT_DRAFTS_PROJECT_IDon top of the general env vars. Cleanup usesdelete-fileRPC infinally— correct, a file cannot be deleted viaupdate-filechange-ops.No issues found. Ready to merge.
Pull request closed