Skip to content

feat(assets): port Nuxt DevTools' Assets tab as a framework-neutral plugin - #138

Open
antfubot wants to merge 10 commits into
mainfrom
feat/assets-plugin
Open

feat(assets): port Nuxt DevTools' Assets tab as a framework-neutral plugin#138
antfubot wants to merge 10 commits into
mainfrom
feat/assets-plugin

Conversation

@antfubot

Copy link
Copy Markdown
Collaborator

Summary

Adds @devframes/plugin-assets — a framework-neutral port of Nuxt DevTools' Assets tab. Manages the files under a directory (<cwd>/public by default): search/filter, grid and tree views, a details panel (preview + metadata + ready-to-copy snippets), drag-and-drop upload, rename, multi-select delete, folder creation, and inline text editing. A live chokidar watcher keeps every connected client in sync. Built as a Preact SPA — the first plugin to use Preact, further proving devframe's framework-neutral client layer (now Vue, React, Solid, Svelte, and Preact).

Real byte serving (<img>/<video>/downloads) goes through ctx.views.hostStatic(), and uploads stream over devframe's binary streaming-channel primitive rather than a base64-in-JSON action, so large files don't bloat the wire payload.

Core changes in support of this

DevframeDefinition.capabilities.build was declared in the types but never consumed anywhere. Since a static export of this plugin can never carry real bytes (no live host to serve them) or write actions (excluded from every static dump already), it isn't a meaningful deployment target — so this wires that existing field up for real:

  • createCac skips registering the build subcommand when capabilities.build === false.
  • createBuild throws DF0042 for the same case unless { force: true } is passed, closing the gap for callers who invoke it directly, bypassing the CLI.
  • The assets plugin sets capabilities: { build: false } by default (overridable via { build: true }).

Both covered by new tests (packages/devframe/src/adapters/__tests__/{cac,build}.test.ts).

Plugin surface

RPC (devframes:plugin:assets:*): list, capabilities, read-image-meta, read-text (always registered) and upload, rename, delete, mkdir, write-text (registered only when write is enabled — on by default; --read-only / { write: false } to disable). open-in-editor / reveal-in-folder reuse devframe's launchEditor/open utilities, always registered regardless of write.

Options: dir (default <cwd>/public), write (default true), uploadExtensions (defaults to Nuxt DevTools' own allow-list), build (default false, see above).

Testing

  • plugins/assets/test/assets.test.ts — 14 integration tests over real HTTP+WS: list/capabilities, mkdir+streaming-upload+list, image/text reads, rename (extension-preserving, collision-checked), bulk delete, write-text, path-traversal and disallowed-extension rejection, write: false gating, and the live change-broadcast.
  • New core tests for capabilities.build in createCac/createBuild.
  • Storybook stories for the presentational view components (AssetPreview, AssetGridItem, Toolbar, CodeSnippets, AssetDetails).
  • Wired into examples/minimal-vite-devframe-hub alongside every other built-in plugin.

Full repo: pnpm lint && pnpm typecheck && pnpm build and the full vitest run (907 tests) all pass locally.

Docs

docs/plugins/assets.md (+ index/sidebar entries), docs/errors/DF0042.md.


This PR was created with the help of an agent.

…lugin

Adds @devframes/plugin-assets, a Preact SPA that browses, previews,
uploads, renames, and deletes the files in a managed directory
(default <cwd>/public). Full RPC surface (list/capabilities/read-text/
read-image-meta/upload/rename/delete/mkdir/write-text/open-in-editor/
reveal-in-folder), a live chokidar-backed change broadcast, real byte
serving via ctx.views.hostStatic(), and binary uploads over devframe's
streaming-channel primitive. Preact ports the same @antfu/design class
vocabulary every other plugin uses.

Core changes in support of this:
- DevframeDefinition.capabilities.build now actually does something:
  createCac skips the build subcommand when it's false, and
  createBuild throws DF0042 for the same case unless {force: true} is
  passed. The assets plugin uses this by default since a static export
  can never carry real bytes or write actions.
- Wired into alias.ts/tsconfig.base.json/turbo.json/vitest.config.ts
  and dogfooded in examples/minimal-vite-devframe-hub.

Co-authored-by: opencode <noreply@opencode.ai>
@netlify

netlify Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploy Preview for devfra ready!

Name Link
🔨 Latest commit 45c26ac
🔍 Latest deploy log https://app.netlify.com/projects/devfra/deploys/6a68cd12663d04000892fc55
😎 Deploy Preview https://deploy-preview-138--devfra.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

antfubot and others added 9 commits July 28, 2026 04:28
…o upload

- Fold the search box, asset count, and action buttons into the single
  top nav bar instead of a second toolbar row; the error banner moves to
  its own slim strip below.
- DropZone now listens for file drags across the whole frame whenever
  write is enabled, so dropping files opens the upload overlay without
  first clicking Upload. A drag-depth counter keeps the overlay from
  flickering, and only drags carrying files (not text/element drags)
  trigger it.

Co-authored-by: opencode <noreply@opencode.ai>
…text

Adds a small, varied public/ fixture set (SVG + raster PNG images,
nested folders, JSON/Markdown/CSS/txt) so the plugin's dev server and
Storybook show real content out of the box. public/ is dev-only — it's
not in the package's published files.

Also broadens the text-type classifier to cover common web text
formats (css/scss/less/html/xml/vue/svelte/astro/env/ini/conf/sh) so
they get an inline preview + text editor instead of the download-only
'other' fallback.

Co-authored-by: opencode <noreply@opencode.ai>
…ne type filter

- Fix broken images in the plugin dev server: in devMiddleware mode the
  SPA is served by Vite while ctx.views.hostStatic() mounts the raw bytes
  on the side-car origin, so origin-relative <img> URLs 404'd against
  Vite. Re-serve the managed directory on Vite's own origin in the dev
  config so the relative publicPath resolves.
- Details view is now an in-layout right side panel (the listing shrinks
  beside it) instead of an overlay drawer; removed the Drawer component.
- Replace the extension-filter dropdown with an always-visible inline
  type-filter chip row (image/video/audio/font/text/other) modeled on
  vitejs/devtools' DataSearchPanel; shared TYPE_META drives the chips and
  the tree icons.

Co-authored-by: opencode <noreply@opencode.ai>
The managed public/ directory is already served by whatever host the
plugin is mounted into (Vite, Nuxt, any framework serves public/ at /),
so the plugin no longer stands up its own /__…-raw/ route. Each asset's
publicPath is now baseURL + its path (baseURL default '/'), pointing at
the host's own URLs.

- Add baseURL option (where the host serves the dir) and serveStatic
  (self-serve, off by default). setupAssets only calls ctx.views.
  hostStatic() when serveStatic is set.
- The standalone CLI is its own host, so createAssetsCli() enables
  serveStatic (under a dedicated base so it doesn't collide with the SPA).
- Plugin dev server: point Vite's own publicDir at the managed dir so
  Vite serves the fixtures at /, and drop the ad-hoc raw-serving
  middleware. Gated to 'serve' so fixtures never bake into dist.
- Docs + README + API snapshots updated.

Co-authored-by: opencode <noreply@opencode.ai>
# Conflicts:
#	pnpm-lock.yaml
#	pnpm-workspace.yaml
…upload modal

- Details panel is now a resizable right-hand side panel (default 480px,
  drag its left edge; width persists to localStorage) instead of a fixed
  w-96 column.
- Upload button opens the native file picker directly and uploads the
  chosen files immediately — the file-grid/confirm modal is gone.
  Whole-frame drag-and-drop still works, now uploading dropped files
  directly with a lightweight non-blocking hint overlay (useFileDrop).
- Remove the 'Edit' action from the details panel, and with it the
  now-orphaned write-text RPC (its only consumer). Text files still
  preview via read-text.

Docs + API snapshots updated; the registration test no longer invokes
the external-launching RPCs (which spawned OS processes and hung the
runner) — it asserts registration on the server context instead.

Co-authored-by: opencode <noreply@opencode.ai>
Swap the Preact SPA for Vue 3 SFCs consuming @antfu/design's Vue
components directly (ActionButton, ActionIconButton, DisplayBadge,
FormTextInput, FormCheckbox, LayoutTabs, LayoutToolbar, OverlayModal),
matching the inspect/og plugins. Deletes the hand-rolled Preact UI kit
(components/ui/*) in favour of the shared components; hooks become Vue
composables; utils and the framework-neutral node/RPC layer are
unchanged, so the package's public API (and its snapshots) is untouched.

- package.json: preact/@preact/preset-vite/@storybook/preact-vite →
  vue/@vitejs/plugin-vue/@storybook/vue3-vite, plus reka-ui, floating-vue,
  colorjs.io (peer deps of the @antfu/design components used). tsconfig
  drops the Preact JSX options; uno content globs add .vue.
- Preserves every behaviour: resizable side panel, inline type-filter
  chips, direct file-dialog upload + whole-frame drag hint, live refresh,
  multi-select bulk delete, host-served previews.
- cleanupUnusedCatalogs pruned the now-unused @storybook/preact-vite
  catalog entry.

Co-authored-by: opencode <noreply@opencode.ai>
# Conflicts:
#	pnpm-lock.yaml
#	turbo.json
#	vitest.config.ts
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.

2 participants