issue-207: Implement penpot_instance_component with id remapping #240
No reviewers
Labels
No labels
bug
chore
documentation
enhancement
feature
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
david/pi-extensions-and-skills!240
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/issue-207/implement-penpot-instance-component-with-id-remapp"
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?
207: Implement penpot_instance_component with id remapping
Closes #207 — 207.
Generated in 16m 59s.
Review findings
Now let's begin the fixes. First, the pure core (High 1) — stop transmitting the child shapes list:All fixes are applied and the penpot suite (including the new integration test) is green. The full
npm testreproduces only the documented pre-existing failure. Here is the annotated findings document.Review Findings — 207
Summary
All eight findings were addressed in a single pass. The clone no longer transmits each container's remapped
shapeslist (it sendsshapes: []and lets the server'sadd-shapemaintain the child list), with the pure assertions and a newinstanceCommit.test.tsintegration test updated to pin the applied tree; aCHANGELOG.mdentry was added; the deadunlinkedLibraryMessagepath was replaced with an internal-invariant error and the docs/description reworded; the missing integration coverage and open live gap were recorded infindings.md; and the four Low findings (unused import, deadLibrarySource.name, duplicated name-bounding helper, and the misleading "library file" label for local components) were resolved.npm teststays green apart from the pre-existing, unrelatedscripts/local-install.test.mjs:1875failure documented in the review.Critical
High
extensions/penpot/src/instance.ts:351— The clone forwards each source frame'sshapesarray into the stagedadd-objpayload, and the children are then staged as their ownadd-objchanges. Penpot's server-sideadd-shapeappends a child id to its parent'sshapeslist when the child'sadd-objis applied (that is the behaviour the repo's own stub model documents), so if it does not deduplicate, every child id lands in the parent list twice. This is the only composition primitive that sends a non-emptyshapesarray (baseShapealways emitsshapes: []for frames), and the shared stub silently dedupes (if (!shapes.includes(...))), so neither the pure tests norcomposedScreen.test.tscan detect a double-append — and there is no end-to-end validation of this payload. Suggested fix: do not transmit the remapped child list (emitshapes: []/deleteshapesand let Penpot build the list as the children are added, matchingpenpot_add_frame), and update the pure assertions that pinroot.obj.shapes/panel.obj.shapes. If the pre-populated array is in fact required, pin the dedupe behaviour with an integration test (see below) before merging.CHANGELOG.md:9— No entry was added under## [Unreleased] / ### Added. Every prior penpot tool-landing commit in this repo (e.g.032e677,4ff820c,ba1fb50) updatedCHANGELOG.mdas part of the same change; this one registers a new tool and updates the README/SKILL docs but leaves the changelog silent. Suggested fix: add an### Addedbullet describingpenpot_instance_component(component resolution by name across the file and its linked libraries, main-instance subtree clone with fresh ids andshapeRef,componentId/componentFile/componentRooton the root, positioning atx/y, the refusal paths, and the read-only-until-commit guarantee), matching the style of the neighbouring entries.Medium
extensions/penpot/src/tools/instance.ts:476—unlinkedLibraryMessageis unreachable:componentis only ever resolved fromcandidates, which is built fromownComponents(all withfileId === params.file_id) plus the successfully-readsources; so whencomponent.fileId !== params.file_idthesources.find(...)lookup can never returnundefined. The dead branch also backs a documentation claim that does not exist in behaviour (the README/SKILL/description say an unlinked library is "refused with a specific remedy"; in practice an unlinked component is reported by the generic missing-name message, which merely appendsLINK_LIBRARY_HINT). Suggested fix: deleteunlinkedLibraryMessageand replace thesource === undefinedbranch with an internal-invariant error, or reword the docs to say the unlinked case is surfaced through the missing-name message with the link hint; add a test that actually exercises whatever path is kept.extensions/penpot/findings.md:1204— Component instancing has no end-to-end coverage and the live-validation gap is not recorded. The tool's tests stubglobalThis.fetchwith cannedget-filebodies, so the produced instance payloads are never applied to the shared stateful Penpot model (src/tools/fixtures/penpotStubModel.ts) that validates required keys and parent/shapesmaintenance; the design/implementation plan lists component instancing as an integration item, and every comparable tool change (issues #201, #205, #206) recorded its validation state or gaps infindings.md. Suggested fix: add an integration test (analogous tocomposedScreen.test.ts) that drivesinstanceComponentToolagainst the shared stub model — a consumer file plus a linked library holding a component and its main instance — then commits and asserts the applied tree; and add a "Still open" note tofindings.mdstating the instancing payload was never accepted by a real server.Low
extensions/penpot/src/tools/instance.ts:56—StagedChangeToolResultis imported but never used (the result type is declared fromAgentToolResult<InstanceComponentToolDetails>here, whereasframe.ts/rect.tsuse it asAddXToolResult = StagedChangeToolResult & {…}). Suggested fix: either remove the unused import or adopt the sibling tools' pattern (export type InstanceComponentToolResult = StagedChangeToolResult & { details: InstanceComponentToolDetails }) for consistency.extensions/penpot/src/tools/instance.ts:120—LibrarySource.nameis populated (line 227) but never read anywhere in the tool; it is dead state. Suggested fix: remove the field and the...(entry.name === undefined ? {} : { name: entry.name })spread, or use the library name in the refusal/note text (e.g.library "<name>" [id]).extensions/penpot/src/tools/instance.ts:238—availableComponentNamesre-derives the distinct, sorted, bounded name list thatresolveAssetalready returns in themissingfailure (failure.available+failure.truncated), so the truncation/bounding rules are duplicated and can drift. Suggested fix: passresolved.available/resolved.truncatedintomissingComponentMessage(or accept theResolveFailure) and delete the helper.extensions/penpot/src/tools/instance.ts:315—missingMainInstancePageMessage(and the sibling at line 328) always says "library file [id]" even when the owning file is the consumer's own file for a local component, which points the agent at a non-existent library. Suggested fix: derive the label the same way the success note does (component.fileId === params.file_id ? "this file" : \library [id]``) and phrase both errors with it.Documentation
Updated files: