- add SplitPanel to share left/right shell and narrow menu handling
- reuse InspectHeaderProps for tab header + actions across tabs
- refactor variables/artifacts tabs to plug into shared split layout
- align right-side header/close behavior and consolidate empty/loading flows
Consolidate duplicated TabHeader + close button layout (8 occurrences) into a single
InspectLayout wrapper. Replace boolean props with children slots for better composition.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enhance getFileIconType to accept an extension parameter and cover all
13 FileAppearanceTypeEnum types using an O(1) Map lookup. Update all
call sites to pass the API-provided extension for accurate icon display.
Refactor the variable inspect panel into a tabbed layout with Variables
and Artifacts tabs. Extract variable logic into VariablesTab, add new
ArtifactsTab with sandbox file tree selection and preview pane, and
improve accessibility across tree nodes and interactive elements.
Use useRef to store saveFile reference and remove it from useEffect
dependencies to prevent cleanup from re-triggering on reference changes.
Also normalize metadata before comparison when clearing dirty state to
ensure filtered tools match correctly.
Simpler approach: disable the view picker toggle when preview is running,
preventing users from switching views during active runs.
This replaces the previous mounted ref guard approach (commits a0188bd9b5,
b7f1eb9b7b, 8332f0de2b) which added complexity to handle post-unmount
operations. Disabling the toggle is more direct and follows KISS principle.
Changes:
- Add disabled prop to ViewPicker based on isResponding state
- Revert mounted ref guards in use-chat-flow-control.ts
- Revert isMountedRef parameter in use-nodes/edges-interactions-without-sync.ts
- Revert defensive type check in markdown-utils.ts (no longer needed)
In React StrictMode (dev mode), effects are run twice to help detect
side effects. The cleanup-only pattern left isMountedRef as false after
StrictMode's simulated unmount-remount cycle, causing stop/cancel
operations to be skipped even when the component was mounted.
Now the effect setup explicitly sets isMountedRef.current = true,
ensuring correct behavior in both development and production.
When switching from graph view to skill view during an active preview run,
SSE callbacks continue executing and attempt to update ReactFlow node/edge
states. This could cause errors since the component is unmounted.
Add optional `isMountedRef` parameter to `useNodesInteractionsWithoutSync`
and `useEdgesInteractionsWithoutSync` hooks. When provided, operations are
skipped if the component has unmounted, preventing potential errors while
allowing the SSE connection to continue running in the background.
BREAKING CHANGE: `useNodesInteractionsWithoutSync` and
`useEdgesInteractionsWithoutSync` now accept an optional `isMountedRef`
parameter. Existing callers are unaffected as the parameter is optional.
The sidebar layout was broken when ArtifactsSection expanded - it would
squeeze the FileTree and neither area could scroll. This restructures the
layout so each section has its own scroll container with proper height
constraints.
Remove enabled condition so data is fetched when component mounts,
allowing blue dot to show when files exist even before expanding.
TanStack Query handles request deduplication automatically.
- Replace placeholder with functional ArtifactsSection component
- Add ArtifactsTree component for file tree rendering
- Support expand/collapse with lazy loading
- Show blue dot indicator when collapsed with files
- Add empty state card with hint text
- Add download button on file hover
- Add i18n translations (en-US, zh-Hans)
Cover Ctrl+S and Cmd+S save triggers, guard clauses for start tab and
null active tab, success/error toast notifications, and fallback
registry integration.
Move global keyboard shortcut handling from component-level hook to
SkillSaveProvider, eliminating duplicate event listener registrations
and race conditions. Delete use-skill-file-save hook as its logic is
now consolidated in the provider with direct store access.
- use cleanup-based save on tab switch with stable fallback snapshots
- add fallback registry for metadata-only autosave consistency
- add autosave/save-manager tests
Centralize file save operations using Context/Provider pattern for better
maintainability. Add auto-save on tab switch, visibility change, page unload,
and component unmount.
Refactor StartTabContent into separate components following Figma design specs:
- ActionCard: reusable card with icon, title, description
- SectionHeader: title/xl-semi-bold header with description
- CreateImportSection: 3-column grid layout for Create/Import cards
- SkillTemplatesSection: templates area with placeholder
Align styles with Figma: 3-col grid, 16px title, proper spacing and padding.
Add i18n translations for all user-facing text (en-US, zh-Hans).
- Add guards in tool-block component to skip metadata read/write when Start tab is active
- Add guard in tool-picker-block to prevent writing tool config to Start tab
- Add guard in use-sync-tree-with-active-tab to skip tree sync for Start tab
- Add START_TAB_ID constant and StartTabItem/StartTabContent components
- Default to Start tab when no file tabs are open
- Optimize zustand selectors to subscribe to specific Map values instead of
entire Map objects, reducing unnecessary re-renders when other tabs change
- Refactor useSkillFileSave to accept precise values instead of Map/Set
Display a notice at the bottom of SQLite table preview when data
is truncated due to PREVIEW_ROW_LIMIT (1000 rows), informing users
that additional rows are not displayed.
Optimize folder upload by creating folders at the same depth level in
parallel and uploading all files concurrently. This reduces upload time
from O(n) sequential requests to O(depth) folder requests + 1 file request.
Add overflow-hidden to SkillPageLayout and min-w-0 to flex children
to ensure wide content (like SQLite tables with many columns) scrolls
internally rather than causing the entire page to scroll horizontally.
- Add min-w-0 to flex containers for proper text truncation
- Use w-max on table to ensure columns don't collapse
- Simplify table selector when only one table exists (remove dropdown)
Switch from whitelist to blacklist pattern for determining editable files.
Files are now editable unless they are known binary types (audio, archives,
executables, Office documents, fonts, etc.), enabling support for any
runtime-generated text files without needing to add extensions one by one.
- Convert clickable div to semantic button in artifacts-section
- Add aria-hidden to decorative icons
- Add aria-label to rename inputs and hidden file inputs
- Add i18n keys for artifacts section and rename labels
- Support ignore file extensions (.gitignore, etc.)
Replace event-based auto-expand trigger with Zustand state-driven
approach. Now both external file uploads and internal node drag use
the same isDragOver state as the single source of truth for folder
auto-expand timing (1s blink, 2s expand).
Simplify file drop hooks by removing the unnecessary useUnifiedDrag
wrapper that became redundant after internal node drag was migrated
to react-arborist's built-in system. Now useFolderFileDrop and
useRootFileDrop directly use useFileDrop, reducing code complexity
and eliminating unused treeChildren prop drilling.
Switch from native HTML5 drag to react-arborist's built-in drag system
for internal node drag-and-drop. The HTML5Backend used by react-arborist
was intercepting dragstart events, preventing native drag from working.
- Add onMove callback and disableDrop validation to Tree component
- Sync react-arborist drag state (isDragging, willReceiveDrop) to Zustand
- Simplify use-node-move to only handle API execution
- Update use-unified-drag to only handle external file uploads
- External file drops continue to work via native HTML5 events
Implement unified drag system that supports both internal node moves
and external file uploads with consistent UI feedback. Uses native
HTML5 drag API with shared visual states (isDragOver, isBlinking,
DragActionTooltip showing 'Move to' or 'Upload to').
Move searchTerm from props drilling to zustand store for cleaner
architecture. Remove unnecessary controlled/uncontrolled pattern
and unused debounce logic since search is pure frontend filtering.
- Add fileTreeSearchTerm state to file-tree-slice
- Remove useState and props from main.tsx
- Simplify sidebar-search-add.tsx to read/write store directly
- Add empty state UI with reset filter button
Remove the Office file placeholder that only showed "Preview will be
supported in a future update" without any download option. Office files
(pdf, doc, docx, xls, xlsx, ppt, pptx) now fall through to the generic
"unsupported file" handler which provides a download button.
Removed:
- OfficeFilePlaceholder component
- isOfficeFile function and OFFICE_EXTENSIONS constant
- isOffice flag from useFileTypeInfo hook
- i18n keys for officePlaceholder
This simplifies the file type handling to just three categories:
- Editable: markdown, code, text files → editor
- Previewable: image, video files → media preview
- Everything else: download button
Change useSkillFileData to use isEditable instead of isMediaFile:
- Editable files (markdown, code, text) fetch file content for editing
- Non-editable files (image, video, office, unsupported) fetch download URL
This fixes the download button for unsupported files which was incorrectly
using file content (UTF-8 decoded garbage) instead of the presigned URL.
Extract business logic into dedicated hooks to reduce component complexity:
- useFileTypeInfo: file type detection (markdown, code, image, video, etc.)
- useSkillFileData: data fetching with conditional API calls
- useSkillFileSave: save logic with Ctrl+S keyboard shortcut
Also fix Vercel best practice: use ternary instead of && for conditional rendering.
Previously, media files were fetched via getFileContent API which decodes
binary data as UTF-8, resulting in corrupted strings that cannot be used
as img/video src. Now media files use getFileDownloadUrl API to get a
presigned URL, enabling proper preview of images and videos of any size.
- Add constants.ts with ROOT_ID, CONTEXT_MENU_TYPE, NODE_MENU_TYPE
- Add root utilities to tree-utils.ts (isRootId, toApiParentId, etc.)
- Replace '__root__' with ROOT_ID for consistent root identifier
- Replace inline 'blank'/'root' strings with constants
- Use NodeMenuType for type-safe menu type props
- Remove duplicate ContextMenuType from types.ts, use from constants.ts
Remove redundant createTargetNodeId and use selectedTreeNodeId for both
visual highlight and creation target. This simplifies the state management
by having a single source of truth for tree selection, similar to VSCode's
file explorer behavior where both files and folders can be selected.
Split use-file-operations.ts (248 lines) into smaller focused hooks:
- use-create-operations.ts for file/folder creation and upload
- use-modify-operations.ts for rename and delete operations
- use-file-operations.ts now serves as orchestrator maintaining backward compatibility
Extract TreeNodeIcon component from tree-node.tsx for cleaner separation of concerns.
Add brief comments to drag hooks explaining their purpose and relationships.
When dragging files over a closed folder, the highlight now blinks
during the second half of the 2-second hover period to signal that
the folder is about to expand. This provides better visual feedback
similar to VSCode's drag-and-drop behavior.
Use Promise.all for concurrent file uploads instead of sequential
processing, improving upload performance for multiple files. Also
add isFileDrag check to handleFolderDragOver for consistency with
other drag handlers.
Enable users to drag files from their system directly into the file tree
to upload them. Files can be dropped on the tree container (uploads to root)
or on specific folders. Hovering over a closed folder for 2 seconds auto-
expands it. Uses Zustand for drag state management instead of React Context
for better performance.