feat: data-inspector plugin (jora live-query workbench), ctx.services, storage scopes#101
Merged
Conversation
…very viewer) Validates the live-query data-viewer architecture end to end: server-side jora over live registered objects (incl. the ViteDevServer serving the page), result normalizer, devframe RPC round-trip, remote stat-mode autocomplete, and a themed discovery struct view in devframe-style chrome. Stage-1 node spike passes 13/13 checks; findings and hazards documented in the prototype README. Throwaway code — lives on this spike branch as a primary source, not for merging into main.
✅ Deploy Preview for devfra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
… source contract, saved queries
Renames the spike to data-inspector and reworks it along the agreed
direction:
- data sources follow { id, title, description?, getData(), static? }
with dynamic register/unregister + change listeners; static sources
memoize getData()
- saved queries (title/description, id-keyed) persisted per scope:
user -> node_modules/.data-inspector, project -> .devframe/data-inspector
(committable, shared with the team)
- SPA rebuilt as Vue 3 + @antfu/design on the canonical uno preset:
split-pane workbench, debounced auto-run, client-side jora syntax gate,
non-destructive errors (last good result stays), stats bar with query
time + payload size, remote suggestions, synced dark mode
Full e2e verified in-browser; spike still 13/13. Findings captured in
the prototype README.
…ush padding - empty query now runs $ so every source lands on its entire object - query settings (ignore functions / _-prefixed / $-prefixed keys), persisted client-side and applied to both results and the skeleton - 'available data' panel: query-independent type skeleton of the active source (ancestor-path cycle detection keeps shared refs expanded) - manual re-run button in the stats bar - left column sections (editor, skeleton, saved queries) resize via the same @antfu/design LayoutSplitPane setup as the outer split - discovery page padding zeroed via --discovery-page-padding-*
…badges, URL state
- FilterOptions { excludeFunctions, excludeUnderscoreProps, excludeDollarProps }
replace the old settings names; saved queries become source-agnostic
recipes { query, title?, description?, ...FilterOptions } with title
optional (untitled ids derive from a stable query hash)
- data sources can provide suggested queries ({ queries?: Query[] }),
listed read-only next to saved ones; loading any recipe applies its
filter options
- save query now opens an OverlayModal dialog (query preview, active
filters, title/description, scope)
- discovery struct annotations badge exotic values: Function, Map(n),
Set(n), Date, class names, circular refs
- source, query, and filters persist in the URL (shareable/restorable)
…panel, modal filters - query drafts persist in localStorage per data source; switching sources restores (or resets) the draft, and a query arriving via the URL seeds the draft for its source so shared links win - SkeletonPanel replaced by DataSourceInfoPanel: source title/description plus a one-level data shape in a plain code block (no discovery); clicking a prop sets it as the query and runs - the save-query modal always shows all three filter options with their on/off state
- display transform strips $class/$type meta before rendering: class
instances show only real props, function stubs collapse to {}, Map/Set
wrappers unwrap to their entries/values, Date/RegExp/BigInt/... render
as bare values; badges carry the type info instead, via WeakMap
side-tables (object identity for objects, parent+key for primitives)
- discovery's built-in query actions are enabled: the struct value-actions
popup now offers 'Create a subquery from the path' (pipes the path onto
the query) and 'Append path to current query' (textual append), both
emitting up to set the editor and re-run
…nified query list - executions (auto-run included) no longer clear the suggestion dropdown; it closes on accept, Escape, blur, or source switch - the popover gets an explicit solid surface (white / #1e1e1e) with stronger elevation so nothing reads through it in either scheme - suggested and saved queries render through one unified list (resolves the TODO), with per-entry icon, filter markers, scope/suggested badge, and delete only for saved entries
Promotes the data-inspector spike to a real plugin and lands the two core
primitives it design-required:
devframe core:
- ctx.services — typed, namespaced cross-plugin service registry
(provide/get/whenAvailable, augmentable DevframeServicesRegistry,
DF0037 on duplicate providers); whenAvailable absorbs setup-order
differences between provider and consumer
- getStorageDir scopes become workspace/project/global: workspace is the
committable .devframe/ dir (team-shared), project is the per-checkout
node_modules dir (the old 'workspace'), global stays per-user; all
hosts and call sites migrated (settings and dock prefs now persist
under 'project')
@devframes/plugin-data-inspector:
- process-global source registry (globalThis Symbol store): sources are
{ id, title, description?, icon?, data: value | (async) factory,
static?, queries? }; registerDataSource() needs no context, the same
store is provided as the 'devframes:plugin:data-inspector:sources'
context service, and changes broadcast to connected UIs
- isomorphic engine (normalize/skeleton/jora bridges) — the Map/Set
methods duck-type live collections AND their normalized tags, so
saved queries stay portable between live and static modes
- Vue + @antfu/design SPA with discovery's CodeMirror jora editor
(remote stat-mode suggestions), struct viewer with type badges,
shape panel, filters, per-source drafts, URL state, saved queries
(workspace/project scopes via the new storage classes)
- standalone CLI: inspect .json/.jsonl files, attach to a process
running the in-process agent (loopback + pre-shared-token auth by
default, discovery file handshake), and build self-contained static
exports that run the engine client-side
- unit tests for registry/engine/saved-queries/file loaders; core
services tests; API snapshots updated
The prototype example is replaced by the plugin; the minimal Vite hub
dogfoods it with the live ViteDevServer registered as a source.
The example hub builds against the plugin's dist; without an explicit task entry and dependsOn edge, CI built the example before the plugin existed and rolldown failed to resolve the package.
A combined dts graph let rolldown hoist entry declarations into shared chunks nondeterministically across runs, flaking the tsnapi snapshots in CI (some matrix cells emitted inline declarations, others re-exports). One dts graph per entry has nothing to split: declarations always inline, verified byte-identical across rebuilds. Snapshots regenerated against the inline form.
- built-in example source: a small live playground graph (Maps, Sets, class instances, circulars, BigInt, a query-time getter) with suggested queries; on by default, disabled via exampleSource: false, suppressed automatically by the agent and by the CLI when data files are given - docs/plugins/data-inspector.md: using the plugin, providing data sources (registry + context service + contract), CLI, agent attach, static export, RPC surface; registered in the plugins sidebar/overview - the SPA header links to the docs page, and an empty-source state points at the providing-data-sources section
…t only The built-in example source is now always registered unless explicitly opted out (option exampleSource: false; CLI --no-example; agent env DEVFRAME_DATA_INSPECTOR_EXAMPLE=0) — the agent and file-serving CLI no longer suppress it automatically. Beyond the playground branch it now answers real questions: the devframe context (registered RPC functions, services, storage dirs), OS info, and live process stats via query-time getters, with matching suggested queries.
Two context-level primitives for plugin interop: - ctx.services — a typed, namespaced cross-plugin service registry (provide/get/whenAvailable, augmentable DevframeServicesRegistry, DF0037 on duplicate providers). Providers ship a types-only augmentation so consumers get full typing without a runtime dependency, and whenAvailable absorbs setup-order differences. - getStorageDir scopes become workspace/project/global: 'workspace' is the committable <workspaceRoot>/.devframe/ dir (team-shared, new), 'project' is the per-checkout node_modules dir (the previous 'workspace' semantics), 'global' stays per-user. All hosts and call sites migrated: scoped project settings and the hub's dock/user settings persist under 'project'. Docs: a Cross-Plugin Services guide page, storage-scope reference on the definition page, the hub host example implements all three scopes, and a DF0037 error page. API snapshots updated.
…select, polished save/list UI
- pnpm dev now runs a full devframe dev server: the SPA vite config
mounts dataInspectorVitePlugin({ devMiddleware: true }), so the HMR
frontend has a live RPC + WS backend (with the example source) instead
of a backend-less SPA
- rename the /agent subpath export to /inject (dir, exports, tsdown,
alias, imports, docs); the API is unchanged
- new DataSourceSelect component replaces the generic FormSelect for the
source picker: renders each source's icon (fallback i-ph:database-duotone)
and a one-line description in the dropdown
- Save-query dialog uses ActionToggleGroup for the save location
(Just me / Shared with team) with a contextual hint
- SavedQueriesPanel renders filter markers as pretty pill badges and drops
the per-row suggested/scope badge
- example source gains a filter-carrying suggested query to exercise it
DataSourceSelect used inline <svg> for the trigger caret and the item check indicator (copied from FormSelect). Replace both with i-ph:caret-down and i-ph:check spans rendered through the shared presetIcons mask, matching the design-system convention of icons everywhere.
App.vue was a 231-line mix of header, data-source, and query UI. Split the left column into focused components and slim the root to a pure composition surface (231 -> 86 lines): - AppHeader.vue — brand, connection status, docs + dark-mode buttons (reads the connection/scheme singletons directly, no props) - DataSourcePanel.vue — source select, details toggle, data-shape panel, empty state - QueryPanel.vue — Jora query header (clear/run), editor, filter settings The workbench is shared app context, so panels inject it (workbenchKey / injectWorkbench) rather than receiving it as a prop and mutating it (which vue/no-mutating-props rightly forbids). Query-composition helpers (queryProp / applySubquery / appendPath) moved onto the workbench so the panels call them directly instead of App relaying handlers.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Adds @devframes/plugin-data-inspector — an interactive jora query workbench over live server-side objects — together with the two core primitives its design required. Grew out of the in-PR spike (kept in history; the prototype example is replaced by the real plugin).
Devframe core
ctx.services— typed, namespaced cross-plugin service registry (provide/get/whenAvailable, augmentableDevframeServicesRegistry,DF0037on duplicate providers).whenAvailableabsorbs setup-order differences, giving every plugin pair a dependency-free communication channel.getStorageDirnow takes'workspace' | 'project' | 'global': workspace is the committable.devframe/dir (team-shared), project the per-checkoutnode_modulesdir (previous 'workspace' semantics), global per-user. All hosts and call sites migrated.The plugin
globalThisSymbol store) —registerDataSource({ id, title, data: value | (async) factory, static?, queries?, icon? })works with no context from anywhere (plugins, hosts, CLIs, agents); also exposed as a typed context service; registry changes push to connected UIs.devframe-data-inspector stats.json trace.jsonl(one static source per file),attach(connect the UI to another process running the in-process agent — loopback + pre-shared-token auth by default, discovery-file handshake),build(self-contained static export; the isomorphic engine runs queries client-side, so saved recipes stay portable).@devframes/plugin-data-inspector/agent— explicit import orDEVFRAME_DATA_INSPECTOR=1 node --import …for zero-code-change attach. Documented as eval-grade access; treat like a debugger port.Verification
716/716 tests (new units for registry, engine incl. static-portability, saved queries, file loaders; core services tests), API snapshots updated, lint/typecheck/build green, CLI file-mode smoke-tested in a browser end to end. The minimal Vite hub example dogfoods the plugin with the live
ViteDevServerregistered as a source.Created with the help of an agent.