Implement file-library linking and validate the export/import round trip #205

Closed
opened 2026-09-14 23:12:47 +00:00 by david · 1 comment
Owner

Summary

Validate the .penpot export/import round trip against the live instance and close the gap it exposes: an imported library file is not automatically connected to consuming files, so this step also implements and validates the library-linking tools (penpot_link_library, penpot_unlink_library, penpot_list_libraries) that make imported components reachable — a prerequisite for component instancing in the next milestone.

Background

Depends on: #203, #204

The round trip was proven during design validation — a file was exported, downloaded, and re-imported into a fresh project with the same frame/rect/text and the same colour + typography assets. What was also discovered is the gap: has-file-libraries returned false and get-file-libraries returned [] for a file in the same project as the imported library, so an imported library is not usable by a consumer until it is explicitly linked via link-file-to-library.

That matters immediately: the next milestone instances components from the imported library, and data.components on the consuming file will not include them without a link. Rather than discovering this mid-instancing, this step implements the linking tools and proves the whole path — import → link → component visible on the consumer → available to stage.

Required environment: PENPOT_URL, PENPOT_TOKEN, the scratch project, and a fresh throwaway project for the re-import half.

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/

docs/reference/pi-coding-agent/ and docs/reference/typebox/

Implementation Details

Build: the linking tools

Create extensions/penpot/src/tools/libraries.ts exposing:

  • penpot_list_libraries — params file_id (required). Returns whether the file has libraries (has-file-libraries) and which ones (get-file-libraries): id, name, and any other fields the command returns. Must not error when the file has none.
  • penpot_link_library — params file_id (required), library_file_id (required). Calls link-file-to-library, then verifies by re-reading get-file-libraries and confirming the library appears (do not trust the mutation's response). Idempotent: linking an already-linked library reports that rather than failing.
  • penpot_unlink_library — params file_id (required), library_file_id (required). Unlinks and verifies by re-reading; idempotent when not linked.

All three are writes on the consumer file, so penpot_link_library/penpot_unlink_library must pass the designated-target guard for file_id before sending anything. Register the tools in src/index.ts.

  1. Export a scratch file that carries a frame, a rect, a text and at least one colour + typography asset.
  2. Import the artifact into a fresh throwaway project (using the import tool) and confirm the created file appears there in the UI.
  3. Open the imported file and confirm the same shapes and the same colour/typography assets arrived.
  4. On a separate consumer file, call penpot_list_libraries and confirm it reports no libraries for a file in the same project as the imported library — reproducing the gap.
  5. Call penpot_link_library with the imported library's file id, then re-run penpot_list_libraries and confirm the library is now listed; verify the same in the Penpot UI's library panel.
  6. Read the consuming file with penpot_get_file (or penpot_list_library) and confirm the library's assets/components are now reachable.
  7. Confirm the link set here is exactly what the next milestone needs: penpot_get_file on the consumer must expose the component(s) with their ids, and penpot_list_library must resolve the component names the instancing step will use. If the components are still not reachable after linking, record exactly why and stop — the next milestone is blocked on this.

Record in extensions/penpot/findings.md under "Library import/export and linking": the artifact's contents (entry list), what survived the round trip and what did not, the exact has-file-libraries/get-file-libraries results before and after linking, and the command responses observed (including whether linking needed any additional parameter).

Clean up the throwaway project afterwards only if it contains nothing but the re-imported file.

Acceptance Criteria

  • penpot_list_libraries, penpot_link_library and penpot_unlink_library are implemented, registered, and unit-tested with a stubbed client.
  • penpot_link_library and penpot_unlink_library verify the change by re-reading get-file-libraries, and are idempotent.
  • Both mutating tools refuse a file_id outside the designated-target allow-list, before sending any request.
  • A file exported and re-imported into a fresh project carries the same frame/rect/text and the same colour + typography assets (verified in the UI and by re-reading).
  • The "imported library is not linked by default" behaviour is reproduced and recorded with the exact command output.
  • After penpot_link_library, the consumer file lists the library, the Penpot UI shows it, and the library's components are reachable via the extension's read tools.
  • extensions/penpot/findings.md records the round-trip and linking evidence, including anything that did not survive the round trip.
  • Any defect found in the import/export tools is fixed in code, not merely noted.

Test Plan

node --test extensions/penpot/src/tools/libraries.test.ts

Manual, against the live instance:

# in pi
/reload
# penpot_authorize_target(project_id=<scratch project>)
# penpot_export_library(file_id=<scratch file>)          -> record out_path
# penpot_authorize_target(project_id=<fresh project>)
# penpot_import_library(project_id=<fresh project>, path=<out_path>)  -> record created file id
# penpot_list_libraries(file_id=<consumer file>)         -> expect no libraries
# penpot_link_library(file_id=<consumer file>, library_file_id=<imported file id>)
# penpot_list_libraries(file_id=<consumer file>)         -> expect the library listed

Then in the Penpot UI: open the imported file (confirm contents), open the consumer file's library panel (confirm the connected library and its components).

## Summary Validate the `.penpot` export/import round trip against the live instance and close the gap it exposes: an imported library file is **not** automatically connected to consuming files, so this step also implements and validates the library-linking tools (`penpot_link_library`, `penpot_unlink_library`, `penpot_list_libraries`) that make imported components reachable — a prerequisite for component instancing in the next milestone. ## Background **Depends on:** #203, #204 The round trip was proven during design validation — a file was exported, downloaded, and re-imported into a fresh project with the same frame/rect/text and the same colour + typography assets. What was **also** discovered is the gap: `has-file-libraries` returned `false` and `get-file-libraries` returned `[]` for a file in the same project as the imported library, so an imported library is not usable by a consumer until it is explicitly linked via `link-file-to-library`. That matters immediately: the next milestone instances components from the imported library, and `data.components` on the consuming file will not include them without a link. Rather than discovering this mid-instancing, this step implements the linking tools and proves the whole path — import → link → component visible on the consumer → available to stage. Required environment: `PENPOT_URL`, `PENPOT_TOKEN`, the scratch project, and a fresh throwaway project for the re-import half. ## 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/`** - `<PENPOT_URL>/api/main/doc/openapi.json` — the exact parameters of `link-file-to-library`, `unlink-file-from-library`, `get-file-libraries`, `has-file-libraries`, and `import-binfile`/`export-binfile` on this instance. - https://help.penpot.app/user-guide/design-systems/libraries/ — what "connected libraries" means in the UI, where the library list lives, and what a designer expects after linking. This is the behaviour the tools must mirror. - https://help.penpot.app/user-guide/export-import/export-import-files/ — what "Export shared libraries" carries, so the validation can state precisely what did and did not survive the round trip. - https://help.penpot.app/user-guide/import-export/ — the import flow into a project. - https://help.penpot.app/user-guide/design-systems/assets/ — how a linked library's colours/typographies/components appear on a consumer file in the UI. **`docs/reference/pi-coding-agent/`** and **`docs/reference/typebox/`** - https://pi.dev/docs/latest/extensions — tool registration, structured `details`, and returning a refusal/error as a normal result. - https://github.com/sinclairzx81/typebox — string/array parameter schemas. ## Implementation Details ### Build: the linking tools Create `extensions/penpot/src/tools/libraries.ts` exposing: - **`penpot_list_libraries`** — params `file_id` (required). Returns whether the file has libraries (`has-file-libraries`) and which ones (`get-file-libraries`): id, name, and any other fields the command returns. Must not error when the file has none. - **`penpot_link_library`** — params `file_id` (required), `library_file_id` (required). Calls `link-file-to-library`, then **verifies** by re-reading `get-file-libraries` and confirming the library appears (do not trust the mutation's response). Idempotent: linking an already-linked library reports that rather than failing. - **`penpot_unlink_library`** — params `file_id` (required), `library_file_id` (required). Unlinks and verifies by re-reading; idempotent when not linked. All three are **writes on the consumer file**, so `penpot_link_library`/`penpot_unlink_library` must pass the designated-target guard for `file_id` before sending anything. Register the tools in `src/index.ts`. ### Validate: the round trip and the link 1. Export a scratch file that carries a frame, a rect, a text and at least one colour + typography asset. 2. Import the artifact into a **fresh** throwaway project (using the import tool) and confirm the created file appears there in the UI. 3. Open the imported file and confirm the same shapes and the same colour/typography assets arrived. 4. On a separate consumer file, call `penpot_list_libraries` and confirm it reports no libraries for a file in the same project as the imported library — reproducing the gap. 5. Call `penpot_link_library` with the imported library's file id, then re-run `penpot_list_libraries` and confirm the library is now listed; verify the same in the Penpot UI's library panel. 6. Read the consuming file with `penpot_get_file` (or `penpot_list_library`) and confirm the library's assets/components are now reachable. 7. **Confirm the link set here is exactly what the next milestone needs**: `penpot_get_file` on the consumer must expose the component(s) with their ids, and `penpot_list_library` must resolve the component names the instancing step will use. If the components are still not reachable after linking, record exactly why and stop — the next milestone is blocked on this. Record in `extensions/penpot/findings.md` under "Library import/export and linking": the artifact's contents (entry list), what survived the round trip and what did not, the exact `has-file-libraries`/`get-file-libraries` results before and after linking, and the command responses observed (including whether linking needed any additional parameter). Clean up the throwaway project afterwards only if it contains nothing but the re-imported file. ## Acceptance Criteria - [ ] `penpot_list_libraries`, `penpot_link_library` and `penpot_unlink_library` are implemented, registered, and unit-tested with a stubbed client. - [ ] `penpot_link_library` and `penpot_unlink_library` verify the change by re-reading `get-file-libraries`, and are idempotent. - [ ] Both mutating tools refuse a `file_id` outside the designated-target allow-list, before sending any request. - [ ] A file exported and re-imported into a fresh project carries the same frame/rect/text and the same colour + typography assets (verified in the UI and by re-reading). - [ ] The "imported library is not linked by default" behaviour is reproduced and recorded with the exact command output. - [ ] After `penpot_link_library`, the consumer file lists the library, the Penpot UI shows it, and the library's components are reachable via the extension's read tools. - [ ] `extensions/penpot/findings.md` records the round-trip and linking evidence, including anything that did **not** survive the round trip. - [ ] Any defect found in the import/export tools is fixed in code, not merely noted. ## Test Plan ```bash node --test extensions/penpot/src/tools/libraries.test.ts ``` Manual, against the live instance: ```bash # in pi /reload # penpot_authorize_target(project_id=<scratch project>) # penpot_export_library(file_id=<scratch file>) -> record out_path # penpot_authorize_target(project_id=<fresh project>) # penpot_import_library(project_id=<fresh project>, path=<out_path>) -> record created file id # penpot_list_libraries(file_id=<consumer file>) -> expect no libraries # penpot_link_library(file_id=<consumer file>, library_file_id=<imported file id>) # penpot_list_libraries(file_id=<consumer file>) -> expect the library listed ``` Then in the Penpot UI: open the imported file (confirm contents), open the consumer file's library panel (confirm the connected library and its components).
david closed this issue 2026-09-15 04:51:01 +00:00
Author
Owner

pi-loop opened and merged a pull request for this issue: #238

pi-loop opened and merged a pull request for this issue: https://git.excelera.net/david/pi-extensions-and-skills/pulls/238
Sign in to join this conversation.
No project
No assignees
1 participant
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
david/pi-extensions-and-skills#205
No description provided.