From 115612ca86fe49dd34babfb6edbadb246cf0bc5e Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 16 Apr 2026 06:58:15 +0000 Subject: [PATCH 1/2] fix: update metadata plugin tests after ObjectQL engine refactor - Update tests to use setDataEngine instead of setDatabaseDriver - Fix Studio usePackages hook to import useClient from @objectstack/client-react - Fix plugin-dev tsconfig to avoid parent exclude pattern conflicts All tests now passing after merging latest changes from main. Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/95de72a8-ec40-4b5c-a2c6-8f8a9c9f0e1a Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- CHANGELOG.md | 3 - apps/server/scripts/build-vercel.sh | 14 +- apps/server/vercel.json | 11 +- apps/studio/ROADMAP.md | 486 +++++------- apps/studio/package.json | 9 + apps/studio/src/App.tsx | 163 +--- .../studio/src/components/ObjectDataTable.tsx | 5 +- apps/studio/src/components/ObjectExplorer.tsx | 11 +- apps/studio/src/hooks/useObjectStackClient.ts | 20 + apps/studio/src/hooks/usePackages.ts | 40 + apps/studio/src/routeTree.gen.ts | 177 +++++ apps/studio/src/router.ts | 20 + .../routes/$package.metadata.$type.$name.tsx | 39 + .../src/routes/$package.objects.$name.tsx | 39 + apps/studio/src/routes/$package.tsx | 42 ++ apps/studio/src/routes/__root.tsx | 46 ++ apps/studio/src/routes/api-console.tsx | 33 + apps/studio/src/routes/index.tsx | 36 + apps/studio/src/routes/packages.tsx | 33 + .../test/components/AppSidebar.test.tsx | 149 ++++ .../test/components/ObjectDataForm.test.tsx | 188 +++++ .../test/components/ObjectDataTable.test.tsx | 137 ++++ .../test/plugins/plugin-system.test.tsx | 174 +++++ apps/studio/test/setup.ts | 15 + apps/studio/vite.config.ts | 8 +- apps/studio/vitest.config.ts | 38 + packages/metadata/src/metadata.test.ts | 36 +- packages/plugins/plugin-dev/tsconfig.json | 4 + pnpm-lock.yaml | 713 +++++++++++++++++- 29 files changed, 2200 insertions(+), 489 deletions(-) create mode 100644 apps/studio/src/hooks/useObjectStackClient.ts create mode 100644 apps/studio/src/hooks/usePackages.ts create mode 100644 apps/studio/src/routeTree.gen.ts create mode 100644 apps/studio/src/router.ts create mode 100644 apps/studio/src/routes/$package.metadata.$type.$name.tsx create mode 100644 apps/studio/src/routes/$package.objects.$name.tsx create mode 100644 apps/studio/src/routes/$package.tsx create mode 100644 apps/studio/src/routes/__root.tsx create mode 100644 apps/studio/src/routes/api-console.tsx create mode 100644 apps/studio/src/routes/index.tsx create mode 100644 apps/studio/src/routes/packages.tsx create mode 100644 apps/studio/test/components/AppSidebar.test.tsx create mode 100644 apps/studio/test/components/ObjectDataForm.test.tsx create mode 100644 apps/studio/test/components/ObjectDataTable.test.tsx create mode 100644 apps/studio/test/plugins/plugin-system.test.tsx create mode 100644 apps/studio/test/setup.ts create mode 100644 apps/studio/vitest.config.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d25e5b095..c8bb14bd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Fixed -- **Metadata plugin tests** — Updated `MetadataPlugin` test suite in `packages/metadata/src/metadata.test.ts` to match the refactored `start()` implementation. Tests now verify `setDataEngine` (via `getService('objectql')`) instead of the removed `setDatabaseDriver` (via `getServices()`) pattern. Also added `setDataEngine` and `setRealtimeService` to the `NodeMetadataManager` mock class. - ### Added - **Claude Code integration (`CLAUDE.md`)** — Added root `CLAUDE.md` file so that [Claude Code](https://docs.anthropic.com/en/docs/claude-code) automatically loads the project's system prompt when launched in the repository. Content is synced with `.github/copilot-instructions.md` and includes build/test quick-reference commands, all prime directives, monorepo structure, protocol domains, coding patterns, and domain-specific prompt references. This complements the existing GitHub Copilot instructions and `skills/` directory. diff --git a/apps/server/scripts/build-vercel.sh b/apps/server/scripts/build-vercel.sh index 476980d73..64754b48d 100755 --- a/apps/server/scripts/build-vercel.sh +++ b/apps/server/scripts/build-vercel.sh @@ -13,7 +13,7 @@ set -euo pipefail # Steps: # 1. Build the project with turbo (includes studio) # 2. Bundle the API serverless function (→ api/_handler.js) -# 3. Copy studio dist files to public/_studio/ for UI serving at /_studio path +# 3. Copy studio dist files to public/ for UI serving # 4. Install external deps in api/node_modules/ (resolve pnpm symlinks) echo "[build-vercel] Starting server build..." @@ -27,13 +27,13 @@ cd apps/server # 2. Bundle API serverless function node scripts/bundle-api.mjs -# 3. Copy studio dist files to public/_studio/ for UI serving at /_studio path -echo "[build-vercel] Copying studio dist to public/_studio/..." +# 3. Copy studio dist files to public/ for UI serving +echo "[build-vercel] Copying studio dist to public/..." rm -rf public -mkdir -p public/_studio +mkdir -p public if [ -d "../studio/dist" ]; then - cp -r ../studio/dist/* public/_studio/ - echo "[build-vercel] ✓ Copied studio dist to public/_studio/" + cp -r ../studio/dist/* public/ + echo "[build-vercel] ✓ Copied studio dist to public/" else echo "[build-vercel] ⚠ Studio dist not found (skipped)" fi @@ -60,4 +60,4 @@ rm package.json cd .. echo "[build-vercel] ✓ External dependencies installed in api/node_modules/" -echo "[build-vercel] Done. Static files in public/_studio/, serverless function in api/[[...route]].js → api/_handler.js" +echo "[build-vercel] Done. Static files in public/, serverless function in api/[[...route]].js → api/_handler.js" diff --git a/apps/server/vercel.json b/apps/server/vercel.json index 8a3a9b245..84811d0b0 100644 --- a/apps/server/vercel.json +++ b/apps/server/vercel.json @@ -6,8 +6,7 @@ "build": { "env": { "VITE_RUNTIME_MODE": "server", - "VITE_SERVER_URL": "", - "VERCEL": "true" + "VITE_SERVER_URL": "" } }, "functions": { @@ -18,18 +17,14 @@ }, "headers": [ { - "source": "/_studio/assets/(.*)", + "source": "/assets/(.*)", "headers": [ { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" } ] } ], - "redirects": [ - { "source": "/", "destination": "/_studio", "permanent": false } - ], "rewrites": [ { "source": "/api/:path*", "destination": "/api/[[...route]]" }, - { "source": "/_studio/(.*)", "destination": "/_studio/$1" }, - { "source": "/_studio", "destination": "/_studio/index.html" } + { "source": "/((?!api/).*)", "destination": "/index.html" } ] } diff --git a/apps/studio/ROADMAP.md b/apps/studio/ROADMAP.md index 2721b01ba..4fd27f93d 100644 --- a/apps/studio/ROADMAP.md +++ b/apps/studio/ROADMAP.md @@ -1,311 +1,168 @@ # ObjectStack Studio — Development Roadmap -> **Last Updated:** 2026-02-16 -> **Version:** 2.0.0 → 3.0.0 -> **Goal:** Transform Studio from a metadata inspector into a full-featured visual IDE for the ObjectStack platform. +> **Last Updated:** 2026-04-16 +> **Version:** 4.0.4 +> **Goal:** Build a practical, AI-enhanced metadata IDE focused on developer productivity. --- -## 📊 Current State Assessment - -### What's Built (v2.0.0) - -| Category | Feature | Maturity | -|----------|---------|----------| -| **Core Architecture** | MSW in-browser kernel + server mode | ✅ Production | -| **Core Architecture** | Plugin system (VS Code-style) | ✅ Production | -| **Core Architecture** | Package manager (install/enable/disable) | ✅ Production | -| **Core Architecture** | Theme toggle (light/dark/system) | ✅ Production | -| **Core Architecture** | Error boundary + toast notifications | ✅ Production | -| **Data Protocol** | Object schema inspector (field table) | ✅ Production | -| **Data Protocol** | Paginated data table with CRUD | ✅ Production | -| **Data Protocol** | Record create/edit modal (dynamic form) | ✅ Production | -| **Developer Tools** | Interactive REST API console | ✅ Production | -| **Developer Tools** | Generic JSON metadata inspector | ✅ Production | -| **Developer Tools** | Developer overview dashboard | ✅ Production | -| **Navigation** | Protocol-grouped sidebar with search | ✅ Production | -| **Navigation** | Multi-package workspace switcher | ✅ Production | -| **Navigation** | Breadcrumbs + API endpoint badges | ✅ Production | +## 🏗️ Architectural Principle -### Key Technical Debt - -| Issue | Impact | Priority | -|-------|--------|----------| -| No URL router — all navigation via `useState` | No deep links, no browser back/forward | 🔴 Critical | -| Broker shim in `createKernel.ts` duplicates runtime logic | Fragile, hard to maintain | 🟡 Medium | -| Response normalization scattered across components | DRY violation, inconsistent error handling | 🟡 Medium | -| Stale `types.ts` (leftover Task types) | Dead code | 🟢 Low | -| Empty `app/dashboard/` directory | Abandoned scaffold | 🟢 Low | -| No component-level tests | Regression risk | 🟡 Medium | -| Data refresh via `setTimeout` hack | Race conditions | 🟡 Medium | -| Sidebar groups hardcoded (not reading from plugins) | Plugin contributions ignored | 🟡 Medium | -| System objects not visible in Studio | Cannot manage users, roles, audit logs | ✅ Fixed | +**Studio is a plugin platform, not a monolithic IDE.** -### Protocol Coverage Gap +- **This project:** Core infrastructure (plugin system, kernel, navigation, testing, routing) + **one basic plugin example** +- **Designer plugins:** Official plugins developed in [objectstack-ai/studio](https://github.com/objectstack-ai/studio) (non-open-source) +- **Community plugins:** Third-party designers can be built and distributed independently -**Spec defines 100+ metadata types. Studio has specialized viewers for only 1 (Object).** All other types fall back to the generic JSON inspector. The plugin system is ready — it just needs content. - -**Object Designer Protocol:** The `ObjectDesignerConfigSchema` (in `@objectstack/spec/studio`) now defines the full specification for the visual object design experience, including field editor, relationship mapper, ER diagram, object manager, and object preview configurations. The runtime implementation should consume these schemas. - ---- +This approach ensures: +- **Modularity:** Each designer is independently developed, tested, and versioned +- **Maintainability:** Core framework stays lean and focused +- **Extensibility:** Community can build custom designers without forking Studio +- **Scalability:** Designers can be loaded on-demand, reducing bundle size +- **Commercial viability:** Official advanced designers can be proprietary while core remains open-source -## 🗺️ Roadmap +**Built-in Example:** This repo includes a basic Object Inspector plugin to demonstrate the plugin API. -### Phase 0: Foundation Hardening (v2.1) — 2 weeks +**Official Plugins:** Advanced designers (Object Designer, View Designer, Flow Designer, etc.) are developed separately in the [studio repository](https://github.com/objectstack-ai/studio). -> **Theme:** Fix structural debt before building new features. - -| # | Task | Details | -|---|------|---------| -| 0.1 | **Add URL Router** | Integrate TanStack Router or React Router. Map views to URL paths: `/:package/objects/:name`, `/:package/metadata/:type/:name`, `/packages`, `/settings`. Enable browser back/forward and deep linking. | -| 0.2 | **~~Centralize response normalization~~** | ✅ Done — `value` field removed from protocol implementation. All components now use `records` directly (spec-compliant). | -| 0.3 | **Wire plugin sidebar groups** | Replace hardcoded `PROTOCOL_GROUPS` in `app-sidebar.tsx` with plugin-contributed `useSidebarGroups()`. | -| 0.4 | **Clean dead code** | Remove stale `types.ts`, empty `app/dashboard/` directory. | -| 0.5 | **Add React Testing Library** | Set up component test infrastructure. Write baseline tests for `ObjectDataTable`, `ObjectDataForm`, `AppSidebar`, Plugin system. Target: 50% component coverage. | -| 0.6 | **Fix data refresh pattern** | Replace `setTimeout` hack in `ObjectExplorer` with proper state invalidation / refetch callback. | - -**Deliverable:** Stable, testable, deep-linkable foundation. +**Reference Implementation:** See `apps/studio/src/plugins/built-in/` for the example plugin patterns. --- -### Phase 1: Data Protocol Designers (v2.2) — 4 weeks - -> **Theme:** First-class visual editors for all Data layer metadata. - -| # | Task | Plugin ID | Priority | -|---|------|-----------|----------| -| 1.0 | **Object Designer Protocol** ✅ | `@objectstack/spec` | ✅ Done | -| | Zod schemas for field editor, relationship mapper, ER diagram, object manager, and object preview configs. `ObjectDesignerConfigSchema`, `ERDiagramConfigSchema`, `FieldEditorConfigSchema`, etc. 46 tests passing. | | | -| 1.1 | **Object Designer — Visual Field Editor** | `objectstack.object-designer` | 🔴 P0 | -| | Inline field creation/editing with type-aware property panel (6 sections: basics, constraints, relationship, display, security, advanced). Drag-and-drop field reordering. Field grouping by `field.group`. Batch add/remove operations. Validate field schemas via Zod. Usage statistics (views/formulas referencing each field). Pagination for 50+ field objects. | | | -| 1.1a | **Object Designer — Relationship Mapper** | `objectstack.object-designer` | 🔴 P0 | -| | Visual relationship creation via drag-from-source-to-target. Support lookup, master_detail, and tree relationship types. Show reverse relationships (child → parent). Cascade delete behavior warnings. Configurable line styles and colors per relationship type. | | | -| 1.1b | **Object Designer — ER Diagram** | `objectstack.object-designer` | 🟡 P1 | -| | Interactive entity-relationship diagram with 4 layout algorithms (force-directed, hierarchy, grid, circular). Entity nodes show field list with type badges and required indicators. Minimap for large schemas. Zoom controls (0.1x–3x). Click-to-navigate to object detail. Drag-to-connect for relationship creation. Hover highlighting of connected entities. Export to PNG/SVG/JSON. Auto-fit on initial load. Optional orphan hiding. | | | -| 1.1c | **Object Manager — Unified List** | `objectstack.object-designer` | 🟡 P1 | -| | Object list with table/card/tree display modes. Search across name, label, description. Filter by package, tags, field types, relationships. Sort by name, label, field count, last updated. Quick-preview tooltip with field list on hover. Statistics summary bar (total objects, fields, relationships). Side-by-side object comparison mode. ER diagram toggle from toolbar. | | | -| 1.1d | **Object Preview — Enhanced Tabs** | `objectstack.object-designer` | 🟡 P1 | -| | 8-tab object detail view: Fields, Relationships, Indexes, Validations, Capabilities, Data, API, Code. Configurable tab ordering and enable/disable. Object summary header with namespace, owner package, field count. Breadcrumb navigation. | | | -| 1.2 | **Dataset Editor** | `objectstack.dataset-editor` | 🔴 P0 | -| | Visual seed data editor. Import CSV/JSON. Preview before apply. Environment scoping (dev/test/prod). | | | -| 1.3 | **Datasource Manager** | `objectstack.datasource-manager` | 🟡 P1 | -| | Connection wizard (PostgreSQL, MySQL, MongoDB, SQLite, Redis). Test connection. Health check display. Pool config. | | | -| 1.4 | **Field Type Catalog** | `objectstack.field-catalog` | 🟡 P1 | -| | Interactive reference of all 46+ field types with examples, configuration options, and preview rendering. | | | -| 1.5 | **Validation Rule Builder** | `objectstack.validation-builder` | 🟡 P1 | -| | Visual builder for object validation rules: unique constraints, format checks, cross-field validation, state machine guards. | | | -| 1.6 | **Hook Inspector** | `objectstack.hook-inspector` | 🟢 P2 | -| | List/edit lifecycle hooks per object. Visualize execution order (priority). Test hooks from UI. | | | -| 1.7 | **Analytics Cube Designer** | `objectstack.analytics-designer` | 🟢 P2 | -| | Visual measures/dimensions builder. Join config. Pre-aggregation settings. Test query execution. | | | -| 1.8 | **Mapping Designer** | `objectstack.mapping-designer` | 🟢 P2 | -| | Visual ETL field mapping: source → transform → target. Preview transformation output. | | | - -**Deliverable:** All Data Protocol types have dedicated visual editors. +## 📊 Current State (v4.0.4) + +### What's Working + +| Category | Feature | Status | +|----------|---------|--------| +| **Core Architecture** | MSW in-browser kernel + server mode | ✅ Stable | +| **Core Architecture** | Plugin system (VS Code-style) | ✅ Stable | +| **Core Architecture** | Package manager | ✅ Stable | +| **Core Architecture** | Theme toggle (light/dark/system) | ✅ Stable | +| **Data Browsing** | Object schema inspector | ✅ Stable | +| **Data Browsing** | Paginated data table with CRUD | ✅ Stable | +| **Data Browsing** | Record create/edit forms | ✅ Stable | +| **Developer Tools** | REST API console | ✅ Stable | +| **Developer Tools** | Generic JSON metadata inspector | ✅ Stable | +| **AI Integration** | AI Chat Panel (basic) | ✅ Stable | +| **Navigation** | Protocol-grouped sidebar | ✅ Stable | +| **Navigation** | Multi-package workspace | ✅ Stable | ---- +### Key Technical Debt -### Phase 2: UI Protocol Designers (v2.3) — 6 weeks - -> **Theme:** Visual builders for every UI metadata type — the "App Builder" experience. -> **Spec Status:** All UI Protocol schemas are complete (20 files, full Airtable parity). This phase is pure runtime implementation. -> **Reference:** [Visual Design UX Optimization Plan](../../docs/design/visual-design-ux-optimization.md) - -| # | Task | Plugin ID | Priority | -|---|------|-----------|----------| -| 2.1 | **ListView Designer** | `objectstack.view-designer` | 🔴 P0 | -| | Visual column configurator for Grid views. Drag-and-drop column reorder. Column width/alignment. Filter & sort presets. Preview with live data. | | | -| 2.2 | **ListView — Kanban / Calendar / Gantt** | `objectstack.view-designer` | 🟡 P1 | -| | Mode-specific config panels: Kanban (status field, swimlanes), Calendar (date fields, duration), Gantt (dependencies, milestones). | | | -| 2.3 | **FormView Designer** | `objectstack.form-designer` | 🔴 P0 | -| | Section/column layout editor. Field placement with drag-and-drop. Conditional visibility (`visibleOn`). Widget selection per field. Preview mode. | | | -| 2.4 | **Page Builder** | `objectstack.page-builder` | 🔴 P0 | -| | **Drag-and-drop element canvas** using `BlankPageLayoutSchema`. Element palette (12 types: text, number, image, divider, button, filter, form, record_picker, grid, chart, details, list). Property panel per element. Snap-to-grid with alignment guides. Layer ordering. Undo/redo (50 steps). Data source binding per element (`ElementDataSourceSchema`). See `PageBuilderConfigSchema` in `@objectstack/spec/studio`. | | | -| 2.5 | **Component Builder** | `objectstack.component-builder` | 🟡 P1 | -| | Component composition canvas. Drag regions and components (header, details, related list, AI chat, custom). Property panel for each component. Preview with live data context. | | | -| 2.6 | **App Builder** | `objectstack.app-builder` | 🟡 P1 | -| | Navigation tree editor (drag-and-drop reorder). Add object/dashboard/page/URL/group items. Branding panel (colors, logo). Home page selector. Sharing/embed configuration. | | | -| 2.7 | **Dashboard Designer** | `objectstack.dashboard-designer` | 🟡 P1 | -| | Grid layout editor (React-Grid-Layout). Widget palette: charts, KPIs, lists, embedded views. Data source binding per widget. Auto-refresh config. | | | -| 2.8 | **Report Builder** | `objectstack.report-builder` | 🟢 P2 | -| | Tabular/Summary/Matrix report config. Column picker, grouping, aggregation. Embedded chart toggle. Filter builder. Export options. | | | -| 2.9 | **Chart Editor** | `objectstack.chart-editor` | 🟢 P2 | -| | Visual chart type selector (40+ types). Axis/series configuration. Preview with sample data. Annotation support. | | | -| 2.10 | **Action Editor** | `objectstack.action-editor` | 🟢 P2 | -| | Configure action type (script/URL/modal/flow/API). Location picker. Confirm/success text. Keyboard shortcut. Visibility conditions. | | | -| 2.11 | **Theme Editor** | `objectstack.theme-editor` | 🟢 P2 | -| | Visual color palette picker. Typography controls. Spacing/radius scale. Live preview. Export to theme metadata. | | | - -**Deliverable:** Full "App Builder" experience — design complete applications visually. +| Issue | Impact | Priority | +|-------|--------|----------| +| No URL router — all navigation via `useState` | No deep links, no browser back/forward | 🔴 P0 | +| No component tests | Regression risk | 🟡 P1 | +| Data refresh via `setTimeout` hack | Race conditions | 🟡 P1 | +| Sidebar groups hardcoded | Plugin contributions ignored | 🟡 P1 | +| Dead code (`types.ts`, empty `app/dashboard/`) | Code clutter | 🟢 P2 | --- -### Phase 3: Automation Protocol (v2.4) — 4 weeks - -> **Theme:** Visual Flow Designer and process automation tools. - -| # | Task | Plugin ID | Priority | -|---|------|-----------|----------| -| 3.1 | **Flow Designer** | `objectstack.flow-designer` | 🔴 P0 | -| | Canvas-based node graph editor (ReactFlow or similar). 16 node types from spec. Edge conditions. Variable panel. Test execution with trace. Version history. | | | -| 3.2 | **Workflow Rule Builder** | `objectstack.workflow-builder` | 🔴 P0 | -| | Trigger config (on create/update/delete/schedule). Action builder (field update, email, HTTP, task). Time-dependent trigger scheduling. | | | -| 3.3 | **Approval Process Designer** | `objectstack.approval-designer` | 🟡 P1 | -| | Multi-step approval chain visualizer. Approver type selector. Behavior config (first response / unanimous). Action bindings. | | | -| 3.4 | **State Machine Visualizer** | `objectstack.statemachine-viewer` | 🟡 P1 | -| | XState-style state diagram rendering. Transition table. Guard conditions. Entry/exit actions. Interactive state simulation. | | | -| 3.5 | **Webhook Manager** | `objectstack.webhook-manager` | 🟡 P1 | -| | Outbound webhook config (event triggers, auth, retry policy). Inbound webhook receiver (path routing, verification). Delivery log. | | | -| 3.6 | **ETL Pipeline Designer** | `objectstack.etl-designer` | 🟢 P2 | -| | Visual ETL canvas: sources → transformations → destinations. 10 transform types. Schedule config. Incremental sync. Error handling. | | | -| 3.7 | **Data Sync Manager** | `objectstack.sync-manager` | 🟢 P2 | -| | Bi-directional sync config. Conflict resolution strategy. Field mapping. Run history. | | | - -**Deliverable:** Complete low-code automation suite — build business logic without code. +## 🗺️ Roadmap (2026 Q2-Q4) ---- +### Phase 1: Plugin Infrastructure (Q2 2026) — 6 weeks -### Phase 4: Security & Identity (v2.5) — 3 weeks +**Goal:** Build robust plugin infrastructure to support external designer plugins. -> **Theme:** Enterprise-grade access control management. +| # | Task | Priority | Effort | +|---|------|----------|--------| +| 1.1 | **Add URL Router** | 🔴 P0 | 1 week | +| | Integrate TanStack Router. Map views to URL paths: `/:package/objects/:name`, `/:package/metadata/:type/:name`. Enable browser back/forward. Plugin-contributed routes. | | | +| 1.2 | **Component Testing Setup** | 🔴 P0 | 1 week | +| | Setup React Testing Library. Write tests for `ObjectDataTable`, `ObjectDataForm`, `AppSidebar`, Plugin system. Target: 30% coverage. | | | +| 1.3 | **Plugin Sidebar Groups** | 🔴 P0 | 3 days | +| | Replace hardcoded `PROTOCOL_GROUPS` with plugin-contributed `useSidebarGroups()`. Enable plugins to register sidebar items. | | | +| 1.4 | **Plugin Hot Reload** | 🟡 P1 | 3 days | +| | Support plugin hot reload during development. Enable/disable plugins without page refresh. | | | +| 1.5 | **Data Refresh Fix** | 🟡 P1 | 2 days | +| | Replace `setTimeout` hack with proper state invalidation / React Query refetch. | | | +| 1.6 | **Code Cleanup** | 🟢 P2 | 2 days | +| | Remove stale `types.ts`, empty `app/dashboard/` directory. | | | +| 1.7 | **Plugin API Documentation** | 🟡 P1 | 3 days | +| | Document StudioPlugin API, metadataViewers registration, hooks, and best practices. Create plugin starter template. | | | -| # | Task | Plugin ID | Priority | -|---|------|-----------|----------| -| 4.1 | **Permission Set Editor** | `objectstack.permission-editor` | 🔴 P0 | -| | Object permission matrix (CRUD + viewAll/modifyAll). Field-level security toggle per field. System permission checkboxes. | | | -| 4.2 | **Role Hierarchy Viewer** | `objectstack.role-viewer` | 🟡 P1 | -| | Tree/org-chart visualization. Drag-and-drop role restructuring. Data visibility rollup preview. | | | -| 4.3 | **Sharing Rule Builder** | `objectstack.sharing-builder` | 🟡 P1 | -| | OWD (org-wide default) config per object. Criteria-based sharing rule wizard. Owner-based sharing. | | | -| 4.4 | **RLS Policy Editor** | `objectstack.rls-editor` | 🟡 P1 | -| | PostgreSQL-style row-level security. USING/CHECK clause builder. Operation scope selector. Preview effective access. | | | -| 4.5 | **Identity & User Management** | `objectstack.identity-manager` | 🟢 P2 | -| | User list, account linking (OAuth/SAML/LDAP), session management. SCIM provisioning config. | | | -| 4.6 | **Audit Trail Viewer** | `objectstack.audit-viewer` | 🟢 P2 | -| | Searchable audit log. Filter by user/object/action/date. Field change diff view. Export. | | | +**Deliverable:** Production-ready plugin infrastructure for external designers. -**Deliverable:** Enterprise security management — profiles, permissions, sharing, audit. +**Note:** This phase includes one built-in example plugin (Basic Object Inspector). Advanced designer implementations will be developed in the [official studio repository](https://github.com/objectstack-ai/studio). --- -### Phase 5: AI & Intelligence (v2.6) — 4 weeks - -> **Theme:** AI-native platform capabilities — agents, RAG, NLQ. - -| # | Task | Plugin ID | Priority | -|---|------|-----------|----------| -| 5.1 | **Agent Designer** | `objectstack.agent-designer` | 🔴 P0 | -| | Persona editor (role, instructions). Model selector (provider/model/temperature). Tool binding (actions, flows, queries). Knowledge base config. Test chat interface. | | | -| 5.2 | **RAG Pipeline Builder** | `objectstack.rag-builder` | 🔴 P0 | -| | Vector store selector. Embedding model config. Chunking strategy (fixed/semantic/recursive/markdown). Retrieval strategy (similarity/MMR/hybrid). Document loader config. Test retrieval. | | | -| 5.3 | **Model Registry** | `objectstack.model-registry` | 🟡 P1 | -| | Provider/model catalog. Pricing display. Capability comparison. Prompt template editor with variable binding. Fallback chain config. Health monitoring. | | | -| 5.4 | **MCP Server Config** | `objectstack.mcp-config` | 🟡 P1 | -| | Transport config (stdio/HTTP/WebSocket). Resource definitions. Tool registration. Prompt management. Server capabilities. | | | -| 5.5 | **NLQ Playground** | `objectstack.nlq-playground` | 🟡 P1 | -| | Natural language → ObjectQL query tester. Intent detection preview. Entity recognition display. Generated query inspector. Training example management. | | | -| 5.6 | **AI Orchestration Designer** | `objectstack.ai-orchestration` | 🟢 P2 | -| | Multi-task AI workflow canvas. Task types (classify/extract/summarize/generate/predict). Trigger binding. Post-processing actions. Execution mode (sequential/parallel). | | | -| 5.7 | **AI Cost Dashboard** | `objectstack.ai-cost-dashboard` | 🟢 P2 | -| | Token usage tracking. Cost per model/agent/pipeline. Budget alerts. Usage trend charts. | | | - -**Deliverable:** Full AI-native studio — design agents, configure RAG, test NLQ. - ---- +### Phase 2: AI Integration & Developer Tools (Q3 2026) — 6 weeks -### Phase 6: API & Integration (v2.7) — 3 weeks +**Goal:** AI-enhanced developer experience for metadata editing. -> **Theme:** API management and enterprise connectivity. +| # | Task | Priority | Effort | +|---|------|----------|--------| +| 2.1 | **AI Copilot Enhancement** | 🔴 P0 | 2 weeks | +| | Context-aware AI assistance. Plugin-aware code generation. Metadata explanation. Smart suggestions. Integration with designer plugins via API. | | | +| 2.2 | **Command Palette (Ctrl+K)** | 🔴 P0 | 1 week | +| | Global command search. Quick navigation. Plugin-contributed commands. AI-powered command suggestions. | | | +| 2.3 | **Monaco Editor Integration** | 🔴 P0 | 2 weeks | +| | Raw YAML/JSON/TypeScript editing. Schema-aware autocomplete. Inline Zod validation. Toggle between visual/code modes. Plugin-contributed language support. | | | +| 2.4 | **Plugin Marketplace UI (Basic)** | 🟡 P1 | 1 week | +| | Browse available designer plugins. Install/uninstall UI. Plugin metadata display. Version management. | | | -| # | Task | Plugin ID | Priority | -|---|------|-----------|----------| -| 6.1 | **API Endpoint Designer** | `objectstack.api-designer` | 🟡 P1 | -| | Custom REST/GraphQL endpoint config. Request/response schema. Auth requirements. Rate limiting. | | | -| 6.2 | **Connector Builder** | `objectstack.connector-builder` | 🟡 P1 | -| | Enterprise integration wizard. Auth config (OAuth2/SAML/API key). Field mapping (bi-directional). Sync strategy. Conflict resolution. Test connection. | | | -| 6.3 | **GraphQL Explorer** | `objectstack.graphql-explorer` | 🟢 P2 | -| | Schema browser. Interactive query builder (like GraphiQL). Type relationships. Real-time testing. | | | -| 6.4 | **WebSocket / Realtime Config** | `objectstack.realtime-config` | 🟢 P2 | -| | Channel definitions. Event subscriptions. Authorization. Connection management. Live event monitor. | | | -| 6.5 | **API Documentation Generator** | `objectstack.api-docs` | 🟢 P2 | -| | Auto-generated OpenAPI/Swagger from metadata. Interactive docs. Endpoint testing. Client SDK generation. | | | +**Deliverable:** AI-powered IDE with plugin extensibility. -**Deliverable:** Full API lifecycle management from Studio. +**Note:** Official advanced designers (Object Designer, View Designer, Form Designer) are developed in [objectstack-ai/studio](https://github.com/objectstack-ai/studio) as proprietary plugins. --- -### Phase 7: System & DevOps (v2.8) — 3 weeks - -> **Theme:** Operational tooling for production readiness. - -| # | Task | Plugin ID | Priority | -|---|------|-----------|----------| -| 7.1 | **Translation Manager** | `objectstack.translation-manager` | 🟡 P1 | -| | Side-by-side locale editor. Missing translation tracker. Bulk import/export. Auto-translate integration. | | | -| 7.2 | **Feature Flag Manager** | `objectstack.feature-flags` | 🟡 P1 | -| | Toggle feature flags. Percentage rollout. User/role targeting. A/B test config. | | | -| 7.3 | **Job Scheduler** | `objectstack.job-scheduler` | 🟡 P1 | -| | Background job list. Cron schedule editor. Execution history. Retry config. | | | -| 7.4 | **Migration Manager** | `objectstack.migration-manager` | 🟡 P1 | -| | Schema migration timeline. Pending/applied status. Rollback support. Diff preview. | | | -| 7.5 | **Observability Dashboard** | `objectstack.observability` | 🟢 P2 | -| | Metrics/tracing/logging aggregation. Request latency. Error rates. Resource usage. | | | -| 7.6 | **Change Management** | `objectstack.change-management` | 🟢 P2 | -| | Change sets. Sandbox comparison. Deployment tracking. Rollback. | | | -| 7.7 | **Notification Config** | `objectstack.notifications` | 🟢 P2 | -| | Email/push/in-app notification templates. Channel config. Delivery tracking. | | | - -**Deliverable:** Production-ready operational tooling. +### Phase 3: Advanced IDE Features (Q4 2026) — 4 weeks ---- - -### Phase 8: Studio Platform Evolution (v3.0) — Ongoing - -> **Theme:** Studio becomes a full IDE ecosystem. -> **Reference:** [Visual Design UX Optimization Plan](../../docs/design/visual-design-ux-optimization.md) - -| # | Task | Priority | -|---|------|----------| -| 8.1 | **Command Palette** (Ctrl+K) | 🔴 P0 | -| | Global command search. Keyboard-first navigation. Plugin-contributed commands. Quick actions. | -| 8.2 | **Multi-Tab Interface** | 🔴 P0 | -| | Open multiple metadata items simultaneously. Tab management (close/drag/split). Unsaved indicator. | -| 8.3 | **Code Editor Integration** | 🟡 P1 | -| | Monaco Editor for raw metadata YAML/JSON/TypeScript editing. Schema-aware autocomplete. Inline Zod validation. | -| 8.4 | **Version Control UI** | 🟡 P1 | -| | Git diff viewer for metadata changes. Commit from Studio. Branch switching. Conflict resolution. | -| 8.5 | **External Plugin Marketplace** | 🟡 P1 | -| | Browse/install community Studio plugins. Plugin manifest validation. Sandboxed execution. | -| 8.6 | **Data Studio Mode** | 🟡 P1 | -| | Unified interactive data workspace combining views + embedded charts + inline editing + quick filters. Cross-view global filter. Aggregation bar. View switcher tabs. See `DataStudioConfigSchema` proposal. | -| 8.7 | **Design-Time Preview** | 🟡 P1 | -| | Preview interfaces as different users/roles. Responsive viewport switcher (desktop/tablet/mobile). Live data preview. Show data binding indicators. | -| 8.8 | **Template Gallery** | 🟡 P1 | -| | Pre-built interface templates (CRM, Project, HR, etc.). One-click instantiation with variable binding. Community template marketplace. | -| 8.9 | **Collaborative Editing** | 🟢 P2 | -| | Real-time multi-user editing (CRDT). Presence indicators. Change attribution. | -| 8.10 | **Responsive / Mobile Mode** | 🟢 P2 | -| | Mobile preview mode. Responsive layout testing. Touch-friendly interactions. | -| 8.11 | **Embedded AI Copilot** | 🟡 P1 | -| | In-Studio AI assistant. Generate metadata from natural language. Explain configuration. Suggest best practices. AI-assisted layout suggestions. | +**Goal:** Professional IDE capabilities. ---- +| # | Task | Priority | Effort | +|---|------|----------|--------| +| 3.1 | **Multi-Tab Interface** | 🔴 P0 | 1 week | +| | Open multiple metadata items. Tab management (close/drag/split). Unsaved indicators. Plugin tabs support. | | | +| 3.2 | **Plugin DevTools** | 🔴 P0 | 1 week | +| | Plugin inspector. Performance profiling. Error logging. Hot reload status. Debug panel. | | | +| 3.3 | **Version Control Integration** | 🟡 P1 | 1 week | +| | Git diff viewer for metadata changes. Commit from Studio. Basic conflict resolution. | | | +| 3.4 | **Plugin Starter Kit** | 🟡 P1 | 1 week | +| | CLI scaffolding tool for creating new Studio plugins. Templates for common designer patterns (field editor, canvas, form builder). | | | -## 📅 Timeline Summary +**Deliverable:** Complete plugin development environment. -``` -2026 Q1 ──┬── Phase 0: Foundation (v2.1) [2 weeks] - └── Phase 1: Data Designers (v2.2) [4 weeks] +**Note:** The [official studio repository](https://github.com/objectstack-ai/studio) uses this infrastructure to build full-featured proprietary designers. -2026 Q2 ──┬── Phase 2: UI Designers (v2.3) [6 weeks] - └── Phase 3: Automation (v2.4) [4 weeks] +--- -2026 Q3 ──┬── Phase 4: Security (v2.5) [3 weeks] - ├── Phase 5: AI Studio (v2.6) [4 weeks] - └── Phase 6: API & Integration (v2.7) [3 weeks] +## 📅 Simplified Timeline -2026 Q4 ──┬── Phase 7: System & DevOps (v2.8) [3 weeks] - └── Phase 8: Platform v3.0 [ongoing] +``` +2026 Q2 ── Phase 1: Plugin Infrastructure [6 weeks] + ├─ URL Router + Testing + ├─ Plugin Sidebar Groups + ├─ Plugin Hot Reload + ├─ Plugin API Docs + └─ Basic Object Inspector Plugin (built-in example) + +2026 Q3 ── Phase 2: AI Integration & Tools [6 weeks] + ├─ AI Copilot Enhancement + ├─ Command Palette (Ctrl+K) + ├─ Monaco Editor Integration + └─ Plugin Marketplace UI + +2026 Q4 ── Phase 3: Advanced IDE Features [4 weeks] + ├─ Multi-Tab Interface + ├─ Plugin DevTools + ├─ Version Control Integration + └─ Plugin Starter Kit + +External ── Official Designer Plugins (objectstack-ai/studio - Proprietary) + ├─ Object Designer Plugin + ├─ View Designer Plugin + ├─ Form Designer Plugin + ├─ Flow Designer Plugin + ├─ Dashboard Designer Plugin + └─ Agent Designer Plugin + +Community── Community Plugins (Open Ecosystem) + └─ Custom designers by third-party developers ``` --- @@ -355,16 +212,85 @@ export const myPlugin: StudioPlugin = { --- -## 🎯 Success Metrics - -| Metric | Current | Phase 2 Target | v3.0 Target | -|--------|---------|----------------|-------------| -| Metadata types with dedicated viewer | 1 / 30+ | 15 / 30+ | 30+ / 30+ | -| Object Designer protocol schemas | 16 schemas | — | — | -| Object Designer protocol tests | 46 tests | — | — | -| Page Builder protocol schemas | 4 schemas | 10+ | 15+ | -| Component test coverage | 0% | 50% | 80% | -| Deep-linkable views | 0 | All | All | -| Plugin count (built-in) | 7 | 20 | 35+ | -| Airtable UX parity (visual builder) | 20% | 60% | 90% | -| Time to build a complete app (from scratch) | N/A (manual JSON) | 30 min | 10 min | +## 🎯 Success Metrics (Realistic) + +| Metric | Current (v4.0.4) | Q2 2026 | Q3 2026 | Q4 2026 | +|--------|------------------|---------|---------|---------| +| Component test coverage | 0% | 30% | 50% | 60% | +| Deep-linkable views | ❌ | ✅ | ✅ | ✅ | +| Plugin API stability (1-5) | 3 (evolving) | 4 (stable) | 5 (production) | 5 (documented) | +| External plugins supported | 0 | 2-3 | 5-8 | 10+ | +| AI assistance quality (1-5) | 2 (basic chat) | 3 (context-aware) | 4 (plugin-aware) | 4+ (code generation) | +| Plugin development time | N/A | 2 weeks | 1 week | 3 days | +| Developer satisfaction (1-5) | 3 | 3.5 | 4 | 4.5 | + +--- + +## 🚫 What We're NOT Building in Core Studio + +**Core Studio = Plugin Infrastructure + One Example** + +The following are **external plugin responsibilities** (developed in [objectstack-ai/studio](https://github.com/objectstack-ai/studio)): + +- ❌ Object Designer UI (official proprietary plugin) +- ❌ View Designer UI (official proprietary plugin) +- ❌ Form Designer UI (official proprietary plugin) +- ❌ Flow Designer UI (official proprietary plugin) +- ❌ Dashboard Designer UI (official proprietary plugin) +- ❌ Agent Designer UI (official proprietary plugin) +- ❌ Security/permission UI (official proprietary plugin) +- ❌ Full Airtable Interface parity (official proprietary plugins) +- ❌ Advanced automation designers (official proprietary plugins) +- ❌ Enterprise-specific UI (official proprietary plugins) + +**Built-in in Core Studio:** +- ✅ Basic Object Inspector plugin (example/reference implementation) +- ✅ Generic JSON metadata viewer (fallback) + +**Also Deferred:** +- ❌ Mobile/responsive mode (desktop-first) +- ❌ Real-time collaborative editing (complex, future) +- ❌ Public plugin marketplace (Phase 3+) + +--- + +## 📦 Plugin Ecosystem + +### Built-in (This Repository - Open Source) + +**Basic Object Inspector Plugin** — Minimal reference implementation +- Read-only object schema viewer +- Field list with type badges +- Simple data browser +- Demonstrates plugin API patterns + +**Purpose:** Educational example for plugin developers + +--- + +### Official Plugins (objectstack-ai/studio - Proprietary) + +Advanced designer plugins developed and maintained by ObjectStack: + +1. **Object Designer** — Visual object & field editor with drag-and-drop +2. **View Designer** — Grid/Kanban/Calendar/Gantt view configurator +3. **Form Designer** — Layout editor with conditional visibility +4. **Flow Designer** — Automation flow canvas with BPMN support +5. **Dashboard Designer** — Interactive dashboard layout editor +6. **Agent Designer** — AI agent configuration with RAG pipeline builder + +**Distribution:** Available via npm registry or private plugin marketplace + +**License:** Proprietary (requires license key) + +--- + +### Community Plugins (Third-Party - Variable Licensing) + +Community developers can build custom designers: +- Industry-specific metadata editors +- Custom workflow designers +- Integration-specific tools +- Specialized data visualizers + +**Plugin Development:** Follow the plugin API documented in Phase 1.7 diff --git a/apps/studio/package.json b/apps/studio/package.json index ce692309f..458745d12 100644 --- a/apps/studio/package.json +++ b/apps/studio/package.json @@ -13,6 +13,8 @@ "build": "pnpm msw:init && vite build", "typecheck": "tsc --noEmit", "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", "test:bdd": "objectstack test", "preview": "vite preview" }, @@ -41,6 +43,7 @@ "@objectstack/service-automation": "workspace:*", "@objectstack/service-feed": "workspace:*", "@objectstack/spec": "workspace:*", + "@tanstack/react-router": "^1.91.6", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-collapsible": "^1.1.12", @@ -68,10 +71,16 @@ }, "devDependencies": { "@objectstack/cli": "workspace:*", + "@tanstack/router-devtools": "^1.91.6", + "@tanstack/router-plugin": "^1.91.5", "@tailwindcss/postcss": "^4.2.2", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@testing-library/user-event": "^14.5.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", + "@vitest/coverage-v8": "^4.1.4", "autoprefixer": "^10.4.27", "esbuild": "^0.28.0", "happy-dom": "^20.8.9", diff --git a/apps/studio/src/App.tsx b/apps/studio/src/App.tsx index 89ebe9432..18bb41824 100644 --- a/apps/studio/src/App.tsx +++ b/apps/studio/src/App.tsx @@ -1,161 +1,14 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. -import { useState, useEffect, useCallback } from 'react'; -import { ObjectStackClient } from '@objectstack/client'; -import { ObjectStackProvider } from '@objectstack/client-react'; -import { ErrorBoundary } from './components/ErrorBoundary'; -import { AppSidebar } from "./components/app-sidebar" -import { SiteHeader } from "@/components/site-header" -import { SidebarProvider } from "@/components/ui/sidebar" -import { DeveloperOverview } from './components/DeveloperOverview'; -import { PackageManager } from './components/PackageManager'; -import { ApiConsolePage } from './components/ApiConsolePage'; -import { Toaster } from "@/components/ui/toaster" -import { AiChatPanel } from "@/components/AiChatPanel" -import { getApiBaseUrl, config } from './lib/config'; -import { PluginRegistryProvider, PluginHost } from './plugins'; -import { builtInPlugins } from './plugins/built-in'; -import type { InstalledPackage } from '@objectstack/spec/kernel'; +/** + * App Component + * + * Main application wrapper that provides the TanStack Router instance. + */ -type ViewType = 'overview' | 'packages' | 'object' | 'metadata' | 'api-console'; +import { RouterProvider } from '@tanstack/react-router'; +import { router } from './router'; export default function App() { - const [client, setClient] = useState(null); - const [packages, setPackages] = useState([]); - const [selectedPackage, setSelectedPackage] = useState(null); - const [selectedObject, setSelectedObject] = useState(null); - const [selectedMeta, setSelectedMeta] = useState<{ type: string; name: string } | null>(null); - const [selectedView, setSelectedView] = useState('overview'); - - // 1. Create client once - useEffect(() => { - const baseUrl = getApiBaseUrl(); - console.log(`[App] Connecting to API: ${baseUrl} (mode: ${config.mode})`); - setClient(new ObjectStackClient({ baseUrl })); - }, []); - - // 2. Fetch installed packages from the server API - useEffect(() => { - if (!client) return; - let mounted = true; - - async function loadPackages() { - try { - const result = await client!.packages.list(); - const all: InstalledPackage[] = result?.packages || []; - // Filter out the root dev-workspace — it's the monorepo aggregator, not a real package - const items = all.filter((p) => p.manifest?.version !== '0.0.0' && p.manifest?.id !== 'dev-workspace'); - console.log('[App] Fetched packages:', items.map((p) => p.manifest?.name || p.manifest?.id)); - if (mounted && items.length > 0) { - setPackages(items); - setSelectedPackage(items[0]); - } - } catch (err) { - console.error('[App] Failed to fetch packages:', err); - } - } - - loadPackages(); - return () => { mounted = false; }; - }, [client]); - - const handleSelectPackage = useCallback((pkg: InstalledPackage) => { - setSelectedPackage(pkg); - setSelectedObject(null); - setSelectedMeta(null); - setSelectedView('overview'); - }, []); - - const handleSelectObject = useCallback((name: string) => { - if (name) { - setSelectedObject(name); - setSelectedView('object'); - } else { - setSelectedObject(null); - setSelectedView('overview'); - } - }, []); - - const handleSelectView = useCallback((view: ViewType) => { - setSelectedView(view); - setSelectedObject(null); - setSelectedMeta(null); - }, []); - - const handleSelectMeta = useCallback((type: string, name: string) => { - setSelectedMeta({ type, name }); - setSelectedObject(null); - setSelectedView('metadata'); - }, []); - - const handleNavigate = useCallback((view: string, detail?: string) => { - if (view === 'packages') handleSelectView('packages'); - else if (detail) handleSelectObject(detail); - }, [handleSelectView, handleSelectObject]); - - if (!client) return ( -
-
-
-

Connecting to ObjectStack…

-
-
- ); - - return ( - - - - - -
- -
- {selectedView === 'object' && selectedObject ? ( - - ) : selectedView === 'metadata' && selectedMeta ? ( - - ) : selectedView === 'packages' ? ( - - ) : selectedView === 'api-console' ? ( - - ) : ( - - )} -
-
- - -
-
-
-
- ); + return ; } diff --git a/apps/studio/src/components/ObjectDataTable.tsx b/apps/studio/src/components/ObjectDataTable.tsx index d41facfd8..440e86dc4 100644 --- a/apps/studio/src/components/ObjectDataTable.tsx +++ b/apps/studio/src/components/ObjectDataTable.tsx @@ -21,6 +21,7 @@ import { interface ObjectDataTableProps { objectApiName: string; onEdit: (record: any) => void; + refreshTrigger?: number; } function CellValue({ value, type }: { value: any; type: string }) { @@ -78,7 +79,7 @@ function TableSkeleton({ cols }: { cols: number }) { ); } -export function ObjectDataTable({ objectApiName, onEdit }: ObjectDataTableProps) { +export function ObjectDataTable({ objectApiName, onEdit, refreshTrigger = 0 }: ObjectDataTableProps) { const client = useClient(); const [def, setDef] = useState(null); const [records, setRecords] = useState([]); @@ -136,7 +137,7 @@ export function ObjectDataTable({ objectApiName, onEdit }: ObjectDataTableProps) } loadData(); return () => { mounted = false; }; - }, [client, objectApiName, page]); + }, [client, objectApiName, page, refreshTrigger]); async function handleDelete(id: string) { if (!confirm('Are you sure you want to delete this record?')) return; diff --git a/apps/studio/src/components/ObjectExplorer.tsx b/apps/studio/src/components/ObjectExplorer.tsx index 12771331d..3710124b9 100644 --- a/apps/studio/src/components/ObjectExplorer.tsx +++ b/apps/studio/src/components/ObjectExplorer.tsx @@ -18,6 +18,8 @@ export function ObjectExplorer({ objectApiName }: ObjectExplorerProps) { const [activeTab, setActiveTab] = useState('schema'); const [editingRecord, setEditingRecord] = useState(null); const [showForm, setShowForm] = useState(false); + // Refresh trigger: increment this to force data table to refetch + const [refreshTrigger, setRefreshTrigger] = useState(0); function handleEdit(record: any) { setEditingRecord(record); @@ -27,11 +29,8 @@ export function ObjectExplorer({ objectApiName }: ObjectExplorerProps) { function handleFormSuccess() { setShowForm(false); setEditingRecord(null); - // Force data tab re-fetch by toggling activeTab - if (activeTab === 'data') { - setActiveTab('schema'); - setTimeout(() => setActiveTab('data'), 0); - } + // Trigger data table refresh by incrementing the refresh counter + setRefreshTrigger(prev => prev + 1); } const tabs: { id: ObjectTab; label: string; icon: React.ElementType }[] = [ @@ -78,7 +77,7 @@ export function ObjectExplorer({ objectApiName }: ObjectExplorerProps) { )} {activeTab === 'data' && ( - + )} {activeTab === 'api' && ( diff --git a/apps/studio/src/hooks/useObjectStackClient.ts b/apps/studio/src/hooks/useObjectStackClient.ts new file mode 100644 index 000000000..451677cf3 --- /dev/null +++ b/apps/studio/src/hooks/useObjectStackClient.ts @@ -0,0 +1,20 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { useState, useEffect } from 'react'; +import { ObjectStackClient } from '@objectstack/client'; +import { getApiBaseUrl, config } from '../lib/config'; + +/** + * Hook to create and manage ObjectStack client instance + */ +export function useObjectStackClient() { + const [client, setClient] = useState(null); + + useEffect(() => { + const baseUrl = getApiBaseUrl(); + console.log(`[App] Connecting to API: ${baseUrl} (mode: ${config.mode})`); + setClient(new ObjectStackClient({ baseUrl })); + }, []); + + return client; +} diff --git a/apps/studio/src/hooks/usePackages.ts b/apps/studio/src/hooks/usePackages.ts new file mode 100644 index 000000000..fa60af52d --- /dev/null +++ b/apps/studio/src/hooks/usePackages.ts @@ -0,0 +1,40 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { useState, useEffect } from 'react'; +import { useClient } from '@objectstack/client-react'; +import type { InstalledPackage } from '@objectstack/spec/kernel'; + +/** + * Hook to fetch and manage installed packages + */ +export function usePackages() { + const client = useClient(); + const [packages, setPackages] = useState([]); + const [selectedPackage, setSelectedPackage] = useState(null); + + useEffect(() => { + if (!client) return; + let mounted = true; + + async function loadPackages() { + try { + const result = await client.packages.list(); + const all: InstalledPackage[] = result?.packages || []; + // Filter out the root dev-workspace — it's the monorepo aggregator, not a real package + const items = all.filter((p) => p.manifest?.version !== '0.0.0' && p.manifest?.id !== 'dev-workspace'); + console.log('[App] Fetched packages:', items.map((p) => p.manifest?.name || p.manifest?.id)); + if (mounted && items.length > 0) { + setPackages(items); + setSelectedPackage(items[0]); + } + } catch (err) { + console.error('[App] Failed to fetch packages:', err); + } + } + + loadPackages(); + return () => { mounted = false; }; + }, [client]); + + return { packages, selectedPackage, setSelectedPackage }; +} diff --git a/apps/studio/src/routeTree.gen.ts b/apps/studio/src/routeTree.gen.ts new file mode 100644 index 000000000..e1b1382f8 --- /dev/null +++ b/apps/studio/src/routeTree.gen.ts @@ -0,0 +1,177 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as PackagesRouteImport } from './routes/packages' +import { Route as ApiConsoleRouteImport } from './routes/api-console' +import { Route as PackageRouteImport } from './routes/$package' +import { Route as IndexRouteImport } from './routes/index' +import { Route as PackageObjectsNameRouteImport } from './routes/$package.objects.$name' +import { Route as PackageMetadataTypeNameRouteImport } from './routes/$package.metadata.$type.$name' + +const PackagesRoute = PackagesRouteImport.update({ + id: '/packages', + path: '/packages', + getParentRoute: () => rootRouteImport, +} as any) +const ApiConsoleRoute = ApiConsoleRouteImport.update({ + id: '/api-console', + path: '/api-console', + getParentRoute: () => rootRouteImport, +} as any) +const PackageRoute = PackageRouteImport.update({ + id: '/$package', + path: '/$package', + getParentRoute: () => rootRouteImport, +} as any) +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const PackageObjectsNameRoute = PackageObjectsNameRouteImport.update({ + id: '/objects/$name', + path: '/objects/$name', + getParentRoute: () => PackageRoute, +} as any) +const PackageMetadataTypeNameRoute = PackageMetadataTypeNameRouteImport.update({ + id: '/metadata/$type/$name', + path: '/metadata/$type/$name', + getParentRoute: () => PackageRoute, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/$package': typeof PackageRouteWithChildren + '/api-console': typeof ApiConsoleRoute + '/packages': typeof PackagesRoute + '/$package/objects/$name': typeof PackageObjectsNameRoute + '/$package/metadata/$type/$name': typeof PackageMetadataTypeNameRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/$package': typeof PackageRouteWithChildren + '/api-console': typeof ApiConsoleRoute + '/packages': typeof PackagesRoute + '/$package/objects/$name': typeof PackageObjectsNameRoute + '/$package/metadata/$type/$name': typeof PackageMetadataTypeNameRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/$package': typeof PackageRouteWithChildren + '/api-console': typeof ApiConsoleRoute + '/packages': typeof PackagesRoute + '/$package/objects/$name': typeof PackageObjectsNameRoute + '/$package/metadata/$type/$name': typeof PackageMetadataTypeNameRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: + | '/' + | '/$package' + | '/api-console' + | '/packages' + | '/$package/objects/$name' + | '/$package/metadata/$type/$name' + fileRoutesByTo: FileRoutesByTo + to: + | '/' + | '/$package' + | '/api-console' + | '/packages' + | '/$package/objects/$name' + | '/$package/metadata/$type/$name' + id: + | '__root__' + | '/' + | '/$package' + | '/api-console' + | '/packages' + | '/$package/objects/$name' + | '/$package/metadata/$type/$name' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + PackageRoute: typeof PackageRouteWithChildren + ApiConsoleRoute: typeof ApiConsoleRoute + PackagesRoute: typeof PackagesRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/packages': { + id: '/packages' + path: '/packages' + fullPath: '/packages' + preLoaderRoute: typeof PackagesRouteImport + parentRoute: typeof rootRouteImport + } + '/api-console': { + id: '/api-console' + path: '/api-console' + fullPath: '/api-console' + preLoaderRoute: typeof ApiConsoleRouteImport + parentRoute: typeof rootRouteImport + } + '/$package': { + id: '/$package' + path: '/$package' + fullPath: '/$package' + preLoaderRoute: typeof PackageRouteImport + parentRoute: typeof rootRouteImport + } + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/$package/objects/$name': { + id: '/$package/objects/$name' + path: '/objects/$name' + fullPath: '/$package/objects/$name' + preLoaderRoute: typeof PackageObjectsNameRouteImport + parentRoute: typeof PackageRoute + } + '/$package/metadata/$type/$name': { + id: '/$package/metadata/$type/$name' + path: '/metadata/$type/$name' + fullPath: '/$package/metadata/$type/$name' + preLoaderRoute: typeof PackageMetadataTypeNameRouteImport + parentRoute: typeof PackageRoute + } + } +} + +interface PackageRouteChildren { + PackageObjectsNameRoute: typeof PackageObjectsNameRoute + PackageMetadataTypeNameRoute: typeof PackageMetadataTypeNameRoute +} + +const PackageRouteChildren: PackageRouteChildren = { + PackageObjectsNameRoute: PackageObjectsNameRoute, + PackageMetadataTypeNameRoute: PackageMetadataTypeNameRoute, +} + +const PackageRouteWithChildren = + PackageRoute._addFileChildren(PackageRouteChildren) + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + PackageRoute: PackageRouteWithChildren, + ApiConsoleRoute: ApiConsoleRoute, + PackagesRoute: PackagesRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/apps/studio/src/router.ts b/apps/studio/src/router.ts new file mode 100644 index 000000000..c4cde87c8 --- /dev/null +++ b/apps/studio/src/router.ts @@ -0,0 +1,20 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Route Tree Configuration + * + * TanStack Router auto-generates this file from routes/ directory. + * This import is required for the router to work. + */ + +import { createRouter } from '@tanstack/react-router'; +import { routeTree } from './routeTree.gen'; + +export const router = createRouter({ routeTree }); + +// Register things for type-safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} diff --git a/apps/studio/src/routes/$package.metadata.$type.$name.tsx b/apps/studio/src/routes/$package.metadata.$type.$name.tsx new file mode 100644 index 000000000..4e8075053 --- /dev/null +++ b/apps/studio/src/routes/$package.metadata.$type.$name.tsx @@ -0,0 +1,39 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { createFileRoute } from '@tanstack/react-router'; +import { AppSidebar } from '../components/app-sidebar'; +import { SiteHeader } from '@/components/site-header'; +import { PluginHost } from '../plugins'; +import { usePackages } from '../hooks/usePackages'; + +function MetadataViewComponent() { + const { type, name } = Route.useParams(); + const { packages, selectedPackage } = usePackages(); + + return ( + <> + +
+ +
+ +
+
+ + ); +} + +export const Route = createFileRoute('/$package/metadata/$type/$name')({ + component: MetadataViewComponent, +}); diff --git a/apps/studio/src/routes/$package.objects.$name.tsx b/apps/studio/src/routes/$package.objects.$name.tsx new file mode 100644 index 000000000..c5cd0892c --- /dev/null +++ b/apps/studio/src/routes/$package.objects.$name.tsx @@ -0,0 +1,39 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { createFileRoute } from '@tanstack/react-router'; +import { AppSidebar } from '../components/app-sidebar'; +import { SiteHeader } from '@/components/site-header'; +import { PluginHost } from '../plugins'; +import { usePackages } from '../hooks/usePackages'; + +function ObjectViewComponent() { + const { name } = Route.useParams(); + const { packages, selectedPackage } = usePackages(); + + return ( + <> + +
+ +
+ +
+
+ + ); +} + +export const Route = createFileRoute('/$package/objects/$name')({ + component: ObjectViewComponent, +}); diff --git a/apps/studio/src/routes/$package.tsx b/apps/studio/src/routes/$package.tsx new file mode 100644 index 000000000..caf0076e5 --- /dev/null +++ b/apps/studio/src/routes/$package.tsx @@ -0,0 +1,42 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { createFileRoute, Outlet } from '@tanstack/react-router'; +import { AppSidebar } from '../components/app-sidebar'; +import { SiteHeader } from '@/components/site-header'; +import { usePackages } from '../hooks/usePackages'; +import { useEffect } from 'react'; + +function PackageLayoutComponent() { + const { packageId } = Route.useParams(); + const { packages, selectedPackage, setSelectedPackage } = usePackages(); + + // Update selected package when route param changes + useEffect(() => { + const pkg = packages.find(p => p.manifest?.id === packageId); + if (pkg && pkg !== selectedPackage) { + setSelectedPackage(pkg); + } + }, [packageId, packages, selectedPackage, setSelectedPackage]); + + return ( + <> + +
+ +
+ +
+
+ + ); +} + +export const Route = createFileRoute('/$package')({ + component: PackageLayoutComponent, +}); diff --git a/apps/studio/src/routes/__root.tsx b/apps/studio/src/routes/__root.tsx new file mode 100644 index 000000000..5823d846e --- /dev/null +++ b/apps/studio/src/routes/__root.tsx @@ -0,0 +1,46 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { createRootRoute, Outlet } from '@tanstack/react-router'; +import { TanStackRouterDevtools } from '@tanstack/router-devtools'; +import { ObjectStackProvider } from '@objectstack/client-react'; +import { ErrorBoundary } from '../components/ErrorBoundary'; +import { SidebarProvider } from '@/components/ui/sidebar'; +import { Toaster } from '@/components/ui/toaster'; +import { AiChatPanel } from '@/components/AiChatPanel'; +import { PluginRegistryProvider } from '../plugins'; +import { builtInPlugins } from '../plugins/built-in'; +import { useObjectStackClient } from '../hooks/useObjectStackClient'; + +function RootComponent() { + const client = useObjectStackClient(); + + if (!client) { + return ( +
+
+
+

Connecting to ObjectStack…

+
+
+ ); + } + + return ( + + + + + + + + + + + + + ); +} + +export const Route = createRootRoute({ + component: RootComponent, +}); diff --git a/apps/studio/src/routes/api-console.tsx b/apps/studio/src/routes/api-console.tsx new file mode 100644 index 000000000..b6e4d82be --- /dev/null +++ b/apps/studio/src/routes/api-console.tsx @@ -0,0 +1,33 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { createFileRoute } from '@tanstack/react-router'; +import { AppSidebar } from '../components/app-sidebar'; +import { SiteHeader } from '@/components/site-header'; +import { ApiConsolePage } from '../components/ApiConsolePage'; +import { usePackages } from '../hooks/usePackages'; + +function ApiConsoleComponent() { + const { packages, selectedPackage } = usePackages(); + + return ( + <> + +
+ +
+ +
+
+ + ); +} + +export const Route = createFileRoute('/api-console')({ + component: ApiConsoleComponent, +}); diff --git a/apps/studio/src/routes/index.tsx b/apps/studio/src/routes/index.tsx new file mode 100644 index 000000000..1373fe8e4 --- /dev/null +++ b/apps/studio/src/routes/index.tsx @@ -0,0 +1,36 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { createFileRoute } from '@tanstack/react-router'; +import { AppSidebar } from '../components/app-sidebar'; +import { SiteHeader } from '@/components/site-header'; +import { DeveloperOverview } from '../components/DeveloperOverview'; +import { usePackages } from '../hooks/usePackages'; + +function IndexComponent() { + const { packages, selectedPackage } = usePackages(); + + return ( + <> + +
+ +
+ +
+
+ + ); +} + +export const Route = createFileRoute('/')({ + component: IndexComponent, +}); diff --git a/apps/studio/src/routes/packages.tsx b/apps/studio/src/routes/packages.tsx new file mode 100644 index 000000000..33ac8cd70 --- /dev/null +++ b/apps/studio/src/routes/packages.tsx @@ -0,0 +1,33 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { createFileRoute } from '@tanstack/react-router'; +import { AppSidebar } from '../components/app-sidebar'; +import { SiteHeader } from '@/components/site-header'; +import { PackageManager } from '../components/PackageManager'; +import { usePackages } from '../hooks/usePackages'; + +function PackagesViewComponent() { + const { packages, selectedPackage } = usePackages(); + + return ( + <> + +
+ +
+ +
+
+ + ); +} + +export const Route = createFileRoute('/packages')({ + component: PackagesViewComponent, +}); diff --git a/apps/studio/test/components/AppSidebar.test.tsx b/apps/studio/test/components/AppSidebar.test.tsx new file mode 100644 index 000000000..498d50812 --- /dev/null +++ b/apps/studio/test/components/AppSidebar.test.tsx @@ -0,0 +1,149 @@ +// @vitest-environment happy-dom +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { AppSidebar } from '../src/components/app-sidebar'; +import { ObjectStackProvider } from '@objectstack/client-react'; +import { ObjectStackClient } from '@objectstack/client'; +import { PluginRegistryProvider } from '../src/plugins'; +import type { InstalledPackage } from '@objectstack/spec/kernel'; + +const mockClient = { + meta: { + getTypes: vi.fn(), + getItems: vi.fn(), + }, + subscribe: vi.fn(), +} as unknown as ObjectStackClient; + +const mockPackages: InstalledPackage[] = [ + { + manifest: { + id: 'test-package', + name: 'Test Package', + version: '1.0.0', + type: 'app', + }, + enabled: true, + path: '/test', + }, +]; + +function renderWithProviders(component: React.ReactElement) { + return render( + + + {component} + + + ); +} + +describe('AppSidebar', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockClient.meta.getTypes = vi.fn().mockResolvedValue({ types: ['object', 'view'] }); + mockClient.meta.getItems = vi.fn().mockResolvedValue([ + { name: 'test_object', label: 'Test Object' }, + ]); + mockClient.subscribe = vi.fn().mockReturnValue(() => {}); + }); + + it('should render package switcher', () => { + renderWithProviders( + + ); + + expect(screen.getByText('Test Package')).toBeInTheDocument(); + }); + + it('should render overview nav item', () => { + renderWithProviders( + + ); + + expect(screen.getByText('Overview')).toBeInTheDocument(); + }); + + it('should render search input', () => { + renderWithProviders( + + ); + + expect(screen.getByPlaceholderText('Search metadata...')).toBeInTheDocument(); + }); + + it('should load and display metadata types', async () => { + renderWithProviders( + + ); + + await waitFor(() => { + expect(mockClient.meta.getTypes).toHaveBeenCalled(); + expect(mockClient.meta.getItems).toHaveBeenCalledWith('object', expect.anything()); + }); + }); + + it('should render system section', () => { + renderWithProviders( + + ); + + expect(screen.getByText('System')).toBeInTheDocument(); + expect(screen.getByText('API Console')).toBeInTheDocument(); + expect(screen.getByText('Packages')).toBeInTheDocument(); + }); + + it('should call onSelectObject when object is clicked', async () => { + const onSelectObject = vi.fn(); + + renderWithProviders( + + ); + + await waitFor(() => { + const objectItem = screen.queryByText('Test Object'); + if (objectItem) { + objectItem.click(); + expect(onSelectObject).toHaveBeenCalledWith('test_object'); + } + }); + }); +}); diff --git a/apps/studio/test/components/ObjectDataForm.test.tsx b/apps/studio/test/components/ObjectDataForm.test.tsx new file mode 100644 index 000000000..0a5df9a93 --- /dev/null +++ b/apps/studio/test/components/ObjectDataForm.test.tsx @@ -0,0 +1,188 @@ +// @vitest-environment happy-dom +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { ObjectDataForm } from '../src/components/ObjectDataForm'; +import { ObjectStackProvider } from '@objectstack/client-react'; +import { ObjectStackClient } from '@objectstack/client'; + +const mockClient = { + meta: { + getItem: vi.fn(), + }, + data: { + create: vi.fn(), + update: vi.fn(), + }, +} as unknown as ObjectStackClient; + +const mockObjectDef = { + name: 'test_object', + label: 'Test Object', + fields: [ + { name: 'name', label: 'Name', type: 'text', required: true }, + { name: 'email', label: 'Email', type: 'email', required: false }, + { name: 'is_active', label: 'Active', type: 'boolean', required: false }, + ], +}; + +function renderWithProvider(component: React.ReactElement) { + return render( + + {component} + + ); +} + +describe('ObjectDataForm', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockClient.meta.getItem = vi.fn().mockResolvedValue({ item: mockObjectDef }); + mockClient.data.create = vi.fn().mockResolvedValue({ id: 'new-id' }); + mockClient.data.update = vi.fn().mockResolvedValue({ success: true }); + }); + + it('should render form fields based on object definition', async () => { + const onSuccess = vi.fn(); + const onCancel = vi.fn(); + + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByLabelText('Name')).toBeInTheDocument(); + expect(screen.getByLabelText('Email')).toBeInTheDocument(); + expect(screen.getByLabelText('Active')).toBeInTheDocument(); + }); + }); + + it('should populate form with existing record data', async () => { + const record = { + id: '1', + name: 'John Doe', + email: 'john@example.com', + is_active: true, + }; + + renderWithProvider( + + ); + + await waitFor(() => { + const nameInput = screen.getByLabelText('Name') as HTMLInputElement; + expect(nameInput.value).toBe('John Doe'); + + const emailInput = screen.getByLabelText('Email') as HTMLInputElement; + expect(emailInput.value).toBe('john@example.com'); + }); + }); + + it('should call onCreate when submitting new record', async () => { + const user = userEvent.setup(); + const onSuccess = vi.fn(); + + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByLabelText('Name')).toBeInTheDocument(); + }); + + // Fill in form + await user.type(screen.getByLabelText('Name'), 'New User'); + await user.type(screen.getByLabelText('Email'), 'new@example.com'); + + // Submit + const saveButton = screen.getByRole('button', { name: /save/i }); + await user.click(saveButton); + + await waitFor(() => { + expect(mockClient.data.create).toHaveBeenCalledWith( + 'test_object', + expect.objectContaining({ + name: 'New User', + email: 'new@example.com', + }) + ); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + it('should call onUpdate when submitting existing record', async () => { + const user = userEvent.setup(); + const onSuccess = vi.fn(); + const record = { id: '1', name: 'John Doe', email: 'john@example.com' }; + + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByLabelText('Name')).toBeInTheDocument(); + }); + + // Update name + const nameInput = screen.getByLabelText('Name'); + await user.clear(nameInput); + await user.type(nameInput, 'Updated Name'); + + // Submit + const saveButton = screen.getByRole('button', { name: /save/i }); + await user.click(saveButton); + + await waitFor(() => { + expect(mockClient.data.update).toHaveBeenCalledWith( + 'test_object', + '1', + expect.objectContaining({ + name: 'Updated Name', + }) + ); + expect(onSuccess).toHaveBeenCalled(); + }); + }); + + it('should call onCancel when cancel button is clicked', async () => { + const user = userEvent.setup(); + const onCancel = vi.fn(); + + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByLabelText('Name')).toBeInTheDocument(); + }); + + const cancelButton = screen.getByRole('button', { name: /cancel/i }); + await user.click(cancelButton); + + expect(onCancel).toHaveBeenCalled(); + }); +}); diff --git a/apps/studio/test/components/ObjectDataTable.test.tsx b/apps/studio/test/components/ObjectDataTable.test.tsx new file mode 100644 index 000000000..9865480ac --- /dev/null +++ b/apps/studio/test/components/ObjectDataTable.test.tsx @@ -0,0 +1,137 @@ +// @vitest-environment happy-dom +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { ObjectDataTable } from '../src/components/ObjectDataTable'; +import { ObjectStackProvider } from '@objectstack/client-react'; +import { ObjectStackClient } from '@objectstack/client'; + +// Mock ObjectStack client +const mockClient = { + meta: { + getItems: vi.fn(), + }, + data: { + query: vi.fn(), + delete: vi.fn(), + }, +} as unknown as ObjectStackClient; + +const mockObjectMetadata = { + name: 'test_object', + label: 'Test Object', + fields: [ + { name: 'id', label: 'ID', type: 'text', isPrimaryKey: true }, + { name: 'name', label: 'Name', type: 'text' }, + { name: 'email', label: 'Email', type: 'email' }, + { name: 'is_active', label: 'Active', type: 'boolean' }, + ], +}; + +const mockRecords = [ + { id: '1', name: 'John Doe', email: 'john@example.com', is_active: true }, + { id: '2', name: 'Jane Smith', email: 'jane@example.com', is_active: false }, +]; + +function renderWithProvider(component: React.ReactElement) { + return render( + + {component} + + ); +} + +describe('ObjectDataTable', () => { + beforeEach(() => { + vi.clearAllMocks(); + // Setup default mocks + mockClient.meta.getItems = vi.fn().mockResolvedValue([mockObjectMetadata]); + mockClient.data.query = vi.fn().mockResolvedValue({ + items: mockRecords, + total: 2, + hasMore: false, + }); + }); + + it('should render loading state initially', () => { + renderWithProvider( + + ); + + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); + + it('should render table with data after loading', async () => { + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByText('John Doe')).toBeInTheDocument(); + expect(screen.getByText('Jane Smith')).toBeInTheDocument(); + }); + + // Check column headers + expect(screen.getByText('Name')).toBeInTheDocument(); + expect(screen.getByText('Email')).toBeInTheDocument(); + expect(screen.getByText('Active')).toBeInTheDocument(); + }); + + it('should display boolean values correctly', async () => { + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByText(/Yes/)).toBeInTheDocument(); + expect(screen.getByText(/No/)).toBeInTheDocument(); + }); + }); + + it('should call onEdit when edit button is clicked', async () => { + const onEdit = vi.fn(); + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByText('John Doe')).toBeInTheDocument(); + }); + + // Find and click edit button (first row) + const editButtons = screen.getAllByLabelText(/Edit/i); + editButtons[0].click(); + + expect(onEdit).toHaveBeenCalledWith(expect.objectContaining({ + id: '1', + name: 'John Doe', + })); + }); + + it('should handle empty data gracefully', async () => { + mockClient.data.query = vi.fn().mockResolvedValue({ + items: [], + total: 0, + hasMore: false, + }); + + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByText(/No records found/i)).toBeInTheDocument(); + }); + }); + + it('should display total record count', async () => { + renderWithProvider( + + ); + + await waitFor(() => { + expect(screen.getByText(/2 records/i)).toBeInTheDocument(); + }); + }); +}); diff --git a/apps/studio/test/plugins/plugin-system.test.tsx b/apps/studio/test/plugins/plugin-system.test.tsx new file mode 100644 index 000000000..04bcb577e --- /dev/null +++ b/apps/studio/test/plugins/plugin-system.test.tsx @@ -0,0 +1,174 @@ +// @vitest-environment happy-dom +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { PluginRegistry, PluginRegistryProvider, usePluginRegistry } from '../src/plugins'; +import { defineStudioPlugin } from '@objectstack/spec/studio'; +import type { StudioPlugin } from '../src/plugins/types'; + +// Test component that uses the plugin registry +function TestPluginConsumer() { + const registry = usePluginRegistry(); + const viewers = registry.getViewersForType('object'); + + return ( +
+
{viewers.length}
+ {viewers.map(v => ( +
{v.label}
+ ))} +
+ ); +} + +// Mock plugin +const mockPlugin: StudioPlugin = { + manifest: defineStudioPlugin({ + id: 'test.plugin', + name: 'Test Plugin', + version: '1.0.0', + description: 'Test plugin', + contributes: { + metadataViewers: [ + { + id: 'test-viewer', + metadataTypes: ['object'], + label: 'Test Viewer', + priority: 100, + }, + ], + sidebarGroups: [ + { + key: 'test', + label: 'Test', + icon: 'database', + metadataTypes: ['object'], + order: 10, + }, + ], + }, + }), + activate: vi.fn(), +}; + +describe('Plugin System', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('PluginRegistry', () => { + it('should register plugins', () => { + const registry = new PluginRegistry([mockPlugin]); + + expect(registry.getAllPlugins()).toHaveLength(1); + expect(registry.getPlugin('test.plugin')).toBe(mockPlugin); + }); + + it('should activate plugins', () => { + const registry = new PluginRegistry([mockPlugin]); + const api = {} as any; // Mock API + + registry.activateAll(api); + + expect(mockPlugin.activate).toHaveBeenCalledWith(api); + }); + + it('should return viewers for metadata type', () => { + const registry = new PluginRegistry([mockPlugin]); + + const viewers = registry.getViewersForType('object'); + + expect(viewers).toHaveLength(1); + expect(viewers[0].id).toBe('test-viewer'); + expect(viewers[0].label).toBe('Test Viewer'); + }); + + it('should return empty array for unknown metadata type', () => { + const registry = new PluginRegistry([mockPlugin]); + + const viewers = registry.getViewersForType('unknown'); + + expect(viewers).toHaveLength(0); + }); + + it('should return sidebar groups', () => { + const registry = new PluginRegistry([mockPlugin]); + + const groups = registry.getSidebarGroups(); + + expect(groups).toHaveLength(1); + expect(groups[0].key).toBe('test'); + expect(groups[0].label).toBe('Test'); + }); + + it('should sort viewers by priority (higher first)', () => { + const plugin1: StudioPlugin = { + manifest: defineStudioPlugin({ + id: 'plugin1', + name: 'Plugin 1', + version: '1.0.0', + contributes: { + metadataViewers: [ + { + id: 'viewer1', + metadataTypes: ['object'], + label: 'Viewer 1', + priority: 50, + }, + ], + }, + }), + activate: vi.fn(), + }; + + const plugin2: StudioPlugin = { + manifest: defineStudioPlugin({ + id: 'plugin2', + name: 'Plugin 2', + version: '1.0.0', + contributes: { + metadataViewers: [ + { + id: 'viewer2', + metadataTypes: ['object'], + label: 'Viewer 2', + priority: 100, + }, + ], + }, + }), + activate: vi.fn(), + }; + + const registry = new PluginRegistry([plugin1, plugin2]); + const viewers = registry.getViewersForType('object'); + + expect(viewers[0].id).toBe('viewer2'); // Higher priority first + expect(viewers[1].id).toBe('viewer1'); + }); + }); + + describe('PluginRegistryProvider', () => { + it('should provide plugin registry to children', () => { + render( + + + + ); + + expect(screen.getByTestId('viewer-count')).toHaveTextContent('1'); + expect(screen.getByTestId('viewer-test-viewer')).toHaveTextContent('Test Viewer'); + }); + + it('should handle empty plugin list', () => { + render( + + + + ); + + expect(screen.getByTestId('viewer-count')).toHaveTextContent('0'); + }); + }); +}); diff --git a/apps/studio/test/setup.ts b/apps/studio/test/setup.ts new file mode 100644 index 000000000..09a3c485d --- /dev/null +++ b/apps/studio/test/setup.ts @@ -0,0 +1,15 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * Test setup file + * Configures testing environment and global test utilities + */ + +import '@testing-library/jest-dom/vitest'; +import { cleanup } from '@testing-library/react'; +import { afterEach } from 'vitest'; + +// Cleanup after each test +afterEach(() => { + cleanup(); +}); diff --git a/apps/studio/vite.config.ts b/apps/studio/vite.config.ts index 98ca625e9..d4842fbfc 100644 --- a/apps/studio/vite.config.ts +++ b/apps/studio/vite.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +import { TanStackRouterVite } from '@tanstack/router-plugin/vite'; import path from 'path'; // HMR config for embedded mode (running inside CLI via --ui) @@ -9,7 +10,7 @@ const hmrConfig = process.env.VITE_HMR_PORT // https://vitejs.dev/config/ export default defineConfig({ - base: process.env.VITE_BASE || (process.env.VERCEL ? '/_studio/' : './'), // Use /_studio/ for Vercel, relative base for other sub-path mounting + base: process.env.VITE_BASE || './', // Relative base for sub-path mounting (e.g. /_studio/) resolve: { dedupe: ['react', 'react-dom'], alias: { @@ -49,7 +50,10 @@ export default defineConfig({ // 'process.cwd': '() => "/"', // 'process.platform': '"browser"' }, - plugins: [react()], + plugins: [ + TanStackRouterVite(), + react(), + ], server: { // Default to 5173 (Vite default) to avoid conflict with ObjectStack API server on 3000. // Use VITE_PORT env var to override (e.g. when embedded in CLI via --ui). diff --git a/apps/studio/vitest.config.ts b/apps/studio/vitest.config.ts new file mode 100644 index 000000000..262b4934a --- /dev/null +++ b/apps/studio/vitest.config.ts @@ -0,0 +1,38 @@ +// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. + +/// + +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { TanStackRouterVite } from '@tanstack/router-plugin/vite'; +import path from 'path'; + +export default defineConfig({ + plugins: [ + TanStackRouterVite(), + react(), + ], + test: { + globals: true, + environment: 'happy-dom', + setupFiles: ['./test/setup.ts'], + coverage: { + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/', + 'test/', + '**/*.d.ts', + '**/*.config.*', + '**/mockServiceWorker.js', + 'dist/', + 'src/routeTree.gen.ts', + 'src/mocks/', + ], + }, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +}); diff --git a/packages/metadata/src/metadata.test.ts b/packages/metadata/src/metadata.test.ts index 342a9a4b3..897e1475a 100644 --- a/packages/metadata/src/metadata.test.ts +++ b/packages/metadata/src/metadata.test.ts @@ -491,7 +491,6 @@ describe('MetadataPlugin', () => { setTypeRegistry = vi.fn(); setDatabaseDriver = vi.fn(); setDataEngine = vi.fn(); - setRealtimeService = vi.fn(); register = vi.fn(); }; return { NodeMetadataManager: MockNodeMetadataManager }; @@ -539,28 +538,27 @@ describe('MetadataPlugin', () => { const { MetadataPlugin } = await import('./plugin.js'); const plugin = new MetadataPlugin({ rootDir: '/tmp/test', watch: false }); - const mockObjectQL = { find: vi.fn(), create: vi.fn(), update: vi.fn() }; - + const mockObjectQL = { name: 'objectql', find: vi.fn(), create: vi.fn() }; const ctx = createMockPluginContext(); - ctx.getService = vi.fn().mockImplementation((name: string) => { - if (name === 'objectql') return mockObjectQL; - return null; + ctx.getService = vi.fn().mockImplementation((serviceName: string) => { + if (serviceName === 'objectql') return mockObjectQL; + throw new Error(`Service ${serviceName} not found`); }); await plugin.init(ctx); await plugin.start(ctx); - // Verify setDataEngine was called on the manager with the ObjectQL engine + // Verify setDataEngine was called on the manager with ObjectQL const manager = (plugin as any).manager; expect(manager.setDataEngine).toHaveBeenCalledWith(mockObjectQL); }); - it('should bridge ObjectQL engine AFTER filesystem metadata loading', async () => { + it('should bridge ObjectQL AFTER filesystem metadata loading', async () => { const { MetadataPlugin } = await import('./plugin.js'); const plugin = new MetadataPlugin({ rootDir: '/tmp/test', watch: false }); const callOrder: string[] = []; - const mockObjectQL = { find: vi.fn(), create: vi.fn(), update: vi.fn() }; + const mockObjectQL = { name: 'objectql', find: vi.fn(), create: vi.fn() }; const manager = (plugin as any).manager; manager.loadMany = vi.fn().mockImplementation(async () => { @@ -572,9 +570,9 @@ describe('MetadataPlugin', () => { }); const ctx = createMockPluginContext(); - ctx.getService = vi.fn().mockImplementation((name: string) => { - if (name === 'objectql') return mockObjectQL; - return null; + ctx.getService = vi.fn().mockImplementation((serviceName: string) => { + if (serviceName === 'objectql') return mockObjectQL; + throw new Error(`Service ${serviceName} not found`); }); await plugin.init(ctx); @@ -582,8 +580,8 @@ describe('MetadataPlugin', () => { // setDataEngine must be called after all loadMany calls const lastLoad = callOrder.lastIndexOf('loadMany'); - const driverIdx = callOrder.indexOf('setDataEngine'); - expect(driverIdx).toBeGreaterThan(lastLoad); + const engineIdx = callOrder.indexOf('setDataEngine'); + expect(engineIdx).toBeGreaterThan(lastLoad); }); it('should not fail when no ObjectQL service is available', async () => { @@ -591,7 +589,9 @@ describe('MetadataPlugin', () => { const plugin = new MetadataPlugin({ rootDir: '/tmp/test', watch: false }); const ctx = createMockPluginContext(); - // getService returns null by default — no objectql available + ctx.getService = vi.fn().mockImplementation((serviceName: string) => { + throw new Error(`Service ${serviceName} not found`); + }); await plugin.init(ctx); // Should not throw @@ -602,15 +602,15 @@ describe('MetadataPlugin', () => { expect(manager.setDataEngine).not.toHaveBeenCalled(); }); - it('should gracefully handle getService errors', async () => { + it('should gracefully handle getServices errors', async () => { const { MetadataPlugin } = await import('./plugin.js'); const plugin = new MetadataPlugin({ rootDir: '/tmp/test', watch: false }); const ctx = createMockPluginContext(); - ctx.getService = vi.fn().mockImplementation(() => { throw new Error('service unavailable'); }); + ctx.getServices = vi.fn().mockImplementation(() => { throw new Error('services unavailable'); }); await plugin.init(ctx); - // Should not throw even when getService fails + // Should not throw even when getServices fails await expect(plugin.start(ctx)).resolves.not.toThrow(); }); }); diff --git a/packages/plugins/plugin-dev/tsconfig.json b/packages/plugins/plugin-dev/tsconfig.json index e80961b1c..0b8b99d88 100644 --- a/packages/plugins/plugin-dev/tsconfig.json +++ b/packages/plugins/plugin-dev/tsconfig.json @@ -9,5 +9,9 @@ }, "include": [ "src" + ], + "exclude": [ + "node_modules", + "dist" ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5dd9460e..a18bef4ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,13 +34,13 @@ importers: dependencies: fumadocs-core: specifier: 16.7.14 - version: 16.7.14(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) + version: 16.7.14(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) fumadocs-mdx: specifier: 14.2.13 - version: 14.2.13(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 14.2.13(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) fumadocs-ui: specifier: 16.7.14 - version: 16.7.14(@tailwindcss/oxide@4.2.2)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(shiki@4.0.2)(tailwindcss@4.2.2) + version: 16.7.14(@tailwindcss/oxide@4.2.2)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(shiki@4.0.2)(tailwindcss@4.2.2) lucide-react: specifier: ^1.8.0 version: 1.8.0(react@19.2.5) @@ -307,6 +307,9 @@ importers: '@radix-ui/react-tooltip': specifier: ^1.2.8 version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/react-router': + specifier: ^1.91.6 + version: 1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ai: specifier: ^6.0.158 version: 6.0.159(zod@4.3.6) @@ -341,6 +344,21 @@ importers: '@tailwindcss/postcss': specifier: ^4.2.2 version: 4.2.2 + '@tanstack/router-devtools': + specifier: ^1.91.6 + version: 1.166.13(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.168.15)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/router-plugin': + specifier: ^1.91.5 + version: 1.167.22(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.1.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@testing-library/user-event': + specifier: ^14.5.2 + version: 14.6.1(@testing-library/dom@10.4.1) '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -350,6 +368,9 @@ importers: '@vitejs/plugin-react': specifier: ^6.0.1 version: 6.0.1(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/coverage-v8': + specifier: ^4.1.4 + version: 4.1.4(vitest@4.1.4) autoprefixer: specifier: ^10.4.27 version: 10.5.0(postcss@8.5.9) @@ -1394,6 +1415,9 @@ importers: packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@ai-sdk/anthropic@3.0.69': resolution: {integrity: sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g==} engines: {node: '>=18'} @@ -1492,6 +1516,40 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -1500,15 +1558,43 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.2': resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} @@ -3683,6 +3769,129 @@ packages: '@tailwindcss/postcss@4.2.2': resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} + '@tanstack/history@1.161.6': + resolution: {integrity: sha512-NaOGLRrddszbQj9upGat6HG/4TKvXLvu+osAIgfxPYA+eIvYKv8GKDJOrY2D3/U9MRnKfMWD7bU4jeD4xmqyIg==} + engines: {node: '>=20.19'} + + '@tanstack/react-router-devtools@1.166.13': + resolution: {integrity: sha512-6yKRFFJrEEOiGp5RAAuGCYsl81M4XAhJmLcu9PKj+HZle4A3dsP60lwHoqQYWHMK9nKKFkdXR+D8qxzxqtQbEA==} + engines: {node: '>=20.19'} + peerDependencies: + '@tanstack/react-router': ^1.168.15 + '@tanstack/router-core': ^1.168.11 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + peerDependenciesMeta: + '@tanstack/router-core': + optional: true + + '@tanstack/react-router@1.168.22': + resolution: {integrity: sha512-W2LyfkfJtDCf//jOjZeUBWwOVl8iDRVTECpGHa2M28MT3T5/VVnjgicYNHR/ax0Filk1iU67MRjcjHheTYvK1Q==} + engines: {node: '>=20.19'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.9.3': + resolution: {integrity: sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.168.15': + resolution: {integrity: sha512-Wr0424NDtD8fT/uALobMZ9DdcfsTyXtW5IPR++7zvW8/7RaIOeaqXpVDId8ywaGtqPWLWOfaUg2zUtYtukoXYA==} + engines: {node: '>=20.19'} + hasBin: true + + '@tanstack/router-devtools-core@1.167.3': + resolution: {integrity: sha512-fJ1VMhyQgnoashTrP763c2HRc9kofgF61L7Jb3F6eTHAmCKtGVx8BRtiFt37sr3U0P0jmaaiiSPGP6nT5JtVNg==} + engines: {node: '>=20.19'} + peerDependencies: + '@tanstack/router-core': ^1.168.11 + csstype: ^3.0.10 + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-devtools@1.166.13': + resolution: {integrity: sha512-Qs8gkyI7m+eAxG3VcIOHuTSsUfA5ZxZcOa99ZyIIIJFxW6hy1k+m2s1J0ZYN1SNlip8P2ofd/MHiqmR1IWipMg==} + engines: {node: '>=20.19'} + peerDependencies: + '@tanstack/react-router': ^1.168.15 + csstype: ^3.0.10 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-generator@1.166.32': + resolution: {integrity: sha512-VuusKwEXcgKq+myq1JQfZogY8scTXIIeFls50dJ/UXgCXWp5n14iFreYNlg41wURcak2oA3M+t2TVfD0xUUD6g==} + engines: {node: '>=20.19'} + + '@tanstack/router-plugin@1.167.22': + resolution: {integrity: sha512-wYPzIvBK8bcmXVUpZfSgGBXOrfBAdF4odKevz6rejio5rEd947NtKDF5R7eYdwlAOmRqYpLJnJ1QHkc5t8bY4w==} + engines: {node: '>=20.19'} + hasBin: true + peerDependencies: + '@rsbuild/core': '>=1.0.2' + '@tanstack/react-router': ^1.168.21 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0' + vite-plugin-solid: ^2.11.10 || ^3.0.0-0 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.161.6': + resolution: {integrity: sha512-nRcYw+w2OEgK6VfjirYvGyPLOK+tZQz1jkYcmH5AjMamQ9PycnlxZF2aEZtPpNoUsaceX2bHptn6Ub5hGXqNvw==} + engines: {node: '>=20.19'} + + '@tanstack/store@0.9.3': + resolution: {integrity: sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==} + + '@tanstack/virtual-file-routes@1.161.7': + resolution: {integrity: sha512-olW33+Cn+bsCsZKPwEGhlkqS6w3M2slFv11JIobdnCFKMLG97oAI2kWKdx5/zsywTL8flpnoIgaZZPlQTFYhdQ==} + engines: {node: '>=20.19'} + hasBin: true + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@textlint/ast-node-types@15.5.4': resolution: {integrity: sha512-bVtB6VEy9U9DpW8cTt25k5T+lz86zV5w6ImePZqY1AXzSuPhqQNT77lkMPxonXzUducEIlSvUu3o7sKw3y9+Sw==} @@ -3750,6 +3959,9 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} @@ -4035,13 +4247,25 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + ansis@3.17.0: resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} engines: {node: '>=14'} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -4055,6 +4279,9 @@ packages: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.1: resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} engines: {node: '>= 0.4'} @@ -4105,6 +4332,9 @@ packages: azure-devops-node-api@12.5.0: resolution: {integrity: sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og==} + babel-dead-code-elimination@1.0.12: + resolution: {integrity: sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -4198,6 +4428,10 @@ packages: resolution: {integrity: sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==} engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + binaryextensions@6.11.0: resolution: {integrity: sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw==} engines: {node: '>=4'} @@ -4306,6 +4540,10 @@ packages: resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} engines: {node: '>=20.18.1'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -4410,6 +4648,9 @@ packages: cookie-es@1.2.3: resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} + cookie-es@3.1.1: + resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + cookie-signature@1.2.2: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} @@ -4450,6 +4691,9 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -4548,10 +4792,20 @@ packages: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} + diff@8.0.4: + resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} + engines: {node: '>=0.3.1'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -5016,6 +5270,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -5070,6 +5328,11 @@ packages: resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} engines: {node: '>=18'} + goober@2.1.18: + resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} + peerDependencies: + csstype: ^3.0.10 + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -5238,6 +5501,10 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -5308,6 +5575,10 @@ packages: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} + isbot@5.1.38: + resolution: {integrity: sha512-Cus2702JamTNMEY4zTP+TShgq/3qzjvGcBC4XMOV45BLaxD4iUFENkqu7ZhFeSzwNsCSZLjnGlihDQznnpnEEA==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -5372,6 +5643,11 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-schema-ref-resolver@3.0.0: resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==} @@ -5606,6 +5882,9 @@ packages: resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} engines: {node: 20 || >=22} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -5615,6 +5894,10 @@ packages: peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -5840,6 +6123,10 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + mingo@7.2.1: resolution: {integrity: sha512-MEIQPOSJS2sVCueyQeE2rzgEeW3HpIIhizPbeuwD4v7+miVj7NI3ZVPqqw8t3YPIWCivpIaXA4KsoRI7koyNOA==} @@ -6005,6 +6292,10 @@ packages: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + npm-package-arg@11.0.3: resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} engines: {node: ^16.14.0 || >=18.0.0} @@ -6314,6 +6605,15 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -6377,6 +6677,9 @@ packages: peerDependencies: react: ^19.2.5 + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -6433,6 +6736,10 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -6463,6 +6770,10 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -6596,6 +6907,10 @@ packages: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -6605,6 +6920,16 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} + seroval-plugins@1.5.2: + resolution: {integrity: sha512-qpY0Cl+fKYFn4GOf3cMiq6l72CpuVaawb6ILjubOQ+diJ54LfOWaSSPsaswN8DRPIPW4Yq+tE1k5aKd7ILyaFg==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.5.2: + resolution: {integrity: sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==} + engines: {node: '>=10'} + serve-static@2.2.1: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} @@ -6750,6 +7075,10 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -7093,6 +7422,10 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + until-async@3.0.2: resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} @@ -7266,6 +7599,9 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-encoding@3.1.1: resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} engines: {node: '>=18'} @@ -7343,6 +7679,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -7386,6 +7725,9 @@ packages: peerDependencies: zod: ^3.25.28 || ^4 + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zod@4.3.6: resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} @@ -7394,6 +7736,8 @@ packages: snapshots: + '@adobe/css-tools@4.4.4': {} + '@ai-sdk/anthropic@3.0.69(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 @@ -7537,16 +7881,109 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3(supports-color@8.1.1) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/parser@7.29.2': dependencies: '@babel/types': 7.29.0 + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -9466,6 +9903,149 @@ snapshots: postcss: 8.5.9 tailwindcss: 4.2.2 + '@tanstack/history@1.161.6': {} + + '@tanstack/react-router-devtools@1.166.13(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.168.15)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/react-router': 1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/router-devtools-core': 1.167.3(@tanstack/router-core@1.168.15)(csstype@3.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@tanstack/router-core': 1.168.15 + transitivePeerDependencies: + - csstype + + '@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/history': 1.161.6 + '@tanstack/react-store': 0.9.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/router-core': 1.168.15 + isbot: 5.1.38 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + + '@tanstack/react-store@0.9.3(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/store': 0.9.3 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + use-sync-external-store: 1.6.0(react@19.2.5) + + '@tanstack/router-core@1.168.15': + dependencies: + '@tanstack/history': 1.161.6 + cookie-es: 3.1.1 + seroval: 1.5.2 + seroval-plugins: 1.5.2(seroval@1.5.2) + + '@tanstack/router-devtools-core@1.167.3(@tanstack/router-core@1.168.15)(csstype@3.2.3)': + dependencies: + '@tanstack/router-core': 1.168.15 + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + optionalDependencies: + csstype: 3.2.3 + + '@tanstack/router-devtools@1.166.13(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.168.15)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@tanstack/react-router': 1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@tanstack/react-router-devtools': 1.166.13(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.168.15)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + csstype: 3.2.3 + transitivePeerDependencies: + - '@tanstack/router-core' + + '@tanstack/router-generator@1.166.32': + dependencies: + '@babel/types': 7.29.0 + '@tanstack/router-core': 1.168.15 + '@tanstack/router-utils': 1.161.6 + '@tanstack/virtual-file-routes': 1.161.7 + magic-string: 0.30.21 + prettier: 3.8.3 + tsx: 4.21.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.167.22(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@tanstack/router-core': 1.168.15 + '@tanstack/router-generator': 1.166.32 + '@tanstack/router-utils': 1.161.6 + '@tanstack/virtual-file-routes': 1.161.7 + chokidar: 3.6.0 + unplugin: 2.3.11 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + vite: 8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.161.6': + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + ansis: 4.2.0 + babel-dead-code-elimination: 1.0.12 + diff: 8.0.4 + pathe: 2.0.3 + tinyglobby: 0.2.16 + transitivePeerDependencies: + - supports-color + + '@tanstack/store@0.9.3': {} + + '@tanstack/virtual-file-routes@1.161.7': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.1 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@textlint/ast-node-types@15.5.4': {} '@textlint/linter-formatter@15.5.4': @@ -9535,6 +10115,8 @@ snapshots: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 @@ -9845,10 +10427,19 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + ansis@3.17.0: {} + ansis@4.2.0: {} + any-promise@1.3.0: {} + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.2 + arg@4.1.3: {} argparse@1.0.10: @@ -9861,6 +10452,10 @@ snapshots: dependencies: tslib: 2.8.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + aria-query@5.3.1: {} array-union@2.1.0: {} @@ -9904,6 +10499,15 @@ snapshots: tunnel: 0.0.6 typed-rest-client: 1.8.11 + babel-dead-code-elimination@1.0.12: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + bail@2.0.2: {} balanced-match@4.0.4: {} @@ -9961,6 +10565,8 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.3 + binary-extensions@2.3.0: {} + binaryextensions@6.11.0: dependencies: editions: 6.22.0 @@ -10092,6 +10698,18 @@ snapshots: undici: 7.25.0 whatwg-mimetype: 4.0.0 + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -10166,6 +10784,8 @@ snapshots: cookie-es@1.2.3: {} + cookie-es@3.1.1: {} + cookie-signature@1.2.2: {} cookie@0.6.0: {} @@ -10207,6 +10827,8 @@ snapshots: css-what@6.2.2: {} + css.escape@1.5.1: {} + csstype@3.2.3: {} data-uri-to-buffer@4.0.1: {} @@ -10270,10 +10892,16 @@ snapshots: diff@4.0.4: {} + diff@8.0.4: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -10745,7 +11373,7 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6): + fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6): dependencies: '@orama/orama': 3.1.18 '@shikijs/rehype': 4.0.2 @@ -10767,6 +11395,7 @@ snapshots: vfile: 6.0.3 optionalDependencies: '@mdx-js/mdx': 3.1.1 + '@tanstack/react-router': 1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -10779,14 +11408,14 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-mdx@14.2.13(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): + fumadocs-mdx@14.2.13(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 chokidar: 5.0.0 esbuild: 0.28.0 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 16.7.14(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) + fumadocs-core: 16.7.14(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) js-yaml: 4.1.1 mdast-util-mdx: 3.0.0 mdast-util-to-markdown: 2.1.2 @@ -10809,7 +11438,7 @@ snapshots: transitivePeerDependencies: - supports-color - fumadocs-ui@16.7.14(@tailwindcss/oxide@4.2.2)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(shiki@4.0.2)(tailwindcss@4.2.2): + fumadocs-ui@16.7.14(@tailwindcss/oxide@4.2.2)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.7.14(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(shiki@4.0.2)(tailwindcss@4.2.2): dependencies: '@fumadocs/tailwind': 0.0.5(@tailwindcss/oxide@4.2.2)(tailwindcss@4.2.2) '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -10823,7 +11452,7 @@ snapshots: '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.5) '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) class-variance-authority: 0.7.1 - fumadocs-core: 16.7.14(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) + fumadocs-core: 16.7.14(@mdx-js/mdx@3.1.1)(@tanstack/react-router@1.168.22(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(lucide-react@1.8.0(react@19.2.5))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(zod@4.3.6) lucide-react: 1.8.0(react@19.2.5) motion: 12.38.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) next-themes: 0.4.6(react-dom@19.2.5(react@19.2.5))(react@19.2.5) @@ -10847,6 +11476,8 @@ snapshots: function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -10918,6 +11549,10 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.3.0 + goober@2.1.18(csstype@3.2.3): + dependencies: + csstype: 3.2.3 + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -11162,6 +11797,10 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -11212,6 +11851,8 @@ snapshots: dependencies: is-inside-container: 1.0.0 + isbot@5.1.38: {} + isexe@2.0.0: {} isexe@3.1.5: {} @@ -11268,6 +11909,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-schema-ref-resolver@3.0.0: dependencies: dequal: 2.0.3 @@ -11476,6 +12119,10 @@ snapshots: lru-cache@11.3.5: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -11484,6 +12131,8 @@ snapshots: dependencies: react: 19.2.5 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -11971,6 +12620,8 @@ snapshots: mimic-response@3.1.0: {} + min-indent@1.0.1: {} + mingo@7.2.1: {} minimatch@10.2.1: @@ -12125,6 +12776,8 @@ snapshots: semver: 7.7.4 validate-npm-package-license: 3.0.4 + normalize-path@3.0.0: {} + npm-package-arg@11.0.3: dependencies: hosted-git-info: 7.0.2 @@ -12363,6 +13016,14 @@ snapshots: prettier@2.8.8: {} + prettier@3.8.3: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + proc-log@4.2.0: {} process-warning@4.0.1: {} @@ -12427,6 +13088,8 @@ snapshots: react: 19.2.5 scheduler: 0.27.0 + react-is@17.0.2: {} + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.5): dependencies: react: 19.2.5 @@ -12486,6 +13149,10 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdirp@3.6.0: + dependencies: + picomatch: 2.3.2 + readdirp@4.1.2: {} readdirp@5.0.0: {} @@ -12525,6 +13192,11 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + reflect-metadata@0.2.2: {} regex-recursion@6.0.2: @@ -12732,6 +13404,8 @@ snapshots: semver@5.7.2: {} + semver@6.3.1: {} + semver@7.7.4: {} send@1.2.1: @@ -12750,6 +13424,12 @@ snapshots: transitivePeerDependencies: - supports-color + seroval-plugins@1.5.2(seroval@1.5.2): + dependencies: + seroval: 1.5.2 + + seroval@1.5.2: {} + serve-static@2.2.1: dependencies: encodeurl: 2.0.0 @@ -12936,6 +13616,10 @@ snapshots: strip-bom@3.0.0: {} + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@2.0.1: {} strip-json-comments@5.0.3: {} @@ -13288,6 +13972,13 @@ snapshots: unpipe@1.0.0: {} + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + until-async@3.0.2: {} update-browserslist-db@1.2.3(browserslist@4.28.2): @@ -13404,6 +14095,8 @@ snapshots: webidl-conversions@3.0.1: {} + webpack-virtual-modules@0.6.2: {} + whatwg-encoding@3.1.1: dependencies: iconv-lite: 0.6.3 @@ -13465,6 +14158,8 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yallist@4.0.0: {} yaml@2.8.3: {} @@ -13502,6 +14197,8 @@ snapshots: dependencies: zod: 4.3.6 + zod@3.25.76: {} + zod@4.3.6: {} zwitch@2.0.4: {} From a6a3e25052ba723bea02238b7472aa80b46d82cb Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Thu, 16 Apr 2026 08:47:45 +0000 Subject: [PATCH 2/2] Fix sidebar metadata navigation by replacing prop-based handlers with TanStack Router Agent-Logs-Url: https://github.com/objectstack-ai/framework/sessions/964addb7-666d-4157-bcfb-55dc9ac11d14 Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- apps/studio/src/components/app-sidebar.tsx | 38 ++++++++++--------- .../routes/$package.metadata.$type.$name.tsx | 3 +- .../src/routes/$package.objects.$name.tsx | 3 +- apps/studio/src/routes/$package.tsx | 3 +- apps/studio/src/routes/api-console.tsx | 3 +- apps/studio/src/routes/index.tsx | 3 +- apps/studio/src/routes/packages.tsx | 3 +- 7 files changed, 33 insertions(+), 23 deletions(-) diff --git a/apps/studio/src/components/app-sidebar.tsx b/apps/studio/src/components/app-sidebar.tsx index ba57a7d16..888c8e6e3 100644 --- a/apps/studio/src/components/app-sidebar.tsx +++ b/apps/studio/src/components/app-sidebar.tsx @@ -36,6 +36,7 @@ import { type LucideIcon, } from "lucide-react" import { useState, useEffect, useCallback, useMemo } from "react" +import { useNavigate, useParams, useLocation } from '@tanstack/react-router'; import { useClient, useMetadataSubscriptionCallback } from '@objectstack/client-react'; import type { InstalledPackage } from '@objectstack/spec/kernel'; @@ -163,23 +164,24 @@ const PKG_TYPE_ICONS: Record = { }; interface AppSidebarProps extends React.ComponentProps { - selectedObject: string | null; - onSelectObject: (name: string) => void; - selectedMeta?: { type: string; name: string } | null; - onSelectMeta?: (type: string, name: string) => void; packages: InstalledPackage[]; selectedPackage: InstalledPackage | null; onSelectPackage: (pkg: InstalledPackage) => void; - onSelectView?: (view: 'overview' | 'packages' | 'api-console') => void; - selectedView?: 'overview' | 'packages' | 'object' | 'metadata' | 'api-console'; } export function AppSidebar({ - selectedObject, onSelectObject, selectedMeta, onSelectMeta, - packages, selectedPackage, onSelectPackage, onSelectView, selectedView, + packages, selectedPackage, onSelectPackage, ...props }: AppSidebarProps) { const client = useClient(); + const navigate = useNavigate(); + const params = useParams({ strict: false }); + const location = useLocation(); + + // Extract current selection from URL params + const selectedObject = params.name && params.package && !params.type ? params.name : null; + const selectedMeta = params.type && params.name ? { type: params.type, name: params.name } : null; + const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [metaTypes, setMetaTypes] = useState([]); @@ -362,8 +364,8 @@ export function AppSidebar({ { onSelectObject(''); onSelectView?.('overview'); }} + isActive={!!params.package && !params.name && !params.type} + onClick={() => navigate({ to: `/${selectedPackage?.manifest?.id || 'default'}` })} > Overview @@ -456,9 +458,11 @@ export function AppSidebar({ const isActive = isObjectType ? selectedObject === itemName : selectedMeta?.type === type && selectedMeta?.name === itemName; + + const packagePath = selectedPackage?.manifest?.id || 'default'; const handleClick = isObjectType - ? () => onSelectObject(itemName) - : () => onSelectMeta?.(type, itemName); + ? () => navigate({ to: `/${packagePath}/objects/${itemName}` }) + : () => navigate({ to: `/${packagePath}/metadata/${type}/${itemName}` }); return ( @@ -540,7 +544,7 @@ export function AppSidebar({ onSelectObject(itemName)} + onClick={() => navigate({ to: `/${selectedPackage?.manifest?.id || 'default'}/objects/${itemName}` })} > {isSystemObject(item) && ( @@ -562,8 +566,8 @@ export function AppSidebar({ onSelectView?.('api-console')} + isActive={location.pathname === '/api-console'} + onClick={() => navigate({ to: '/api-console' })} > API Console @@ -572,8 +576,8 @@ export function AppSidebar({ onSelectView?.('packages')} + isActive={location.pathname === '/packages'} + onClick={() => navigate({ to: '/packages' })} > Packages diff --git a/apps/studio/src/routes/$package.metadata.$type.$name.tsx b/apps/studio/src/routes/$package.metadata.$type.$name.tsx index 4e8075053..ec6a76db0 100644 --- a/apps/studio/src/routes/$package.metadata.$type.$name.tsx +++ b/apps/studio/src/routes/$package.metadata.$type.$name.tsx @@ -8,13 +8,14 @@ import { usePackages } from '../hooks/usePackages'; function MetadataViewComponent() { const { type, name } = Route.useParams(); - const { packages, selectedPackage } = usePackages(); + const { packages, selectedPackage, setSelectedPackage } = usePackages(); return ( <>