Skip to content

feat(plugins): zip package upload + refactor(web): token convergence and shared primitives - #17

Merged
nextroad-dev merged 5 commits into
mainfrom
dev
Oct 6, 2026
Merged

nextroad-dev merged 5 commits into
mainfrom
dev

Conversation

@nextroad-dev

@nextroad-dev nextroad-dev commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

Plugins can now be uploaded as one zip package instead of a pasted manifest plus a separate .mjs. The zip carries manifest.json (manifest + package block), a single entry .mjs, and optional README / CHANGELOG / LICENSE and a PNG/WebP icon.

Spec: MuseCanvas-Connector wiki/plugin-package-spec.md. Author template and pack/check tool: same branch in MuseCanvas-Connector.

Changes

  • providers: new plugin-package.ts validates the zip before anything is stored:

    • central directory parsing; encryption, zip64 and unsupported compression methods are rejected

    • path safety

    • file whitelist and size / entry-count / compression-ratio limits

    • manifest + package block, entry source scan, icon and doc checks

    macOS Finder junk (__MACOSX/, .DS_Store) is ignored but still counted toward limits. The scanner now accepts esbuild's export { x as default }.

  • api:

    • package multipart field installs a zip. The legacy manifest + file upload still works and returns a PLUGIN_UPLOAD_LEGACY_FORMAT warning.

    • The entry is stored under the existing key format, so the worker's fetch path is unchanged. The zip and icon are stored alongside.

    • New GET routes for the icon, the original package and the docs. The list payload carries only docs flags.

  • database: migration 0029_provider_plugin_packages.sql adds package columns to provider_plugins.

  • worker: zip rows require the bundle's manifest to equal manifest.json (PLUGIN_MANIFEST_MISMATCH otherwise). Legacy rows keep the identity check.

  • web-next:

    • The upload dialog takes one zip.

    • Installed plugins show the icon, package metadata, file list, lazily loaded docs (plain text) and a package download.

  • dependency: fflate added to @musecanvas/providers.

Verification

  • pnpm -r --if-present typecheck: pass.

  • Tests, all passing:

    | Package | Passing |

    |---|---|

    | contracts | 41 |

    | providers | 208 |

    | database | 27 |

    | api | 160 |

    | worker | 64 |

    | integration | 25 |

    Integration includes a mocked pack → install → load → submit round trip, plus a manifest-drift rejection.

  • web-next typecheck was also run against the committed tree alone, without other uncommitted work in the shared worktree.

  • Not done: no browser or deployed smoke test of the upload UI yet.

Deployment notes

  • Migration 0029 must run on deploy.

  • Plugin upload stays behind ALLOW_PLUGIN_UPLOAD=true.


Second commit — refactor(web): design-token convergence and shared primitives

Independent of the plugin feature; apps/web-next only. It adds no dependency, changes no API

contract, touches no schema and adds no migration.

What changed

  • globals.css — Tailwind's --default-transition-duration / --default-transition-timing-function

    now point at --motion-fast / --ease-standard, so a plain transition-colors is token-timed and

    cannot drift back to the 150ms default. Removed the dead --shadow-sm/md/lg aliases and corrected

    .motion-hover-fade's comment (it animates opacity/transform, never colour). 31 hand-written

    transition declarations collapsed into transition-colors.

  • ui/size.ts (new) — iconSize / iconSlot / controlSquare maps, replacing 78 hand-written

    h-[var(--icon-x)] w-[var(--icon-x)]-style pairs. Fixes three real violations: stepper's bare

    h-8 w-8, and two h-6 min-h-6 w-6 in the library that were fighting the button recipe's own

    min-height token.

  • ui/table.tsx (new) — DataTable / TableFrame / TableHead(Cell) / TableBody / TableRow /

    TableCell / TableStateRow / TableSkeletonRow, with a dense variant for the plugin file

    manifest. All 8 hand-written admin tables migrated; colSpan now derives from one declared

    columns per frame instead of a per-view constant. Nothing outside ui/table.tsx renders <table>.

  • ui/section-header.tsx (new) — the repeated admin section heading, 4 call sites.

  • lib/format.ts (new) — one date module, absorbing three private formatDate / formatDateTime

    copies in account, library and lightbox.

  • library — split the URL-sync effect so a filter change can no longer write the previous query's

    page depth, while a restored ?page=N link still survives its forward walk.

  • scripts/check-web-tokens.mjs + .github/workflows/web-checks.yml (new) — fail on undefined

    tokens, raw colours, off-scale motion and hand-written size pairs; advisory-only report of tokens with

    no consumer. The guard is a regression gate, not a bug-fix: token references were already clean.

Verification (this commit)

  • tsc --noEmit, next build and node scripts/check-web-tokens.mjs: all exit 0. Compiled-CSS

    assertions: transition-duration:var(--motion-fast) present, Tailwind's 150ms default gone, all 10

    size-[var(--token)] rules emit both width and height, no --shadow-sm/md/lg.

  • The guard was proven non-vacuous by injecting one violation per rule into a scratch copy → exit 1 with

    exact file:line, then removed.

  • Table migration equivalence: a per-table audit of captions, th scope, aria-sort, aria-busy,

    state-row variants and colSpan, plus an SSR render of the primitives through react-dom/server

    (the rendered class sets match the pre-migration markup).

Deployed for review on 10.7.89.132 (frontend-only image swap)

  • ghcr.io/nextroad-dev/musecanvas-web-next:sha-66a7b5b

    @sha256:6a81e22ce9686f8bd7f81aa577d930cd5430a24ae4579d81c1a46fe83391b4fd

  • Deployed with pull web-next + up -d --no-deps web-next, so only musecanvas-web-next-1 was

    recreated; the before/after docker ps diff is exactly that one line. The other 7 containers kept

    their previous start time, which is also the proof that db-migrate never ran.

    --no-deps matters here: web-next declares depends_on: api, and without it Compose resolves the

    whole stack and would run this branch's migration against production.

  • Verified: /api/health/ready 200 setupComplete:true (same as the pre-deploy baseline) · 8 admin

    routes 307 → /login?from=/admin, no 500 · served chunk and served CSS contain revision-unique

    markers that are absent from the previous image.

  • Not verified: every admin screen behind login — the migrated tables have not been rendered

    against real data (no credentials here), nor has the interactive library search.

    tests/e2e/admin-panel.mjs was deliberately not run: it POSTs 5 invitation codes into the

    production database.

  • Current production state while this PR is open: web-next = this branch's sha-66a7b5b, while

    api/worker/nginx remain on main's latest. Migration 0029 is therefore still unapplied —

    it will run when the backend is next deployed.

Additional full-product release (9ab8464)

  • Replace Lucide with pinned Phosphor 2.1.10, explicit weights and CSR/SSR-safe imports.

  • Integrate admin detail/confirmation flows and generation workbench state/cache improvements.

  • Add owner-scoped Canvas CRUD and confirmation-gated Canvas Agent backend; additive migrations 0030/0031, agent disabled by default.

  • Preserve standardized image response-body timeout/safety diagnostics; regression tests reproduced failures before the fix.

  • Verified: root typecheck, 679 tests passed / 2 skipped; provider targeted 57 passed; admin 45 and generation integration 8 passed; frontend production build and 56 fixture UI cases passed.

  • Icon-only comparable gzip increase accepted by owner; actual network transfer/performance is not implied.

  • Dev image publication and backed-up deployment validation are in progress. Do not merge until CI and deployment acceptance succeed.

Release validation update

  • Release head: f28798e (CI prerequisite ordering fix only; product sources unchanged from 9ab8464).
  • All PR checks passed; exact-head image publication: https://github.com/nextroad-dev/MuseCanvas/actions/runs/37432946535 .
  • Protected backup restored into isolated PG17; all 31 migrations + seed applied and repeated successfully.
  • 46 real PostgreSQL/production API synthetic checks passed, with core data and SMTP/storage fingerprints preserved; no external provider calls or queue events.
  • Production switch/acceptance and merge remain pending.

nextroad-dev and others added 2 commits October 3, 2026 18:47
Plugins can now be uploaded as one .zip carrying manifest.json (the
manifest plus a `package` block), a single entry .mjs and optional
README/CHANGELOG/LICENSE and a PNG/WebP icon.

- providers: plugin-package validates the zip before anything is stored:
  central directory parsing (no encryption/zip64/odd methods), path
  safety, file whitelist and size/entry/ratio limits, manifest + package
  block, entry scan, icon and doc checks. macOS Finder junk is ignored
  but still counted toward limits. The scanner accepts esbuild's
  `export { x as default }`.
- api: the `package` multipart field installs a zip; the legacy
  manifest + file form still works and returns a
  PLUGIN_UPLOAD_LEGACY_FORMAT warning. The entry is stored under the
  existing key format; the zip and icon are stored alongside. New GET
  routes serve the icon, the original package and the docs; the list
  payload carries only `docs` flags.
- database: migration 0029 adds package columns to provider_plugins.
- worker: zip rows require the bundle manifest to equal manifest.json
  (PLUGIN_MANIFEST_MISMATCH otherwise).
- web-next: the upload dialog takes one zip; installed plugins show the
  icon, package metadata, file list, lazily loaded docs and a download.

Spec: MuseCanvas-Connector wiki/plugin-package-spec.md
…token guard

The tokens in globals.css were already the single source of truth, but call
sites kept re-spelling them: 61 `h-[var(--icon-x)] w-[var(--icon-x)]` pairs,
10 square-control pairs, 31 hand-written transition duration/easing
declarations, and 8 admin tables that each re-declared the same table shell.

- globals.css: point Tailwind's `--default-transition-*` at `--motion-fast` /
  `--ease-standard`, so a plain `transition-colors` is token-timed and cannot
  drift back to the 150ms default. Drop the dead `--shadow-sm/md/lg` aliases
  (no call site referenced them) and correct `.motion-hover-fade`'s comment:
  it animates opacity/transform, never colour.
- ui/size: `iconSize` / `iconSlot` / `controlSquare` token->utility maps, and
  all 78 hand-written size pairs replaced with them. Fixes three real
  violations: stepper's bare `h-8 w-8`, and two `h-6 min-h-6 w-6` in the
  library that were fighting the button recipe's own min-height token.
- ui/table: DataTable / TableFrame / TableHead(Cell) / TableBody / TableRow /
  TableCell / TableStateRow / TableSkeletonRow, with a `dense` variant for the
  plugin file manifest. All 8 admin tables migrated; colSpan now derives from
  one declared `columns` per frame instead of a per-view constant.
- ui/section-header: the repeated admin section heading, 4 call sites.
- lib/format: one date module, absorbing three private formatDate /
  formatDateTime copies across account, library and lightbox.
- library: split the URL-sync effect so a filter change can no longer write the
  previous query's page depth, and a restored `?page=N` link survives its walk.
- scripts/check-web-tokens.mjs (+ web-checks.yml): fail on undefined tokens,
  raw colours, off-scale motion and hand-written size pairs. Advisory-only
  report of tokens with no consumer.
@nextroad-dev nextroad-dev changed the title feat(plugins): upload plugins as a single zip package feat(plugins): zip package upload + refactor(web): token convergence and shared primitives Oct 5, 2026
@nextroad-dev
nextroad-dev merged commit a1ad6d8 into main Oct 6, 2026
12 checks passed
@nextroad-dev

Copy link
Copy Markdown
Owner Author

Release acceptance completed before merge: f28798e immutable images deployed; migration history 31 / 0031, Canvas Agent remains disabled. All PR checks and exact-head image publication passed. Isolated real PostgreSQL/production API: 46 checks and core data/config preservation passed. Production proxy boundary: 16 checks; real public homepage/login browser matrix: 8 cases + 4 auth guards, no runtime exceptions/overflow. Protected backups and old application images/source retained. No paid generation, production login, SMTP/S3 writes or user deletion used as smoke tests. Merged without protection bypass; deployment remains digest-pinned, independent of main latest publication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant