2026-02-11 16:45:07 -08:00
|
|
|
"use client";
|
|
|
|
|
|
2026-02-12 00:29:39 -08:00
|
|
|
import { Suspense, useEffect, useState, useCallback, useRef, useMemo } from "react";
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
import { useSearchParams, useRouter } from "next/navigation";
|
2026-02-11 16:45:07 -08:00
|
|
|
import { WorkspaceSidebar } from "../components/workspace/workspace-sidebar";
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
import { type TreeNode } from "../components/workspace/file-manager-tree";
|
|
|
|
|
import { useWorkspaceWatcher } from "../hooks/use-workspace-watcher";
|
2026-02-11 16:45:07 -08:00
|
|
|
import { ObjectTable } from "../components/workspace/object-table";
|
|
|
|
|
import { ObjectKanban } from "../components/workspace/object-kanban";
|
|
|
|
|
import { DocumentView } from "../components/workspace/document-view";
|
2026-02-21 13:10:32 -08:00
|
|
|
import { FileViewer, isSpreadsheetFile } from "../components/workspace/file-viewer";
|
|
|
|
|
import { HtmlViewer } from "../components/workspace/html-viewer";
|
2026-02-13 18:06:59 -08:00
|
|
|
import { CodeViewer } from "../components/workspace/code-viewer";
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
import { MediaViewer, detectMediaType, type MediaType } from "../components/workspace/media-viewer";
|
2026-02-15 18:18:15 -08:00
|
|
|
import { DatabaseViewer, DuckDBMissing } from "../components/workspace/database-viewer";
|
2026-02-11 16:45:07 -08:00
|
|
|
import { Breadcrumbs } from "../components/workspace/breadcrumbs";
|
2026-02-13 19:24:24 -08:00
|
|
|
import { ChatSessionsSidebar } from "../components/workspace/chat-sessions-sidebar";
|
2026-02-11 16:45:07 -08:00
|
|
|
import { EmptyState } from "../components/workspace/empty-state";
|
2026-02-11 18:35:35 -08:00
|
|
|
import { ReportViewer } from "../components/charts/report-viewer";
|
2026-02-19 14:59:34 -08:00
|
|
|
import { ChatPanel, type ChatPanelHandle, type SubagentSpawnInfo } from "../components/chat-panel";
|
|
|
|
|
import { SubagentPanel } from "../components/subagent-panel";
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
import { EntryDetailModal } from "../components/workspace/entry-detail-modal";
|
|
|
|
|
import { useSearchIndex } from "@/lib/search-index";
|
|
|
|
|
import { parseWorkspaceLink, isWorkspaceLink } from "@/lib/workspace-links";
|
2026-02-13 18:06:59 -08:00
|
|
|
import { isCodeFile } from "@/lib/report-utils";
|
2026-02-12 13:37:40 -08:00
|
|
|
import { CronDashboard } from "../components/cron/cron-dashboard";
|
|
|
|
|
import { CronJobDetail } from "../components/cron/cron-job-detail";
|
|
|
|
|
import type { CronJob, CronJobsResponse } from "../types/cron";
|
2026-02-16 00:30:13 -08:00
|
|
|
import { useIsMobile } from "../hooks/use-mobile";
|
2026-02-17 00:36:01 -08:00
|
|
|
import { ObjectFilterBar } from "../components/workspace/object-filter-bar";
|
2026-02-19 14:59:34 -08:00
|
|
|
import { type FilterGroup, type SortRule, type SavedView, emptyFilterGroup, serializeFilters } from "@/lib/object-filters";
|
2026-02-19 16:50:52 -08:00
|
|
|
import { UnicodeSpinner } from "../components/unicode-spinner";
|
2026-02-11 16:45:07 -08:00
|
|
|
|
|
|
|
|
// --- Types ---
|
|
|
|
|
|
|
|
|
|
type WorkspaceContext = {
|
|
|
|
|
exists: boolean;
|
|
|
|
|
organization?: { id?: string; name?: string; slug?: string };
|
|
|
|
|
members?: Array<{ id: string; name: string; email: string; role: string }>;
|
|
|
|
|
};
|
|
|
|
|
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
type ReverseRelation = {
|
|
|
|
|
fieldName: string;
|
|
|
|
|
sourceObjectName: string;
|
|
|
|
|
sourceObjectId: string;
|
|
|
|
|
displayField: string;
|
|
|
|
|
entries: Record<string, Array<{ id: string; label: string }>>;
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
type ObjectData = {
|
|
|
|
|
object: {
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
icon?: string;
|
|
|
|
|
default_view?: string;
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
display_field?: string;
|
2026-02-11 16:45:07 -08:00
|
|
|
};
|
|
|
|
|
fields: Array<{
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
type: string;
|
|
|
|
|
enum_values?: string[];
|
|
|
|
|
enum_colors?: string[];
|
|
|
|
|
enum_multiple?: boolean;
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
related_object_id?: string;
|
|
|
|
|
relationship_type?: string;
|
|
|
|
|
related_object_name?: string;
|
2026-02-11 16:45:07 -08:00
|
|
|
sort_order?: number;
|
|
|
|
|
}>;
|
|
|
|
|
statuses: Array<{
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
color?: string;
|
|
|
|
|
sort_order?: number;
|
|
|
|
|
}>;
|
|
|
|
|
entries: Record<string, unknown>[];
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
relationLabels?: Record<string, Record<string, string>>;
|
|
|
|
|
reverseRelations?: ReverseRelation[];
|
|
|
|
|
effectiveDisplayField?: string;
|
2026-02-17 00:36:01 -08:00
|
|
|
savedViews?: import("@/lib/object-filters").SavedView[];
|
|
|
|
|
activeView?: string;
|
2026-02-19 14:59:34 -08:00
|
|
|
totalCount?: number;
|
|
|
|
|
page?: number;
|
|
|
|
|
pageSize?: number;
|
2026-02-11 16:45:07 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type FileData = {
|
|
|
|
|
content: string;
|
2026-02-13 18:06:59 -08:00
|
|
|
type: "markdown" | "yaml" | "code" | "text";
|
2026-02-11 16:45:07 -08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ContentState =
|
|
|
|
|
| { kind: "none" }
|
|
|
|
|
| { kind: "loading" }
|
|
|
|
|
| { kind: "object"; data: ObjectData }
|
|
|
|
|
| { kind: "document"; data: FileData; title: string }
|
|
|
|
|
| { kind: "file"; data: FileData; filename: string }
|
2026-02-13 18:06:59 -08:00
|
|
|
| { kind: "code"; data: FileData; filename: string }
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
| { kind: "media"; url: string; mediaType: MediaType; filename: string; filePath: string }
|
2026-02-21 13:10:32 -08:00
|
|
|
| { kind: "spreadsheet"; url: string; filename: string }
|
|
|
|
|
| { kind: "html"; rawUrl: string; contentUrl: string; filename: string }
|
2026-02-11 17:01:28 -08:00
|
|
|
| { kind: "database"; dbPath: string; filename: string }
|
2026-02-11 18:35:35 -08:00
|
|
|
| { kind: "report"; reportPath: string; filename: string }
|
2026-02-12 13:37:40 -08:00
|
|
|
| { kind: "directory"; node: TreeNode }
|
|
|
|
|
| { kind: "cron-dashboard" }
|
2026-02-15 18:19:13 -08:00
|
|
|
| { kind: "cron-job"; jobId: string; job: CronJob }
|
|
|
|
|
| { kind: "duckdb-missing" };
|
2026-02-11 16:45:07 -08:00
|
|
|
|
2026-02-19 17:46:54 -08:00
|
|
|
type SidebarPreviewContent =
|
|
|
|
|
| { kind: "document"; data: FileData; title: string }
|
|
|
|
|
| { kind: "file"; data: FileData; filename: string }
|
|
|
|
|
| { kind: "code"; data: FileData; filename: string }
|
|
|
|
|
| { kind: "media"; url: string; mediaType: MediaType; filename: string; filePath: string }
|
|
|
|
|
| { kind: "database"; dbPath: string; filename: string }
|
|
|
|
|
| { kind: "directory"; path: string; name: string };
|
|
|
|
|
|
|
|
|
|
type ChatSidebarPreviewState =
|
|
|
|
|
| { status: "loading"; path: string; filename: string }
|
|
|
|
|
| { status: "error"; path: string; filename: string; message: string }
|
|
|
|
|
| { status: "ready"; path: string; filename: string; content: SidebarPreviewContent };
|
|
|
|
|
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
type WebSession = {
|
|
|
|
|
id: string;
|
|
|
|
|
title: string;
|
|
|
|
|
createdAt: number;
|
|
|
|
|
updatedAt: number;
|
|
|
|
|
messageCount: number;
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
// --- Helpers ---
|
|
|
|
|
|
2026-02-15 18:18:15 -08:00
|
|
|
/** Detect virtual paths (skills, memories) that live outside the main workspace. */
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
function isVirtualPath(path: string): boolean {
|
2026-02-19 17:46:54 -08:00
|
|
|
return path.startsWith("~") && !path.startsWith("~/");
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-13 15:16:36 -08:00
|
|
|
/** Detect absolute filesystem paths (browse mode). */
|
|
|
|
|
function isAbsolutePath(path: string): boolean {
|
|
|
|
|
return path.startsWith("/");
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-19 17:46:54 -08:00
|
|
|
/** Detect home-relative filesystem paths (e.g. ~/Desktop/file.txt). */
|
|
|
|
|
function isHomeRelativePath(path: string): boolean {
|
|
|
|
|
return path.startsWith("~/");
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-13 15:16:36 -08:00
|
|
|
/** Pick the right file API endpoint based on virtual vs real vs absolute paths. */
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
function fileApiUrl(path: string): string {
|
2026-02-13 15:16:36 -08:00
|
|
|
if (isVirtualPath(path)) {
|
|
|
|
|
return `/api/workspace/virtual-file?path=${encodeURIComponent(path)}`;
|
|
|
|
|
}
|
2026-02-19 17:46:54 -08:00
|
|
|
if (isAbsolutePath(path) || isHomeRelativePath(path)) {
|
2026-02-13 15:16:36 -08:00
|
|
|
return `/api/workspace/browse-file?path=${encodeURIComponent(path)}`;
|
|
|
|
|
}
|
|
|
|
|
return `/api/workspace/file?path=${encodeURIComponent(path)}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Pick the right raw file URL for media preview. */
|
|
|
|
|
function rawFileUrl(path: string): string {
|
2026-02-19 17:46:54 -08:00
|
|
|
if (isAbsolutePath(path) || isHomeRelativePath(path)) {
|
2026-02-13 15:16:36 -08:00
|
|
|
return `/api/workspace/browse-file?path=${encodeURIComponent(path)}&raw=true`;
|
|
|
|
|
}
|
|
|
|
|
return `/api/workspace/raw-file?path=${encodeURIComponent(path)}`;
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 15:42:23 -08:00
|
|
|
const LEFT_SIDEBAR_MIN = 200;
|
|
|
|
|
const LEFT_SIDEBAR_MAX = 480;
|
|
|
|
|
const RIGHT_SIDEBAR_MIN = 260;
|
2026-02-19 17:46:54 -08:00
|
|
|
const RIGHT_SIDEBAR_MAX = 900;
|
2026-02-19 15:42:23 -08:00
|
|
|
const STORAGE_LEFT = "ironclaw-workspace-left-sidebar-width";
|
|
|
|
|
const STORAGE_RIGHT = "ironclaw-workspace-right-sidebar-width";
|
|
|
|
|
|
|
|
|
|
function clamp(n: number, min: number, max: number): number {
|
|
|
|
|
return Math.min(max, Math.max(min, n));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Vertical resize handle; uses cursor position so the handle follows the mouse (no stuck-at-limit). */
|
|
|
|
|
function ResizeHandle({
|
|
|
|
|
mode,
|
|
|
|
|
containerRef,
|
|
|
|
|
min,
|
|
|
|
|
max,
|
|
|
|
|
onResize,
|
|
|
|
|
}: {
|
|
|
|
|
mode: "left" | "right";
|
|
|
|
|
containerRef: React.RefObject<HTMLElement | null>;
|
|
|
|
|
min: number;
|
|
|
|
|
max: number;
|
|
|
|
|
onResize: (width: number) => void;
|
|
|
|
|
}) {
|
|
|
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
|
|
|
const onMouseDown = useCallback(
|
|
|
|
|
(e: React.MouseEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setIsDragging(true);
|
|
|
|
|
const move = (ev: MouseEvent) => {
|
|
|
|
|
const el = containerRef.current;
|
|
|
|
|
if (!el) {return;}
|
|
|
|
|
const rect = el.getBoundingClientRect();
|
|
|
|
|
const width =
|
|
|
|
|
mode === "left"
|
|
|
|
|
? ev.clientX - rect.left
|
|
|
|
|
: rect.right - ev.clientX;
|
|
|
|
|
onResize(clamp(width, min, max));
|
|
|
|
|
};
|
|
|
|
|
const up = () => {
|
|
|
|
|
setIsDragging(false);
|
|
|
|
|
document.removeEventListener("mousemove", move);
|
|
|
|
|
document.removeEventListener("mouseup", up);
|
|
|
|
|
document.body.style.removeProperty("user-select");
|
|
|
|
|
document.body.style.removeProperty("cursor");
|
2026-02-19 17:46:54 -08:00
|
|
|
document.body.classList.remove("resizing");
|
2026-02-19 15:42:23 -08:00
|
|
|
};
|
|
|
|
|
document.body.style.setProperty("user-select", "none");
|
|
|
|
|
document.body.style.setProperty("cursor", "col-resize");
|
2026-02-19 17:46:54 -08:00
|
|
|
document.body.classList.add("resizing");
|
2026-02-19 15:42:23 -08:00
|
|
|
document.addEventListener("mousemove", move);
|
|
|
|
|
document.addEventListener("mouseup", up);
|
|
|
|
|
},
|
|
|
|
|
[containerRef, mode, min, max, onResize],
|
|
|
|
|
);
|
|
|
|
|
const showHover = isDragging || undefined;
|
|
|
|
|
return (
|
|
|
|
|
<div
|
|
|
|
|
role="separator"
|
|
|
|
|
aria-orientation="vertical"
|
|
|
|
|
onMouseDown={onMouseDown}
|
2026-02-19 21:58:10 -08:00
|
|
|
className={`cursor-col-resize flex justify-center transition-colors ${showHover ? "bg-blue-600/30" : "hover:bg-blue-600/30"}`}
|
|
|
|
|
style={{ position: "absolute", [mode === "left" ? "right" : "left"]: -2, top: 0, bottom: 0, width: 4, zIndex: 20 }}
|
2026-02-19 15:42:23 -08:00
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
/** Find a node in the tree by exact path. */
|
2026-02-11 16:45:07 -08:00
|
|
|
function findNode(
|
|
|
|
|
tree: TreeNode[],
|
|
|
|
|
path: string,
|
|
|
|
|
): TreeNode | null {
|
|
|
|
|
for (const node of tree) {
|
|
|
|
|
if (node.path === path) {return node;}
|
|
|
|
|
if (node.children) {
|
|
|
|
|
const found = findNode(node.children, path);
|
|
|
|
|
if (found) {return found;}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Extract the object name from a tree path (last segment). */
|
|
|
|
|
function objectNameFromPath(path: string): string {
|
|
|
|
|
const segments = path.split("/");
|
|
|
|
|
return segments[segments.length - 1];
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-19 17:46:54 -08:00
|
|
|
/** Infer a tree node type from filename extension for ad-hoc path previews. */
|
|
|
|
|
function inferNodeTypeFromFileName(fileName: string): TreeNode["type"] {
|
|
|
|
|
const ext = fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
|
|
|
if (ext === "md" || ext === "mdx") {return "document";}
|
|
|
|
|
if (ext === "duckdb" || ext === "sqlite" || ext === "sqlite3" || ext === "db") {return "database";}
|
|
|
|
|
return "file";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Normalize chat path references (supports file:// URLs). */
|
|
|
|
|
function normalizeChatPath(path: string): string {
|
|
|
|
|
const trimmed = path.trim();
|
|
|
|
|
if (!trimmed.startsWith("file://")) {
|
|
|
|
|
return trimmed;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const url = new URL(trimmed);
|
|
|
|
|
if (url.protocol !== "file:") {
|
|
|
|
|
return trimmed;
|
|
|
|
|
}
|
|
|
|
|
const decoded = decodeURIComponent(url.pathname);
|
|
|
|
|
// Windows file URLs are /C:/... in URL form
|
|
|
|
|
if (/^\/[A-Za-z]:\//.test(decoded)) {
|
|
|
|
|
return decoded.slice(1);
|
|
|
|
|
}
|
|
|
|
|
return decoded;
|
|
|
|
|
} catch {
|
|
|
|
|
return trimmed;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
/**
|
|
|
|
|
* Resolve a path with fallback strategies:
|
|
|
|
|
* 1. Exact match
|
|
|
|
|
* 2. Try with knowledge/ prefix
|
|
|
|
|
* 3. Try stripping knowledge/ prefix
|
|
|
|
|
* 4. Match last segment against object names
|
|
|
|
|
*/
|
|
|
|
|
function resolveNode(
|
|
|
|
|
tree: TreeNode[],
|
|
|
|
|
path: string,
|
|
|
|
|
): TreeNode | null {
|
|
|
|
|
let node = findNode(tree, path);
|
|
|
|
|
if (node) {return node;}
|
|
|
|
|
|
|
|
|
|
if (!path.startsWith("knowledge/")) {
|
|
|
|
|
node = findNode(tree, `knowledge/${path}`);
|
|
|
|
|
if (node) {return node;}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path.startsWith("knowledge/")) {
|
|
|
|
|
node = findNode(tree, path.slice("knowledge/".length));
|
|
|
|
|
if (node) {return node;}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const lastSegment = path.split("/").pop();
|
|
|
|
|
if (lastSegment) {
|
|
|
|
|
function findByName(nodes: TreeNode[]): TreeNode | null {
|
|
|
|
|
for (const n of nodes) {
|
|
|
|
|
if (n.type === "object" && objectNameFromPath(n.path) === lastSegment) {return n;}
|
|
|
|
|
if (n.children) {
|
|
|
|
|
const found = findByName(n.children);
|
|
|
|
|
if (found) {return found;}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
node = findByName(tree);
|
|
|
|
|
if (node) {return node;}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
// --- Main Page ---
|
|
|
|
|
|
|
|
|
|
export default function WorkspacePage() {
|
2026-02-12 00:29:39 -08:00
|
|
|
return (
|
|
|
|
|
<Suspense fallback={
|
|
|
|
|
<div className="flex h-screen items-center justify-center" style={{ background: "var(--color-bg)" }}>
|
2026-02-19 16:50:52 -08:00
|
|
|
<UnicodeSpinner name="braille" className="text-2xl" style={{ color: "var(--color-text-muted)" }} />
|
2026-02-12 00:29:39 -08:00
|
|
|
</div>
|
|
|
|
|
}>
|
|
|
|
|
<WorkspacePageInner />
|
|
|
|
|
</Suspense>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function WorkspacePageInner() {
|
2026-02-11 16:45:07 -08:00
|
|
|
const searchParams = useSearchParams();
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
const router = useRouter();
|
2026-02-11 16:45:07 -08:00
|
|
|
const initialPathHandled = useRef(false);
|
|
|
|
|
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
// Chat panel ref for session management
|
|
|
|
|
const chatRef = useRef<ChatPanelHandle>(null);
|
2026-02-17 01:34:33 -08:00
|
|
|
// Compact (file-scoped) chat panel ref for sidebar drag-and-drop
|
|
|
|
|
const compactChatRef = useRef<ChatPanelHandle>(null);
|
2026-02-19 15:42:23 -08:00
|
|
|
// Root layout ref for resize handle position (handle follows cursor)
|
|
|
|
|
const layoutRef = useRef<HTMLDivElement>(null);
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
|
2026-02-13 15:16:36 -08:00
|
|
|
// Live-reactive tree via SSE watcher (with browse-mode support)
|
|
|
|
|
const {
|
|
|
|
|
tree, loading: treeLoading, exists: workspaceExists, refresh: refreshTree,
|
2026-02-19 14:59:34 -08:00
|
|
|
reconnect: reconnectWorkspace,
|
2026-02-13 15:52:13 -08:00
|
|
|
browseDir, setBrowseDir, parentDir: browseParentDir, workspaceRoot, openclawDir,
|
2026-02-19 14:59:34 -08:00
|
|
|
activeProfile,
|
2026-02-21 14:39:55 -08:00
|
|
|
showHidden, setShowHidden,
|
2026-02-13 15:16:36 -08:00
|
|
|
} = useWorkspaceWatcher();
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
|
2026-02-19 14:59:34 -08:00
|
|
|
// handleProfileSwitch is defined below fetchSessions/fetchCronJobs (avoids TDZ)
|
|
|
|
|
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
// Search index for @ mention fuzzy search (files + entries)
|
|
|
|
|
const { search: searchIndex } = useSearchIndex();
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
const [context, setContext] = useState<WorkspaceContext | null>(null);
|
|
|
|
|
const [activePath, setActivePath] = useState<string | null>(null);
|
|
|
|
|
const [content, setContent] = useState<ContentState>({ kind: "none" });
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
const [showChatSidebar, setShowChatSidebar] = useState(true);
|
2026-02-19 17:46:54 -08:00
|
|
|
const [chatSidebarPreview, setChatSidebarPreview] = useState<ChatSidebarPreviewState | null>(null);
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
// Chat session state
|
|
|
|
|
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
|
|
|
|
const [sessions, setSessions] = useState<WebSession[]>([]);
|
2026-02-19 16:50:52 -08:00
|
|
|
const [sessionsLoading, setSessionsLoading] = useState(true);
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
const [sidebarRefreshKey, setSidebarRefreshKey] = useState(0);
|
2026-02-15 23:42:13 -08:00
|
|
|
const [streamingSessionIds, setStreamingSessionIds] = useState<Set<string>>(new Set());
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
|
2026-02-19 14:59:34 -08:00
|
|
|
// Subagent tracking
|
|
|
|
|
const [subagents, setSubagents] = useState<SubagentSpawnInfo[]>([]);
|
|
|
|
|
const [activeSubagentKey, setActiveSubagentKey] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
const handleSubagentSpawned = useCallback((info: SubagentSpawnInfo) => {
|
|
|
|
|
setSubagents((prev) => {
|
|
|
|
|
const idx = prev.findIndex((sa) => sa.childSessionKey === info.childSessionKey);
|
|
|
|
|
if (idx >= 0) {
|
|
|
|
|
// Update status if changed
|
|
|
|
|
if (prev[idx].status === info.status) {return prev;}
|
|
|
|
|
const updated = [...prev];
|
|
|
|
|
updated[idx] = { ...prev[idx], ...info };
|
|
|
|
|
return updated;
|
|
|
|
|
}
|
|
|
|
|
return [...prev, info];
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleSelectSubagent = useCallback((sessionKey: string) => {
|
|
|
|
|
setActiveSubagentKey(sessionKey);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleBackFromSubagent = useCallback(() => {
|
|
|
|
|
setActiveSubagentKey(null);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Navigate to a subagent panel when its card is clicked in the chat
|
|
|
|
|
const handleSubagentClickFromChat = useCallback((task: string) => {
|
|
|
|
|
const match = subagents.find((sa) => sa.task === task);
|
|
|
|
|
if (match) {
|
|
|
|
|
setActiveSubagentKey(match.childSessionKey);
|
|
|
|
|
}
|
|
|
|
|
}, [subagents]);
|
|
|
|
|
|
|
|
|
|
// Find the active subagent's info for the panel
|
|
|
|
|
const activeSubagent = useMemo(() => {
|
|
|
|
|
if (!activeSubagentKey) {return null;}
|
|
|
|
|
return subagents.find((sa) => sa.childSessionKey === activeSubagentKey) ?? null;
|
|
|
|
|
}, [activeSubagentKey, subagents]);
|
|
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
// Cron jobs state
|
|
|
|
|
const [cronJobs, setCronJobs] = useState<CronJob[]>([]);
|
|
|
|
|
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
// Entry detail modal state
|
|
|
|
|
const [entryModal, setEntryModal] = useState<{
|
|
|
|
|
objectName: string;
|
|
|
|
|
entryId: string;
|
|
|
|
|
} | null>(null);
|
|
|
|
|
|
2026-02-16 00:30:13 -08:00
|
|
|
// Mobile responsive state
|
|
|
|
|
const isMobile = useIsMobile();
|
|
|
|
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
|
|
|
const [chatSessionsOpen, setChatSessionsOpen] = useState(false);
|
|
|
|
|
|
2026-02-19 23:15:39 -08:00
|
|
|
// Sidebar collapse state (desktop only).
|
|
|
|
|
const [leftSidebarCollapsed, setLeftSidebarCollapsed] = useState(false);
|
|
|
|
|
const [rightSidebarCollapsed, setRightSidebarCollapsed] = useState(false);
|
|
|
|
|
|
2026-02-19 16:50:52 -08:00
|
|
|
// Resizable sidebar widths (desktop only; persisted in localStorage).
|
|
|
|
|
// Use static defaults so server and client match on first render (avoid hydration mismatch).
|
|
|
|
|
const [leftSidebarWidth, setLeftSidebarWidth] = useState(260);
|
|
|
|
|
const [rightSidebarWidth, setRightSidebarWidth] = useState(320);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const left = window.localStorage.getItem(STORAGE_LEFT);
|
|
|
|
|
const nLeft = left ? parseInt(left, 10) : NaN;
|
|
|
|
|
if (Number.isFinite(nLeft)) {
|
|
|
|
|
setLeftSidebarWidth(clamp(nLeft, LEFT_SIDEBAR_MIN, LEFT_SIDEBAR_MAX));
|
|
|
|
|
}
|
|
|
|
|
const right = window.localStorage.getItem(STORAGE_RIGHT);
|
|
|
|
|
const nRight = right ? parseInt(right, 10) : NaN;
|
|
|
|
|
if (Number.isFinite(nRight)) {
|
|
|
|
|
setRightSidebarWidth(clamp(nRight, RIGHT_SIDEBAR_MIN, RIGHT_SIDEBAR_MAX));
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
2026-02-19 15:42:23 -08:00
|
|
|
useEffect(() => {
|
|
|
|
|
window.localStorage.setItem(STORAGE_LEFT, String(leftSidebarWidth));
|
|
|
|
|
}, [leftSidebarWidth]);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
window.localStorage.setItem(STORAGE_RIGHT, String(rightSidebarWidth));
|
|
|
|
|
}, [rightSidebarWidth]);
|
|
|
|
|
|
2026-02-19 23:15:39 -08:00
|
|
|
// Keyboard shortcuts: Cmd+B = toggle left sidebar, Cmd+Shift+B = toggle right sidebar
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const handler = (e: KeyboardEvent) => {
|
|
|
|
|
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "b") {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
if (e.shiftKey) {
|
|
|
|
|
setRightSidebarCollapsed((v) => !v);
|
|
|
|
|
} else {
|
|
|
|
|
setLeftSidebarCollapsed((v) => !v);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
window.addEventListener("keydown", handler);
|
|
|
|
|
return () => window.removeEventListener("keydown", handler);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
// Derive file context for chat sidebar directly from activePath (stable across loading).
|
|
|
|
|
// Exclude reserved virtual paths (~chats, ~cron, etc.) where file-scoped chat is irrelevant.
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
const fileContext = useMemo(() => {
|
|
|
|
|
if (!activePath) {return undefined;}
|
2026-02-12 13:37:40 -08:00
|
|
|
if (isVirtualPath(activePath)) {return undefined;}
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
const filename = activePath.split("/").pop() || activePath;
|
2026-02-13 18:57:15 -08:00
|
|
|
return { path: activePath, filename, isDirectory: content.kind === "directory" };
|
|
|
|
|
}, [activePath, content.kind]);
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
|
|
|
|
|
// Update content state when the agent edits the file (live reload)
|
|
|
|
|
const handleFileChanged = useCallback((newContent: string) => {
|
|
|
|
|
setContent((prev) => {
|
|
|
|
|
if (prev.kind === "document") {
|
|
|
|
|
return { ...prev, data: { ...prev.data, content: newContent } };
|
|
|
|
|
}
|
2026-02-13 18:06:59 -08:00
|
|
|
if (prev.kind === "file" || prev.kind === "code") {
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
return { ...prev, data: { ...prev.data, content: newContent } };
|
|
|
|
|
}
|
|
|
|
|
return prev;
|
|
|
|
|
});
|
|
|
|
|
}, []);
|
2026-02-11 16:45:07 -08:00
|
|
|
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
// Fetch workspace context on mount
|
2026-02-11 16:45:07 -08:00
|
|
|
useEffect(() => {
|
|
|
|
|
let cancelled = false;
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
async function loadContext() {
|
2026-02-11 16:45:07 -08:00
|
|
|
try {
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
const res = await fetch("/api/workspace/context");
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
if (!cancelled) {setContext(data);}
|
2026-02-11 16:45:07 -08:00
|
|
|
} catch {
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
// ignore
|
2026-02-11 16:45:07 -08:00
|
|
|
}
|
|
|
|
|
}
|
2026-02-13 15:52:13 -08:00
|
|
|
void loadContext();
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
return () => { cancelled = true; };
|
2026-02-11 16:45:07 -08:00
|
|
|
}, []);
|
|
|
|
|
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
// Fetch chat sessions
|
|
|
|
|
const fetchSessions = useCallback(async () => {
|
2026-02-19 16:50:52 -08:00
|
|
|
setSessionsLoading(true);
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
try {
|
|
|
|
|
const res = await fetch("/api/web-sessions");
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
setSessions(data.sessions ?? []);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
2026-02-19 16:50:52 -08:00
|
|
|
} finally {
|
|
|
|
|
setSessionsLoading(false);
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-02-13 15:52:13 -08:00
|
|
|
void fetchSessions();
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
}, [fetchSessions, sidebarRefreshKey]);
|
|
|
|
|
|
|
|
|
|
const refreshSessions = useCallback(() => {
|
|
|
|
|
setSidebarRefreshKey((k) => k + 1);
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-02-19 16:50:52 -08:00
|
|
|
const handleDeleteSession = useCallback(
|
|
|
|
|
async (sessionId: string) => {
|
|
|
|
|
const res = await fetch(`/api/web-sessions/${sessionId}`, { method: "DELETE" });
|
|
|
|
|
if (!res.ok) {return;}
|
|
|
|
|
if (activeSessionId === sessionId) {
|
|
|
|
|
setActiveSessionId(null);
|
|
|
|
|
setActiveSubagentKey(null);
|
|
|
|
|
const remaining = sessions.filter((s) => s.id !== sessionId);
|
|
|
|
|
if (remaining.length > 0) {
|
|
|
|
|
const next = remaining[0];
|
|
|
|
|
setActiveSessionId(next.id);
|
|
|
|
|
void chatRef.current?.loadSession(next.id);
|
|
|
|
|
} else {
|
|
|
|
|
void chatRef.current?.newSession();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
void fetchSessions();
|
|
|
|
|
},
|
|
|
|
|
[activeSessionId, sessions, fetchSessions],
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-19 22:54:23 -08:00
|
|
|
const handleRenameSession = useCallback(
|
|
|
|
|
async (sessionId: string, newTitle: string) => {
|
|
|
|
|
await fetch(`/api/web-sessions/${sessionId}`, {
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ title: newTitle }),
|
|
|
|
|
});
|
|
|
|
|
void fetchSessions();
|
|
|
|
|
},
|
|
|
|
|
[fetchSessions],
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-15 23:42:13 -08:00
|
|
|
// Poll for active (streaming) agent runs so the sidebar can show indicators.
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let cancelled = false;
|
|
|
|
|
const poll = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch("/api/chat/active");
|
|
|
|
|
if (cancelled) {return;}
|
|
|
|
|
const data = await res.json();
|
|
|
|
|
const ids: string[] = data.sessionIds ?? [];
|
|
|
|
|
setStreamingSessionIds((prev) => {
|
|
|
|
|
// Only update state if the set actually changed (avoid re-renders).
|
|
|
|
|
if (prev.size === ids.length && ids.every((id) => prev.has(id))) {return prev;}
|
|
|
|
|
return new Set(ids);
|
|
|
|
|
});
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
void poll();
|
|
|
|
|
const id = setInterval(poll, 3_000);
|
|
|
|
|
return () => { cancelled = true; clearInterval(id); };
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
// Fetch cron jobs for sidebar
|
|
|
|
|
const fetchCronJobs = useCallback(async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch("/api/cron/jobs");
|
|
|
|
|
const data: CronJobsResponse = await res.json();
|
|
|
|
|
setCronJobs(data.jobs ?? []);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore - cron might not be configured
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
2026-02-13 15:52:13 -08:00
|
|
|
void fetchCronJobs();
|
2026-02-12 13:37:40 -08:00
|
|
|
const id = setInterval(fetchCronJobs, 30_000);
|
|
|
|
|
return () => clearInterval(id);
|
|
|
|
|
}, [fetchCronJobs]);
|
|
|
|
|
|
2026-02-19 14:59:34 -08:00
|
|
|
// After profile switch or workspace creation, reconnect SSE + refresh all data
|
|
|
|
|
const handleProfileSwitch = useCallback(() => {
|
|
|
|
|
reconnectWorkspace();
|
|
|
|
|
void fetchSessions();
|
|
|
|
|
void fetchCronJobs();
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
setActiveSessionId(null);
|
|
|
|
|
setSubagents([]);
|
|
|
|
|
setActiveSubagentKey(null);
|
|
|
|
|
}, [reconnectWorkspace, fetchSessions, fetchCronJobs]);
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
// Load content when path changes
|
|
|
|
|
const loadContent = useCallback(
|
|
|
|
|
async (node: TreeNode) => {
|
|
|
|
|
setActivePath(node.path);
|
|
|
|
|
setContent({ kind: "loading" });
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
if (node.type === "object") {
|
|
|
|
|
const name = objectNameFromPath(node.path);
|
|
|
|
|
const res = await fetch(`/api/workspace/objects/${encodeURIComponent(name)}`);
|
|
|
|
|
if (!res.ok) {
|
2026-02-15 18:19:13 -08:00
|
|
|
const errData = await res.json().catch(() => ({}));
|
|
|
|
|
if (errData.code === "DUCKDB_NOT_INSTALLED") {
|
|
|
|
|
setContent({ kind: "duckdb-missing" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-11 16:45:07 -08:00
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const data: ObjectData = await res.json();
|
|
|
|
|
setContent({ kind: "object", data });
|
|
|
|
|
} else if (node.type === "document") {
|
2026-02-15 18:18:15 -08:00
|
|
|
// Use virtual-file API for ~skills/ paths
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
const res = await fetch(fileApiUrl(node.path));
|
2026-02-11 16:45:07 -08:00
|
|
|
if (!res.ok) {
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const data: FileData = await res.json();
|
|
|
|
|
setContent({
|
|
|
|
|
kind: "document",
|
|
|
|
|
data,
|
|
|
|
|
title: node.name.replace(/\.md$/, ""),
|
|
|
|
|
});
|
2026-02-11 17:01:28 -08:00
|
|
|
} else if (node.type === "database") {
|
|
|
|
|
setContent({ kind: "database", dbPath: node.path, filename: node.name });
|
2026-02-11 18:35:35 -08:00
|
|
|
} else if (node.type === "report") {
|
|
|
|
|
setContent({ kind: "report", reportPath: node.path, filename: node.name });
|
2026-02-11 16:45:07 -08:00
|
|
|
} else if (node.type === "file") {
|
2026-02-21 13:10:32 -08:00
|
|
|
// Spreadsheet files get their own binary viewer
|
|
|
|
|
if (isSpreadsheetFile(node.name)) {
|
|
|
|
|
const url = rawFileUrl(node.path);
|
|
|
|
|
setContent({ kind: "spreadsheet", url, filename: node.name });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HTML files get an iframe preview
|
|
|
|
|
const ext = node.name.split(".").pop()?.toLowerCase() ?? "";
|
|
|
|
|
if (ext === "html" || ext === "htm") {
|
|
|
|
|
setContent({ kind: "html", rawUrl: rawFileUrl(node.path), contentUrl: fileApiUrl(node.path), filename: node.name });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
// Check if this is a media file (image/video/audio/pdf)
|
|
|
|
|
const mediaType = detectMediaType(node.name);
|
|
|
|
|
if (mediaType) {
|
2026-02-13 15:16:36 -08:00
|
|
|
const url = rawFileUrl(node.path);
|
|
|
|
|
setContent({ kind: "media", url, mediaType, filename: node.name, filePath: node.path });
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
const res = await fetch(fileApiUrl(node.path));
|
2026-02-11 16:45:07 -08:00
|
|
|
if (!res.ok) {
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const data: FileData = await res.json();
|
2026-02-20 12:45:42 -08:00
|
|
|
// Route code files to the syntax-highlighted CodeViewer
|
2026-02-13 18:06:59 -08:00
|
|
|
if (isCodeFile(node.name)) {
|
|
|
|
|
setContent({ kind: "code", data, filename: node.name });
|
|
|
|
|
} else {
|
|
|
|
|
setContent({ kind: "file", data, filename: node.name });
|
|
|
|
|
}
|
2026-02-11 16:45:07 -08:00
|
|
|
} else if (node.type === "folder") {
|
|
|
|
|
setContent({ kind: "directory", node });
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleNodeSelect = useCallback(
|
|
|
|
|
(node: TreeNode) => {
|
2026-02-13 15:52:13 -08:00
|
|
|
// --- Browse-mode: detect special OpenClaw directories ---
|
|
|
|
|
// When the user clicks a known OpenClaw folder while browsing the
|
|
|
|
|
// filesystem, switch back to workspace mode or show the appropriate
|
|
|
|
|
// dashboard instead of showing raw files.
|
|
|
|
|
if (browseDir && isAbsolutePath(node.path)) {
|
2026-02-15 18:18:15 -08:00
|
|
|
// Clicking the workspace root → restore full workspace mode
|
2026-02-13 15:52:13 -08:00
|
|
|
if (workspaceRoot && node.path === workspaceRoot) {
|
|
|
|
|
setBrowseDir(null);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (openclawDir) {
|
|
|
|
|
// Clicking the cron directory → show cron dashboard
|
|
|
|
|
if (node.path === openclawDir + "/cron") {
|
|
|
|
|
setBrowseDir(null);
|
|
|
|
|
setActivePath("~cron");
|
|
|
|
|
setContent({ kind: "cron-dashboard" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-19 14:59:34 -08:00
|
|
|
// Clicking any web-chat directory → switch to workspace mode & open chats
|
|
|
|
|
if (openclawDir && node.path.startsWith(openclawDir + "/web-chat")) {
|
2026-02-13 15:52:13 -08:00
|
|
|
setBrowseDir(null);
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
void chatRef.current?.newSession();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-13 18:06:59 -08:00
|
|
|
// Clicking a folder in browse mode → navigate into it so the tree
|
2026-02-13 18:57:15 -08:00
|
|
|
// is fetched fresh, AND show it in the main panel with the chat sidebar.
|
|
|
|
|
// Children come from the live tree (same data source as the sidebar),
|
|
|
|
|
// not from the stale node snapshot.
|
2026-02-13 18:06:59 -08:00
|
|
|
if (node.type === "folder") {
|
|
|
|
|
setBrowseDir(node.path);
|
2026-02-13 18:57:15 -08:00
|
|
|
setActivePath(node.path);
|
|
|
|
|
setContent({ kind: "directory", node: { name: node.name, path: node.path, type: "folder" } });
|
2026-02-13 18:06:59 -08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-02-13 15:52:13 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Virtual path handlers (workspace mode) ---
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
// Intercept chat folder item clicks
|
|
|
|
|
if (node.path.startsWith("~chats/")) {
|
|
|
|
|
const sessionId = node.path.slice("~chats/".length);
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
setActiveSessionId(sessionId);
|
2026-02-13 15:52:13 -08:00
|
|
|
void chatRef.current?.loadSession(sessionId);
|
2026-02-12 13:37:40 -08:00
|
|
|
// URL is synced by the activeSessionId effect
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Clicking the Chats folder itself opens a new chat
|
|
|
|
|
if (node.path === "~chats") {
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
2026-02-13 15:52:13 -08:00
|
|
|
void chatRef.current?.newSession();
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-12 13:37:40 -08:00
|
|
|
// Intercept cron job item clicks
|
|
|
|
|
if (node.path.startsWith("~cron/")) {
|
|
|
|
|
const jobId = node.path.slice("~cron/".length);
|
|
|
|
|
const job = cronJobs.find((j) => j.id === jobId);
|
|
|
|
|
if (job) {
|
|
|
|
|
setActivePath(node.path);
|
|
|
|
|
setContent({ kind: "cron-job", jobId, job });
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Clicking the Cron folder itself opens the dashboard
|
|
|
|
|
if (node.path === "~cron") {
|
|
|
|
|
setActivePath(node.path);
|
|
|
|
|
setContent({ kind: "cron-dashboard" });
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-13 15:52:13 -08:00
|
|
|
void loadContent(node);
|
2026-02-11 16:45:07 -08:00
|
|
|
},
|
2026-02-13 15:52:13 -08:00
|
|
|
[loadContent, router, cronJobs, browseDir, workspaceRoot, openclawDir, setBrowseDir],
|
2026-02-11 16:45:07 -08:00
|
|
|
);
|
|
|
|
|
|
2026-02-19 17:46:54 -08:00
|
|
|
const loadSidebarPreviewFromNode = useCallback(
|
|
|
|
|
async (node: TreeNode): Promise<SidebarPreviewContent | null> => {
|
|
|
|
|
if (node.type === "folder") {
|
|
|
|
|
return { kind: "directory", path: node.path, name: node.name };
|
|
|
|
|
}
|
|
|
|
|
if (node.type === "database") {
|
|
|
|
|
return { kind: "database", dbPath: node.path, filename: node.name };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mediaType = detectMediaType(node.name);
|
|
|
|
|
if (mediaType) {
|
|
|
|
|
return {
|
|
|
|
|
kind: "media",
|
|
|
|
|
url: rawFileUrl(node.path),
|
|
|
|
|
mediaType,
|
|
|
|
|
filename: node.name,
|
|
|
|
|
filePath: node.path,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const res = await fetch(fileApiUrl(node.path));
|
|
|
|
|
if (!res.ok) {return null;}
|
|
|
|
|
const data: FileData = await res.json();
|
|
|
|
|
|
|
|
|
|
if (node.type === "document" || data.type === "markdown") {
|
|
|
|
|
return {
|
|
|
|
|
kind: "document",
|
|
|
|
|
data,
|
|
|
|
|
title: node.name.replace(/\.mdx?$/, ""),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
if (isCodeFile(node.name)) {
|
|
|
|
|
return { kind: "code", data, filename: node.name };
|
|
|
|
|
}
|
|
|
|
|
return { kind: "file", data, filename: node.name };
|
|
|
|
|
},
|
|
|
|
|
[],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Open inline file-path mentions from chat.
|
|
|
|
|
// In chat mode, render a Dropbox-style preview in the right sidebar.
|
|
|
|
|
const handleFilePathClickFromChat = useCallback(
|
|
|
|
|
async (rawPath: string) => {
|
|
|
|
|
const inputPath = normalizeChatPath(rawPath);
|
|
|
|
|
if (!inputPath) {return false;}
|
|
|
|
|
|
|
|
|
|
// Desktop behavior: always use right-sidebar preview for chat path clicks.
|
|
|
|
|
const shouldPreviewInSidebar = !isMobile;
|
|
|
|
|
|
|
|
|
|
const openNode = async (node: TreeNode) => {
|
|
|
|
|
if (!shouldPreviewInSidebar) {
|
|
|
|
|
handleNodeSelect(node);
|
|
|
|
|
setShowChatSidebar(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure we are in main-chat layout so the preview panel is visible.
|
|
|
|
|
if (activePath || content.kind !== "none") {
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setChatSidebarPreview({
|
|
|
|
|
status: "loading",
|
|
|
|
|
path: node.path,
|
|
|
|
|
filename: node.name,
|
|
|
|
|
});
|
|
|
|
|
const previewContent = await loadSidebarPreviewFromNode(node);
|
|
|
|
|
if (!previewContent) {
|
|
|
|
|
setChatSidebarPreview({
|
|
|
|
|
status: "error",
|
|
|
|
|
path: node.path,
|
|
|
|
|
filename: node.name,
|
|
|
|
|
message: "Could not preview this file.",
|
|
|
|
|
});
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
setChatSidebarPreview({
|
|
|
|
|
status: "ready",
|
|
|
|
|
path: node.path,
|
|
|
|
|
filename: node.name,
|
|
|
|
|
content: previewContent,
|
|
|
|
|
});
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// For workspace-relative paths, prefer the live tree so we preserve semantics.
|
|
|
|
|
if (
|
|
|
|
|
!isAbsolutePath(inputPath) &&
|
|
|
|
|
!isHomeRelativePath(inputPath) &&
|
|
|
|
|
!inputPath.startsWith("./") &&
|
|
|
|
|
!inputPath.startsWith("../")
|
|
|
|
|
) {
|
|
|
|
|
const node = resolveNode(tree, inputPath);
|
|
|
|
|
if (node) {
|
|
|
|
|
return await openNode(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/workspace/path-info?path=${encodeURIComponent(inputPath)}`);
|
|
|
|
|
if (!res.ok) {return false;}
|
|
|
|
|
const info = await res.json() as {
|
|
|
|
|
path?: string;
|
|
|
|
|
name?: string;
|
|
|
|
|
type?: "file" | "directory" | "other";
|
|
|
|
|
};
|
|
|
|
|
if (!info.path || !info.name || !info.type) {return false;}
|
|
|
|
|
|
|
|
|
|
// If this absolute path is inside the current workspace, map it
|
|
|
|
|
// back to a workspace-relative node first.
|
|
|
|
|
if (workspaceRoot && (info.path === workspaceRoot || info.path.startsWith(`${workspaceRoot}/`))) {
|
|
|
|
|
const relPath = info.path === workspaceRoot ? "" : info.path.slice(workspaceRoot.length + 1);
|
|
|
|
|
if (relPath) {
|
|
|
|
|
const node = resolveNode(tree, relPath);
|
|
|
|
|
if (node) {
|
|
|
|
|
return await openNode(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.type === "directory") {
|
|
|
|
|
const dirNode: TreeNode = { name: info.name, path: info.path, type: "folder" };
|
|
|
|
|
if (shouldPreviewInSidebar) {
|
|
|
|
|
return await openNode(dirNode);
|
|
|
|
|
}
|
|
|
|
|
setBrowseDir(info.path);
|
|
|
|
|
setActivePath(info.path);
|
|
|
|
|
setContent({
|
|
|
|
|
kind: "directory",
|
|
|
|
|
node: { name: info.name, path: info.path, type: "folder" },
|
|
|
|
|
});
|
|
|
|
|
setShowChatSidebar(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.type === "file") {
|
|
|
|
|
const fileNode: TreeNode = {
|
|
|
|
|
name: info.name,
|
|
|
|
|
path: info.path,
|
|
|
|
|
type: inferNodeTypeFromFileName(info.name),
|
|
|
|
|
};
|
|
|
|
|
if (shouldPreviewInSidebar) {
|
|
|
|
|
return await openNode(fileNode);
|
|
|
|
|
}
|
|
|
|
|
const parentDir = info.path.split("/").slice(0, -1).join("/") || "/";
|
|
|
|
|
if (isAbsolutePath(info.path)) {
|
|
|
|
|
setBrowseDir(parentDir);
|
|
|
|
|
}
|
|
|
|
|
await loadContent(fileNode);
|
|
|
|
|
setShowChatSidebar(true);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// Ignore -- chat message bubble shows inline error state.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
[activePath, content.kind, isMobile, tree, handleNodeSelect, workspaceRoot, loadSidebarPreviewFromNode, setBrowseDir, loadContent, router],
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-13 19:24:24 -08:00
|
|
|
// Build the enhanced tree: real tree + Cron virtual folder at the bottom
|
|
|
|
|
// (Chat sessions live in the right sidebar, not in the tree.)
|
2026-02-13 15:16:36 -08:00
|
|
|
// In browse mode, skip virtual folders (they only apply to workspace mode)
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
const enhancedTree = useMemo(() => {
|
2026-02-13 15:16:36 -08:00
|
|
|
if (browseDir) {
|
|
|
|
|
return tree;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
const cronStatusIcon = (job: CronJob) => {
|
|
|
|
|
if (!job.enabled) {return "\u25CB";} // circle outline
|
|
|
|
|
if (job.state.runningAtMs) {return "\u25CF";} // filled circle
|
|
|
|
|
if (job.state.lastStatus === "error") {return "\u25C6";} // diamond
|
|
|
|
|
if (job.state.lastStatus === "ok") {return "\u2713";} // check
|
|
|
|
|
return "\u25CB";
|
|
|
|
|
};
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
const cronChildren: TreeNode[] = cronJobs.map((j) => ({
|
|
|
|
|
name: `${cronStatusIcon(j)} ${j.name}`,
|
|
|
|
|
path: `~cron/${j.id}`,
|
|
|
|
|
type: "file" as const,
|
|
|
|
|
virtual: true,
|
|
|
|
|
}));
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
const cronFolder: TreeNode = {
|
|
|
|
|
name: "Cron",
|
|
|
|
|
path: "~cron",
|
|
|
|
|
type: "folder",
|
|
|
|
|
virtual: true,
|
|
|
|
|
children: cronChildren.length > 0 ? cronChildren : undefined,
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-13 19:24:24 -08:00
|
|
|
return [...tree, cronFolder];
|
|
|
|
|
}, [tree, cronJobs, browseDir]);
|
2026-02-13 15:16:36 -08:00
|
|
|
|
|
|
|
|
// Compute the effective parentDir for ".." navigation.
|
|
|
|
|
// In browse mode: use browseParentDir from the API.
|
|
|
|
|
// In workspace mode: use the parent of the workspace root (allows escaping workspace).
|
|
|
|
|
const effectiveParentDir = useMemo(() => {
|
|
|
|
|
if (browseDir) {
|
|
|
|
|
return browseParentDir;
|
|
|
|
|
}
|
|
|
|
|
// In workspace mode, allow ".." to go up from workspace root
|
|
|
|
|
if (workspaceRoot) {
|
|
|
|
|
const parent = workspaceRoot === "/" ? null : workspaceRoot.split("/").slice(0, -1).join("/") || "/";
|
|
|
|
|
return parent;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}, [browseDir, browseParentDir, workspaceRoot]);
|
|
|
|
|
|
|
|
|
|
// Handle ".." navigation
|
|
|
|
|
const handleNavigateUp = useCallback(() => {
|
|
|
|
|
if (effectiveParentDir != null) {
|
|
|
|
|
setBrowseDir(effectiveParentDir);
|
|
|
|
|
}
|
|
|
|
|
}, [effectiveParentDir, setBrowseDir]);
|
|
|
|
|
|
|
|
|
|
// Return to workspace mode
|
|
|
|
|
const handleGoHome = useCallback(() => {
|
|
|
|
|
setBrowseDir(null);
|
|
|
|
|
}, [setBrowseDir]);
|
2026-02-12 13:37:40 -08:00
|
|
|
|
2026-02-15 23:00:25 -08:00
|
|
|
// Navigate to the main chat / home panel
|
|
|
|
|
const handleGoToChat = useCallback(() => {
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
}, [router]);
|
|
|
|
|
|
2026-02-17 01:34:33 -08:00
|
|
|
// Insert a file mention into the chat editor when a sidebar item is dropped on the chat input.
|
|
|
|
|
// Try the main chat panel first; fall back to the compact (file-scoped) panel.
|
2026-02-15 23:42:13 -08:00
|
|
|
const handleSidebarExternalDrop = useCallback((node: TreeNode) => {
|
2026-02-17 01:34:33 -08:00
|
|
|
const target = chatRef.current ?? compactChatRef.current;
|
|
|
|
|
target?.insertFileMention?.(node.name, node.path);
|
2026-02-15 23:42:13 -08:00
|
|
|
}, []);
|
|
|
|
|
|
2026-02-13 18:06:59 -08:00
|
|
|
// Handle file search selection: navigate sidebar to the file's location and open it
|
|
|
|
|
const handleFileSearchSelect = useCallback(
|
|
|
|
|
(item: { name: string; path: string; type: string }) => {
|
|
|
|
|
if (item.type === "folder") {
|
2026-02-13 18:57:15 -08:00
|
|
|
// Navigate the sidebar into the folder and show it in the main panel.
|
|
|
|
|
// Children come from the live tree (same data source as the sidebar).
|
2026-02-13 18:06:59 -08:00
|
|
|
setBrowseDir(item.path);
|
2026-02-13 18:57:15 -08:00
|
|
|
setActivePath(item.path);
|
|
|
|
|
setContent({ kind: "directory", node: { name: item.name, path: item.path, type: "folder" } });
|
2026-02-13 18:06:59 -08:00
|
|
|
} else {
|
|
|
|
|
// Navigate the sidebar to the parent directory of the file
|
|
|
|
|
const parentOfFile = item.path.split("/").slice(0, -1).join("/") || "/";
|
|
|
|
|
setBrowseDir(parentOfFile);
|
|
|
|
|
// Open the file in the main panel
|
|
|
|
|
const node: TreeNode = {
|
|
|
|
|
name: item.name,
|
|
|
|
|
path: item.path,
|
|
|
|
|
type: item.type as TreeNode["type"],
|
|
|
|
|
};
|
|
|
|
|
void loadContent(node);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[setBrowseDir, loadContent],
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
// Sync URL bar with active content / chat state.
|
|
|
|
|
// Uses window.location instead of searchParams in the comparison to
|
|
|
|
|
// avoid a circular dependency (searchParams updates → effect fires →
|
2026-02-20 12:45:42 -08:00
|
|
|
// router.replace → searchParams updates → …).
|
2026-02-12 13:37:40 -08:00
|
|
|
useEffect(() => {
|
|
|
|
|
const current = new URLSearchParams(window.location.search);
|
|
|
|
|
|
|
|
|
|
if (activePath) {
|
|
|
|
|
// File / content mode — path takes priority over chat.
|
|
|
|
|
if (current.get("path") !== activePath || current.has("chat")) {
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
params.set("path", activePath);
|
|
|
|
|
const entry = current.get("entry");
|
|
|
|
|
if (entry) {params.set("entry", entry);}
|
2026-02-21 13:10:32 -08:00
|
|
|
router.push(`/workspace?${params.toString()}`, { scroll: false });
|
2026-02-12 13:37:40 -08:00
|
|
|
}
|
|
|
|
|
} else if (activeSessionId) {
|
|
|
|
|
// Chat mode — no file selected.
|
|
|
|
|
if (current.get("chat") !== activeSessionId || current.has("path")) {
|
2026-02-21 13:10:32 -08:00
|
|
|
router.push(`/workspace?chat=${encodeURIComponent(activeSessionId)}`, { scroll: false });
|
2026-02-12 13:37:40 -08:00
|
|
|
}
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
}
|
2026-02-12 13:37:40 -08:00
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- intentionally excludes searchParams to avoid infinite loop
|
|
|
|
|
}, [activePath, activeSessionId, router]);
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
|
|
|
|
|
// Open entry modal handler
|
|
|
|
|
const handleOpenEntry = useCallback(
|
|
|
|
|
(objectName: string, entryId: string) => {
|
|
|
|
|
setEntryModal({ objectName, entryId });
|
|
|
|
|
const params = new URLSearchParams(searchParams.toString());
|
|
|
|
|
params.set("entry", `${objectName}:${entryId}`);
|
2026-02-21 13:10:32 -08:00
|
|
|
router.push(`/workspace?${params.toString()}`, { scroll: false });
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
},
|
|
|
|
|
[searchParams, router],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Close entry modal handler
|
|
|
|
|
const handleCloseEntry = useCallback(() => {
|
|
|
|
|
setEntryModal(null);
|
|
|
|
|
const params = new URLSearchParams(searchParams.toString());
|
|
|
|
|
params.delete("entry");
|
|
|
|
|
const qs = params.toString();
|
|
|
|
|
router.replace(qs ? `/workspace?${qs}` : "/workspace", { scroll: false });
|
|
|
|
|
}, [searchParams, router]);
|
|
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
// Auto-navigate to path/chat from URL query params after tree loads
|
2026-02-11 16:45:07 -08:00
|
|
|
useEffect(() => {
|
|
|
|
|
if (initialPathHandled.current || treeLoading || tree.length === 0) {return;}
|
|
|
|
|
|
|
|
|
|
const pathParam = searchParams.get("path");
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
const entryParam = searchParams.get("entry");
|
2026-02-12 13:37:40 -08:00
|
|
|
const chatParam = searchParams.get("chat");
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
if (pathParam) {
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
const node = resolveNode(tree, pathParam);
|
2026-02-11 16:45:07 -08:00
|
|
|
if (node) {
|
|
|
|
|
initialPathHandled.current = true;
|
2026-02-13 15:52:13 -08:00
|
|
|
void loadContent(node);
|
2026-02-11 16:45:07 -08:00
|
|
|
}
|
2026-02-12 13:37:40 -08:00
|
|
|
} else if (chatParam) {
|
|
|
|
|
// Restore the active chat session from URL
|
|
|
|
|
initialPathHandled.current = true;
|
|
|
|
|
setActiveSessionId(chatParam);
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
2026-02-13 15:52:13 -08:00
|
|
|
void chatRef.current?.loadSession(chatParam);
|
2026-02-11 16:45:07 -08:00
|
|
|
}
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
|
|
|
|
|
// Also open entry modal from URL if present
|
|
|
|
|
if (entryParam && entryParam.includes(":")) {
|
|
|
|
|
const [objName, eid] = entryParam.split(":", 2);
|
|
|
|
|
if (objName && eid) {
|
|
|
|
|
setEntryModal({ objectName: objName, entryId: eid });
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-11 16:45:07 -08:00
|
|
|
}, [tree, treeLoading, searchParams, loadContent]);
|
|
|
|
|
|
2026-02-15 18:19:13 -08:00
|
|
|
// Handle ?send= URL parameter: open a new chat session and auto-send the message.
|
|
|
|
|
// Used by the "Install DuckDB" button and similar in-app triggers.
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const sendParam = searchParams.get("send");
|
|
|
|
|
if (!sendParam) {return;}
|
|
|
|
|
|
|
|
|
|
// Clear the send param from the URL immediately
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
|
|
|
|
|
// Show the main chat (clear any active file/content)
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
|
|
|
|
|
// Give ChatPanel a frame to mount, then send the message
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
void chatRef.current?.sendNewMessage(sendParam);
|
|
|
|
|
});
|
|
|
|
|
}, [searchParams, router]);
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
const handleBreadcrumbNavigate = useCallback(
|
|
|
|
|
(path: string) => {
|
|
|
|
|
if (!path) {
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-13 19:24:24 -08:00
|
|
|
|
|
|
|
|
// Absolute paths (browse mode): navigate the sidebar directly.
|
|
|
|
|
// Intermediate parent folders aren't in the browse-mode tree, so
|
|
|
|
|
// resolveNode would fail — call setBrowseDir to update the sidebar.
|
|
|
|
|
if (isAbsolutePath(path)) {
|
|
|
|
|
const name = path.split("/").pop() || path;
|
|
|
|
|
setBrowseDir(path);
|
|
|
|
|
setActivePath(path);
|
|
|
|
|
setContent({ kind: "directory", node: { name, path, type: "folder" } });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Relative paths (workspace mode): resolve and navigate via handleNodeSelect
|
|
|
|
|
// so virtual paths, chat context, etc. are all handled properly.
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
const node = resolveNode(tree, path);
|
2026-02-11 16:45:07 -08:00
|
|
|
if (node) {
|
2026-02-13 19:24:24 -08:00
|
|
|
handleNodeSelect(node);
|
2026-02-11 16:45:07 -08:00
|
|
|
}
|
|
|
|
|
},
|
2026-02-13 19:24:24 -08:00
|
|
|
[tree, handleNodeSelect, setBrowseDir],
|
2026-02-11 16:45:07 -08:00
|
|
|
);
|
|
|
|
|
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
// Navigate to an object by name (used by relation links)
|
|
|
|
|
const handleNavigateToObject = useCallback(
|
|
|
|
|
(objectName: string) => {
|
|
|
|
|
function findObjectNode(nodes: TreeNode[]): TreeNode | null {
|
|
|
|
|
for (const node of nodes) {
|
|
|
|
|
if (node.type === "object" && objectNameFromPath(node.path) === objectName) {
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
if (node.children) {
|
|
|
|
|
const found = findObjectNode(node.children);
|
|
|
|
|
if (found) {return found;}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const node = findObjectNode(tree);
|
2026-02-13 15:52:13 -08:00
|
|
|
if (node) {void loadContent(node);}
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
},
|
|
|
|
|
[tree, loadContent],
|
|
|
|
|
);
|
|
|
|
|
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
/**
|
|
|
|
|
* Unified navigate handler for links in the editor and read mode.
|
|
|
|
|
* Handles /workspace?entry=..., /workspace?path=..., and legacy relative paths.
|
|
|
|
|
*/
|
|
|
|
|
const handleEditorNavigate = useCallback(
|
|
|
|
|
(href: string) => {
|
|
|
|
|
// Try parsing as a workspace URL first (/workspace?entry=... or /workspace?path=...)
|
|
|
|
|
const parsed = parseWorkspaceLink(href);
|
|
|
|
|
if (parsed) {
|
|
|
|
|
if (parsed.kind === "entry") {
|
|
|
|
|
handleOpenEntry(parsed.objectName, parsed.entryId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// File/object link -- resolve using the path from the URL
|
|
|
|
|
const node = resolveNode(tree, parsed.path);
|
|
|
|
|
if (node) {
|
|
|
|
|
handleNodeSelect(node);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fallback: treat as a raw relative path (legacy links)
|
|
|
|
|
const node = resolveNode(tree, href);
|
|
|
|
|
if (node) {
|
|
|
|
|
handleNodeSelect(node);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[tree, handleNodeSelect, handleOpenEntry],
|
|
|
|
|
);
|
|
|
|
|
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
// Refresh the currently displayed object (e.g. after changing display field)
|
|
|
|
|
const refreshCurrentObject = useCallback(async () => {
|
|
|
|
|
if (content.kind !== "object") {return;}
|
|
|
|
|
const name = content.data.object.name;
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(`/api/workspace/objects/${encodeURIComponent(name)}`);
|
|
|
|
|
if (!res.ok) {return;}
|
|
|
|
|
const data: ObjectData = await res.json();
|
|
|
|
|
setContent({ kind: "object", data });
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
}, [content]);
|
|
|
|
|
|
2026-02-17 00:36:01 -08:00
|
|
|
// Auto-refresh the current object view when the workspace tree updates.
|
|
|
|
|
// The SSE watcher triggers tree refreshes on any file change (including
|
|
|
|
|
// .object.yaml edits by the AI agent). We track the tree reference and
|
|
|
|
|
// re-fetch the object data so saved views/filters update live.
|
|
|
|
|
const prevTreeRef = useRef(tree);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (prevTreeRef.current === tree) {return;}
|
|
|
|
|
prevTreeRef.current = tree;
|
|
|
|
|
if (content.kind === "object") {
|
|
|
|
|
void refreshCurrentObject();
|
|
|
|
|
}
|
|
|
|
|
}, [tree, content.kind, refreshCurrentObject]);
|
|
|
|
|
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
// Top-level safety net: catch workspace link clicks anywhere in the page
|
|
|
|
|
// to prevent full-page navigation and handle via client-side state instead.
|
|
|
|
|
const handleContainerClick = useCallback(
|
|
|
|
|
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
|
|
|
const target = event.target as HTMLElement;
|
|
|
|
|
const link = target.closest("a");
|
|
|
|
|
if (!link) {return;}
|
|
|
|
|
const href = link.getAttribute("href");
|
|
|
|
|
if (!href) {return;}
|
|
|
|
|
// Intercept /workspace?... links to handle them in-app
|
|
|
|
|
if (isWorkspaceLink(href)) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.stopPropagation();
|
|
|
|
|
handleEditorNavigate(href);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[handleEditorNavigate],
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
// Cron navigation handlers
|
|
|
|
|
const handleSelectCronJob = useCallback((jobId: string) => {
|
|
|
|
|
const job = cronJobs.find((j) => j.id === jobId);
|
|
|
|
|
if (job) {
|
|
|
|
|
setActivePath(`~cron/${jobId}`);
|
|
|
|
|
setContent({ kind: "cron-job", jobId, job });
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
}
|
|
|
|
|
}, [cronJobs, router]);
|
|
|
|
|
|
|
|
|
|
const handleBackToCronDashboard = useCallback(() => {
|
|
|
|
|
setActivePath("~cron");
|
|
|
|
|
setContent({ kind: "cron-dashboard" });
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
}, [router]);
|
|
|
|
|
|
2026-02-13 19:24:24 -08:00
|
|
|
// Derive the active session's title for the header / right sidebar
|
|
|
|
|
const activeSessionTitle = useMemo(() => {
|
|
|
|
|
if (!activeSessionId) {return undefined;}
|
2026-02-17 00:38:09 -08:00
|
|
|
const s = sessions.find((sess) => sess.id === activeSessionId);
|
2026-02-13 19:24:24 -08:00
|
|
|
return s?.title || undefined;
|
|
|
|
|
}, [activeSessionId, sessions]);
|
|
|
|
|
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
// Whether to show the main ChatPanel (no file/content selected)
|
|
|
|
|
const showMainChat = !activePath || content.kind === "none";
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
return (
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
2026-02-19 15:42:23 -08:00
|
|
|
<div
|
|
|
|
|
ref={layoutRef}
|
|
|
|
|
className="flex h-screen"
|
2026-02-19 21:58:10 -08:00
|
|
|
style={{ background: "var(--color-main-bg)" }}
|
2026-02-19 15:42:23 -08:00
|
|
|
onClick={handleContainerClick}
|
|
|
|
|
>
|
|
|
|
|
{/* Left sidebar — static on desktop (resizable), drawer overlay on mobile */}
|
2026-02-16 00:30:13 -08:00
|
|
|
{isMobile ? (
|
|
|
|
|
sidebarOpen && (
|
|
|
|
|
<WorkspaceSidebar
|
|
|
|
|
tree={enhancedTree}
|
|
|
|
|
activePath={activePath}
|
|
|
|
|
onSelect={(node) => { handleNodeSelect(node); setSidebarOpen(false); }}
|
|
|
|
|
onRefresh={refreshTree}
|
|
|
|
|
orgName={context?.organization?.name}
|
|
|
|
|
loading={treeLoading}
|
|
|
|
|
browseDir={browseDir}
|
|
|
|
|
parentDir={effectiveParentDir}
|
|
|
|
|
onNavigateUp={handleNavigateUp}
|
|
|
|
|
onGoHome={handleGoHome}
|
|
|
|
|
onFileSearchSelect={(item) => { handleFileSearchSelect?.(item); setSidebarOpen(false); }}
|
|
|
|
|
workspaceRoot={workspaceRoot}
|
|
|
|
|
onGoToChat={() => { handleGoToChat(); setSidebarOpen(false); }}
|
|
|
|
|
onExternalDrop={handleSidebarExternalDrop}
|
2026-02-19 14:59:34 -08:00
|
|
|
activeProfile={activeProfile}
|
|
|
|
|
onProfileSwitch={handleProfileSwitch}
|
2026-02-21 14:39:55 -08:00
|
|
|
showHidden={showHidden}
|
|
|
|
|
onToggleHidden={() => setShowHidden((v) => !v)}
|
2026-02-16 00:30:13 -08:00
|
|
|
mobile
|
|
|
|
|
onClose={() => setSidebarOpen(false)}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
) : (
|
2026-02-19 15:42:23 -08:00
|
|
|
<>
|
2026-02-19 23:15:39 -08:00
|
|
|
{!leftSidebarCollapsed && (
|
2026-02-19 16:50:52 -08:00
|
|
|
<div
|
2026-02-19 21:58:10 -08:00
|
|
|
className="flex shrink-0 flex-col relative"
|
2026-02-19 16:50:52 -08:00
|
|
|
style={{ width: leftSidebarWidth, minWidth: leftSidebarWidth }}
|
|
|
|
|
>
|
2026-02-19 21:58:10 -08:00
|
|
|
<ResizeHandle
|
|
|
|
|
mode="left"
|
|
|
|
|
containerRef={layoutRef}
|
|
|
|
|
min={LEFT_SIDEBAR_MIN}
|
|
|
|
|
max={LEFT_SIDEBAR_MAX}
|
|
|
|
|
onResize={setLeftSidebarWidth}
|
|
|
|
|
/>
|
2026-02-19 15:42:23 -08:00
|
|
|
<WorkspaceSidebar
|
|
|
|
|
tree={enhancedTree}
|
|
|
|
|
activePath={activePath}
|
|
|
|
|
onSelect={handleNodeSelect}
|
|
|
|
|
onRefresh={refreshTree}
|
|
|
|
|
orgName={context?.organization?.name}
|
|
|
|
|
loading={treeLoading}
|
|
|
|
|
browseDir={browseDir}
|
|
|
|
|
parentDir={effectiveParentDir}
|
|
|
|
|
onNavigateUp={handleNavigateUp}
|
|
|
|
|
onGoHome={handleGoHome}
|
|
|
|
|
onFileSearchSelect={handleFileSearchSelect}
|
|
|
|
|
workspaceRoot={workspaceRoot}
|
|
|
|
|
onGoToChat={handleGoToChat}
|
|
|
|
|
onExternalDrop={handleSidebarExternalDrop}
|
|
|
|
|
activeProfile={activeProfile}
|
|
|
|
|
onProfileSwitch={handleProfileSwitch}
|
2026-02-21 14:39:55 -08:00
|
|
|
showHidden={showHidden}
|
|
|
|
|
onToggleHidden={() => setShowHidden((v) => !v)}
|
2026-02-19 15:42:23 -08:00
|
|
|
width={leftSidebarWidth}
|
2026-02-19 23:15:39 -08:00
|
|
|
onCollapse={() => setLeftSidebarCollapsed(true)}
|
2026-02-19 15:42:23 -08:00
|
|
|
/>
|
|
|
|
|
</div>
|
2026-02-19 23:15:39 -08:00
|
|
|
)}
|
2026-02-19 15:42:23 -08:00
|
|
|
</>
|
2026-02-16 00:30:13 -08:00
|
|
|
)}
|
2026-02-11 16:45:07 -08:00
|
|
|
|
2026-02-19 23:15:39 -08:00
|
|
|
{/* Expand left sidebar button (shown when collapsed) */}
|
|
|
|
|
{!isMobile && leftSidebarCollapsed && (
|
|
|
|
|
<div className="shrink-0 flex flex-col items-center pt-2.5 px-1.5">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setLeftSidebarCollapsed(false)}
|
|
|
|
|
className="p-1.5 rounded-md transition-colors hover:bg-black/5"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Show sidebar (⌘B)"
|
|
|
|
|
>
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
|
|
|
<path d="M9 3v18" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
{/* Main content */}
|
2026-02-19 21:58:10 -08:00
|
|
|
<main className="flex-1 flex flex-col min-w-0 overflow-hidden" style={{ background: "var(--color-main-bg)" }}>
|
2026-02-16 00:30:13 -08:00
|
|
|
{/* Mobile top bar — always visible on mobile */}
|
|
|
|
|
{isMobile && (
|
|
|
|
|
<div
|
|
|
|
|
className="px-3 py-2 border-b flex-shrink-0 flex items-center justify-between gap-2"
|
|
|
|
|
style={{ borderColor: "var(--color-border)", background: "var(--color-surface)" }}
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setSidebarOpen(true)}
|
|
|
|
|
className="p-2 rounded-lg flex-shrink-0"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Open sidebar"
|
|
|
|
|
>
|
|
|
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<line x1="4" x2="20" y1="12" y2="12" /><line x1="4" x2="20" y1="6" y2="6" /><line x1="4" x2="20" y1="18" y2="18" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
<div className="flex-1 min-w-0 text-sm font-medium truncate" style={{ color: "var(--color-text)" }}>
|
|
|
|
|
{activePath ? activePath.split("/").pop() : (context?.organization?.name || "Workspace")}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
{activePath && content.kind !== "none" && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
}}
|
|
|
|
|
className="p-2 rounded-lg flex-shrink-0"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Back to chat"
|
|
|
|
|
>
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="m12 19-7-7 7-7" /><path d="M19 12H5" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
{showMainChat && (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setChatSessionsOpen(true)}
|
|
|
|
|
className="p-2 rounded-lg flex-shrink-0"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Chat sessions"
|
|
|
|
|
>
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{/* When a file is selected: show top bar with breadcrumbs (desktop only, mobile has unified top bar) */}
|
|
|
|
|
{!isMobile && activePath && content.kind !== "none" && (
|
2026-02-11 16:45:07 -08:00
|
|
|
<div
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
className="px-6 border-b flex-shrink-0 flex items-center justify-between"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{ borderColor: "var(--color-border)" }}
|
|
|
|
|
>
|
|
|
|
|
<Breadcrumbs
|
|
|
|
|
path={activePath}
|
|
|
|
|
onNavigate={handleBreadcrumbNavigate}
|
|
|
|
|
/>
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
<div className="flex items-center gap-1">
|
|
|
|
|
{/* Back to chat button */}
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setActivePath(null);
|
|
|
|
|
setContent({ kind: "none" });
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
}}
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="p-1.5 rounded-lg flex-shrink-0"
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Back to chat"
|
|
|
|
|
>
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="m12 19-7-7 7-7" /><path d="M19 12H5" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
2026-02-13 18:57:15 -08:00
|
|
|
{/* Chat sidebar toggle (hidden for reserved/virtual paths) */}
|
|
|
|
|
{fileContext && (
|
2026-02-12 13:37:40 -08:00
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setShowChatSidebar((v) => !v)}
|
|
|
|
|
className="p-1.5 rounded-lg flex-shrink-0"
|
|
|
|
|
style={{
|
|
|
|
|
color: showChatSidebar ? "var(--color-accent)" : "var(--color-text-muted)",
|
|
|
|
|
background: showChatSidebar ? "var(--color-accent-light)" : "transparent",
|
|
|
|
|
}}
|
2026-02-13 18:57:15 -08:00
|
|
|
title={showChatSidebar ? "Hide chat" : fileContext.isDirectory ? "Chat about this folder" : "Chat about this file"}
|
2026-02-12 13:37:40 -08:00
|
|
|
>
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
</div>
|
2026-02-11 16:45:07 -08:00
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
{/* Content area */}
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
<div className="flex-1 flex min-h-0">
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
{showMainChat ? (
|
|
|
|
|
/* Main chat view (default when no file is selected) */
|
2026-02-13 19:24:24 -08:00
|
|
|
<>
|
2026-02-19 21:58:10 -08:00
|
|
|
<div className="flex-1 flex flex-col min-w-0" style={{ background: "var(--color-main-bg)" }}>
|
2026-02-19 14:59:34 -08:00
|
|
|
{activeSubagent ? (
|
|
|
|
|
<SubagentPanel
|
|
|
|
|
sessionKey={activeSubagent.childSessionKey}
|
|
|
|
|
task={activeSubagent.task}
|
|
|
|
|
label={activeSubagent.label}
|
|
|
|
|
onBack={handleBackFromSubagent}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
2026-02-13 19:24:24 -08:00
|
|
|
<ChatPanel
|
|
|
|
|
ref={chatRef}
|
|
|
|
|
sessionTitle={activeSessionTitle}
|
2026-02-15 23:42:13 -08:00
|
|
|
initialSessionId={activeSessionId ?? undefined}
|
2026-02-13 19:24:24 -08:00
|
|
|
onActiveSessionChange={(id) => {
|
|
|
|
|
setActiveSessionId(id);
|
2026-02-19 14:59:34 -08:00
|
|
|
setActiveSubagentKey(null);
|
2026-02-13 19:24:24 -08:00
|
|
|
}}
|
|
|
|
|
onSessionsChange={refreshSessions}
|
2026-02-19 14:59:34 -08:00
|
|
|
onSubagentSpawned={handleSubagentSpawned}
|
|
|
|
|
onSubagentClick={handleSubagentClickFromChat}
|
2026-02-19 17:46:54 -08:00
|
|
|
onFilePathClick={handleFilePathClickFromChat}
|
2026-02-19 16:50:52 -08:00
|
|
|
onDeleteSession={handleDeleteSession}
|
2026-02-19 22:54:23 -08:00
|
|
|
onRenameSession={handleRenameSession}
|
2026-02-16 00:30:13 -08:00
|
|
|
compact={isMobile}
|
2026-02-13 19:24:24 -08:00
|
|
|
/>
|
2026-02-19 14:59:34 -08:00
|
|
|
)}
|
2026-02-13 19:24:24 -08:00
|
|
|
</div>
|
2026-02-16 00:30:13 -08:00
|
|
|
{/* Chat sessions sidebar — static on desktop, drawer overlay on mobile */}
|
|
|
|
|
{isMobile ? (
|
|
|
|
|
chatSessionsOpen && (
|
|
|
|
|
<ChatSessionsSidebar
|
|
|
|
|
sessions={sessions}
|
|
|
|
|
activeSessionId={activeSessionId}
|
|
|
|
|
activeSessionTitle={activeSessionTitle}
|
|
|
|
|
streamingSessionIds={streamingSessionIds}
|
2026-02-19 14:59:34 -08:00
|
|
|
subagents={subagents}
|
|
|
|
|
activeSubagentKey={activeSubagentKey}
|
2026-02-19 16:50:52 -08:00
|
|
|
loading={sessionsLoading}
|
2026-02-16 00:30:13 -08:00
|
|
|
onSelectSession={(sessionId) => {
|
|
|
|
|
setActiveSessionId(sessionId);
|
2026-02-19 14:59:34 -08:00
|
|
|
setActiveSubagentKey(null);
|
2026-02-16 00:30:13 -08:00
|
|
|
void chatRef.current?.loadSession(sessionId);
|
|
|
|
|
}}
|
|
|
|
|
onNewSession={() => {
|
|
|
|
|
setActiveSessionId(null);
|
2026-02-19 14:59:34 -08:00
|
|
|
setActiveSubagentKey(null);
|
2026-02-16 00:30:13 -08:00
|
|
|
void chatRef.current?.newSession();
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
setChatSessionsOpen(false);
|
|
|
|
|
}}
|
2026-02-19 14:59:34 -08:00
|
|
|
onSelectSubagent={handleSelectSubagent}
|
2026-02-19 16:50:52 -08:00
|
|
|
onDeleteSession={handleDeleteSession}
|
2026-02-19 22:54:23 -08:00
|
|
|
onRenameSession={handleRenameSession}
|
2026-02-16 00:30:13 -08:00
|
|
|
mobile
|
|
|
|
|
onClose={() => setChatSessionsOpen(false)}
|
|
|
|
|
/>
|
|
|
|
|
)
|
|
|
|
|
) : (
|
2026-02-19 15:42:23 -08:00
|
|
|
<>
|
2026-02-19 23:15:39 -08:00
|
|
|
{!rightSidebarCollapsed && (
|
2026-02-19 16:50:52 -08:00
|
|
|
<div
|
2026-02-19 21:58:10 -08:00
|
|
|
className="flex shrink-0 flex-col relative"
|
|
|
|
|
style={{ width: rightSidebarWidth, minWidth: rightSidebarWidth, background: "var(--color-sidebar-bg)" }}
|
2026-02-19 16:50:52 -08:00
|
|
|
>
|
2026-02-19 21:58:10 -08:00
|
|
|
<ResizeHandle
|
|
|
|
|
mode="right"
|
|
|
|
|
containerRef={layoutRef}
|
|
|
|
|
min={RIGHT_SIDEBAR_MIN}
|
|
|
|
|
max={RIGHT_SIDEBAR_MAX}
|
|
|
|
|
onResize={setRightSidebarWidth}
|
|
|
|
|
/>
|
2026-02-19 17:46:54 -08:00
|
|
|
{chatSidebarPreview ? (
|
|
|
|
|
<ChatSidebarPreview
|
|
|
|
|
preview={chatSidebarPreview}
|
|
|
|
|
onClose={() => setChatSidebarPreview(null)}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<ChatSessionsSidebar
|
|
|
|
|
sessions={sessions}
|
|
|
|
|
activeSessionId={activeSessionId}
|
|
|
|
|
activeSessionTitle={activeSessionTitle}
|
|
|
|
|
streamingSessionIds={streamingSessionIds}
|
|
|
|
|
subagents={subagents}
|
|
|
|
|
activeSubagentKey={activeSubagentKey}
|
|
|
|
|
loading={sessionsLoading}
|
|
|
|
|
onSelectSession={(sessionId) => {
|
|
|
|
|
setActiveSessionId(sessionId);
|
|
|
|
|
setActiveSubagentKey(null);
|
|
|
|
|
void chatRef.current?.loadSession(sessionId);
|
|
|
|
|
}}
|
|
|
|
|
onNewSession={() => {
|
|
|
|
|
setActiveSessionId(null);
|
|
|
|
|
setActiveSubagentKey(null);
|
|
|
|
|
void chatRef.current?.newSession();
|
|
|
|
|
router.replace("/workspace", { scroll: false });
|
|
|
|
|
}}
|
|
|
|
|
onSelectSubagent={handleSelectSubagent}
|
|
|
|
|
onDeleteSession={handleDeleteSession}
|
2026-02-19 22:54:23 -08:00
|
|
|
onRenameSession={handleRenameSession}
|
2026-02-19 23:15:39 -08:00
|
|
|
onCollapse={() => setRightSidebarCollapsed(true)}
|
2026-02-19 17:46:54 -08:00
|
|
|
width={rightSidebarWidth}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2026-02-19 15:42:23 -08:00
|
|
|
</div>
|
2026-02-19 23:15:39 -08:00
|
|
|
)}
|
|
|
|
|
{rightSidebarCollapsed && (
|
|
|
|
|
<div className="shrink-0 flex flex-col items-center pt-2.5 px-1.5">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => setRightSidebarCollapsed(false)}
|
|
|
|
|
className="p-1.5 rounded-md transition-colors hover:bg-black/5"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Show chat sidebar (⌘⇧B)"
|
|
|
|
|
>
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<rect width="18" height="18" x="3" y="3" rx="2" />
|
|
|
|
|
<path d="M15 3v18" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2026-02-19 15:42:23 -08:00
|
|
|
</>
|
2026-02-16 00:30:13 -08:00
|
|
|
)}
|
2026-02-13 19:24:24 -08:00
|
|
|
</>
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
{/* File content area */}
|
|
|
|
|
<div className="flex-1 overflow-y-auto">
|
|
|
|
|
<ContentRenderer
|
|
|
|
|
content={content}
|
|
|
|
|
workspaceExists={workspaceExists}
|
|
|
|
|
tree={tree}
|
|
|
|
|
activePath={activePath}
|
2026-02-13 18:57:15 -08:00
|
|
|
browseDir={browseDir}
|
|
|
|
|
treeLoading={treeLoading}
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
members={context?.members}
|
|
|
|
|
onNodeSelect={handleNodeSelect}
|
|
|
|
|
onNavigateToObject={handleNavigateToObject}
|
|
|
|
|
onRefreshObject={refreshCurrentObject}
|
|
|
|
|
onRefreshTree={refreshTree}
|
|
|
|
|
onNavigate={handleEditorNavigate}
|
|
|
|
|
onOpenEntry={handleOpenEntry}
|
|
|
|
|
searchFn={searchIndex}
|
2026-02-12 13:37:40 -08:00
|
|
|
onSelectCronJob={handleSelectCronJob}
|
|
|
|
|
onBackToCronDashboard={handleBackToCronDashboard}
|
2026-02-19 14:59:34 -08:00
|
|
|
onWorkspaceCreated={handleProfileSwitch}
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-16 00:30:13 -08:00
|
|
|
{/* Chat sidebar (file/folder-scoped) — hidden for reserved paths, hidden on mobile */}
|
2026-02-19 23:15:39 -08:00
|
|
|
{!isMobile && fileContext && showChatSidebar && !rightSidebarCollapsed && (
|
2026-02-19 15:42:23 -08:00
|
|
|
<>
|
|
|
|
|
<aside
|
2026-02-19 21:58:10 -08:00
|
|
|
className="flex-shrink-0 border-l flex flex-col relative"
|
2026-02-19 15:42:23 -08:00
|
|
|
style={{
|
|
|
|
|
width: rightSidebarWidth,
|
|
|
|
|
borderColor: "var(--color-border)",
|
|
|
|
|
background: "var(--color-bg)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-02-19 21:58:10 -08:00
|
|
|
<ResizeHandle
|
|
|
|
|
mode="right"
|
|
|
|
|
containerRef={layoutRef}
|
|
|
|
|
min={RIGHT_SIDEBAR_MIN}
|
|
|
|
|
max={RIGHT_SIDEBAR_MAX}
|
|
|
|
|
onResize={setRightSidebarWidth}
|
|
|
|
|
/>
|
2026-02-19 15:42:23 -08:00
|
|
|
<ChatPanel
|
|
|
|
|
ref={compactChatRef}
|
|
|
|
|
compact
|
|
|
|
|
fileContext={fileContext}
|
|
|
|
|
onFileChanged={handleFileChanged}
|
2026-02-19 17:46:54 -08:00
|
|
|
onFilePathClick={handleFilePathClickFromChat}
|
2026-02-19 15:42:23 -08:00
|
|
|
/>
|
|
|
|
|
</aside>
|
|
|
|
|
</>
|
Dench workspace: virtual folders (Skills, Memories, Chats), landing page, and unified workspace layout
Add virtual folder system that surfaces Skills, Memories, and Chat sessions
in the workspace sidebar alongside real dench files. Rearchitect the home
page into a landing hub and move the ChatPanel into the workspace as the
default view.
New API route — virtual-file:
- apps/web/app/api/workspace/virtual-file/route.ts: new GET/POST API that
resolves virtual paths (~skills/*, ~memories/*) to absolute filesystem
paths in ~/.openclaw/skills/ d ~/.openclaw/workspace/. Includes path
traversal protection and directory allowlisting.
Tree API — virtual folder builders:
- apps/web/app/api/workspace/tree/route.ts: add `virtual` field to TreeNode
type. Add ildSkillsVirtualFolder() scanning ~/.openclaw/skills/ and
~/.openclaw/workspace/skills/ with SKILL.md frontmatter parsing (name +
emoji). Add buildMemoriesVirtualFolder() scanning MEMORY.md and daily
logs from ~/.openclaw/workspace/memory/. Virtual folders are appended
after real workspace entries and are also returned when no dench root
exists.
File manager tree — virtual node awareness:
- apps/web/app/components/workspace/file-manager-tree.tsx: add isVirtualNode()
helper and RESERVED_FOLDER_NAMES set (Chats, Skills, Memories). Virtual
nodes show a lock badge, disable drag-and-drop, block rename/delete, and
reject reserved names during create/rename. Add ChatBubbleIcon for ~chats/
paths.
Markdown editor — virtual path routing:
- apps/web/app/components/workspace/markdown-editor.tsx: save to
/api/workspace/virtual-file for paths starting with ~ instead of the
regular /api/workspace/file endpoint.
Home page redesign:
- apps/web/app/page.tsx: replace the chat-first layout (SidebarhatPanel)
with a branded landing page showing the OpenClaw Dench heading, tagline,
and a single "Open Workspace" CTA linking to /workspace.
Workspace page — unified layout with integrated chat:
- apps/web/app/workspace/page.tsx: ChatPanel is now the default main view
when no file is selected. Add session fetching from /api/web-sessions and
build an enhanced tree with a virtual "Chats" folder listing all sessions.
Clicking ~chats/<id> loads the session; clicking ~chats starts a new one.
Add isVirtualPath()/fileApiUrl() helpers for virtual file reads. Add a
"Back to chat" button in the top bar alongside the chat sidebar toggle.
Sidebar + empty-state cosmetic updates:
- apps/web/app/components/workspace/workspace-sidebar.tsx: rename BackIcon
to HomeIcon (house SVG), change label from "Back to Chat" to "Home".
- apps/web/app/components/workspace/empty-state.tsx: update link text from
"Back to Chat" to "Back to Home".
2026-02-11 22:09:59 -08:00
|
|
|
)}
|
|
|
|
|
</>
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
)}
|
2026-02-11 16:45:07 -08:00
|
|
|
</div>
|
|
|
|
|
</main>
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
|
|
|
|
|
{/* Entry detail modal (rendered on top of everything) */}
|
|
|
|
|
{entryModal && (
|
|
|
|
|
<EntryDetailModal
|
|
|
|
|
objectName={entryModal.objectName}
|
|
|
|
|
entryId={entryModal.entryId}
|
|
|
|
|
members={context?.members}
|
|
|
|
|
onClose={handleCloseEntry}
|
|
|
|
|
onNavigateEntry={(objName, eid) => handleOpenEntry(objName, eid)}
|
|
|
|
|
onNavigateObject={(objName) => {
|
|
|
|
|
handleCloseEntry();
|
|
|
|
|
handleNavigateToObject(objName);
|
|
|
|
|
}}
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
onRefresh={refreshCurrentObject}
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
/>
|
|
|
|
|
)}
|
2026-02-11 16:45:07 -08:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-19 17:46:54 -08:00
|
|
|
function previewFileTypeBadge(filename: string): { label: string; color: string } {
|
|
|
|
|
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
|
|
|
if (ext === "pdf") {return { label: "PDF", color: "#ef4444" };}
|
|
|
|
|
if (["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "heic", "avif"].includes(ext)) {return { label: "Image", color: "#3b82f6" };}
|
|
|
|
|
if (["mp4", "webm", "mov", "avi", "mkv"].includes(ext)) {return { label: "Video", color: "#8b5cf6" };}
|
|
|
|
|
if (["mp3", "wav", "ogg", "m4a", "aac", "flac"].includes(ext)) {return { label: "Audio", color: "#f59e0b" };}
|
|
|
|
|
if (["md", "mdx"].includes(ext)) {return { label: "Markdown", color: "#10b981" };}
|
|
|
|
|
if (["ts", "tsx", "js", "jsx", "py", "go", "rs", "java", "rb", "swift", "kt", "c", "cpp", "h"].includes(ext)) {return { label: ext.toUpperCase(), color: "#3b82f6" };}
|
|
|
|
|
if (["json", "yaml", "yml", "toml", "xml", "csv"].includes(ext)) {return { label: ext.toUpperCase(), color: "#6b7280" };}
|
|
|
|
|
if (["duckdb", "sqlite", "sqlite3", "db"].includes(ext)) {return { label: "Database", color: "#6366f1" };}
|
|
|
|
|
return { label: ext.toUpperCase() || "File", color: "#6b7280" };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function shortenPreviewPath(p: string): string {
|
|
|
|
|
return p.replace(/^\/Users\/[^/]+/, "~").replace(/^\/home\/[^/]+/, "~");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ChatSidebarPreview({
|
|
|
|
|
preview,
|
|
|
|
|
onClose,
|
|
|
|
|
}: {
|
|
|
|
|
preview: ChatSidebarPreviewState;
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
}) {
|
|
|
|
|
const badge = previewFileTypeBadge(preview.filename);
|
|
|
|
|
|
|
|
|
|
const openInFinder = useCallback(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await fetch("/api/workspace/open-file", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ path: preview.path, reveal: true }),
|
|
|
|
|
});
|
|
|
|
|
} catch { /* ignore */ }
|
|
|
|
|
}, [preview.path]);
|
|
|
|
|
|
|
|
|
|
const openWithSystem = useCallback(async () => {
|
|
|
|
|
try {
|
|
|
|
|
await fetch("/api/workspace/open-file", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ path: preview.path }),
|
|
|
|
|
});
|
|
|
|
|
} catch { /* ignore */ }
|
|
|
|
|
}, [preview.path]);
|
|
|
|
|
|
|
|
|
|
const downloadUrl = preview.status === "ready" && preview.content.kind === "media"
|
|
|
|
|
? preview.content.url
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
let body: React.ReactNode;
|
|
|
|
|
|
|
|
|
|
if (preview.status === "loading") {
|
|
|
|
|
body = (
|
|
|
|
|
<div className="flex flex-col h-full items-center justify-center gap-3">
|
|
|
|
|
<UnicodeSpinner
|
|
|
|
|
name="braille"
|
|
|
|
|
className="text-2xl"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
/>
|
|
|
|
|
<p className="text-xs" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
Loading preview...
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else if (preview.status === "error") {
|
|
|
|
|
body = (
|
|
|
|
|
<div className="flex flex-col h-full items-center justify-center gap-4 px-6">
|
|
|
|
|
<div
|
|
|
|
|
className="w-14 h-14 rounded-2xl flex items-center justify-center"
|
|
|
|
|
style={{ background: "color-mix(in srgb, var(--color-error) 10%, transparent)" }}
|
|
|
|
|
>
|
|
|
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-error)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<circle cx="12" cy="12" r="10" />
|
|
|
|
|
<line x1="15" x2="9" y1="9" y2="15" />
|
|
|
|
|
<line x1="9" x2="15" y1="9" y2="15" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<p className="text-sm font-medium" style={{ color: "var(--color-text)" }}>
|
|
|
|
|
Preview unavailable
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-xs mt-1" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
{preview.message}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else {
|
|
|
|
|
const c = preview.content;
|
|
|
|
|
switch (c.kind) {
|
|
|
|
|
case "media":
|
|
|
|
|
if (c.mediaType === "pdf") {
|
|
|
|
|
// Hide the browser's built-in PDF toolbar for a cleaner look
|
|
|
|
|
const pdfUrl = c.url + (c.url.includes("#") ? "&" : "#") + "toolbar=0&navpanes=0&scrollbar=1";
|
|
|
|
|
body = (
|
|
|
|
|
<iframe
|
|
|
|
|
src={pdfUrl}
|
|
|
|
|
className="w-full h-full"
|
|
|
|
|
style={{ border: "none", colorScheme: "light" }}
|
|
|
|
|
title={`Preview: ${c.filename}`}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
} else if (c.mediaType === "image") {
|
|
|
|
|
body = (
|
|
|
|
|
<div className="flex items-center justify-center h-full p-4 overflow-auto" style={{ background: "var(--color-bg)" }}>
|
|
|
|
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
|
|
|
|
<img
|
|
|
|
|
src={c.url}
|
|
|
|
|
alt={c.filename}
|
|
|
|
|
className="max-w-full max-h-full object-contain rounded-lg"
|
|
|
|
|
style={{ boxShadow: "0 2px 16px rgba(0,0,0,0.08)" }}
|
|
|
|
|
draggable={false}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else if (c.mediaType === "video") {
|
|
|
|
|
body = (
|
|
|
|
|
<div className="flex items-center justify-center h-full p-4" style={{ background: "#000" }}>
|
|
|
|
|
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
|
|
|
|
<video src={c.url} controls className="max-w-full max-h-full rounded-lg" />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
} else if (c.mediaType === "audio") {
|
|
|
|
|
body = (
|
|
|
|
|
<div className="flex flex-col items-center justify-center h-full gap-6 px-6">
|
|
|
|
|
<div
|
|
|
|
|
className="w-20 h-20 rounded-2xl flex items-center justify-center"
|
|
|
|
|
style={{ background: "linear-gradient(135deg, #f59e0b20, #f59e0b08)" }}
|
|
|
|
|
>
|
|
|
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M9 18V5l12-2v13" /><circle cx="6" cy="18" r="3" /><circle cx="18" cy="16" r="3" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
|
|
|
|
<audio src={c.url} controls className="w-full" />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case "document":
|
|
|
|
|
body = (
|
|
|
|
|
<div className="p-5 overflow-auto h-full">
|
|
|
|
|
<div className="workspace-prose text-sm">
|
|
|
|
|
<DocumentView
|
|
|
|
|
content={c.data.content}
|
|
|
|
|
title={c.title}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case "code":
|
|
|
|
|
body = (
|
|
|
|
|
<div className="overflow-auto h-full">
|
|
|
|
|
<CodeViewer content={c.data.content} filename={c.filename} />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case "file":
|
|
|
|
|
body = (
|
|
|
|
|
<div className="overflow-auto h-full">
|
|
|
|
|
<FileViewer content={c.data.content} filename={c.filename} type={c.data.type === "yaml" ? "yaml" : "text"} />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case "database":
|
|
|
|
|
body = (
|
|
|
|
|
<div className="overflow-auto h-full">
|
|
|
|
|
<DatabaseViewer dbPath={c.dbPath} filename={c.filename} />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
case "directory":
|
|
|
|
|
body = (
|
|
|
|
|
<div className="flex flex-col items-center justify-center h-full gap-4 px-6">
|
|
|
|
|
<div
|
|
|
|
|
className="w-14 h-14 rounded-2xl flex items-center justify-center"
|
|
|
|
|
style={{ background: "color-mix(in srgb, var(--color-accent) 10%, transparent)" }}
|
|
|
|
|
>
|
|
|
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-sm font-medium" style={{ color: "var(--color-text)" }}>
|
|
|
|
|
{c.name}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
body = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<aside
|
|
|
|
|
className="h-full border-l flex flex-col"
|
|
|
|
|
style={{
|
|
|
|
|
borderColor: "var(--color-border)",
|
|
|
|
|
background: "var(--color-bg)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{/* Header: close + filename + badge + actions */}
|
|
|
|
|
<div
|
|
|
|
|
className="px-3 py-2.5 flex items-center gap-2 flex-shrink-0"
|
|
|
|
|
style={{ borderBottom: "1px solid var(--color-border)" }}
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={onClose}
|
|
|
|
|
className="p-1 rounded-md transition-colors flex-shrink-0"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Close preview"
|
|
|
|
|
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.background = "var(--color-surface-hover)"; }}
|
|
|
|
|
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = "transparent"; }}
|
|
|
|
|
>
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M18 6 6 18" /><path d="m6 6 12 12" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<span className="text-[13px] font-medium truncate min-w-0" style={{ color: "var(--color-text)" }}>
|
|
|
|
|
{preview.filename}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
className="text-[10px] font-medium px-1.5 py-[1px] rounded flex-shrink-0"
|
|
|
|
|
style={{
|
|
|
|
|
background: `${badge.color}14`,
|
|
|
|
|
color: badge.color,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{badge.label}
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-0.5 ml-auto flex-shrink-0">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={openWithSystem}
|
|
|
|
|
className="p-1.5 rounded-md transition-colors"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Open with default app"
|
|
|
|
|
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.background = "var(--color-surface-hover)"; }}
|
|
|
|
|
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = "transparent"; }}
|
|
|
|
|
>
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M15 3h6v6" /><path d="M10 14 21 3" /><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
{downloadUrl && (
|
|
|
|
|
<a
|
|
|
|
|
href={downloadUrl}
|
|
|
|
|
download={preview.filename}
|
|
|
|
|
className="p-1.5 rounded-md transition-colors"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Download"
|
|
|
|
|
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.background = "var(--color-surface-hover)"; }}
|
|
|
|
|
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = "transparent"; }}
|
|
|
|
|
>
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" x2="12" y1="15" y2="3" />
|
|
|
|
|
</svg>
|
|
|
|
|
</a>
|
|
|
|
|
)}
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={openInFinder}
|
|
|
|
|
className="p-1.5 rounded-md transition-colors"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
title="Reveal in Finder"
|
|
|
|
|
onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.background = "var(--color-surface-hover)"; }}
|
|
|
|
|
onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.background = "transparent"; }}
|
|
|
|
|
>
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Preview body */}
|
|
|
|
|
<div className="flex-1 min-h-0 overflow-hidden">
|
|
|
|
|
{body}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Footer path */}
|
|
|
|
|
<div
|
|
|
|
|
className="px-3 py-1.5 border-t flex-shrink-0"
|
|
|
|
|
style={{ borderColor: "var(--color-border)" }}
|
|
|
|
|
>
|
|
|
|
|
<p
|
|
|
|
|
className="text-[10px] truncate"
|
|
|
|
|
style={{ color: "var(--color-text-muted)", fontFamily: "'SF Mono', 'Fira Code', monospace" }}
|
|
|
|
|
title={preview.path}
|
|
|
|
|
>
|
|
|
|
|
{shortenPreviewPath(preview.path)}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</aside>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
// --- Content Renderer ---
|
|
|
|
|
|
|
|
|
|
function ContentRenderer({
|
|
|
|
|
content,
|
|
|
|
|
workspaceExists,
|
|
|
|
|
tree,
|
Dench workspace: Tiptap markdown editor, subagent sessions, and error surfacing
── Tiptap Markdown Editor ──
- Add full Tiptap-based WYSIWYG markdown editor (markdown-editor.tsx, 709 LOC)
with bubble menu, auto-save (debounced), image drag-and-drop/paste upload,
table editing, task list checkboxes, and frontmatter preservation on save.
- Add slash command system (slash-command.tsx, 607 LOC) with "/" trigger for
block insertion (headings, lists, tables, code blocks, images, reports) and
"@" trigger for file/document mention with fuzzy search across the workspace
tree.
- Add ReportBlockNode (report-block-node.tsx) — custom Tiptap node that renders
embedded report-json blocks as interactive ReportCard widgets inline in the
editor, with expand/collapse and edit-JSON support.
- Add workspace asset serving API (api/workspace/assets/[...path]/route.ts) to
serve images from the workspace with proper MIME types.
- Add workspace file upload orkspace/upload/route.ts) for multipart
image uploads (10 MB limit, image types only), saving to assets/ directory.
- Add ~500 lines of Tiptap editor CSS to globals.css (editor layout, task lists,
images, tables, slash command dropdown, bubble menu toolbar, code blocks, etc.).
- Add 14 @tiptap/* dependencies to apps/web/package.json (react, starter-kit,
markdown, image, link, table, task-list, suggestion, placeholder, etc.).
── Document View: Edit/Read Mode Toggle ──
- document-view.tsx: Add edit/read mode toggle; defaults to edit mode when a
filePath is available. Lazy-loads MarkdownEditor to keep initial bundle light.
- workspace/page.tsx: Pass activePath, tree, onSave, onNavigate, and
onRefreshTree through to DocumentView for full editor integration with
workspace navigation and tree refresh after saves.
── Subagent Session Isolation ──
- agent-runner.ts: Add RunAgentOptions with optional sessionId; when set, spawns
the agent with --session-key agent:main:subagent:<id> ant so
file-scoped sidebar chats run in isolated sessions independent of the main
agent.
- route.ts (chat API): Accept sessionId from request body and forward it to
runAgent. Resolve workspace file path prefixes (resolveAgentWorkspacePrefix)
so tree-relative paths become agent-cwd-relative.
- chat-panel.tsx: Create per-instance DefaultChatTransport that injects sessionId
via body function and a ref (avoids stale closures). On file change, auto-load
the most recent session and its messages. Refresh session tab list after
streaming ends. Stop ongoing stream when switching sessions.
- register.agent.ts: Add --session-key <key> and --lane <lane> CLI flags.
- agent-via-gateway.ts: Wire sessionKey into session resolution and validation
for both interactive and --stream-json code paths.
- workspace.ts: Add resolveAgentWorkspacePrefix() to map workspace-root-relative
paths to repo-root-relative paths for the agent process.
── Error Surfacing ──
- agent-runner.ts: Add onAgentError callback extraction helpers
(parseAgentErrorMessage, parseErrorBody, parseErrorFromStderr) to surface
API-level errors (402 payment, rate limits, etc.) to the UI. Captures stderr
for fallback error detection on non-zero exit.
- route.ts: Wire onAgentError into the SSE stream as [error]-prefixed text
parts. Improve onError and onClose handlers with clearer error messages and
exit code reporting.
- chat-message.tsx: Detect [error]-prefixed text segments and render them as
styled error banners with alert icon instead of plain text.
- chat-panel.tsx: Restyle the transport-level error bar with themed colors and
an alert icon consistent with in-message error styling.
2026-02-11 20:54:30 -08:00
|
|
|
activePath,
|
2026-02-13 18:57:15 -08:00
|
|
|
browseDir,
|
|
|
|
|
treeLoading,
|
2026-02-11 16:45:07 -08:00
|
|
|
members,
|
|
|
|
|
onNodeSelect,
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
onNavigateToObject,
|
|
|
|
|
onRefreshObject,
|
Dench workspace: Tiptap markdown editor, subagent sessions, and error surfacing
── Tiptap Markdown Editor ──
- Add full Tiptap-based WYSIWYG markdown editor (markdown-editor.tsx, 709 LOC)
with bubble menu, auto-save (debounced), image drag-and-drop/paste upload,
table editing, task list checkboxes, and frontmatter preservation on save.
- Add slash command system (slash-command.tsx, 607 LOC) with "/" trigger for
block insertion (headings, lists, tables, code blocks, images, reports) and
"@" trigger for file/document mention with fuzzy search across the workspace
tree.
- Add ReportBlockNode (report-block-node.tsx) — custom Tiptap node that renders
embedded report-json blocks as interactive ReportCard widgets inline in the
editor, with expand/collapse and edit-JSON support.
- Add workspace asset serving API (api/workspace/assets/[...path]/route.ts) to
serve images from the workspace with proper MIME types.
- Add workspace file upload orkspace/upload/route.ts) for multipart
image uploads (10 MB limit, image types only), saving to assets/ directory.
- Add ~500 lines of Tiptap editor CSS to globals.css (editor layout, task lists,
images, tables, slash command dropdown, bubble menu toolbar, code blocks, etc.).
- Add 14 @tiptap/* dependencies to apps/web/package.json (react, starter-kit,
markdown, image, link, table, task-list, suggestion, placeholder, etc.).
── Document View: Edit/Read Mode Toggle ──
- document-view.tsx: Add edit/read mode toggle; defaults to edit mode when a
filePath is available. Lazy-loads MarkdownEditor to keep initial bundle light.
- workspace/page.tsx: Pass activePath, tree, onSave, onNavigate, and
onRefreshTree through to DocumentView for full editor integration with
workspace navigation and tree refresh after saves.
── Subagent Session Isolation ──
- agent-runner.ts: Add RunAgentOptions with optional sessionId; when set, spawns
the agent with --session-key agent:main:subagent:<id> ant so
file-scoped sidebar chats run in isolated sessions independent of the main
agent.
- route.ts (chat API): Accept sessionId from request body and forward it to
runAgent. Resolve workspace file path prefixes (resolveAgentWorkspacePrefix)
so tree-relative paths become agent-cwd-relative.
- chat-panel.tsx: Create per-instance DefaultChatTransport that injects sessionId
via body function and a ref (avoids stale closures). On file change, auto-load
the most recent session and its messages. Refresh session tab list after
streaming ends. Stop ongoing stream when switching sessions.
- register.agent.ts: Add --session-key <key> and --lane <lane> CLI flags.
- agent-via-gateway.ts: Wire sessionKey into session resolution and validation
for both interactive and --stream-json code paths.
- workspace.ts: Add resolveAgentWorkspacePrefix() to map workspace-root-relative
paths to repo-root-relative paths for the agent process.
── Error Surfacing ──
- agent-runner.ts: Add onAgentError callback extraction helpers
(parseAgentErrorMessage, parseErrorBody, parseErrorFromStderr) to surface
API-level errors (402 payment, rate limits, etc.) to the UI. Captures stderr
for fallback error detection on non-zero exit.
- route.ts: Wire onAgentError into the SSE stream as [error]-prefixed text
parts. Improve onError and onClose handlers with clearer error messages and
exit code reporting.
- chat-message.tsx: Detect [error]-prefixed text segments and render them as
styled error banners with alert icon instead of plain text.
- chat-panel.tsx: Restyle the transport-level error bar with themed colors and
an alert icon consistent with in-message error styling.
2026-02-11 20:54:30 -08:00
|
|
|
onRefreshTree,
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
onNavigate,
|
|
|
|
|
onOpenEntry,
|
|
|
|
|
searchFn,
|
2026-02-12 13:37:40 -08:00
|
|
|
onSelectCronJob,
|
|
|
|
|
onBackToCronDashboard,
|
2026-02-19 14:59:34 -08:00
|
|
|
onWorkspaceCreated,
|
2026-02-11 16:45:07 -08:00
|
|
|
}: {
|
|
|
|
|
content: ContentState;
|
|
|
|
|
workspaceExists: boolean;
|
|
|
|
|
tree: TreeNode[];
|
Dench workspace: Tiptap markdown editor, subagent sessions, and error surfacing
── Tiptap Markdown Editor ──
- Add full Tiptap-based WYSIWYG markdown editor (markdown-editor.tsx, 709 LOC)
with bubble menu, auto-save (debounced), image drag-and-drop/paste upload,
table editing, task list checkboxes, and frontmatter preservation on save.
- Add slash command system (slash-command.tsx, 607 LOC) with "/" trigger for
block insertion (headings, lists, tables, code blocks, images, reports) and
"@" trigger for file/document mention with fuzzy search across the workspace
tree.
- Add ReportBlockNode (report-block-node.tsx) — custom Tiptap node that renders
embedded report-json blocks as interactive ReportCard widgets inline in the
editor, with expand/collapse and edit-JSON support.
- Add workspace asset serving API (api/workspace/assets/[...path]/route.ts) to
serve images from the workspace with proper MIME types.
- Add workspace file upload orkspace/upload/route.ts) for multipart
image uploads (10 MB limit, image types only), saving to assets/ directory.
- Add ~500 lines of Tiptap editor CSS to globals.css (editor layout, task lists,
images, tables, slash command dropdown, bubble menu toolbar, code blocks, etc.).
- Add 14 @tiptap/* dependencies to apps/web/package.json (react, starter-kit,
markdown, image, link, table, task-list, suggestion, placeholder, etc.).
── Document View: Edit/Read Mode Toggle ──
- document-view.tsx: Add edit/read mode toggle; defaults to edit mode when a
filePath is available. Lazy-loads MarkdownEditor to keep initial bundle light.
- workspace/page.tsx: Pass activePath, tree, onSave, onNavigate, and
onRefreshTree through to DocumentView for full editor integration with
workspace navigation and tree refresh after saves.
── Subagent Session Isolation ──
- agent-runner.ts: Add RunAgentOptions with optional sessionId; when set, spawns
the agent with --session-key agent:main:subagent:<id> ant so
file-scoped sidebar chats run in isolated sessions independent of the main
agent.
- route.ts (chat API): Accept sessionId from request body and forward it to
runAgent. Resolve workspace file path prefixes (resolveAgentWorkspacePrefix)
so tree-relative paths become agent-cwd-relative.
- chat-panel.tsx: Create per-instance DefaultChatTransport that injects sessionId
via body function and a ref (avoids stale closures). On file change, auto-load
the most recent session and its messages. Refresh session tab list after
streaming ends. Stop ongoing stream when switching sessions.
- register.agent.ts: Add --session-key <key> and --lane <lane> CLI flags.
- agent-via-gateway.ts: Wire sessionKey into session resolution and validation
for both interactive and --stream-json code paths.
- workspace.ts: Add resolveAgentWorkspacePrefix() to map workspace-root-relative
paths to repo-root-relative paths for the agent process.
── Error Surfacing ──
- agent-runner.ts: Add onAgentError callback extraction helpers
(parseAgentErrorMessage, parseErrorBody, parseErrorFromStderr) to surface
API-level errors (402 payment, rate limits, etc.) to the UI. Captures stderr
for fallback error detection on non-zero exit.
- route.ts: Wire onAgentError into the SSE stream as [error]-prefixed text
parts. Improve onError and onClose handlers with clearer error messages and
exit code reporting.
- chat-message.tsx: Detect [error]-prefixed text segments and render them as
styled error banners with alert icon instead of plain text.
- chat-panel.tsx: Restyle the transport-level error bar with themed colors and
an alert icon consistent with in-message error styling.
2026-02-11 20:54:30 -08:00
|
|
|
activePath: string | null;
|
2026-02-13 18:57:15 -08:00
|
|
|
/** Current browse directory (absolute path), or null in workspace mode. */
|
|
|
|
|
browseDir?: string | null;
|
|
|
|
|
/** Whether the tree is currently being fetched. */
|
|
|
|
|
treeLoading?: boolean;
|
2026-02-11 16:45:07 -08:00
|
|
|
members?: Array<{ id: string; name: string; email: string; role: string }>;
|
|
|
|
|
onNodeSelect: (node: TreeNode) => void;
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
onNavigateToObject: (objectName: string) => void;
|
|
|
|
|
onRefreshObject: () => void;
|
Dench workspace: Tiptap markdown editor, subagent sessions, and error surfacing
── Tiptap Markdown Editor ──
- Add full Tiptap-based WYSIWYG markdown editor (markdown-editor.tsx, 709 LOC)
with bubble menu, auto-save (debounced), image drag-and-drop/paste upload,
table editing, task list checkboxes, and frontmatter preservation on save.
- Add slash command system (slash-command.tsx, 607 LOC) with "/" trigger for
block insertion (headings, lists, tables, code blocks, images, reports) and
"@" trigger for file/document mention with fuzzy search across the workspace
tree.
- Add ReportBlockNode (report-block-node.tsx) — custom Tiptap node that renders
embedded report-json blocks as interactive ReportCard widgets inline in the
editor, with expand/collapse and edit-JSON support.
- Add workspace asset serving API (api/workspace/assets/[...path]/route.ts) to
serve images from the workspace with proper MIME types.
- Add workspace file upload orkspace/upload/route.ts) for multipart
image uploads (10 MB limit, image types only), saving to assets/ directory.
- Add ~500 lines of Tiptap editor CSS to globals.css (editor layout, task lists,
images, tables, slash command dropdown, bubble menu toolbar, code blocks, etc.).
- Add 14 @tiptap/* dependencies to apps/web/package.json (react, starter-kit,
markdown, image, link, table, task-list, suggestion, placeholder, etc.).
── Document View: Edit/Read Mode Toggle ──
- document-view.tsx: Add edit/read mode toggle; defaults to edit mode when a
filePath is available. Lazy-loads MarkdownEditor to keep initial bundle light.
- workspace/page.tsx: Pass activePath, tree, onSave, onNavigate, and
onRefreshTree through to DocumentView for full editor integration with
workspace navigation and tree refresh after saves.
── Subagent Session Isolation ──
- agent-runner.ts: Add RunAgentOptions with optional sessionId; when set, spawns
the agent with --session-key agent:main:subagent:<id> ant so
file-scoped sidebar chats run in isolated sessions independent of the main
agent.
- route.ts (chat API): Accept sessionId from request body and forward it to
runAgent. Resolve workspace file path prefixes (resolveAgentWorkspacePrefix)
so tree-relative paths become agent-cwd-relative.
- chat-panel.tsx: Create per-instance DefaultChatTransport that injects sessionId
via body function and a ref (avoids stale closures). On file change, auto-load
the most recent session and its messages. Refresh session tab list after
streaming ends. Stop ongoing stream when switching sessions.
- register.agent.ts: Add --session-key <key> and --lane <lane> CLI flags.
- agent-via-gateway.ts: Wire sessionKey into session resolution and validation
for both interactive and --stream-json code paths.
- workspace.ts: Add resolveAgentWorkspacePrefix() to map workspace-root-relative
paths to repo-root-relative paths for the agent process.
── Error Surfacing ──
- agent-runner.ts: Add onAgentError callback extraction helpers
(parseAgentErrorMessage, parseErrorBody, parseErrorFromStderr) to surface
API-level errors (402 payment, rate limits, etc.) to the UI. Captures stderr
for fallback error detection on non-zero exit.
- route.ts: Wire onAgentError into the SSE stream as [error]-prefixed text
parts. Improve onError and onClose handlers with clearer error messages and
exit code reporting.
- chat-message.tsx: Detect [error]-prefixed text segments and render them as
styled error banners with alert icon instead of plain text.
- chat-panel.tsx: Restyle the transport-level error bar with themed colors and
an alert icon consistent with in-message error styling.
2026-02-11 20:54:30 -08:00
|
|
|
onRefreshTree: () => void;
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
onNavigate: (href: string) => void;
|
|
|
|
|
onOpenEntry: (objectName: string, entryId: string) => void;
|
|
|
|
|
searchFn: (query: string, limit?: number) => import("@/lib/search-index").SearchIndexItem[];
|
2026-02-12 13:37:40 -08:00
|
|
|
onSelectCronJob: (jobId: string) => void;
|
|
|
|
|
onBackToCronDashboard: () => void;
|
2026-02-19 14:59:34 -08:00
|
|
|
/** Called after a new workspace is created from the empty state. */
|
|
|
|
|
onWorkspaceCreated?: () => void;
|
2026-02-11 16:45:07 -08:00
|
|
|
}) {
|
|
|
|
|
switch (content.kind) {
|
|
|
|
|
case "loading":
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex items-center justify-center h-full">
|
2026-02-19 16:50:52 -08:00
|
|
|
<UnicodeSpinner name="braille" className="text-2xl" style={{ color: "var(--color-text-muted)" }} />
|
2026-02-11 16:45:07 -08:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "object":
|
|
|
|
|
return (
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
<ObjectView
|
|
|
|
|
data={content.data}
|
|
|
|
|
members={members}
|
|
|
|
|
onNavigateToObject={onNavigateToObject}
|
|
|
|
|
onRefreshObject={onRefreshObject}
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
onOpenEntry={onOpenEntry}
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
/>
|
2026-02-11 16:45:07 -08:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "document":
|
|
|
|
|
return (
|
|
|
|
|
<DocumentView
|
|
|
|
|
content={content.data.content}
|
|
|
|
|
title={content.title}
|
Dench workspace: Tiptap markdown editor, subagent sessions, and error surfacing
── Tiptap Markdown Editor ──
- Add full Tiptap-based WYSIWYG markdown editor (markdown-editor.tsx, 709 LOC)
with bubble menu, auto-save (debounced), image drag-and-drop/paste upload,
table editing, task list checkboxes, and frontmatter preservation on save.
- Add slash command system (slash-command.tsx, 607 LOC) with "/" trigger for
block insertion (headings, lists, tables, code blocks, images, reports) and
"@" trigger for file/document mention with fuzzy search across the workspace
tree.
- Add ReportBlockNode (report-block-node.tsx) — custom Tiptap node that renders
embedded report-json blocks as interactive ReportCard widgets inline in the
editor, with expand/collapse and edit-JSON support.
- Add workspace asset serving API (api/workspace/assets/[...path]/route.ts) to
serve images from the workspace with proper MIME types.
- Add workspace file upload orkspace/upload/route.ts) for multipart
image uploads (10 MB limit, image types only), saving to assets/ directory.
- Add ~500 lines of Tiptap editor CSS to globals.css (editor layout, task lists,
images, tables, slash command dropdown, bubble menu toolbar, code blocks, etc.).
- Add 14 @tiptap/* dependencies to apps/web/package.json (react, starter-kit,
markdown, image, link, table, task-list, suggestion, placeholder, etc.).
── Document View: Edit/Read Mode Toggle ──
- document-view.tsx: Add edit/read mode toggle; defaults to edit mode when a
filePath is available. Lazy-loads MarkdownEditor to keep initial bundle light.
- workspace/page.tsx: Pass activePath, tree, onSave, onNavigate, and
onRefreshTree through to DocumentView for full editor integration with
workspace navigation and tree refresh after saves.
── Subagent Session Isolation ──
- agent-runner.ts: Add RunAgentOptions with optional sessionId; when set, spawns
the agent with --session-key agent:main:subagent:<id> ant so
file-scoped sidebar chats run in isolated sessions independent of the main
agent.
- route.ts (chat API): Accept sessionId from request body and forward it to
runAgent. Resolve workspace file path prefixes (resolveAgentWorkspacePrefix)
so tree-relative paths become agent-cwd-relative.
- chat-panel.tsx: Create per-instance DefaultChatTransport that injects sessionId
via body function and a ref (avoids stale closures). On file change, auto-load
the most recent session and its messages. Refresh session tab list after
streaming ends. Stop ongoing stream when switching sessions.
- register.agent.ts: Add --session-key <key> and --lane <lane> CLI flags.
- agent-via-gateway.ts: Wire sessionKey into session resolution and validation
for both interactive and --stream-json code paths.
- workspace.ts: Add resolveAgentWorkspacePrefix() to map workspace-root-relative
paths to repo-root-relative paths for the agent process.
── Error Surfacing ──
- agent-runner.ts: Add onAgentError callback extraction helpers
(parseAgentErrorMessage, parseErrorBody, parseErrorFromStderr) to surface
API-level errors (402 payment, rate limits, etc.) to the UI. Captures stderr
for fallback error detection on non-zero exit.
- route.ts: Wire onAgentError into the SSE stream as [error]-prefixed text
parts. Improve onError and onClose handlers with clearer error messages and
exit code reporting.
- chat-message.tsx: Detect [error]-prefixed text segments and render them as
styled error banners with alert icon instead of plain text.
- chat-panel.tsx: Restyle the transport-level error bar with themed colors and
an alert icon consistent with in-message error styling.
2026-02-11 20:54:30 -08:00
|
|
|
filePath={activePath ?? undefined}
|
|
|
|
|
tree={tree}
|
|
|
|
|
onSave={onRefreshTree}
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
onNavigate={onNavigate}
|
|
|
|
|
searchFn={searchFn}
|
2026-02-11 16:45:07 -08:00
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "file":
|
|
|
|
|
return (
|
|
|
|
|
<FileViewer
|
|
|
|
|
content={content.data.content}
|
|
|
|
|
filename={content.filename}
|
|
|
|
|
type={content.data.type === "yaml" ? "yaml" : "text"}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-13 18:06:59 -08:00
|
|
|
case "code":
|
|
|
|
|
return (
|
|
|
|
|
<CodeViewer
|
|
|
|
|
content={content.data.content}
|
|
|
|
|
filename={content.filename}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
case "media":
|
|
|
|
|
return (
|
|
|
|
|
<MediaViewer
|
|
|
|
|
url={content.url}
|
|
|
|
|
filename={content.filename}
|
|
|
|
|
mediaType={content.mediaType}
|
|
|
|
|
filePath={content.filePath}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-21 13:10:32 -08:00
|
|
|
case "spreadsheet":
|
|
|
|
|
return (
|
|
|
|
|
<FileViewer
|
|
|
|
|
filename={content.filename}
|
|
|
|
|
type="spreadsheet"
|
|
|
|
|
url={content.url}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "html":
|
|
|
|
|
return (
|
|
|
|
|
<HtmlViewer
|
|
|
|
|
rawUrl={content.rawUrl}
|
|
|
|
|
contentUrl={content.contentUrl}
|
|
|
|
|
filename={content.filename}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-11 17:01:28 -08:00
|
|
|
case "database":
|
|
|
|
|
return (
|
|
|
|
|
<DatabaseViewer
|
|
|
|
|
dbPath={content.dbPath}
|
|
|
|
|
filename={content.filename}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-11 18:35:35 -08:00
|
|
|
case "report":
|
|
|
|
|
return (
|
|
|
|
|
<ReportViewer
|
|
|
|
|
reportPath={content.reportPath}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-13 18:57:15 -08:00
|
|
|
case "directory": {
|
|
|
|
|
// In browse mode the top-level tree is the live listing of browseDir
|
|
|
|
|
// (same data source as the sidebar). Use it directly instead of the
|
|
|
|
|
// possibly-stale node.children stored in content state.
|
|
|
|
|
const isBrowseLive = browseDir != null && activePath === browseDir;
|
|
|
|
|
if (isBrowseLive && treeLoading) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex items-center justify-center h-full">
|
2026-02-19 16:50:52 -08:00
|
|
|
<UnicodeSpinner name="braille" className="text-2xl" style={{ color: "var(--color-text-muted)" }} />
|
2026-02-13 18:57:15 -08:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const directoryNode = isBrowseLive
|
|
|
|
|
? { ...content.node, children: tree }
|
|
|
|
|
: content.node;
|
2026-02-11 16:45:07 -08:00
|
|
|
return (
|
|
|
|
|
<DirectoryListing
|
2026-02-13 18:57:15 -08:00
|
|
|
node={directoryNode}
|
2026-02-11 16:45:07 -08:00
|
|
|
onNodeSelect={onNodeSelect}
|
|
|
|
|
/>
|
|
|
|
|
);
|
2026-02-13 18:57:15 -08:00
|
|
|
}
|
2026-02-11 16:45:07 -08:00
|
|
|
|
2026-02-12 13:37:40 -08:00
|
|
|
case "cron-dashboard":
|
|
|
|
|
return (
|
|
|
|
|
<CronDashboard
|
|
|
|
|
onSelectJob={onSelectCronJob}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
case "cron-job":
|
|
|
|
|
return (
|
|
|
|
|
<CronJobDetail
|
|
|
|
|
job={content.job}
|
|
|
|
|
onBack={onBackToCronDashboard}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-15 18:19:13 -08:00
|
|
|
case "duckdb-missing":
|
|
|
|
|
return <DuckDBMissing />;
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
case "none":
|
|
|
|
|
default:
|
|
|
|
|
if (tree.length === 0) {
|
2026-02-19 14:59:34 -08:00
|
|
|
return <EmptyState workspaceExists={workspaceExists} onWorkspaceCreated={onWorkspaceCreated} />;
|
2026-02-11 16:45:07 -08:00
|
|
|
}
|
|
|
|
|
return <WelcomeView tree={tree} onNodeSelect={onNodeSelect} />;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
// --- Object View (header + display field selector + table/kanban) ---
|
|
|
|
|
|
|
|
|
|
function ObjectView({
|
|
|
|
|
data,
|
|
|
|
|
members,
|
|
|
|
|
onNavigateToObject,
|
|
|
|
|
onRefreshObject,
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
onOpenEntry,
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
}: {
|
|
|
|
|
data: ObjectData;
|
|
|
|
|
members?: Array<{ id: string; name: string; email: string; role: string }>;
|
|
|
|
|
onNavigateToObject: (objectName: string) => void;
|
|
|
|
|
onRefreshObject: () => void;
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
onOpenEntry?: (objectName: string, entryId: string) => void;
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
}) {
|
|
|
|
|
const [updatingDisplayField, setUpdatingDisplayField] = useState(false);
|
|
|
|
|
|
2026-02-17 00:36:01 -08:00
|
|
|
// --- Filter state ---
|
|
|
|
|
const [filters, setFilters] = useState<FilterGroup>(() => emptyFilterGroup());
|
|
|
|
|
const [savedViews, setSavedViews] = useState<SavedView[]>(data.savedViews ?? []);
|
|
|
|
|
const [activeViewName, setActiveViewName] = useState<string | undefined>(data.activeView);
|
|
|
|
|
|
2026-02-19 14:59:34 -08:00
|
|
|
// --- Server-side pagination state ---
|
|
|
|
|
const [serverPage, setServerPage] = useState(data.page ?? 1);
|
|
|
|
|
const [serverPageSize, setServerPageSize] = useState(data.pageSize ?? 100);
|
|
|
|
|
const [totalCount, setTotalCount] = useState(data.totalCount ?? data.entries.length);
|
|
|
|
|
const [entries, setEntries] = useState(data.entries);
|
|
|
|
|
const [serverSearch, setServerSearch] = useState("");
|
2026-02-21 18:06:01 -08:00
|
|
|
const [sortRules, _setSortRules] = useState<SortRule[] | undefined>(undefined);
|
2026-02-19 14:59:34 -08:00
|
|
|
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
|
|
|
|
2026-02-17 00:36:01 -08:00
|
|
|
// Column visibility: maps field IDs to boolean (false = hidden)
|
|
|
|
|
const [viewColumns, setViewColumns] = useState<string[] | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
// Convert field-name-based columns list to TanStack VisibilityState keyed by field ID
|
|
|
|
|
const columnVisibility = useMemo(() => {
|
|
|
|
|
if (!viewColumns || viewColumns.length === 0) {return undefined;}
|
|
|
|
|
const vis: Record<string, boolean> = {};
|
|
|
|
|
for (const field of data.fields) {
|
|
|
|
|
vis[field.id] = viewColumns.includes(field.name);
|
|
|
|
|
}
|
|
|
|
|
return vis;
|
|
|
|
|
}, [viewColumns, data.fields]);
|
|
|
|
|
|
2026-02-19 14:59:34 -08:00
|
|
|
// Fetch entries from server with current pagination/filter/sort/search state
|
|
|
|
|
const fetchEntries = useCallback(async (opts?: {
|
|
|
|
|
page?: number;
|
|
|
|
|
pageSize?: number;
|
|
|
|
|
filters?: FilterGroup;
|
|
|
|
|
sort?: SortRule[];
|
|
|
|
|
search?: string;
|
|
|
|
|
}) => {
|
|
|
|
|
const p = opts?.page ?? serverPage;
|
|
|
|
|
const ps = opts?.pageSize ?? serverPageSize;
|
|
|
|
|
const f = opts?.filters ?? filters;
|
|
|
|
|
const s = opts?.sort ?? sortRules;
|
|
|
|
|
const q = opts?.search ?? serverSearch;
|
|
|
|
|
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
params.set("page", String(p));
|
|
|
|
|
params.set("pageSize", String(ps));
|
|
|
|
|
if (f && f.rules.length > 0) {
|
|
|
|
|
params.set("filters", serializeFilters(f));
|
|
|
|
|
}
|
|
|
|
|
if (s && s.length > 0) {
|
|
|
|
|
params.set("sort", JSON.stringify(s));
|
|
|
|
|
}
|
|
|
|
|
if (q) {
|
|
|
|
|
params.set("search", q);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(
|
|
|
|
|
`/api/workspace/objects/${encodeURIComponent(data.object.name)}?${params.toString()}`
|
|
|
|
|
);
|
|
|
|
|
if (!res.ok) {return;}
|
|
|
|
|
const result: ObjectData = await res.json();
|
|
|
|
|
setEntries(result.entries);
|
|
|
|
|
setTotalCount(result.totalCount ?? result.entries.length);
|
|
|
|
|
setServerPage(result.page ?? p);
|
|
|
|
|
setServerPageSize(result.pageSize ?? ps);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
}, [serverPage, serverPageSize, filters, sortRules, serverSearch, data.object.name]);
|
|
|
|
|
|
|
|
|
|
// Sync initial data from props (when parent refreshes via SSE)
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setEntries(data.entries);
|
|
|
|
|
setTotalCount(data.totalCount ?? data.entries.length);
|
|
|
|
|
}, [data.entries, data.totalCount]);
|
|
|
|
|
|
2026-02-17 00:36:01 -08:00
|
|
|
// Sync saved views when data changes (e.g. SSE refresh from AI editing .object.yaml)
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
setSavedViews(data.savedViews ?? []);
|
|
|
|
|
if (data.activeView && data.activeView !== activeViewName) {
|
|
|
|
|
const view = (data.savedViews ?? []).find((v) => v.name === data.activeView);
|
|
|
|
|
if (view) {
|
|
|
|
|
setFilters(view.filters ?? emptyFilterGroup());
|
|
|
|
|
setViewColumns(view.columns);
|
|
|
|
|
setActiveViewName(view.name);
|
2026-02-19 14:59:34 -08:00
|
|
|
// Re-fetch with new filters from the view
|
|
|
|
|
void fetchEntries({ page: 1, filters: view.filters ?? emptyFilterGroup() });
|
2026-02-17 00:36:01 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [data.savedViews, data.activeView]);
|
|
|
|
|
|
2026-02-19 14:59:34 -08:00
|
|
|
// When filters change, reset to page 1 and re-fetch
|
|
|
|
|
const handleFiltersChange = useCallback((newFilters: FilterGroup) => {
|
|
|
|
|
setFilters(newFilters);
|
|
|
|
|
setServerPage(1);
|
|
|
|
|
void fetchEntries({ page: 1, filters: newFilters });
|
|
|
|
|
}, [fetchEntries]);
|
|
|
|
|
|
|
|
|
|
// Server-side search with debounce
|
|
|
|
|
const handleServerSearch = useCallback((query: string) => {
|
|
|
|
|
setServerSearch(query);
|
|
|
|
|
if (searchTimerRef.current) {clearTimeout(searchTimerRef.current);}
|
|
|
|
|
searchTimerRef.current = setTimeout(() => {
|
|
|
|
|
setServerPage(1);
|
|
|
|
|
void fetchEntries({ page: 1, search: query });
|
|
|
|
|
}, 300);
|
|
|
|
|
}, [fetchEntries]);
|
|
|
|
|
|
|
|
|
|
// Page change
|
|
|
|
|
const handlePageChange = useCallback((page: number) => {
|
|
|
|
|
setServerPage(page);
|
|
|
|
|
void fetchEntries({ page });
|
|
|
|
|
}, [fetchEntries]);
|
|
|
|
|
|
|
|
|
|
// Page size change
|
|
|
|
|
const handlePageSizeChange = useCallback((size: number) => {
|
|
|
|
|
setServerPageSize(size);
|
|
|
|
|
setServerPage(1);
|
|
|
|
|
void fetchEntries({ page: 1, pageSize: size });
|
|
|
|
|
}, [fetchEntries]);
|
|
|
|
|
|
|
|
|
|
// Override onRefreshObject to re-fetch with current pagination state
|
|
|
|
|
const handleRefresh = useCallback(() => {
|
|
|
|
|
void fetchEntries();
|
|
|
|
|
onRefreshObject();
|
|
|
|
|
}, [fetchEntries, onRefreshObject]);
|
|
|
|
|
|
|
|
|
|
// Use entries from server (already filtered server-side)
|
|
|
|
|
const filteredEntries = entries;
|
2026-02-17 00:36:01 -08:00
|
|
|
|
|
|
|
|
// Save view to .object.yaml via API
|
|
|
|
|
const handleSaveView = useCallback(async (name: string) => {
|
|
|
|
|
const newView: SavedView = { name, filters, columns: viewColumns };
|
|
|
|
|
const updated = [...savedViews.filter((v) => v.name !== name), newView];
|
|
|
|
|
setSavedViews(updated);
|
|
|
|
|
setActiveViewName(name);
|
|
|
|
|
try {
|
|
|
|
|
await fetch(
|
|
|
|
|
`/api/workspace/objects/${encodeURIComponent(data.object.name)}/views`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ views: updated, activeView: name }),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore save errors
|
|
|
|
|
}
|
|
|
|
|
}, [filters, savedViews, data.object.name]);
|
|
|
|
|
|
|
|
|
|
const handleLoadView = useCallback((view: SavedView) => {
|
2026-02-19 14:59:34 -08:00
|
|
|
const newFilters = view.filters ?? emptyFilterGroup();
|
|
|
|
|
setFilters(newFilters);
|
2026-02-17 00:36:01 -08:00
|
|
|
setViewColumns(view.columns);
|
|
|
|
|
setActiveViewName(view.name);
|
2026-02-19 14:59:34 -08:00
|
|
|
setServerPage(1);
|
|
|
|
|
void fetchEntries({ page: 1, filters: newFilters });
|
|
|
|
|
}, [fetchEntries]);
|
2026-02-17 00:36:01 -08:00
|
|
|
|
|
|
|
|
const handleDeleteView = useCallback(async (name: string) => {
|
|
|
|
|
const updated = savedViews.filter((v) => v.name !== name);
|
|
|
|
|
setSavedViews(updated);
|
|
|
|
|
if (activeViewName === name) {
|
|
|
|
|
setActiveViewName(undefined);
|
|
|
|
|
setFilters(emptyFilterGroup());
|
|
|
|
|
setViewColumns(undefined);
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await fetch(
|
|
|
|
|
`/api/workspace/objects/${encodeURIComponent(data.object.name)}/views`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
views: updated,
|
|
|
|
|
activeView: activeViewName === name ? undefined : activeViewName,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
}, [savedViews, activeViewName, data.object.name]);
|
|
|
|
|
|
|
|
|
|
const handleSetActiveView = useCallback(async (name: string | undefined) => {
|
|
|
|
|
setActiveViewName(name);
|
|
|
|
|
if (!name) {setViewColumns(undefined);}
|
|
|
|
|
try {
|
|
|
|
|
await fetch(
|
|
|
|
|
`/api/workspace/objects/${encodeURIComponent(data.object.name)}/views`,
|
|
|
|
|
{
|
|
|
|
|
method: "PUT",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ views: savedViews, activeView: name }),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
}
|
|
|
|
|
}, [savedViews, data.object.name]);
|
|
|
|
|
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
const handleDisplayFieldChange = async (fieldName: string) => {
|
|
|
|
|
setUpdatingDisplayField(true);
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(
|
|
|
|
|
`/api/workspace/objects/${encodeURIComponent(data.object.name)}/display-field`,
|
|
|
|
|
{
|
|
|
|
|
method: "PATCH",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
body: JSON.stringify({ displayField: fieldName }),
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
if (res.ok) {
|
|
|
|
|
onRefreshObject();
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
// ignore
|
|
|
|
|
} finally {
|
|
|
|
|
setUpdatingDisplayField(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const displayFieldCandidates = data.fields.filter(
|
|
|
|
|
(f) => !["relation", "boolean", "richtext"].includes(f.type),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const hasRelationFields = data.fields.some((f) => f.type === "relation");
|
|
|
|
|
const hasReverseRelations =
|
|
|
|
|
data.reverseRelations && data.reverseRelations.some(
|
|
|
|
|
(rr) => Object.keys(rr.entries).length > 0,
|
|
|
|
|
);
|
|
|
|
|
|
2026-02-17 00:36:01 -08:00
|
|
|
const filterBarMembers = useMemo(
|
|
|
|
|
() => members?.map((m) => ({ id: m.id, name: m.name })),
|
|
|
|
|
[members],
|
|
|
|
|
);
|
|
|
|
|
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
return (
|
|
|
|
|
<div className="p-6">
|
|
|
|
|
{/* Object header */}
|
2026-02-17 00:36:01 -08:00
|
|
|
<div className="mb-4">
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
<h1
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="font-instrument text-3xl tracking-tight capitalize"
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
style={{ color: "var(--color-text)" }}
|
|
|
|
|
>
|
|
|
|
|
{data.object.name}
|
|
|
|
|
</h1>
|
|
|
|
|
{data.object.description && (
|
|
|
|
|
<p
|
|
|
|
|
className="text-sm mt-1"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
>
|
|
|
|
|
{data.object.description}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
<div className="flex items-center gap-3 mt-3 flex-wrap">
|
|
|
|
|
<span
|
|
|
|
|
className="text-xs px-2 py-1 rounded-full"
|
|
|
|
|
style={{
|
|
|
|
|
background: "var(--color-surface)",
|
|
|
|
|
color: "var(--color-text-muted)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
2026-02-19 14:59:34 -08:00
|
|
|
{totalCount} entries
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
</span>
|
|
|
|
|
<span
|
|
|
|
|
className="text-xs px-2 py-1 rounded-full"
|
|
|
|
|
style={{
|
|
|
|
|
background: "var(--color-surface)",
|
|
|
|
|
color: "var(--color-text-muted)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{data.fields.length} fields
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
|
|
{hasRelationFields && (
|
|
|
|
|
<span
|
|
|
|
|
className="text-xs px-2 py-1 rounded-full"
|
|
|
|
|
style={{
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
background: "var(--color-chip-document)",
|
|
|
|
|
color: "var(--color-chip-document-text)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{data.fields.filter((f) => f.type === "relation").length} relation{data.fields.filter((f) => f.type === "relation").length !== 1 ? "s" : ""}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{hasReverseRelations && (
|
|
|
|
|
<span
|
|
|
|
|
className="text-xs px-2 py-1 rounded-full"
|
|
|
|
|
style={{
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
background: "var(--color-chip-database)",
|
|
|
|
|
color: "var(--color-chip-database-text)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{data.reverseRelations!.filter((rr) => Object.keys(rr.entries).length > 0).length} linked from
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{displayFieldCandidates.length > 0 && (
|
|
|
|
|
<div className="flex items-center gap-2 mt-3">
|
|
|
|
|
<span
|
|
|
|
|
className="text-xs"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
>
|
|
|
|
|
Display field:
|
|
|
|
|
</span>
|
|
|
|
|
<select
|
|
|
|
|
value={data.effectiveDisplayField ?? ""}
|
|
|
|
|
onChange={(e) => handleDisplayFieldChange(e.target.value)}
|
|
|
|
|
disabled={updatingDisplayField}
|
|
|
|
|
className="text-xs px-2 py-1 rounded-md outline-none transition-colors cursor-pointer"
|
|
|
|
|
style={{
|
|
|
|
|
background: "var(--color-surface)",
|
|
|
|
|
color: "var(--color-text)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
|
|
|
|
opacity: updatingDisplayField ? 0.5 : 1,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{displayFieldCandidates.map((f) => (
|
|
|
|
|
<option key={f.id} value={f.name}>
|
|
|
|
|
{f.name}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
{updatingDisplayField && (
|
|
|
|
|
<div
|
|
|
|
|
className="w-3 h-3 border border-t-transparent rounded-full animate-spin"
|
|
|
|
|
style={{ borderColor: "var(--color-text-muted)" }}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
<span
|
|
|
|
|
className="text-[10px]"
|
|
|
|
|
style={{ color: "var(--color-text-muted)", opacity: 0.6 }}
|
|
|
|
|
>
|
|
|
|
|
Used when other objects link here
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-17 00:36:01 -08:00
|
|
|
{/* Filter bar */}
|
|
|
|
|
<div
|
|
|
|
|
className="mb-4 py-3 px-4 rounded-lg border"
|
|
|
|
|
style={{
|
|
|
|
|
borderColor: "var(--color-border)",
|
|
|
|
|
background: "var(--color-surface)",
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<ObjectFilterBar
|
|
|
|
|
fields={data.fields}
|
|
|
|
|
filters={filters}
|
2026-02-19 14:59:34 -08:00
|
|
|
onFiltersChange={handleFiltersChange}
|
2026-02-17 00:36:01 -08:00
|
|
|
savedViews={savedViews}
|
|
|
|
|
activeViewName={activeViewName}
|
|
|
|
|
onSaveView={handleSaveView}
|
|
|
|
|
onLoadView={handleLoadView}
|
|
|
|
|
onDeleteView={handleDeleteView}
|
|
|
|
|
onSetActiveView={handleSetActiveView}
|
|
|
|
|
members={filterBarMembers}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
{/* Table or Kanban */}
|
|
|
|
|
{data.object.default_view === "kanban" ? (
|
|
|
|
|
<ObjectKanban
|
|
|
|
|
objectName={data.object.name}
|
|
|
|
|
fields={data.fields}
|
2026-02-17 00:36:01 -08:00
|
|
|
entries={filteredEntries}
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
statuses={data.statuses}
|
|
|
|
|
members={members}
|
|
|
|
|
relationLabels={data.relationLabels}
|
2026-02-15 20:27:36 -08:00
|
|
|
onEntryClick={onOpenEntry ? (entryId) => onOpenEntry(data.object.name, entryId) : undefined}
|
2026-02-19 14:59:34 -08:00
|
|
|
onRefresh={handleRefresh}
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<ObjectTable
|
|
|
|
|
objectName={data.object.name}
|
|
|
|
|
fields={data.fields}
|
2026-02-17 00:36:01 -08:00
|
|
|
entries={filteredEntries}
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
members={members}
|
|
|
|
|
relationLabels={data.relationLabels}
|
|
|
|
|
reverseRelations={data.reverseRelations}
|
|
|
|
|
onNavigateToObject={onNavigateToObject}
|
Dench workspace: unified @ mention search, entry detail modal, and workspace link system
New libraries:
- workspace-links.ts: builders, parsers, and type guards for workspace URLs
(/workspace?path=... for files/objects, /workspace?entry=objName:id for entries).
Replaces ad-hoc relative-path links with real navigable URLs.
- search-index.ts: useSearchIndex hook that fetches a unified search index from
the API and provides Fuse.js-powered fuzzy search across files, objects, and
database entries. Exposes a stable ref-based search function safe for capture
in tiptap extensions.
New API routes:
- GET /api/workspace/search-index: builds a flat search index from the filesystem
tree (knowledge/, reports/, top-level files) and all DuckDB object entries with
display-field labels and preview fields.
- GET /api/workspace/objects/[name]/entries/[id]: fetches a single entry with
all field values, resolved relation labels, reverse relations (incoming links
from other objects), and the effective display field.
New component:
- EntryDetailModal: slide-over modal for viewing an individual entry's fields,
enum badges, user avatars, clickable relation chips (navigate to related
entries), reverse relation sections, and timestamps. Supports Escape to close
and backdrop click dismiss.
Slash command refactor (slash-command.tsx):
- New createWorkspaceMention(searchFn) replaces the old file-only @ mention with
unified search across files, objects, and entries.
- searchItemToSlashItem() converts search index items into tiptap suggestion
items with proper icons, badges (object name pill for entries), and link
insertion commands using real workspace URLs.
- Legacy createFileMention(tree) now delegates to createWorkspaceMention with a
simple substring-match fallback for when no search index is available.
Editor integration (markdown-editor.tsx, document-view.tsx):
- MarkdownEditor accepts optional searchFn prop; when provided, uses
createWorkspaceMention instead of the legacy createFileMention.
- Link click interception now uses the shared isWorkspaceLink() helper and
registers handlers in capture phase for reliable interception.
- DocumentView forwards searchFn to editor and adds a delegated click handler
in read mode to intercept workspace links and navigate via onNavigate.
Object table (object-table.tsx):
- Added onEntryClick prop; table rows are now clickable with cursor-pointer
styling, firing the callback with the entry ID.
Workspace page (page.tsx):
- Integrates useSearchIndex hook and passes search function down to editor.
- Entry detail modal state with URL synchronization (?entry=objName:id param).
- New resolveNode() with fallback strategies: exact match, knowledge/ prefix
toggle, and last-segment object name matching.
- Unified handleEditorNavigate() dispatches /workspace?entry=... to the modal
and /workspace?path=... to file/object navigation.
- URL bar syncs with activePath via router.replace (no full page reloads).
- Top-level container click safety net catches any workspace link clicks that
bubble up unhandled.
Styles (globals.css):
- Added .slash-cmd-item-badge for object-name pills in the @ mention popup.
2026-02-11 21:41:23 -08:00
|
|
|
onEntryClick={onOpenEntry ? (entryId) => onOpenEntry(data.object.name, entryId) : undefined}
|
2026-02-19 14:59:34 -08:00
|
|
|
onRefresh={handleRefresh}
|
2026-02-17 00:36:01 -08:00
|
|
|
columnVisibility={columnVisibility}
|
2026-02-19 14:59:34 -08:00
|
|
|
serverPagination={{
|
|
|
|
|
totalCount,
|
|
|
|
|
page: serverPage,
|
|
|
|
|
pageSize: serverPageSize,
|
|
|
|
|
onPageChange: handlePageChange,
|
|
|
|
|
onPageSizeChange: handlePageSizeChange,
|
|
|
|
|
}}
|
|
|
|
|
onServerSearch={handleServerSearch}
|
Dench workspace: file manager, relation resolution, and chat refactor
File Manager & Filesystem Operations:
- Add FileManagerTree component with drag-and-drop (dnd-kit), inline
rename, right-click context menu, and compact sidebar mode
- Add context-menu component (open, new file/folder, rename, duplicate,
copy, paste, move, delete) rendered via portal
- Add InlineRename component with validation and shake-on-error animation
- Add useWorkspaceWatcher hook with SSE live-reload and polling fallback
- Add API routes: mkdir, rename, copy, move, watch (SSE file-change
events), and DELETE on /api/workspace/file with system-file protection
- Add safeResolveNewPath and isSystemFile helpers to workspace lib
- Replace inline WorkspaceTreeNode in sidebar with shared FileManagerTree
(compact mode), add workspace refresh callback
Object Relation Resolution:
- Resolve relation fields to human-readable display labels server-side
(resolveRelationLabels, resolveDisplayField helpers)
- Add reverse relation discovery (findReverseRelations) — surfaces
incoming links from other objects
- Add display_field column migration (idempotent ALTER TABLE) and
PATCH /api/workspace/objects/[name]/display-field endpoint
- Enrich object API response with relationLabels, reverseRelations,
effectiveDisplayField, and related_object_name per field
- Add RelationCell, RelationChip, ReverseRelationCell, LinkIcon
components to object-table with clickable cross-object navigation
- Add relation label rendering to kanban cards
- Extract ObjectView component in workspace page with display-field
selector dropdown and relation/reverse-relation badge counts
Chat Panel Extraction:
- Extract chat logic from page.tsx into standalone ChatPanel component
with forwardRef/useImperativeHandle for session control
- ChatPanel supports file-scoped sessions (filePath param) and
context-aware file chat sidebar
- Simplify page.tsx to thin orchestrator delegating to ChatPanel
- Add filePath filter to GET /api/web-sessions for scoped session lists
Dependencies:
- Add @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities
- Add duckdbExec and parseRelationValue to workspace lib
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-11 19:22:53 -08:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-11 16:45:07 -08:00
|
|
|
// --- Directory Listing ---
|
|
|
|
|
|
|
|
|
|
function DirectoryListing({
|
|
|
|
|
node,
|
|
|
|
|
onNodeSelect,
|
|
|
|
|
}: {
|
|
|
|
|
node: TreeNode;
|
|
|
|
|
onNodeSelect: (node: TreeNode) => void;
|
|
|
|
|
}) {
|
|
|
|
|
const children = node.children ?? [];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="p-6 max-w-4xl mx-auto">
|
|
|
|
|
<h1
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="font-instrument text-3xl tracking-tight mb-1 capitalize"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{ color: "var(--color-text)" }}
|
|
|
|
|
>
|
|
|
|
|
{node.name}
|
|
|
|
|
</h1>
|
|
|
|
|
<p className="text-sm mb-6" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
{children.length} items
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
{children.length === 0 ? (
|
|
|
|
|
<p className="text-sm" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
This folder is empty.
|
|
|
|
|
</p>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
|
|
|
{children.map((child) => (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
key={child.path}
|
|
|
|
|
onClick={() => onNodeSelect(child)}
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="flex items-center gap-3 p-4 rounded-2xl text-left transition-all duration-100 cursor-pointer"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{
|
|
|
|
|
background: "var(--color-surface)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
boxShadow: "var(--shadow-sm)",
|
2026-02-11 16:45:07 -08:00
|
|
|
}}
|
|
|
|
|
onMouseEnter={(e) => {
|
|
|
|
|
(e.currentTarget as HTMLElement).style.borderColor =
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
"var(--color-border-strong)";
|
2026-02-11 16:45:07 -08:00
|
|
|
(e.currentTarget as HTMLElement).style.transform = "translateY(-1px)";
|
|
|
|
|
}}
|
|
|
|
|
onMouseLeave={(e) => {
|
|
|
|
|
(e.currentTarget as HTMLElement).style.borderColor =
|
|
|
|
|
"var(--color-border)";
|
|
|
|
|
(e.currentTarget as HTMLElement).style.transform = "translateY(0)";
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<span
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{
|
|
|
|
|
background:
|
|
|
|
|
child.type === "object"
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
? "var(--color-chip-object)"
|
2026-02-11 16:45:07 -08:00
|
|
|
: child.type === "document"
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
? "var(--color-chip-document)"
|
2026-02-11 17:01:28 -08:00
|
|
|
: child.type === "database"
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
? "var(--color-chip-database)"
|
2026-02-11 18:35:35 -08:00
|
|
|
: child.type === "report"
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
? "var(--color-chip-report)"
|
2026-02-11 18:35:35 -08:00
|
|
|
: "var(--color-surface-hover)",
|
2026-02-11 16:45:07 -08:00
|
|
|
color:
|
|
|
|
|
child.type === "object"
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
? "var(--color-chip-object-text)"
|
2026-02-11 16:45:07 -08:00
|
|
|
: child.type === "document"
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
? "var(--color-chip-document-text)"
|
2026-02-11 17:01:28 -08:00
|
|
|
: child.type === "database"
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
? "var(--color-chip-database-text)"
|
2026-02-11 18:35:35 -08:00
|
|
|
: child.type === "report"
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
? "var(--color-chip-report-text)"
|
2026-02-11 18:35:35 -08:00
|
|
|
: "var(--color-text-muted)",
|
2026-02-11 16:45:07 -08:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<NodeTypeIcon type={child.type} />
|
|
|
|
|
</span>
|
|
|
|
|
<div className="min-w-0 flex-1">
|
|
|
|
|
<div
|
|
|
|
|
className="text-sm font-medium truncate"
|
|
|
|
|
style={{ color: "var(--color-text)" }}
|
|
|
|
|
>
|
|
|
|
|
{child.name.replace(/\.md$/, "")}
|
|
|
|
|
</div>
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
<div
|
|
|
|
|
className="text-xs capitalize"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
>
|
2026-02-11 16:45:07 -08:00
|
|
|
{child.type}
|
|
|
|
|
{child.children ? ` (${child.children.length})` : ""}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Welcome View (no selection) ---
|
|
|
|
|
|
|
|
|
|
function WelcomeView({
|
|
|
|
|
tree,
|
|
|
|
|
onNodeSelect,
|
|
|
|
|
}: {
|
|
|
|
|
tree: TreeNode[];
|
|
|
|
|
onNodeSelect: (node: TreeNode) => void;
|
|
|
|
|
}) {
|
|
|
|
|
const objects: TreeNode[] = [];
|
|
|
|
|
const documents: TreeNode[] = [];
|
|
|
|
|
|
|
|
|
|
function collect(nodes: TreeNode[]) {
|
|
|
|
|
for (const n of nodes) {
|
|
|
|
|
if (n.type === "object") {objects.push(n);}
|
|
|
|
|
else if (n.type === "document") {documents.push(n);}
|
|
|
|
|
if (n.children) {collect(n.children);}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
collect(tree);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="p-8 max-w-4xl mx-auto">
|
|
|
|
|
<h1
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="font-instrument text-3xl tracking-tight mb-2"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{ color: "var(--color-text)" }}
|
|
|
|
|
>
|
|
|
|
|
Workspace
|
|
|
|
|
</h1>
|
|
|
|
|
<p className="text-sm mb-8" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
Select an item from the sidebar, or browse the sections below.
|
|
|
|
|
</p>
|
|
|
|
|
|
|
|
|
|
{objects.length > 0 && (
|
|
|
|
|
<div className="mb-8">
|
|
|
|
|
<h2
|
|
|
|
|
className="text-sm font-medium uppercase tracking-wider mb-3"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
>
|
|
|
|
|
Objects
|
|
|
|
|
</h2>
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
|
|
|
{objects.map((obj) => (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
key={obj.path}
|
|
|
|
|
onClick={() => onNodeSelect(obj)}
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="flex items-center gap-3 p-4 rounded-2xl text-left transition-all duration-100 cursor-pointer"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{
|
|
|
|
|
background: "var(--color-surface)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
boxShadow: "var(--shadow-sm)",
|
2026-02-11 16:45:07 -08:00
|
|
|
}}
|
|
|
|
|
onMouseEnter={(e) => {
|
|
|
|
|
(e.currentTarget as HTMLElement).style.borderColor =
|
|
|
|
|
"var(--color-accent)";
|
|
|
|
|
}}
|
|
|
|
|
onMouseLeave={(e) => {
|
|
|
|
|
(e.currentTarget as HTMLElement).style.borderColor =
|
|
|
|
|
"var(--color-border)";
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<span
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
background: "var(--color-chip-object)",
|
|
|
|
|
color: "var(--color-chip-object-text)",
|
2026-02-11 16:45:07 -08:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<NodeTypeIcon type="object" />
|
|
|
|
|
</span>
|
|
|
|
|
<div className="min-w-0">
|
|
|
|
|
<div
|
|
|
|
|
className="text-sm font-medium capitalize truncate"
|
|
|
|
|
style={{ color: "var(--color-text)" }}
|
|
|
|
|
>
|
|
|
|
|
{obj.name}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
{obj.defaultView === "kanban" ? "Kanban board" : "Table view"}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{documents.length > 0 && (
|
|
|
|
|
<div>
|
|
|
|
|
<h2
|
|
|
|
|
className="text-sm font-medium uppercase tracking-wider mb-3"
|
|
|
|
|
style={{ color: "var(--color-text-muted)" }}
|
|
|
|
|
>
|
|
|
|
|
Documents
|
|
|
|
|
</h2>
|
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
|
|
|
{documents.map((doc) => (
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
key={doc.path}
|
|
|
|
|
onClick={() => onNodeSelect(doc)}
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="flex items-center gap-3 p-4 rounded-2xl text-left transition-all duration-100 cursor-pointer"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{
|
|
|
|
|
background: "var(--color-surface)",
|
|
|
|
|
border: "1px solid var(--color-border)",
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
boxShadow: "var(--shadow-sm)",
|
2026-02-11 16:45:07 -08:00
|
|
|
}}
|
|
|
|
|
onMouseEnter={(e) => {
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
(e.currentTarget as HTMLElement).style.borderColor = "var(--color-chip-document-text)";
|
2026-02-11 16:45:07 -08:00
|
|
|
}}
|
|
|
|
|
onMouseLeave={(e) => {
|
|
|
|
|
(e.currentTarget as HTMLElement).style.borderColor =
|
|
|
|
|
"var(--color-border)";
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<span
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
className="w-10 h-10 rounded-xl flex items-center justify-center flex-shrink-0"
|
2026-02-11 16:45:07 -08:00
|
|
|
style={{
|
feat(web): full UI redesign with light/dark theme, TanStack data tables, media rendering, and gateway-routed agent execution
Overhaul the Dench web app with a comprehensive visual redesign and several
major feature additions across the chat interface, workspace, and agent
runtime layer.
Theme & Design System
- Replace the dark-only palette with a full light/dark theme system that
respects system preference via localStorage + inline script (no FOUC).
- Introduce new design tokens: glassmorphism surfaces, semantic colors
(success/warning/error/info), object-type chip palettes, and a tiered
shadow scale (sm/md/lg/xl).
- Add Instrument Serif + Inter via Google Fonts for a refined typographic
hierarchy; headings use the serif face, body uses Inter.
- Rebrand UI from "Ironclaw" to "Dench" across the landing page and
metadata.
Chat & Chain-of-Thought
- Rewrite the chain-of-thought component with inline media detection and
rendering — images, video, audio, and PDFs referenced in agent output
are now displayed directly in the conversation thread.
- Add status indicator parts (e.g. "Preparing response...",
"Optimizing session context...") that render as subtle activity badges
instead of verbose reasoning blocks.
- Integrate react-markdown with remark-gfm for proper markdown rendering
in assistant messages (tables, strikethrough, autolinks, etc.).
- Improve report-block splitting and lazy-loaded ReportCard rendering.
Workspace
- Introduce @tanstack/react-table for the object table, replacing the
hand-rolled table with full column sorting, fuzzy filtering via
match-sorter-utils, row selection, and bulk actions.
- Add a new media viewer component for in-workspace image/video/PDF
preview.
- New API routes: bulk-delete entries, field management (CRUD + reorder),
raw-file serving endpoint for media assets.
- Redesign workspace sidebar, empty state, and entry detail modal with
the new theme tokens and improved layout.
Agent Runtime
- Switch web agent execution from --local to gateway-routed mode so
concurrent chat threads share the gateway's lane-based concurrency
system, eliminating cross-process file-lock contention.
- Advertise "tool-events" capability during WebSocket handshake so the
gateway streams tool start/update/result events to the UI.
- Add new agent callback hooks: onLifecycleStart, onCompactionStart/End,
and onToolUpdate for richer real-time feedback.
- Forward media URLs emitted by agent events into the chat stream.
Dependencies
- Add @tanstack/match-sorter-utils and @tanstack/react-table to the web
app.
Published as ironclaw@2026.2.10-1.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 11:17:23 -08:00
|
|
|
background: "var(--color-chip-document)",
|
|
|
|
|
color: "var(--color-chip-document-text)",
|
2026-02-11 16:45:07 -08:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<NodeTypeIcon type="document" />
|
|
|
|
|
</span>
|
|
|
|
|
<div className="min-w-0">
|
|
|
|
|
<div
|
|
|
|
|
className="text-sm font-medium truncate"
|
|
|
|
|
style={{ color: "var(--color-text)" }}
|
|
|
|
|
>
|
|
|
|
|
{doc.name.replace(/\.md$/, "")}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs" style={{ color: "var(--color-text-muted)" }}>
|
|
|
|
|
Document
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</button>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Shared icon for node types ---
|
|
|
|
|
|
|
|
|
|
function NodeTypeIcon({ type }: { type: string }) {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case "object":
|
|
|
|
|
return (
|
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M12 3v18" /><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M3 9h18" /><path d="M3 15h18" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
|
|
|
|
case "document":
|
|
|
|
|
return (
|
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" /><path d="M10 9H8" /><path d="M16 13H8" /><path d="M16 17H8" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
|
|
|
|
case "folder":
|
|
|
|
|
return (
|
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
2026-02-11 17:01:28 -08:00
|
|
|
case "database":
|
|
|
|
|
return (
|
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
|
|
|
|
<path d="M3 5V19A9 3 0 0 0 21 19V5" />
|
|
|
|
|
<path d="M3 12A9 3 0 0 0 21 12" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
2026-02-11 18:35:35 -08:00
|
|
|
case "report":
|
|
|
|
|
return (
|
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<line x1="12" x2="12" y1="20" y2="10" />
|
|
|
|
|
<line x1="18" x2="18" y1="20" y2="4" />
|
|
|
|
|
<line x1="6" x2="6" y1="20" y2="14" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
2026-02-11 16:45:07 -08:00
|
|
|
default:
|
|
|
|
|
return (
|
|
|
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
|
|
|
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v4a2 2 0 0 0 2 2h4" />
|
|
|
|
|
</svg>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|