Skip to content

feat(workbench): ✨ add integrated file management with editor, preview and CRUD#183

Open
HayWolf wants to merge 11 commits into
masterfrom
feat/file-mangement
Open

feat(workbench): ✨ add integrated file management with editor, preview and CRUD#183
HayWolf wants to merge 11 commits into
masterfrom
feat/file-mangement

Conversation

@HayWolf
Copy link
Copy Markdown
Contributor

@HayWolf HayWolf commented May 16, 2026

Summary

  • Add integrated file panel with editor, preview, and full CRUD operations for workspace files
  • Add root-level file/folder creation, copy path, and absolute path copy support in file context menu
  • Replace split-pane editor with overlay preview at dashboard level for cleaner workspace layout

Changes

  • Frontend: New file-editor-view, file-context-menu, file-editor-store, file-type-icons modules; updated project-panel, context-menu, dashboard-overlays, and ui-layout-store
  • Backend (Rust): New commands/file.rs with file CRUD IPC commands, model/file.rs model updates, and 183-line integration test suite in file_commands.rs
  • Assets: File type icon sprite SVG and icon ID mapping for 200+ file types
  • Bridge: New file-commands.ts service layer connecting frontend to Tauri backend

Test Plan

  • Create, rename, delete files and folders via context menu
  • Open file in editor overlay and verify preview rendering
  • Copy file path and absolute path from context menu
  • Verify file type icons display correctly across different extensions
  • Run cargo test --locked --manifest-path src-tauri/Cargo.toml
  • Run npm run typecheck

🤖 Generated with TiyCode

jorben added 6 commits May 16, 2026 00:53
… CRUD

Implement a unified file panel in the right-side drawer that combines
browsing, editing, previewing, creating, deleting and renaming files,
inspired by OpenChamber's FilesView but adapted for TiyCode's
AI-first sidebar layout. Key changes:

Rust backend (5 new Tauri commands):
- file_read/file_write/file_create/file_delete/file_rename with
  workspace-scoped path safety (canonicalize + starts_with guard)
- Binary detection, 5 MB size limit, image base64 encoding
- Path traversal rejection for ".." and path separators in names

Frontend file editor (CodeMirror 6):
- Tab management store with preview/pinned tab semantics (VS Code style)
- Auto-save with 1.5s debounce, dirty state detection, Cmd+S
- Language detection by extension (TS/JS/JSON/CSS/HTML/MD/Rust/Python)
- Preview modes for Markdown, HTML and images
- External content sync when Agent modifies open files

Workbench integration:
- Resizable drawer width (drag handle, min 320px, max 50vw, persisted)
- Split pane layout: file tree above, editor below (draggable divider)
- Dropdown menu (⋮ icon) replacing copy button for file CRUD actions
- New File/Folder dialogs with type toggle, Rename and Delete confirm

Agent file-changed event pipeline:
- ToolCompleted event now carries tool_name field
- AgentRunManager emits "agent-file-changed" for write/edit/patch tools
- Editor auto-reloads non-dirty tabs on receiving the event
… path

Add buttons in the project panel toolbar menu to create new files and
folders at the workspace root. Also add a button to copy the root project
path to clipboard. Refactor tree file opening logic to support directories,
allowing them to be opened in the configured external application. The
`NewFileDialog` component is now exported for reuse at the root level.
Replace the inline tree/editor split-pane layout with a modal
WorkbenchPreviewOverlay for file previews. This frees up panel space
and provides a focused editing view.

- Remove treeSplitRatio state, local storage persistence, and drag
  resize handling from file-editor-store and project-panel
- Open files via overlay instead of inline split pane; add temporary
  click-through during the open transition
- Refactor file type icons to CSS variables for adaptive light/dark
  theming and add folder-specific opacity modifier
- Add overlayClassName prop to WorkbenchPreviewOverlay for custom
  pointer-events control
- Fix flex overflow behavior with min-h-0 on editor and tree containers
…evel

Move the file editor preview overlay from ProjectPanel up to
DashboardOverlays to centralize overlay management at the dashboard
level.

Changes include:
- Lift isFileEditorOverlayOpen and isFileEditorOverlayClickThrough
  state from local React state into UILayoutStore
- Render WorkbenchPreviewOverlay and FileEditorView inside
  DashboardOverlays instead of ProjectPanel
- Pass resolvedWorkspaceId as a prop from DashboardWorkbench rather
  than deriving it inside DashboardOverlays
- Handle Escape key dismissal of the file editor overlay alongside
  other dashboard overlays

This improves separation of concerns and keeps overlay orchestration
in a single location.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 16, 2026

AI Code Review Summary

PR: #183 (feat(workbench): ✨ add integrated file management with editor, preview and CRUD)
Preferred language: English

Overall Assessment

Detected 15 actionable findings, prioritize CRITICAL/HIGH before merge.

Major Findings by Severity

  • CRITICAL (1)
    • src-tauri/src/commands/file.rs:257 - file_delete can recursively delete the workspace root
  • HIGH (6)
    • src-tauri/src/commands/file.rs:300 - file_rename allows workspace escape via new_name == '..'
    • src-tauri/tests/file_commands.rs - File commands integration tests do not invoke the actual command layer
    • src/modules/workbench-shell/model/file-editor-store.ts - Complex file editor store lacks unit tests
    • src/modules/workbench-shell/ui/dashboard-workbench.tsx:1114 - Drawer resize commits store update on every mousemove
    • src/modules/workbench-shell/ui/file-context-menu.tsx - Untested complex CRUD interactions in FileContextMenu
    • src/modules/workbench-shell/ui/file-editor-view.tsx - Untested FileEditorView editor lifecycle and shortcuts
  • MEDIUM (4)
    • src-tauri/tests/file_commands.rs:52 - Tautological assertion in path traversal test
    • src-tauri/tests/file_commands.rs - File command integration tests bypass the command layer
    • src/modules/workbench-shell/ui/file-context-menu.tsx:377 - Mixed path separators on Windows when copying absolute path
    • src/shared/lib/file-type-icons.ts - File type icon resolution logic is untested
  • LOW (4)
    • src-tauri/tests/file_commands.rs:52 - Flawed path traversal test assertion provides false assurance
    • src/modules/workbench-shell/ui/file-editor-view.tsx:380 - Binary file detection relies on hardcoded backend error string
    • src/modules/workbench-shell/ui/source-control-panels.tsx:1645 - Discard dialog uses hardcoded Cancel label
    • src/shared/lib/file-type-icons.ts:116 - Dead icon mappings reference missing sprite IDs

Actionable Suggestions

  • In file_delete, add an explicit guard that rejects paths resolving to the workspace root (e.g., '.' or empty string).
  • In file_rename, validate that new_name is not '.' or '..' and assert new_path.starts_with(root) before invoking tokio::fs::rename.
  • Audit all Rust pattern matches and frontend TypeScript deserializers for ThreadStreamEvent::ToolCompleted to accommodate the new toolName field.
  • Run cargo test --locked --manifest-path src-tauri/Cargo.toml and add integration tests in src-tauri/tests/ for the new file commands covering boundary cases.
  • Fix the tautological assertion in file_commands.rs by invoking safe_resolve and asserting an error result.
  • Rewrite file_commands.rs to call the actual command functions instead of std::fs.
  • Add an upper-bound clamp to readSavedDrawerWidth using MAX_DRAWER_WIDTH_RATIO.
  • Audit FILE_NAME_MAP and EXTENSION_MAP for IDs missing from FILE_TYPE_ICON_IDS and either remove them or add icons.
  • Clarify whether MAX_TABS should be a hard limit or a soft recommendation, and enforce accordingly.
  • Ensure backend file_read, file_write, file_create, file_delete, and file_rename commands canonicalize paths and verify they remain within the workspace root.
  • Ensure file_rename and file_create backend reject names containing path separators (/ or ).
  • Ensure git_restore and git_clean backend commands scope operations to the workspace repository and validate the paths array.

Potential Risks

  • Complete workspace deletion via file_delete with path '.'.
  • File movement outside workspace via file_rename with new_name = '..'.
  • Backend/frontend event deserialization failures due to the added tool_name field in ToolCompleted.
  • Path traversal protections are untested and could regress unnoticed.
  • Users may see incorrect fallback icons for specific file types.
  • Manually corrupted localStorage could break drawer layout temporarily.
  • Path traversal if backend file commands do not enforce workspace root containment.
  • Destructive data loss via git_restore and git_clean if backend scoping is missing.
  • False security assurance from tautological path-safety assertions that cannot fail.
  • Potential XSS if file editor error messages are rendered as HTML in downstream UI components.
  • False confidence from file_commands.rs integration tests
  • State management regressions in file editor and layout stores

Test Suggestions

  • Add backend integration tests for file_read, file_write, file_create, file_delete, and file_rename covering boundary cases (empty path, '.', '..', symlinks).
  • Add tests verifying file_delete rejects the workspace root and file_rename rejects '...'
  • Test git_restore and git_clean error branches with mocked Git output to ensure error classification is robust.
  • Add Rust unit/integration tests that call file command handlers directly with invalid paths.
  • Add Vitest tests for file-editor-store.ts covering tab eviction, auto-save scheduling, and dirty-state tracking.
  • Add a static or runtime validation that all icon map values exist in FILE_TYPE_ICON_IDS.
  • Replace placeholder file command tests with tests that directly invoke the Rust command functions to verify path traversal rejection and workspace scoping.
  • Add backend tests for git_restore and git_clean that verify repository scoping and path validation.
  • Vitest: file-editor-store.ts — openFile, closeTab, eviction, auto-save, saveFile, reloadTab
  • Vitest: file-type-icons.ts — resolveIconId and getFileTypeIconHref branches
  • Rust test: commands/file.rs — path safety, CRUD round-trips, binary detection, size limits, rename validation
  • Rust test: model/git.rs — GitMutationAction::action_name and skill_id for all variants

File-Level Coverage Notes

  • package-lock.json: no-risk (Generated lockfile; verify no suspicious registry URLs.)
  • package.json: low-risk (Ensure all added language packs are needed; unused ones can be removed to reduce bundle bloat.)
  • src-tauri/Cargo.toml: low-risk (Version bump from 0.2.5-rc.4 to 0.2.7-rc.0; verify compatibility in integration tests.)
  • src-tauri/src/commands/file.rs: contains critical and high severity issues (New file commands are well-structured with binary detection and base64 image handling, but path boundary guards are insufficient for delete and rename.)
  • src-tauri/src/commands/git.rs: low-risk (New restore/clean commands follow the established authorize_and_run_git_mutation pattern correctly.)
  • src-tauri/src/commands/mod.rs: no-risk (Trivial module declaration.)
  • src-tauri/src/core/agent_run_event_handler.rs: low-risk (Broadcasts AGENT_FILE_CHANGED defensively; verify frontend handles the new event payload.)
  • src-tauri/src/core/agent_session_execution.rs: low-risk (ToolCompleted emissions updated consistently with the new tool_name field.)
  • src-tauri/src/core/executors/git.rs: low-risk (Error mapping assumes English Git output strings; not a correctness issue but UX may suffer under non-English locales.)
  • src-tauri/src/core/executors/mod.rs: no-risk (Tool routing expanded to include git_restore and git_clean.)
  • src-tauri/src/core/git_manager.rs: no-risk (Wrappers delegate to executor and refresh snapshot as expected.)
  • src-tauri/src/core/policy_engine.rs: no-risk (Correctly categorizes git_restore and git_clean as mutating and plan-hard-denied.)
  • src-tauri/src/ipc/app_events.rs: no-risk (New event constant and payload struct are correctly defined.)
  • src-tauri/src/ipc/frontend_channels.rs: medium-risk (ToolCompleted gained a new field; ensure all consumers are updated.)
  • src-tauri/src/lib.rs: no-risk (New commands are properly registered in the Tauri invoke handler.)
  • src-tauri/src/model/file.rs: no-risk (Simple FileContentDto DTO with correct serialization attributes.)
  • src-tauri/src/core/agent_run_manager_tests.rs: Updated tests to include the new tool_name field in ToolCompleted events. Keeps tests compiling andaligned with struct changes.
  • src-tauri/src/model/git.rs: New enum variants added but no test coverage visible for their mappings.
  • src-tauri/src/model/mod.rs: New module declaration only.
  • src-tauri/tests/file_commands.rs: Tests std::fs operations rather than the actual file command layer. Provides minimal value and creates false confidence. (The file should be treated as a scaffold, not verified coverage.)
  • ... and 22 more file-level entries.

Inline Downgraded Items (processed but not inline)

  • src-tauri/tests/file_commands.rs: File commands integration tests do not invoke the actual command layer (line_missing_or_invalid)
  • src/modules/workbench-shell/model/file-editor-store.ts: Complex file editor store lacks unit tests (line_missing_or_invalid)
  • src/modules/workbench-shell/ui/file-context-menu.tsx: Untested complex CRUD interactions in FileContextMenu (file_level_finding)
  • src/modules/workbench-shell/ui/file-editor-view.tsx: Untested FileEditorView editor lifecycle and shortcuts (file_level_finding)
  • src-tauri/tests/file_commands.rs: File command integration tests bypass the command layer (file_level_finding)
  • src/shared/lib/file-type-icons.ts: File type icon resolution logic is untested (line_missing_or_invalid)

Coverage Status

  • Target files: 42
  • Covered files: 42
  • Uncovered files: 0
  • No-patch/binary covered as file-level: 0
  • Findings with unknown confidence (N/A): 0

Uncovered list:

  • None

No-patch covered list:

  • None

Runtime/Budget

  • Rounds used: 1/4
  • Planned batches: 3
  • Executed batches: 3
  • Sub-agent runs: 10
  • Planner calls: 1
  • Reviewer calls: 15
  • Model calls: 16/64
  • Structured-output summary-only degradation: NO

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 5
  • Findings with unknown confidence: 0
  • Inline comments attempted: 4
  • Target files: 33
  • Covered files: 15
  • Uncovered files: 18
    See the summary comment for detailed analysis and coverage details.

AppError::internal(ErrorSource::System, format!("Failed to read file: {e}"))
})?;

// Image files → base64 data URI

This comment was marked as outdated.

_ => {}
}

// Broadcast file-changed event when a file-mutating tool completes,

This comment was marked as outdated.

// Path safety tests (unit-level, no DB needed)
// ---------------------------------------------------------------------------

#[test]

This comment was marked as outdated.

// Path safety tests (unit-level, no DB needed)
// ---------------------------------------------------------------------------

#[test]

This comment was marked as outdated.

jorben added 2 commits May 16, 2026 09:58
Add comprehensive unit tests for internal file command helpers,
including path resolution, image detection, binary content
identification, and MIME type mapping.

Extract the inline MIME matching logic from `file_read` into a
dedicated `mime_for_extension` function to improve code clarity
and enable direct testing of the mapping behavior.

Test coverage includes:
- safe_resolve path traversal protection and valid path handling
- is_image_extension case-insensitive recognition
- is_binary_content null-byte detection
- End-to-end PNG data URI generation with base64 encoding
- Exhaustive MIME mapping for all supported image extensions
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 20
  • Findings with unknown confidence: 0
  • Inline comments attempted: 16
  • Target files: 35
  • Covered files: 35
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

path: String,
) -> Result<(), AppError> {
let root = resolve_workspace_root(&state, &workspace_id).await?;
let resolved = safe_resolve(&root, &path)?;

This comment was marked as outdated.

// ---------------------------------------------------------------------------

#[tauri::command]
pub async fn file_read(

This comment was marked as outdated.

// ---------------------------------------------------------------------------

#[test]
fn file_write_read_roundtrip() {

This comment was marked as outdated.

}

// Enforce MAX_TABS — evict oldest unpinned, non-dirty tab
while (nextTabs.length > MAX_TABS) {

This comment was marked as outdated.

// Auto-save debounce management
// ---------------------------------------------------------------------------

const autoSaveTimers = new Map<string, ReturnType<typeof setTimeout>>();

This comment was marked as outdated.

// Initial state
// ---------------------------------------------------------------------------

function readSavedDrawerWidth(): number {

This comment was marked as outdated.

const filterResults = filterState.data?.results ?? [];
const isFiltering = normalizedFilter.length > 0;

const handleOpenFileInEditor = useCallback((path: string) => {

This comment was marked as outdated.

const saved = window.localStorage.getItem(DRAWER_WIDTH_STORAGE_KEY);
if (saved) {
const n = parseInt(saved, 10);
if (!isNaN(n) && n >= 320) return n;

This comment was marked as outdated.

<div>
<AlertCircle className="mx-auto mb-2 size-5 text-destructive" />
<p className="text-xs text-muted-foreground">
{activeTab.error === "Binary file — cannot edit"

This comment was marked as outdated.

onClick={() => handleCreateRootEntry(false)}
>
<FilePlus className="size-4 shrink-0 text-app-subtle" />
<span className="min-w-0 flex-1 truncate text-[12px] font-medium">New File</span>

This comment was marked as outdated.

jorben added 3 commits May 16, 2026 10:48
…firmation dialog

Add context menu on Tracked and Untracked change groups in the Git
panel with a destructive 'Discard Changes' / 'Delete File' action.
Executing the action shows a confirmation dialog before proceeding.

- Tracked files: runs 'git restore -- <paths>' to revert to HEAD
- Untracked files: runs 'git clean -f -- <paths>' to delete from disk

Backend: add GitMutationAction::Restore/Clean variants, CLI executor
functions, git_manager methods, authorize_and_run_git_mutation
commands, policy engine registration, and lib.rs invoke_handler.

Frontend: add gitRestore/gitClean bridge (returns
GitMutationResponseDto), ContextMenu on ChangeGroup rows,
discard confirmation dialog, i18n keys for en/zh-CN, and mock mode
support.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR review completed.

  • Findings kept: 15
  • Findings with unknown confidence: 0
  • Inline comments attempted: 9
  • Target files: 42
  • Covered files: 42
  • Uncovered files: 0
    See the summary comment for detailed analysis and coverage details.

}

if resolved.is_dir() {
tokio::fs::remove_dir_all(&resolved).await.map_err(|e| {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL] file_delete can recursively delete the workspace root

When path is '.' or an empty string, safe_resolve returns the workspace root directory, and file_delete proceeds to remove_dir_all the entire workspace.

Suggestion: Reject deletion when the resolved path equals the workspace root (e.g., explicitly block '.' and '' in safe_resolve or add a root-equality guard in file_delete).

Risk: Complete loss of the workspace directory and all its contents.

Confidence: 0.95

[From SubAgent: general]

let parent = resolved_old.parent().ok_or_else(|| {
AppError::validation(ErrorSource::System, "Cannot determine parent directory")
})?;
let new_path = parent.join(&new_name);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] file_rename allows workspace escape via new_name == '..'

An attacker or buggy caller can pass new_name='..' to move a file from the workspace root to the parent directory, escaping the intended boundary.

Suggestion: Reject new_name values that are '.', '..', or empty, and assert new_path.starts_with(root) before renaming.

Risk: Files can be moved outside the workspace, leading to information disclosure or damage to external files.

Confidence: 0.92

[From SubAgent: general]

const onMove = (ev: MouseEvent) => {
const maxW = Math.floor(window.innerWidth * MAX_DRAWER_WIDTH_RATIO);
// Dragging left = increasing width (drawer is on right side)
const newWidth = Math.max(MIN_DRAWER_WIDTH, Math.min(maxW, startWidth + (startX - ev.clientX)));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Drawer resize commits store update on every mousemove

The drawer resize handler writes width changes to the global uiLayoutStore on every mousemove pixel, forcing DashboardWorkbench and any other drawerWidth subscribers to re-render continuously during drag.

Suggestion: Mutate the DOM element width directly via ref during the drag and only commit the final width to the store on mouseup, or throttle/RAF the store updates.

Risk: UI jank and dropped frames during drawer resizing, especially on lower-end hardware.

Confidence: 0.88

[From SubAgent: performance]

// If the joined path were canonicalized, it would NOT start_with(root)
// (unless by coincidence). The command code rejects ".." early.
assert!(
!joined.starts_with(root) || bad.contains(".."),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Tautological assertion in path traversal test

The test claiming to reject '..' traversal uses an assertion that can never fail, providing no confidence in the actual path-safety logic.

Suggestion: Replace the assertion with a real call to the safe_resolve helper (or the command under test) and assert that the returned result is an error for out-of-bound paths.

Risk: False sense of security around path traversal protection; regressions in safe_resolve would not be caught.

Confidence: 0.95

[From SubAgent: general]

<DropdownMenuItem
className="gap-2 text-xs"
onClick={() => {
const absolutePath = workspaceRoot
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] Mixed path separators on Windows when copying absolute path

The Copy Path feature concatenates workspaceRoot and nodePath with a forward slash, which on Windows produces mixed separators (e.g., C:\project\folder/file.ts).

Suggestion: Use the platform-specific separator from the backend or normalize the resulting path before copying.

Risk: Windows users will get malformed absolute paths in the clipboard.

Confidence: 0.90

[From SubAgent: general]

// If the joined path were canonicalized, it would NOT start_with(root)
// (unless by coincidence). The command code rejects ".." early.
assert!(
!joined.starts_with(root) || bad.contains(".."),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Flawed path traversal test assertion provides false assurance

The test rejects_dotdot_traversal asserts that a path is unsafe using !joined.starts_with(root) || bad.contains(".."). Because the input ../../etc/passwd always contains "..", the right-hand side is always true, making the assertion a tautology that can never fail regardless of whether the actual path-safety logic works.

Suggestion: Rewrite the test to invoke the actual safe_resolve helper or command function and assert that it returns an error or that the resolved path remains within the workspace root.

Risk: Provides false confidence that path traversal protections are validated by tests.

Confidence: 0.85

[From SubAgent: security]

<div>
<AlertCircle className="mx-auto mb-2 size-5 text-destructive" />
<p className="text-xs text-muted-foreground">
{activeTab.error === "Binary file — cannot edit"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Binary file detection relies on hardcoded backend error string

The UI switches to a friendly binary-file message only when the error exactly matches a hardcoded English string from the backend.

Suggestion: Use a typed error code (e.g., error.code === 'BINARY_FILE') instead of comparing human-readable text.

Risk: Backend wording changes or localization will break the special-case UI rendering.

Confidence: 0.85

[From SubAgent: general]

variant="outline"
onClick={clearDiscardTarget}
>
Cancel
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Discard dialog uses hardcoded Cancel label

The discard confirmation dialog button uses the literal string 'Cancel' instead of a translation key.

Suggestion: Replace 'Cancel' with t('common.cancel') or an existing equivalent translation key.

Risk: Untranslated UI in non-English locales.

Confidence: 0.95

[From SubAgent: general]

"webpack.config.ts": "webpack",
"esbuild.config.js": "esbuild",
"tsup.config.ts": "tsup",
"components.json": "shadcn-ui",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[LOW] Dead icon mappings reference missing sprite IDs

Several filename and extension mappings resolve to icon IDs that are not present in the sprite set, causing silent fallback to generic icons.

Suggestion: Remove or replace the missing IDs with valid ones from FILE_TYPE_ICON_IDS, and consider adding a static assertion or test that every map value exists in the set.

Risk: User-facing UI shows generic document icons for files that should have custom icons.

Confidence: 0.90

[From SubAgent: general]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants