Implement penpot_add_frame with auto-layout props and absolute-positioning fallback #198
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#198
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Summary
Implement
penpot_add_frame: stage a frame shape with the complete, schema-valid Penpot shape payload, optional auto-layout properties (flex direction, gap, padding, alignment, wrap), and a child-sizing/absolute-positioning fallback path. This is the container every other composition primitive adds into.Background
Depends on: #197
The shape payload is the schema that produced "invalid shape found" until every piece was present. It was then pinned on Penpot 2.17, and this step must reproduce it exactly:
Two details that are easy to get wrong:
frameId/parentId=00000000-0000-0000-0000-000000000000(the root frame).shapesarray when a child is added withparentIdset to that frame — no follow-upmod-objis needed or wanted.Auto-layout field names and enums, confirmed on 2.17 (
layoutGapandlayoutPaddingare objects, not numbers; the UI word "hug" isautoin the data model):layoutflex|gridlayoutFlexDirrow|row-reverse|column|column-reverselayoutGap{rowGap, columnGap}withlayoutGapType: "multiple"layoutPadding{p1, p2, p3, p4}— all four required — withlayoutPaddingType: "multiple"layoutAlignItemsstart|center|end|stretchlayoutJustifyContentstart|center|end|space-between|space-around|space-evenly|stretchlayoutWrapTypewrap|nowrapChild-level layout fields (used by later primitives, but the frame primitive owns the schema helpers for them):
layoutItemHSizing/layoutItemVSizing(fill|fix|auto),layoutItemAbsolute,layoutItemMargin,layoutItemAlignSelf,layoutItemMinW/MaxW/MinH/MaxH,layoutItemZIndex.Errors: with
Accept: application/json, validation failures return readable JSON with a preciseexplain(Malli path). Surface it verbatim.Documentation Required
A separate process downloads these into the listed folders before this issue is implemented. Check the folders for the actual reference material before starting.
docs/reference/penpot-api/selrect,points,transform,transformInverse, fills/strokes, and the layout fields.shapesarrays are maintained.<PENPOT_URL>/api/main/doc/openapi.json— theupdate-file/add-objchange shape on this instance.auto).docs/reference/pi-coding-agent/anddocs/reference/typebox/details.Type.Union/Type.Literalfor the enum parameters (layout_flex_dir,align_items, …) so invalid enums are rejected before staging.Implementation Details
Create
extensions/penpot/src/shapes.ts(pure payload builders — the unit-tested core) andextensions/penpot/src/tools/frame.ts, and register the tool insrc/index.ts.src/shapes.tsbaseShape({ id, type, name, x, y, width, height, rotation? })→ the full shape object withselrect, fourpoints,transform,transformInverse,frameId,parentId,fills,strokes.selrectFrom(x, y, w, h)andpointsFrom(...)— derivex1,y1,x2,y2and the four corner points correctly; these are the fields most likely to be wrong by hand, so derive them, never accept them from the caller.identityTransform()—{a:1,b:0,c:0,d:1,e:0,f:0}for bothtransformandtransformInverse(a non-rotated shape).layoutProps(input)→ thelayout*fields, withlayoutGap/layoutPaddingmapped from friendly numbers into the required objects andlayoutGapType/layoutPaddingTypeset to"multiple". All four padding sides must always be emitted (default the omitted ones sensibly, e.g. uniform padding).addObjChange({ pageId, frameId, parentId, obj })→ the change wrapper, asserting the change-levelframeIdis present (it is required even for top-level shapes).ROOT_ID = "00000000-0000-0000-0000-000000000000".src/tools/frame.ts—penpot_add_frameParameters:
file_id(required),name,x,y,width,height,page_id(optional; default the file's first/only page when unambiguous, else required with a clear error),parent_id(optional; defaults to the root),fill_color/fill_color_ref(optional; the linked-asset form is added in the next issue — accept a plain hex here), and layout options:layout(flex/grid),layout_flex_dir,layout_gap(single number or{row, column}),layout_padding(single number or{top,right,bottom,left}),layout_align_items,layout_justify_content,layout_wrap.add-objchange (never write directly).details, since the caller needs it to add children.layout, inferlayout: "flex"and say so in the result rather than silently ignoring the options.Acceptance Criteria
add-objchange carries a complete shape:id,type,name,x,y,width,height,rotation,selrect(withx1..y2), fourpoints,transform,transformInverse,frameId,parentId,fills,strokes, andshapes: [].selrect,pointsandtransformare derived from position/size, never supplied by the caller.frameIdis always present; top-level frames useROOT_IDfor bothframeIdandparentId.layoutGapandlayoutPaddingare emitted as objects with their*Type: "multiple"companions; all four padding sides are always present.layoutinfersflexand reports the inference.update-filecall.selrect/points/transformderivation, the change wrapper, layout-prop mapping (including partial padding and{row, column}gaps), and the no-layout absolute default.Test Plan
Live validation (requires
PENPOT_URL/PENPOT_TOKEN, scratch file):layout: flex,layout_flex_dir: row, gap 12, padding 16) and commit.Note: the two-tool round trip (stage → commit) is deliberate; if the commit fails, the server's
explainmust be surfaced verbatim to make schema mistakes diagnosable.pi-loop opened and merged a pull request for this issue: #231