From 97f2e58c5b6d4e1c0f3fedf898c831b6e2c6c4f8 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Fri, 23 Oct 2026 02:18:33 +0900 Subject: [PATCH] Move the file list and Settings navigator into components directories caffold-file-list was registered in the file navigator's private directory and caffold-settings-navigator beside the Settings layout, outside the components/ directories that hold Web Component entry points. Both move with their stylesheets and tests; markup and behavior are unchanged. The placement contract now checks all frontend JavaScript instead of only Tasks. --- caffold/src/static_assets.rs | 42 ++++++++++--------- frontend/components/file-navigator.css | 2 +- frontend/components/file-navigator.js | 2 +- .../file-navigator/{ => components}/list.css | 0 .../file-navigator/{ => components}/list.js | 16 +++---- .../{ => components}/list.test.js | 2 +- frontend/pages/(task-workspace)/layout.js | 2 +- .../settings/{ => components}/navigator.css | 0 .../settings/{ => components}/navigator.js | 2 +- .../{ => components}/navigator.test.js | 2 +- .../navigator/components/item.css | 0 .../navigator/components/item.js | 0 .../navigator/components/item.test.js | 2 +- frontend/service-worker.js | 12 +++--- frontend/styles.css | 4 +- .../contracts/appearance-ownership.test.mjs | 14 +++---- .../tests/contracts/css-ownership.test.mjs | 6 +-- .../task-workspace-structure.test.mjs | 9 ++-- frontend/tests/e2e/app-shell.spec.js | 2 +- 19 files changed, 60 insertions(+), 59 deletions(-) rename frontend/components/file-navigator/{ => components}/list.css (100%) rename frontend/components/file-navigator/{ => components}/list.js (98%) rename frontend/components/file-navigator/{ => components}/list.test.js (97%) rename frontend/pages/(task-workspace)/settings/{ => components}/navigator.css (100%) rename frontend/pages/(task-workspace)/settings/{ => components}/navigator.js (99%) rename frontend/pages/(task-workspace)/settings/{ => components}/navigator.test.js (98%) rename frontend/pages/(task-workspace)/settings/{ => components}/navigator/components/item.css (100%) rename frontend/pages/(task-workspace)/settings/{ => components}/navigator/components/item.js (100%) rename frontend/pages/(task-workspace)/settings/{ => components}/navigator/components/item.test.js (98%) diff --git a/caffold/src/static_assets.rs b/caffold/src/static_assets.rs index 18f102dd..778915e7 100644 --- a/caffold/src/static_assets.rs +++ b/caffold/src/static_assets.rs @@ -272,11 +272,11 @@ pub(crate) fn get(path: &str) -> Option { "components/file-navigator.js" => Some(js(include_str!( "../../frontend/components/file-navigator.js" ))), - "components/file-navigator/list.css" => Some(css(include_str!( - "../../frontend/components/file-navigator/list.css" + "components/file-navigator/components/list.css" => Some(css(include_str!( + "../../frontend/components/file-navigator/components/list.css" ))), - "components/file-navigator/list.js" => Some(js(include_str!( - "../../frontend/components/file-navigator/list.js" + "components/file-navigator/components/list.js" => Some(js(include_str!( + "../../frontend/components/file-navigator/components/list.js" ))), "components/pane-resizer.css" => Some(css(include_str!( "../../frontend/components/pane-resizer.css" @@ -380,18 +380,22 @@ pub(crate) fn get(path: &str) -> Option { "pages/(task-workspace)/settings/layout.js" => Some(js(include_str!( "../../frontend/pages/(task-workspace)/settings/layout.js" ))), - "pages/(task-workspace)/settings/navigator.css" => Some(css(include_str!( - "../../frontend/pages/(task-workspace)/settings/navigator.css" + "pages/(task-workspace)/settings/components/navigator.css" => Some(css(include_str!( + "../../frontend/pages/(task-workspace)/settings/components/navigator.css" ))), - "pages/(task-workspace)/settings/navigator.js" => Some(js(include_str!( - "../../frontend/pages/(task-workspace)/settings/navigator.js" - ))), - "pages/(task-workspace)/settings/navigator/components/item.css" => Some(css(include_str!( - "../../frontend/pages/(task-workspace)/settings/navigator/components/item.css" - ))), - "pages/(task-workspace)/settings/navigator/components/item.js" => Some(js(include_str!( - "../../frontend/pages/(task-workspace)/settings/navigator/components/item.js" + "pages/(task-workspace)/settings/components/navigator.js" => Some(js(include_str!( + "../../frontend/pages/(task-workspace)/settings/components/navigator.js" ))), + "pages/(task-workspace)/settings/components/navigator/components/item.css" => { + Some(css(include_str!( + "../../frontend/pages/(task-workspace)/settings/components/navigator/components/item.css" + ))) + } + "pages/(task-workspace)/settings/components/navigator/components/item.js" => { + Some(js(include_str!( + "../../frontend/pages/(task-workspace)/settings/components/navigator/components/item.js" + ))) + } "pages/(task-workspace)/settings/service-status.js" => Some(js(include_str!( "../../frontend/pages/(task-workspace)/settings/service-status.js" ))), @@ -1916,8 +1920,8 @@ mod tests { "pages/(task-workspace)/notes/components/navigator.js", "pages/(task-workspace)/notes/tree.js", "pages/(task-workspace)/settings/layout.js", - "pages/(task-workspace)/settings/navigator.js", - "pages/(task-workspace)/settings/navigator/components/item.js", + "pages/(task-workspace)/settings/components/navigator.js", + "pages/(task-workspace)/settings/components/navigator/components/item.js", "pages/(task-workspace)/settings/service-status.js", "pages/(task-workspace)/settings/keyboard/page.js", "pages/(task-workspace)/settings/files/page.js", @@ -1968,7 +1972,7 @@ mod tests { "pages/(task-workspace)/notes/components/info/components/copy-markdown.css", "pages/(task-workspace)/notes/components/info/components/copy-path.css", "pages/(task-workspace)/notes/components/navigator.css", - "pages/(task-workspace)/settings/navigator/components/item.css", + "pages/(task-workspace)/settings/components/navigator/components/item.css", "pages/(task-workspace)/settings/keyboard/page.css", "pages/(task-workspace)/codex-status/components/reset-credit-dialog.css", ] { @@ -3010,10 +3014,10 @@ mod tests { assert!( file_navigator_css .body - .starts_with(b"@import \"./file-navigator/list.css\"") + .starts_with(b"@import \"./file-navigator/components/list.css\"") ); - let file_list_component = get("components/file-navigator/list.js") + let file_list_component = get("components/file-navigator/components/list.js") .expect("file navigator list component js asset"); assert_eq!( file_list_component.content_type, diff --git a/frontend/components/file-navigator.css b/frontend/components/file-navigator.css index e55922ce..16695843 100644 --- a/frontend/components/file-navigator.css +++ b/frontend/components/file-navigator.css @@ -1,4 +1,4 @@ -@import "./file-navigator/list.css"; +@import "./file-navigator/components/list.css"; caffold-file-navigator { display: block; diff --git a/frontend/components/file-navigator.js b/frontend/components/file-navigator.js index 8abf2511..4de49636 100644 --- a/frontend/components/file-navigator.js +++ b/frontend/components/file-navigator.js @@ -1,6 +1,6 @@ import { listDirectory } from "../api.js"; import { createRefreshCoordinator, subscribeToWatch } from "../watch.js"; -import "./file-navigator/list.js"; +import "./file-navigator/components/list.js"; import { emptyActionHintScope } from "../action-hint-scope.js"; import { emptyScrollSurfaceScope } from "../scroll-scope.js"; diff --git a/frontend/components/file-navigator/list.css b/frontend/components/file-navigator/components/list.css similarity index 100% rename from frontend/components/file-navigator/list.css rename to frontend/components/file-navigator/components/list.css diff --git a/frontend/components/file-navigator/list.js b/frontend/components/file-navigator/components/list.js similarity index 98% rename from frontend/components/file-navigator/list.js rename to frontend/components/file-navigator/components/list.js index 69b4ac95..4d3a2d6e 100644 --- a/frontend/components/file-navigator/list.js +++ b/frontend/components/file-navigator/components/list.js @@ -1,6 +1,6 @@ -import { compactIconButton } from "../../component-styles.js"; -import { listDirectory } from "../../api.js"; -import { entryKindLabel, escapeHtml } from "../dom.js"; +import { compactIconButton } from "#app/component-styles.js"; +import { listDirectory } from "#app/api.js"; +import { entryKindLabel, escapeHtml } from "../../dom.js"; import { errorFileTreeChildren, FILE_TREE_LOAD_EVENT, @@ -8,15 +8,15 @@ import { loadingFileTreeChildren, readyFileTreeChildren, unloadedFileTreeChildren, -} from "../file-tree.js"; -import { renderInlineIcon } from "../icons.js"; -import "../loading-text.js"; +} from "../../file-tree.js"; +import { renderInlineIcon } from "../../icons.js"; +import "../../loading-text.js"; import { buttonActionHintTarget, emptyActionHintScope, mergeActionHintScopes, -} from "../../action-hint-scope.js"; -import { emptyScrollSurfaceScope } from "../../scroll-scope.js"; +} from "#app/action-hint-scope.js"; +import { emptyScrollSurfaceScope } from "#app/scroll-scope.js"; const TREE_LOADING_DELAY_MS = 180; diff --git a/frontend/components/file-navigator/list.test.js b/frontend/components/file-navigator/components/list.test.js similarity index 97% rename from frontend/components/file-navigator/list.test.js rename to frontend/components/file-navigator/components/list.test.js index 3e58baa1..d18ce0ff 100644 --- a/frontend/components/file-navigator/list.test.js +++ b/frontend/components/file-navigator/components/list.test.js @@ -3,7 +3,7 @@ import test, { after } from "node:test"; import { installCustomElementUnitRegistry, -} from "../../tests/support/custom-element-unit.js"; +} from "../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); await import("./list.js"); diff --git a/frontend/pages/(task-workspace)/layout.js b/frontend/pages/(task-workspace)/layout.js index 40b38cab..9a39a045 100644 --- a/frontend/pages/(task-workspace)/layout.js +++ b/frontend/pages/(task-workspace)/layout.js @@ -70,7 +70,7 @@ import "./tasks/components/navigator.js"; import "./tasks/layout.js"; import "./notes/components/navigator.js"; import "./notes/layout.js"; -import "./settings/navigator.js"; +import "./settings/components/navigator.js"; import "./settings/layout.js"; const NAVIGATION_PANE_DEFAULT_WIDTH = 380; diff --git a/frontend/pages/(task-workspace)/settings/navigator.css b/frontend/pages/(task-workspace)/settings/components/navigator.css similarity index 100% rename from frontend/pages/(task-workspace)/settings/navigator.css rename to frontend/pages/(task-workspace)/settings/components/navigator.css diff --git a/frontend/pages/(task-workspace)/settings/navigator.js b/frontend/pages/(task-workspace)/settings/components/navigator.js similarity index 99% rename from frontend/pages/(task-workspace)/settings/navigator.js rename to frontend/pages/(task-workspace)/settings/components/navigator.js index c32cc630..727af692 100644 --- a/frontend/pages/(task-workspace)/settings/navigator.js +++ b/frontend/pages/(task-workspace)/settings/components/navigator.js @@ -1,4 +1,4 @@ -import "../components/workspace-brand.js"; +import "../../components/workspace-brand.js"; import "./navigator/components/item.js"; import { emptyActionHintScope, diff --git a/frontend/pages/(task-workspace)/settings/navigator.test.js b/frontend/pages/(task-workspace)/settings/components/navigator.test.js similarity index 98% rename from frontend/pages/(task-workspace)/settings/navigator.test.js rename to frontend/pages/(task-workspace)/settings/components/navigator.test.js index f66277ab..f54d7b71 100644 --- a/frontend/pages/(task-workspace)/settings/navigator.test.js +++ b/frontend/pages/(task-workspace)/settings/components/navigator.test.js @@ -3,7 +3,7 @@ import test, { after } from "node:test"; import { installCustomElementUnitRegistry, -} from "../../../tests/support/custom-element-unit.js"; +} from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); await import("./navigator.js"); diff --git a/frontend/pages/(task-workspace)/settings/navigator/components/item.css b/frontend/pages/(task-workspace)/settings/components/navigator/components/item.css similarity index 100% rename from frontend/pages/(task-workspace)/settings/navigator/components/item.css rename to frontend/pages/(task-workspace)/settings/components/navigator/components/item.css diff --git a/frontend/pages/(task-workspace)/settings/navigator/components/item.js b/frontend/pages/(task-workspace)/settings/components/navigator/components/item.js similarity index 100% rename from frontend/pages/(task-workspace)/settings/navigator/components/item.js rename to frontend/pages/(task-workspace)/settings/components/navigator/components/item.js diff --git a/frontend/pages/(task-workspace)/settings/navigator/components/item.test.js b/frontend/pages/(task-workspace)/settings/components/navigator/components/item.test.js similarity index 98% rename from frontend/pages/(task-workspace)/settings/navigator/components/item.test.js rename to frontend/pages/(task-workspace)/settings/components/navigator/components/item.test.js index 629d5714..8023f521 100644 --- a/frontend/pages/(task-workspace)/settings/navigator/components/item.test.js +++ b/frontend/pages/(task-workspace)/settings/components/navigator/components/item.test.js @@ -3,7 +3,7 @@ import test, { after } from "node:test"; import { installCustomElementUnitRegistry, -} from "../../../../../tests/support/custom-element-unit.js"; +} from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); await import("./item.js"); diff --git a/frontend/service-worker.js b/frontend/service-worker.js index 6ddf1cf6..3292f025 100644 --- a/frontend/service-worker.js +++ b/frontend/service-worker.js @@ -98,8 +98,8 @@ const APP_SHELL_ASSETS = [ "/assets/components/file-tree.js", "/assets/components/file-navigator.css", "/assets/components/file-navigator.js", - "/assets/components/file-navigator/list.css", - "/assets/components/file-navigator/list.js", + "/assets/components/file-navigator/components/list.css", + "/assets/components/file-navigator/components/list.js", "/assets/components/pane-resizer.css", "/assets/components/pane-resizer.js", "/assets/components/review-responsive.js", @@ -183,10 +183,10 @@ const APP_SHELL_ASSETS = [ "/assets/pages/(task-workspace)/notes/tree.js", "/assets/pages/(task-workspace)/settings/layout.css", "/assets/pages/(task-workspace)/settings/layout.js", - "/assets/pages/(task-workspace)/settings/navigator.css", - "/assets/pages/(task-workspace)/settings/navigator.js", - "/assets/pages/(task-workspace)/settings/navigator/components/item.css", - "/assets/pages/(task-workspace)/settings/navigator/components/item.js", + "/assets/pages/(task-workspace)/settings/components/navigator.css", + "/assets/pages/(task-workspace)/settings/components/navigator.js", + "/assets/pages/(task-workspace)/settings/components/navigator/components/item.css", + "/assets/pages/(task-workspace)/settings/components/navigator/components/item.js", "/assets/pages/(task-workspace)/settings/service-status.js", "/assets/pages/(task-workspace)/settings/components/detail-list.css", "/assets/pages/(task-workspace)/settings/components/detail-list.js", diff --git a/frontend/styles.css b/frontend/styles.css index 5cf7e180..af7233a4 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -35,8 +35,8 @@ @import "./pages/(task-workspace)/notes/components/info/components/copy-path.css"; @import "./pages/(task-workspace)/notes/components/navigator.css"; @import "./pages/(task-workspace)/settings/layout.css"; -@import "./pages/(task-workspace)/settings/navigator.css"; -@import "./pages/(task-workspace)/settings/navigator/components/item.css"; +@import "./pages/(task-workspace)/settings/components/navigator.css"; +@import "./pages/(task-workspace)/settings/components/navigator/components/item.css"; @import "./pages/(task-workspace)/settings/components/detail-list.css"; @import "./pages/(task-workspace)/settings/components/refresh-button.css"; @import "./pages/(task-workspace)/settings/appearance/page.css"; diff --git a/frontend/tests/contracts/appearance-ownership.test.mjs b/frontend/tests/contracts/appearance-ownership.test.mjs index fc2c8a4c..e400781b 100644 --- a/frontend/tests/contracts/appearance-ownership.test.mjs +++ b/frontend/tests/contracts/appearance-ownership.test.mjs @@ -321,8 +321,8 @@ test("structural shadows separate fixed regions from floating elevation", () => [ "pages/(task-workspace)/notes/components/document.css", "pages/(task-workspace)/notes/components/navigator.css", + "pages/(task-workspace)/settings/components/navigator.css", "pages/(task-workspace)/settings/layout.css", - "pages/(task-workspace)/settings/navigator.css", "pages/(task-workspace)/tasks/(detail)/layout.css", "pages/(task-workspace)/tasks/components/navigator.css", "pages/(task-workspace)/tasks/components/task-absence.css", @@ -496,10 +496,10 @@ test("workspace header identity and titles share semantic ownership", () => { "pages/(task-workspace)/tasks/(detail)/(task)/layout.css", ); const settingsNavigator = readFrontend( - "pages/(task-workspace)/settings/navigator.css", + "pages/(task-workspace)/settings/components/navigator.css", ); const settingsNavigatorView = readFrontend( - "pages/(task-workspace)/settings/navigator.js", + "pages/(task-workspace)/settings/components/navigator.js", ); const settingsLayout = readFrontend( "pages/(task-workspace)/settings/layout.css", @@ -684,11 +684,11 @@ test("Settings roles share inherited constraints without sharing leaf selectors" ); } assert.match( - readFrontend("pages/(task-workspace)/settings/navigator.js"), + readFrontend("pages/(task-workspace)/settings/components/navigator.js"), /aria-label="Settings sections"/, ); assert.doesNotMatch( - readFrontend("pages/(task-workspace)/settings/navigator.js"), + readFrontend("pages/(task-workspace)/settings/components/navigator.js"), /Local to this browser/, ); @@ -789,7 +789,7 @@ test("visible inline icons have explicit block geometry from their UI owner", () ["pages/(task-workspace)/tasks/components/archived-task-list.css", ".task-repository-icon", "--task-list-icon-size"], ["pages/(task-workspace)/tasks/components/archived-task-list.css", ".task-row-worktree-icon", "--task-list-icon-size"], ["pages/(task-workspace)/tasks/components/archived-task-list.css", ".task-archived-action-icon", "--interface-icon-size"], - ["components/file-navigator/list.css", ".file-refresh-icon", "--interface-icon-size"], + ["components/file-navigator/components/list.css", ".file-refresh-icon", "--interface-icon-size"], ["components/file-tree.css", ".entry-icon-svg", "--file-tree-icon-size"], ["components/file-viewer.css", ".viewer-refresh-icon", "--interface-icon-size"], ["components/file-viewer.css", ".viewer-info-icon", "--interface-icon-size"], @@ -973,7 +973,7 @@ test("compact icon controls declare shared styles at their own component boundar ["pages/(task-workspace)/tasks/(detail)/(github)/layout", "caffold-task-github-layout", "> .task-github-surface > .task-domain-header > .task-domain-back"], ["pages/(task-workspace)/tasks/(detail)/terminal/page", "caffold-terminal-page", "> .terminal-page-bar > .terminal-page-button"], ["pages/(task-workspace)/tasks/(detail)/(git)/components/controls", "caffold-git-review-controls", "> .git-review-controls > .git-review-refresh"], - ["components/file-navigator/list", "caffold-file-list", "> .file-list-panel > header > .file-list-title-row > .file-list-actions > .file-refresh-button"], + ["components/file-navigator/components/list", "caffold-file-list", "> .file-list-panel > header > .file-list-title-row > .file-list-actions > .file-refresh-button"], ["components/pagination", "caffold-pagination", "> .pagination-panel > .pagination-button"], ["components/file-viewer", "caffold-review-file-viewer", "> .viewer-panel > .viewer-header > .viewer-title-row > .viewer-actions > .viewer-refresh-button"], ["components/file-viewer", "caffold-review-file-viewer", "> .viewer-panel > .viewer-header > .viewer-title-row > .viewer-close-button"], diff --git a/frontend/tests/contracts/css-ownership.test.mjs b/frontend/tests/contracts/css-ownership.test.mjs index 48c7d96f..34077052 100644 --- a/frontend/tests/contracts/css-ownership.test.mjs +++ b/frontend/tests/contracts/css-ownership.test.mjs @@ -16,7 +16,7 @@ const ownership = new Map([ ["components/file-viewer/components/code-viewer.css", ["caffold-code-viewer"]], ["components/file-viewer/components/diff-viewer.css", ["caffold-diff-viewer"]], ["components/file-navigator.css", ["caffold-file-navigator"]], - ["components/file-navigator/list.css", ["caffold-file-list"]], + ["components/file-navigator/components/list.css", ["caffold-file-list"]], ["components/file-tree.css", ["caffold-file-tree"]], [ "components/file-viewer.css", @@ -430,11 +430,11 @@ const ownership = new Map([ ["caffold-settings-workspace"], ], [ - "pages/(task-workspace)/settings/navigator.css", + "pages/(task-workspace)/settings/components/navigator.css", ["caffold-settings-navigator"], ], [ - "pages/(task-workspace)/settings/navigator/components/item.css", + "pages/(task-workspace)/settings/components/navigator/components/item.css", ["caffold-settings-navigator-item"], ], [ diff --git a/frontend/tests/contracts/task-workspace-structure.test.mjs b/frontend/tests/contracts/task-workspace-structure.test.mjs index 30638549..c5a559d0 100644 --- a/frontend/tests/contracts/task-workspace-structure.test.mjs +++ b/frontend/tests/contracts/task-workspace-structure.test.mjs @@ -495,12 +495,9 @@ test("common Detail owns shared surfaces while Task and Section keep subject wor assert.match(tasksLayout, /createAndAdoptTask/); }); -test("Tasks custom elements use routed owners or the nearest components namespace", () => { - const taskRoot = "pages/(task-workspace)/tasks/"; +test("custom elements use routed owners or the nearest components namespace", () => { const invalidOwners = frontendJavascriptFiles() - .filter(([path, source]) => - path.startsWith(taskRoot) && source.includes("customElements.define") - ) + .filter(([, source]) => source.includes("customElements.define")) .filter(([path]) => { const segments = path.split("/"); const fileName = segments.at(-1); @@ -540,7 +537,7 @@ test("workspace brand owns the shared Tasks, Notes, and Settings navigator ident "pages/(task-workspace)/tasks/components/navigator.js", ); const settingsNavigator = readFrontend( - "pages/(task-workspace)/settings/navigator.js", + "pages/(task-workspace)/settings/components/navigator.js", ); const notesNavigator = readFrontend( "pages/(task-workspace)/notes/components/navigator.js", diff --git a/frontend/tests/e2e/app-shell.spec.js b/frontend/tests/e2e/app-shell.spec.js index 80adfe16..06216462 100644 --- a/frontend/tests/e2e/app-shell.spec.js +++ b/frontend/tests/e2e/app-shell.spec.js @@ -66,7 +66,7 @@ test("serves one Task workspace shell and only active static assets", { tag: "@a "/assets/pages/(task-workspace)/codex-status/components/runtime-update-dialog.js", "/assets/pages/(task-workspace)/settings/components/detail-list.css", "/assets/pages/(task-workspace)/settings/components/detail-list.js", - "/assets/components/file-navigator/list.js", + "/assets/components/file-navigator/components/list.js", ]) { expect(serviceWorker).toContain(asset); expect((await request.get(asset)).ok(), asset).toBe(true);