From c66a5b35a4d656e033762ba7c9c237410d478675 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Sat, 24 Oct 2026 21:54:27 +0900 Subject: [PATCH 01/19] Write the pagination as a TypeScript LitElement The frontend can hold TypeScript modules: cargo build strips their types with Node.js and the server serves the result at the source path, beside Lit embedded from frontend/node_modules. The pagination and its page buttons are the first LitElement components, and a reattached pagination no longer reports one click twice. CI checks types with tsc, formats and lints the .ts files with Biome, and runs on Node.js 24. --- .github/workflows/browser-tests.yml | 4 +- .github/workflows/documentation-contracts.yml | 4 +- .github/workflows/frontend-tests.yml | 12 +- .../workflows/macos-packaging-contracts.yml | 4 +- .github/workflows/macos-release.yml | 10 +- .../workflows/repository-tooling-tests.yml | 4 +- .github/workflows/rust-checks.yml | 13 + CONTRIBUTING.md | 5 +- caffold/Cargo.toml | 3 + caffold/build.rs | 61 +++- caffold/src/static_assets.rs | 87 +++++- desktop/macos/tests/macos-release.test.mjs | 1 + docs/architecture/frontend.md | 64 +++- docs/development/macos-local-app.md | 4 +- docs/development/testing.md | 11 +- docs/operations/macos-release.md | 6 +- docs/review/frontend.md | 49 +-- frontend/README.md | 14 +- frontend/action-hint-scope.d.ts | 48 +++ frontend/biome.json | 16 + frontend/components/icons.js | 40 +-- frontend/components/pagination.css | 39 +-- frontend/components/pagination.js | 178 ----------- frontend/components/pagination.test.js | 136 +++------ frontend/components/pagination.ts | 128 ++++++++ .../pagination/components/button.css | 38 +++ .../pagination/components/button.test.js | 94 ++++++ .../pagination/components/button.ts | 115 +++++++ frontend/index.html | 8 +- frontend/package-lock.json | 286 +++++++++++++++++- frontend/package.json | 7 + .../tasks/(detail)/(git)/(log)/list/page.js | 2 +- .../(detail)/(github)/(issues)/list/page.js | 2 +- .../(detail)/(github)/(pulls)/list/page.js | 2 +- frontend/service-worker.js | 13 +- frontend/strip-types.mjs | 30 ++ frontend/styles.css | 1 + .../contracts/appearance-ownership.test.mjs | 15 +- .../tests/contracts/css-ownership.test.mjs | 1 + .../frontend-test-placement.test.mjs | 8 +- frontend/tests/contracts/lit-library.test.mjs | 125 ++++++++ .../tests/contracts/native-imports.test.mjs | 20 +- .../tests/contracts/service-worker.test.mjs | 4 +- .../contracts/typescript-modules.test.mjs | 45 +++ frontend/tests/e2e/component-styles.spec.js | 60 ++++ frontend/tests/e2e/native-imports.spec.js | 82 +++++ frontend/tests/support/custom-element-unit.js | 2 + frontend/tsconfig.json | 19 ++ 48 files changed, 1521 insertions(+), 399 deletions(-) create mode 100644 frontend/action-hint-scope.d.ts create mode 100644 frontend/biome.json delete mode 100644 frontend/components/pagination.js create mode 100644 frontend/components/pagination.ts create mode 100644 frontend/components/pagination/components/button.css create mode 100644 frontend/components/pagination/components/button.test.js create mode 100644 frontend/components/pagination/components/button.ts create mode 100644 frontend/strip-types.mjs create mode 100644 frontend/tests/contracts/lit-library.test.mjs create mode 100644 frontend/tests/contracts/typescript-modules.test.mjs create mode 100644 frontend/tsconfig.json diff --git a/.github/workflows/browser-tests.yml b/.github/workflows/browser-tests.yml index 05aa78a2..7f4274c5 100644 --- a/.github/workflows/browser-tests.yml +++ b/.github/workflows/browser-tests.yml @@ -34,10 +34,10 @@ jobs: - name: Restore Cargo cache uses: Swatinem/rust-cache@v2 - - name: Install Node.js 22 + - name: Install Node.js 24 uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: frontend/package-lock.json diff --git a/.github/workflows/documentation-contracts.yml b/.github/workflows/documentation-contracts.yml index 917b535c..8e5ac258 100644 --- a/.github/workflows/documentation-contracts.yml +++ b/.github/workflows/documentation-contracts.yml @@ -19,10 +19,10 @@ jobs: ref: ${{ github.sha }} persist-credentials: false - - name: Install Node.js 22 + - name: Install Node.js 24 uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 - name: Test documentation contracts run: node --test docs/tests/*.test.mjs diff --git a/.github/workflows/frontend-tests.yml b/.github/workflows/frontend-tests.yml index d2f05137..cf91b2b9 100644 --- a/.github/workflows/frontend-tests.yml +++ b/.github/workflows/frontend-tests.yml @@ -19,10 +19,10 @@ jobs: ref: ${{ github.sha }} persist-credentials: false - - name: Install Node.js 22 + - name: Install Node.js 24 uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 cache: npm cache-dependency-path: frontend/package-lock.json @@ -30,6 +30,14 @@ jobs: run: npm ci working-directory: frontend + - name: Check frontend types + run: npm run typecheck + working-directory: frontend + + - name: Lint frontend TypeScript + run: npm run lint + working-directory: frontend + - name: Test frontend units run: npm run test:unit working-directory: frontend diff --git a/.github/workflows/macos-packaging-contracts.yml b/.github/workflows/macos-packaging-contracts.yml index 32c95769..97961be9 100644 --- a/.github/workflows/macos-packaging-contracts.yml +++ b/.github/workflows/macos-packaging-contracts.yml @@ -19,10 +19,10 @@ jobs: ref: ${{ github.sha }} persist-credentials: false - - name: Install Node.js 22 + - name: Install Node.js 24 uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 # These verify shell scripts, workflow definitions, and documentation, so # they need no macOS host. The Swift programs and the packaging-metadata diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml index c66349e1..f5b14fe3 100644 --- a/.github/workflows/macos-release.yml +++ b/.github/workflows/macos-release.yml @@ -78,12 +78,18 @@ jobs: # Packaging requires a clean main worktree on this runner. git checkout -B main "${REQUESTED_SHA}" - - name: Install Node.js 22 + - name: Install Node.js 24 uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 package-manager-cache: false + # The server embeds Lit from frontend/node_modules and builds its + # TypeScript modules with Node.js. + - name: Install frontend runtime dependencies + run: npm ci --omit=dev + working-directory: frontend + - name: Bump the release version locally if: startsWith(inputs.action, 'release-') id: bump diff --git a/.github/workflows/repository-tooling-tests.yml b/.github/workflows/repository-tooling-tests.yml index 9ab3b448..99c31e08 100644 --- a/.github/workflows/repository-tooling-tests.yml +++ b/.github/workflows/repository-tooling-tests.yml @@ -19,10 +19,10 @@ jobs: ref: ${{ github.sha }} persist-credentials: false - - name: Install Node.js 22 + - name: Install Node.js 24 uses: actions/setup-node@v7 with: - node-version: 22 + node-version: 24 - name: Test repository tooling run: node --test scripts/tests/*.test.mjs diff --git a/.github/workflows/rust-checks.yml b/.github/workflows/rust-checks.yml index 4ab32215..bb8e2d62 100644 --- a/.github/workflows/rust-checks.yml +++ b/.github/workflows/rust-checks.yml @@ -30,6 +30,19 @@ jobs: - name: Restore Cargo cache uses: Swatinem/rust-cache@v2 + - name: Install Node.js 24 + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + cache-dependency-path: frontend/package-lock.json + + # The server embeds Lit from frontend/node_modules and builds its + # TypeScript modules with Node.js. + - name: Install frontend runtime dependencies + run: npm ci --omit=dev + working-directory: frontend + - name: Check formatting run: cargo fmt --check diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index caac8f32..27abafd8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ the changed boundary, and record what was and was not verified. The supported contributor baseline is: - the Rust toolchain pinned by `rust-toolchain.toml`; -- Node.js 22 and npm; +- Node.js 24 or newer and npm; - Git; - CMake and the Xcode Command Line Tools on macOS; - the authenticated standalone Codex CLI for Codex live work; @@ -29,6 +29,9 @@ xcode-select --install The npm package lives in `frontend/`, so JavaScript and browser commands run from that directory while Rust commands stay at the repository root. The +server embeds Lit from `frontend/node_modules` and runs Node.js to build the +frontend's TypeScript modules, so run `npm ci` there before the first Cargo +build in a checkout or worktree, and keep Node.js on `PATH` while building. The ordinary browser suite also needs Playwright's Chromium build: ```sh diff --git a/caffold/Cargo.toml b/caffold/Cargo.toml index b327e224..732023aa 100644 --- a/caffold/Cargo.toml +++ b/caffold/Cargo.toml @@ -58,6 +58,9 @@ whisper-rs = { version = "0.16.0", features = ["tracing_backend"] } [target.'cfg(target_os = "macos")'.dependencies] whisper-rs = { version = "0.16.0", features = ["metal"] } +[build-dependencies] +serde_json = "1.0.140" + [dev-dependencies] tempfile = "3.20.0" tokio = { version = "1.45.1", features = ["test-util"] } diff --git a/caffold/build.rs b/caffold/build.rs index c8e9db22..18b1be51 100644 --- a/caffold/build.rs +++ b/caffold/build.rs @@ -1,6 +1,7 @@ +use serde_json::Value; use std::{ env, fs, - path::PathBuf, + path::{Path, PathBuf}, process::Command, time::{SystemTime, UNIX_EPOCH}, }; @@ -10,6 +11,17 @@ fn main() { println!("cargo:rerun-if-changed=src"); println!("cargo:rerun-if-changed=Cargo.toml"); + let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("Cargo OUT_DIR must be set")); + + if let Err(problem) = check_installed_lit() { + println!("cargo::error={problem}; run `npm ci` in frontend/ before building"); + return; + } + if let Err(problem) = strip_frontend_types(&out_dir.join("frontend")) { + println!("cargo::error={problem}"); + return; + } + let commit = git_output(&["rev-parse", "--short=8", "HEAD"]).unwrap_or_else(|| "unknown".to_string()); let dirty = git_output(&["status", "--porcelain", "--untracked-files=no"]) @@ -28,7 +40,6 @@ fn main() { let module = format!( "export const BUILD_INFO = Object.freeze({{\n id: {build_id:?},\n label: {build_id:?},\n version: {version:?},\n commit: {commit_label:?},\n number: {build_number},\n}});\n" ); - let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("Cargo OUT_DIR must be set")); fs::write(out_dir.join("build-info.js"), module).expect("build info module must be written"); println!("cargo:rustc-env=CAFFOLD_BUILD_ID={build_id}"); @@ -36,6 +47,52 @@ fn main() { println!("cargo:rustc-env=CAFFOLD_BUILD_NUMBER={build_number}"); } +// The static asset table embeds Lit from frontend/node_modules, which must +// hold the version frontend/package.json names. +fn check_installed_lit() -> Result<(), String> { + let wanted = read_json("../frontend/package.json") + .and_then(|package| package["dependencies"]["lit"].as_str().map(str::to_owned)) + .ok_or("frontend/package.json does not name a Lit version")?; + let installed = read_json("../frontend/node_modules/lit/package.json") + .and_then(|package| package["version"].as_str().map(str::to_owned)); + match installed { + Some(installed) if installed == wanted => Ok(()), + Some(installed) => Err(format!( + "frontend/node_modules holds lit {installed}, but frontend/package.json needs {wanted}" + )), + None => Err(format!("frontend/node_modules does not hold lit {wanted}")), + } +} + +// The static asset table embeds each frontend TypeScript module as the +// JavaScript this writes to the same path under `output`. +fn strip_frontend_types(output: &Path) -> Result<(), String> { + let status = Command::new("node") + .args([ + "--disable-warning=ExperimentalWarning", + "../frontend/strip-types.mjs", + "../frontend", + ]) + .arg(output) + .status() + .map_err(|error| { + format!( + "Node.js 24 or newer must be on PATH to build the frontend's TypeScript modules: {error}" + ) + })?; + if status.success() { + Ok(()) + } else { + Err(format!( + "frontend/strip-types.mjs could not write the frontend's TypeScript modules ({status})" + )) + } +} + +fn read_json(path: &str) -> Option { + serde_json::from_str(&fs::read_to_string(path).ok()?).ok() +} + fn git_output(args: &[&str]) -> Option { let output = Command::new("git").args(args).output().ok()?; output diff --git a/caffold/src/static_assets.rs b/caffold/src/static_assets.rs index 778915e7..e6d13487 100644 --- a/caffold/src/static_assets.rs +++ b/caffold/src/static_assets.rs @@ -17,6 +17,31 @@ pub(crate) fn get(path: &str) -> Option { )))), "styles.css" => Some(css(include_str!("../../frontend/styles.css"))), "app.js" => Some(js(include_str!("../../frontend/app.js"))), + "lit/index.js" => Some(js(include_str!("../../frontend/node_modules/lit/index.js"))), + "lit/directives/unsafe-html.js" => Some(js(include_str!( + "../../frontend/node_modules/lit/directives/unsafe-html.js" + ))), + "lit-html/lit-html.js" => Some(js(include_str!( + "../../frontend/node_modules/lit-html/lit-html.js" + ))), + "lit-html/directive.js" => Some(js(include_str!( + "../../frontend/node_modules/lit-html/directive.js" + ))), + "lit-html/directives/unsafe-html.js" => Some(js(include_str!( + "../../frontend/node_modules/lit-html/directives/unsafe-html.js" + ))), + "lit-html/is-server.js" => Some(js(include_str!( + "../../frontend/node_modules/lit-html/is-server.js" + ))), + "lit-element/lit-element.js" => Some(js(include_str!( + "../../frontend/node_modules/lit-element/lit-element.js" + ))), + "@lit/reactive-element/reactive-element.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/reactive-element.js" + ))), + "@lit/reactive-element/css-tag.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/css-tag.js" + ))), "api.js" => Some(js(include_str!("../../frontend/api.js"))), "action-hint-scope.js" => Some(js(include_str!("../../frontend/action-hint-scope.js"))), "component-styles.js" => Some(js(include_str!("../../frontend/component-styles.js"))), @@ -1555,9 +1580,17 @@ pub(crate) fn get(path: &str) -> Option { "components/pagination.css" => Some(css(include_str!( "../../frontend/components/pagination.css" ))), - "components/pagination.js" => { - Some(js(include_str!("../../frontend/components/pagination.js"))) - } + "components/pagination.ts" => Some(js(include_str!(concat!( + env!("OUT_DIR"), + "/frontend/components/pagination.ts" + )))), + "components/pagination/components/button.css" => Some(css(include_str!( + "../../frontend/components/pagination/components/button.css" + ))), + "components/pagination/components/button.ts" => Some(js(include_str!(concat!( + env!("OUT_DIR"), + "/frontend/components/pagination/components/button.ts" + )))), "components/terminal-view.css" => Some(css(include_str!( "../../frontend/components/terminal-view.css" ))), @@ -1651,6 +1684,53 @@ mod tests { assert!(get("component-styles.test.js").is_none()); } + #[test] + fn serves_lit_from_the_installed_packages() { + for path in [ + "lit/index.js", + "lit/directives/unsafe-html.js", + "lit-html/lit-html.js", + "lit-html/directive.js", + "lit-html/directives/unsafe-html.js", + "lit-html/is-server.js", + "lit-element/lit-element.js", + "@lit/reactive-element/reactive-element.js", + "@lit/reactive-element/css-tag.js", + ] { + let asset = get(path).unwrap_or_else(|| panic!("missing Lit asset {path}")); + assert_eq!(asset.content_type, "text/javascript; charset=utf-8"); + // The body is the module itself: a license comment or a module statement. + assert!( + [b"/**".as_slice(), b"import", b"export"] + .iter() + .any(|start| asset.body.starts_with(start)) + ); + } + assert!(get("lit-html/lit-html.js.map").is_none()); + } + + #[test] + fn serves_typescript_modules_as_javascript_without_their_types() { + for (path, class) in [ + ( + "components/pagination.ts", + "class CaffoldPagination extends LitElement", + ), + ( + "components/pagination/components/button.ts", + "class CaffoldPaginationButton extends LitElement", + ), + ] { + let module = get(path).unwrap_or_else(|| panic!("missing TypeScript module {path}")); + assert_eq!(module.content_type, "text/javascript; charset=utf-8"); + let source = std::str::from_utf8(module.body).expect("UTF-8 module"); + assert!(source.contains(class)); + assert!(!source.contains("declare page: number")); + } + assert!(get("components/pagination.js").is_none()); + assert!(get("action-hint-scope.d.ts").is_none()); + } + #[test] fn serves_pwa_icon_assets() { let manifest = get("manifest.webmanifest").expect("manifest asset"); @@ -1960,6 +2040,7 @@ mod tests { ); for path in [ + "components/pagination/components/button.css", "keyboard-navigation/components/hud.css", "keyboard-navigation/components/selector.css", "keyboard-navigation/components/presentation.css", diff --git a/desktop/macos/tests/macos-release.test.mjs b/desktop/macos/tests/macos-release.test.mjs index 2af4433e..afea55a1 100644 --- a/desktop/macos/tests/macos-release.test.mjs +++ b/desktop/macos/tests/macos-release.test.mjs @@ -221,6 +221,7 @@ test("release checks the source before versioning and packaging on macOS", () => assert.ok(macos.includes(`- name: ${name}\n if: startsWith(inputs.action, 'release-')`)); } const ordered = [ + "npm ci --omit=dev", "node scripts/bump-release-version.mjs", 'git commit -m "Release v${RELEASE_VERSION}"', "desktop/macos/test-contracts", "desktop/macos/test-runtime", "cargo test --locked", "cargo clippy --locked --all-targets -- -D warnings", diff --git a/docs/architecture/frontend.md b/docs/architecture/frontend.md index 7ad9b84f..b538bb0e 100644 --- a/docs/architecture/frontend.md +++ b/docs/architecture/frontend.md @@ -37,8 +37,21 @@ directories, an expanded module directory contains no `page.js` or `layout.js`. ## Native module imports -JavaScript modules run directly without a bundler or transpiler. Shared imports -can use three native aliases: +Modules run in the browser without a bundler. A TypeScript module (`.ts`) loses +only its type syntax: when the server is built, `frontend/strip-types.mjs` +replaces that syntax with spaces, and the server serves the result at the +module's own path as JavaScript, so its lines and columns match the source. +Node runs the same `.ts` files directly in unit tests. Because the build only +removes types, modules use TypeScript syntax that erases cleanly, without +`enum`, `namespace`, or decorators. + +A module moves to TypeScript whole: `name.ts` is typed throughout under the +strict check, and `name.js` has not moved yet. While a JavaScript module has +TypeScript callers, a same-stem `name.d.ts` beside it declares the part they +use, and it goes when the module becomes `name.ts`. The TypeScript-modules +contract keeps each `.d.ts` beside its `.js` and no module in both languages. + +Shared imports can use three native aliases: | Prefix | Frontend-relative directory | Example | | --- | --- | --- | @@ -53,8 +66,9 @@ the browser's `/assets/` URL and the frontend package directory respectively. Use relative imports within the same owner and aliases for shared dependencies. Use `#components/` for shared components and `#tasks/` for Task-owned shared -modules; `#app/` covers other app-wide modules. Retain explicit `.js` extensions -and the existing public-entry-point and private-implementation boundaries. +modules; `#app/` covers other app-wide modules. Retain explicit `.js` or `.ts` +extensions and the existing public-entry-point and private-implementation +boundaries. Aliases only change path resolution, not module ownership or registration. Production imports that ascend three or more directories use these aliases. @@ -64,6 +78,38 @@ resolve aliases to the same files, and that those files are served and cached. Worker and AudioWorklet modules do not inherit the document's import map; their imports and asset URLs remain relative or explicit URLs. +Lit is imported through its `lit` package, as `lit` and `lit/…`. The import +map sends those, and the `lit-html`, `lit-element`, and `@lit/reactive-element` +modules they import, to `/assets/`, which the server answers with the release +`frontend/package.json` pins as a runtime dependency, embedded from +`frontend/node_modules` when the server is built. A build therefore needs +`npm ci` in `frontend/` first and Node.js on `PATH` for the TypeScript modules. +Each package's modules reach its core through paths that resolve to one URL, so +a page loads one module per package. Node unit tests resolve the same +specifiers through the packages' exports to their Node builds, which stand in +for the browser globals Node lacks. + +## Rendering + +A component that renders through Lit extends `LitElement` and renders into its +own Light DOM. The state it shows lives in reactive properties. When one +changes, Lit calls `render()` once the code that changed it has finished, so +the DOM changes then rather than at the assignment. Work that reads the +rendered DOM, such as scroll restoration, focus, or measurement, runs in +`updated()`, and an owner that needs a child's rendered DOM awaits the child's +`updateComplete`. Event listeners bound in a template come and go with their +nodes. + +Lit keeps the nodes of a rendered template across later renders and commits +only the bindings whose values changed. Rendering a different template at the +same place, or reordering a list rendered without keys, creates new nodes +there. While a template stays rendered, Lit leaves alone the children of an +element that has no bindings inside. It escapes bound values; raw HTML goes +through its `unsafeHTML` directive. Components choose templates, list keys, and +binding-free containers so that the identity +[Incremental DOM updates](../review/frontend.md#incremental-dom-updates) +requires survives. + ## Routed hierarchy ```text @@ -1976,7 +2022,8 @@ frontend/ |-- git-compare-browser.js |-- git-compare-tree.js |-- markdown-preview.js - |-- pagination.js + |-- pagination.ts + |-- pagination/components/button.ts |-- pdf-viewer.js |-- docx-viewer.js |-- pptx-viewer.js @@ -2122,8 +2169,11 @@ Every production JavaScript/CSS asset must be registered consistently in: - static asset and CSS ownership tests. The service-worker cache and Rust static-asset table are exact manifests of the -assets imported by the active application hierarchy. pdf.js, docx-preview, -pptx-renderer, SheetJS, and xterm.js with its fit addon are the exceptions: +assets imported by the active application hierarchy. Lit's modules are part +of both even though they come from `frontend/node_modules`, and a TypeScript +module is listed at its `.ts` path. pdf.js, +docx-preview, pptx-renderer, SheetJS, and xterm.js with its fit addon are the +exceptions: their owners import pinned releases at first use, and the browser suite answers those URLs from the same versions in `node_modules`. SheetJS comes from its own CDN, which publishes its current releases there rather than on npm, and the diff --git a/docs/development/macos-local-app.md b/docs/development/macos-local-app.md index 9ad2c8b5..00b613a3 100644 --- a/docs/development/macos-local-app.md +++ b/docs/development/macos-local-app.md @@ -13,7 +13,9 @@ backend to reattach to any sessions it still holds. ## Install a local build -From the repository root: +From the repository root, after `npm ci` in `frontend/`; the backend embeds +Lit from `frontend/node_modules` and runs Node.js to build the frontend's +TypeScript modules: ```sh desktop/macos/install-local diff --git a/docs/development/testing.md b/docs/development/testing.md index e398e715..8171dbf3 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -10,7 +10,7 @@ provide different evidence and must be reported separately. The npm package lives in `frontend/`. Run every JavaScript and browser command from that directory; Rust and release commands stay at the repository root. -Use Node.js 22 and the committed npm lockfile: +Use Node.js 24 or newer and the committed npm lockfile: ```sh cd frontend @@ -18,6 +18,11 @@ npm ci npx playwright install chromium ``` +`npm ci` also installs the Lit release the server embeds, so it comes before +the Rust checks as well as the browser suites. Cargo builds run Node.js to +remove the type syntax of the frontend's TypeScript modules, and Node runs +those modules directly in unit tests. + Rust checks use the 1.96 toolchain pinned by `rust-toolchain.toml` and bounded by `Cargo.toml`'s minimum supported version. @@ -61,6 +66,8 @@ one records where it runs from and what it needs: | Command | Run from | Requires | Boundary | | --- | --- | --- | --- | +| `npm run typecheck` | `frontend/` | Node | TypeScript types of the frontend's `.ts` modules | +| `npm run lint` | `frontend/` | Node | Biome formatting, import order, and lint rules of the frontend's `.ts` modules | | `npm run test:unit` | `frontend/` | Node | all focused frontend Node unit tests | | `npm run test:contract` | `frontend/` | Node | frontend policy and browser-infrastructure contracts, plus the repository, release, and protocol contracts that have not yet moved to an owner | | `npm run test:e2e` | `frontend/` | Node, Chromium, a built server | deterministic fixture-backed Playwright coverage | @@ -109,7 +116,7 @@ verification it performs: | Job | Reusable workflow in `.github/workflows/` | Verifies | | --- | --- | --- | -| Frontend Tests | `frontend-tests.yml` | colocated units and frontend contracts | +| Frontend Tests | `frontend-tests.yml` | TypeScript types, formatting and lints of the `.ts` modules, colocated units, and frontend contracts | | Documentation Contracts | `documentation-contracts.yml` | the documentation index, links, and command index, and the user manual's strict build | | Repository Tooling Tests | `repository-tooling-tests.yml` | the release version tooling, by calling it | | macOS Packaging Contracts | `macos-packaging-contracts.yml` | packaging, release, and installer definitions | diff --git a/docs/operations/macos-release.md b/docs/operations/macos-release.md index 1e49fa94..a3179d02 100644 --- a/docs/operations/macos-release.md +++ b/docs/operations/macos-release.md @@ -25,7 +25,9 @@ The app bundle uses: ## Local preparation -Run from a clean `main` worktree: +Run from a clean `main` worktree, after `npm ci` in `frontend/`; the backend +embeds Lit from `frontend/node_modules` and runs Node.js to build the +frontend's TypeScript modules: ```sh desktop/macos/release --dry-run @@ -58,7 +60,7 @@ artifact; it does not publish anything. The Checks and Release entrypoints directly call the same owner workflows on Ubuntu. Each checks out the caller's commit and takes no release-specific inputs: -- `frontend-tests.yml`: frontend units and contracts. +- `frontend-tests.yml`: frontend types, TypeScript formatting and lints, units, and contracts. - `documentation-contracts.yml`: documentation contracts. - `repository-tooling-tests.yml`: release version tooling. - `macos-packaging-contracts.yml`: portable packaging and installer contracts. diff --git a/docs/review/frontend.md b/docs/review/frontend.md index 56562dac..0ff49657 100644 --- a/docs/review/frontend.md +++ b/docs/review/frontend.md @@ -142,6 +142,13 @@ Apply the common [Source Module Ownership](policy.md#source-module-ownership) rules when deciding whether frontend implementation remains private to its current owner or moves to a shared boundary. +A module may be written in TypeScript as `name.ts` instead of `name.js`; the +rules below apply to it the same way. A module moves to TypeScript whole, not +partly to publish its types. A type belongs to the module that owns the +contract it describes, and other modules import it rather than declaring it +again. While a JavaScript module has TypeScript callers, a same-stem +`name.d.ts` declares the part they use until the module becomes `name.ts`. + When one frontend module grows beyond a single file, use an adjacent same-stem directory: `name.js` and `name/` form one ownership boundary. `name.js` is the primary public entry point for non-visual behavior. `name/` contains the @@ -341,25 +348,29 @@ prove that its external observations or side effects are wired correctly. ### Incremental DOM updates -Caffold's frontend direction favors browser-owned, long-lived DOM over adding -a React-style virtual tree or a generic keyed reconciliation layer. This is a -preference for new work and for paths already being changed, not a requirement -to rewrite every existing `render()` method before an adjacent improvement can -land. - -Where the component already owns stable elements, prefer applying the domain -change directly to the smallest affected DOM surface. A title change can update -text, a status change can update its status presentation, and an ordering -change can move the existing row. Equivalent canonical snapshots should -ideally be DOM no-ops. Preserve node identity when it carries animation, focus, -selection, scroll, disclosure, media, or custom-element lifecycle continuity. - -Initial mounting and meaningful structural transitions may still render a -larger subtree when that remains the clearest implementation. Improve broad -render paths incrementally as related behavior is touched; do not turn a local -identity fix into a new rendering framework or an unrelated rewrite. If direct -patching would become brittle, prefer a clearer component ownership boundary or -a deliberately scoped rerender over accumulating hidden DOM assumptions. +Components render through Lit's reactive update: the state a component shows +lives in its reactive properties, and Lit renders after the code that changed +them finishes. Work that reads the rendered DOM belongs after that update, not +right after the state change, and a result awaited across an update is still +checked against its request identity. State transitions and their effects stay +with the owner's transition authority; the update lifecycle renders and does +the DOM work that follows, but it does not start transitions of its own. Do not +add a virtual tree or another rendering layer on top. String-built markup is +converted when its path changes; an adjacent improvement does not have to +convert it first. + +Equivalent canonical snapshots should be DOM no-ops. Node identity that +carries animation, focus, selection, scroll, disclosure, media, or +custom-element lifecycle continuity must survive state changes and list +reordering. Content another owner manages imperatively stays outside the +template's reach. + +Values reach the DOM escaped; raw HTML enters only at places that name its +source. + +A meaningful structural transition may still replace a subtree when nothing in +it carries the state above. If a template would have to encode brittle DOM +assumptions, prefer a clearer component ownership boundary. Do not add a global store merely to coordinate an extraction. Independent API or revision projections must remain independent unless the external source diff --git a/frontend/README.md b/frontend/README.md index 4d57579c..2093e0a6 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -15,6 +15,8 @@ npx playwright install chromium | Command | Boundary | | --- | --- | +| `npm run typecheck` | TypeScript types of the frontend's `.ts` modules | +| `npm run lint` | Biome formatting, import order, and lint rules of the frontend's `.ts` modules; `npx biome check --write` applies the fixes | | `npm run test:unit` | colocated frontend unit tests | | `npm run test:contract` | frontend contracts in `tests/contracts/` | | `npm run test:e2e` | deterministic fixture-backed browser coverage | @@ -39,5 +41,13 @@ working directory. Browser sources are embedded by `caffold/src/static_assets.rs` at build time, so a new runtime asset must be added there and precached by -`service-worker.js`. Nothing in `tests/` or `node_modules/` is part of that -inventory. +`service-worker.js`. Nothing in `tests/` is part of that inventory. From +`node_modules/`, only the Lit modules a page reaches are: the server embeds the +Lit release `package.json` pins under `dependencies`, so a Cargo build needs +`npm ci` here first. `npm ci --omit=dev` installs that dependency alone when the +browser suites and type checks are not needed. To change the release, change +the pin and run `npm install`. A `.ts` module is embedded as +[`strip-types.mjs`](strip-types.mjs) writes it, which needs Node.js on `PATH` +during the build; the +[frontend architecture](../docs/architecture/frontend.md#native-module-imports) +describes how pages and unit tests load both. diff --git a/frontend/action-hint-scope.d.ts b/frontend/action-hint-scope.d.ts new file mode 100644 index 00000000..569e483c --- /dev/null +++ b/frontend/action-hint-scope.d.ts @@ -0,0 +1,48 @@ +// What TypeScript modules use from action-hint-scope.js while it is still +// JavaScript. It goes when that module moves to TypeScript. + +export interface ActionHintScopeOptions { + scopeId?: string; + actionId?: string; + clipRoots?: Element[]; +} + +export interface ActionHintTarget { + id: string; + actionId: string; + label: string; + invalidationOwner: Element; + controlKind: string; + control: HTMLElement; + anchor: Element; + clipRoots: Element[]; + badgeAtEnd: boolean; + activationKey?: string; + isActionable: () => boolean; + activate: () => void; +} + +export interface ActionHintScope { + blocked: boolean; + targets: ActionHintTarget[]; + mutationRoots: Node[]; + scrollRoots: Element[]; +} + +export function emptyActionHintScope(): ActionHintScope; + +export function buttonActionHintTarget(options: { + id: string; + actionId: string; + label: string; + invalidationOwner: Element; + control: HTMLElement; + anchor?: Element; + clipRoots: Element[]; + badgeAtEnd?: boolean; + isActionable: () => boolean; +}): ActionHintTarget; + +export function mergeActionHintScopes( + ...scopes: Array +): ActionHintScope; diff --git a/frontend/biome.json b/frontend/biome.json new file mode 100644 index 00000000..c2b5f6b5 --- /dev/null +++ b/frontend/biome.json @@ -0,0 +1,16 @@ +{ + "$schema": "./node_modules/@biomejs/biome/configuration_schema.json", + "files": { + "includes": ["**/*.ts"] + }, + "formatter": { + "indentStyle": "space" + }, + "linter": { + "rules": { + "nursery": { + "noFloatingPromises": "error" + } + } + } +} diff --git a/frontend/components/icons.js b/frontend/components/icons.js index 50820d2b..e65e359e 100644 --- a/frontend/components/icons.js +++ b/frontend/components/icons.js @@ -1,3 +1,5 @@ +import { html } from "lit"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { escapeHtml } from "./dom.js"; const LUCIDE_CDN = "https://esm.sh/lucide@1.22.0"; @@ -82,24 +84,14 @@ export function renderEntryIcon(entry) { `; } -export function renderInlineIcon(name, label, className = "inline-icon-svg") { - if (!lucideModule) { - return `${escapeHtml(label)}`; - } - - const iconNode = lucideModule[name]; - if (!iconNode || !lucideModule.createElement) { - return `${escapeHtml(label)}`; - } - - const svg = lucideModule.createElement(iconNode, { - class: className, - "aria-hidden": "true", - width: "18", - height: "18", - }); +// The same icon renders the same markup, so drawing it again leaves its SVG +// in place. +export function inlineIcon(name, label, className = "inline-icon-svg") { + return html`${unsafeHTML(inlineIconSvg(name, className))}${label}`; +} - return `${svg.outerHTML}${escapeHtml(label)}`; +export function renderInlineIcon(name, label, className = "inline-icon-svg") { + return `${inlineIconSvg(name, className)}${escapeHtml(label)}`; } function iconForEntry(entry) { @@ -171,6 +163,20 @@ function iconForEntry(entry) { return icon("File", "File"); } +function inlineIconSvg(name, className) { + const iconNode = lucideModule?.[name]; + if (!iconNode || !lucideModule.createElement) { + return ""; + } + + return lucideModule.createElement(iconNode, { + class: className, + "aria-hidden": "true", + width: "18", + height: "18", + }).outerHTML; +} + function renderIconPlaceholder(label) { return ` diff --git a/frontend/components/pagination.css b/frontend/components/pagination.css index a473203e..cbaf6d07 100644 --- a/frontend/components/pagination.css +++ b/frontend/components/pagination.css @@ -4,7 +4,7 @@ caffold-pagination { border-top: 1px solid var(--border); background: var(--surface); - & .pagination-panel { + & > .pagination-panel { display: grid; grid-template-columns: var(--interface-compact-hit-size) @@ -19,42 +19,7 @@ caffold-pagination { padding: 0.3125rem 0.5rem; } - & .pagination-button { - margin-block: 0; - color: var(--control-fg); - } - - & .pagination-button:hover::before, - & .pagination-button:focus-visible::before { - border-color: var(--control-emphasis-border); - background: var(--control-subtle-hover-bg); - } - - & .pagination-button:disabled { - color: var(--disabled-text); - cursor: default; - opacity: 0.55; - } - - & .pagination-button:disabled:hover { - background: transparent; - } - - & .pagination-button:disabled::before, - & .pagination-button:disabled:hover::before { - border-color: var(--border); - background: var(--surface); - } - - & .pagination-icon { - position: relative; - display: block; - width: var(--interface-icon-small-size); - height: var(--interface-icon-small-size); - stroke-width: 2.2; - } - - & .pagination-indicator { + & > .pagination-panel > .pagination-indicator { min-width: 0; color: var(--muted); font-size: 0.74rem; diff --git a/frontend/components/pagination.js b/frontend/components/pagination.js deleted file mode 100644 index 8e62d4f7..00000000 --- a/frontend/components/pagination.js +++ /dev/null @@ -1,178 +0,0 @@ -import { compactIconButton } from "../component-styles.js"; -import { escapeHtml } from "./dom.js"; -import { renderInlineIcon, warmIcons } from "./icons.js"; -import { - buttonActionHintTarget, - emptyActionHintScope, -} from "../action-hint-scope.js"; - -class CaffoldPagination extends HTMLElement { - static observedAttributes = [ - "page", - "total-pages", - "has-previous", - "has-next", - "first-label", - "previous-label", - "next-label", - "last-label", - ]; - - connectedCallback() { - this.addEventListener("click", (event) => { - const button = event.target.closest("button[data-page]"); - if (!button) { - return; - } - - const page = Number.parseInt(button.dataset.page ?? "", 10); - if (!Number.isFinite(page) || page < 1) { - return; - } - - this.dispatchEvent( - new CustomEvent("caffold:change-page", { - bubbles: true, - detail: { page }, - }), - ); - }); - - this.boundIconsReady = () => this.render(); - window.addEventListener("caffold:icons-ready", this.boundIconsReady); - warmIcons(); - this.render(); - } - - disconnectedCallback() { - window.removeEventListener("caffold:icons-ready", this.boundIconsReady); - } - - attributeChangedCallback() { - this.render(); - } - - render() { - if (!this.isConnected) { - return; - } - - const page = this.numberAttribute("page", 1); - const totalPages = this.numberAttribute("total-pages", 0); - if (totalPages <= 1) { - this.innerHTML = ""; - return; - } - - this.innerHTML = ` - - `; - } - - actionHintScope({ scopeId = "", actionId = "", clipRoots = [] } = {}) { - if (!scopeId || !actionId || this.hidden) { - return emptyActionHintScope(); - } - const targets = [...this.querySelectorAll( - ":scope > .pagination-panel > button[data-page-kind][data-page]", - )].flatMap((control) => { - const kind = `${control.dataset.pageKind ?? ""}`; - const page = `${control.dataset.page ?? ""}`; - if (!kind || !page || control.disabled) { - return []; - } - return [buttonActionHintTarget({ - invalidationOwner: this, - id: `${scopeId}:page:${kind}:${encodeURIComponent(page)}`, - actionId, - label: control.getAttribute("aria-label") || `Open page ${page}`, - control, - clipRoots: [...clipRoots], - isActionable: () => - this.isConnected && - !this.hidden && - this.querySelector( - `:scope > .pagination-panel > button[data-page-kind="${CSS.escape(kind)}"][data-page="${CSS.escape(page)}"]`, - ) === control && - !control.disabled, - })]; - }); - return { - blocked: false, - targets, - mutationRoots: [this], - scrollRoots: [], - }; - } - - renderPageButton({ kind, icon, label, page, disabled }) { - return ` - - `; - } - - numberAttribute(name, fallback) { - const value = Number.parseInt(this.getAttribute(name) ?? "", 10); - return Number.isFinite(value) ? value : fallback; - } - - labelAttribute(name, fallback) { - return this.getAttribute(name) ?? fallback; - } - - ariaLabel() { - return this.getAttribute("aria-label") ?? "Pagination"; - } -} - -await compactIconButton.register( - "caffold-pagination", - "> .pagination-panel > .pagination-button", -); - -customElements.define("caffold-pagination", CaffoldPagination); diff --git a/frontend/components/pagination.test.js b/frontend/components/pagination.test.js index 22f835e6..9a9f9c55 100644 --- a/frontend/components/pagination.test.js +++ b/frontend/components/pagination.test.js @@ -5,105 +5,59 @@ import { installCustomElementUnitRegistry, } from "../tests/support/custom-element-unit.js"; -const previousCss = globalThis.CSS; -globalThis.CSS = { escape: (value) => `${value}` }; const registry = installCustomElementUnitRegistry(); -await import("./pagination.js"); +await import("./pagination.ts"); const pagination = registry.element("caffold-pagination").prototype; -after(() => { - registry.restore(); - if (previousCss === undefined) { - delete globalThis.CSS; - } else { - globalThis.CSS = previousCss; - } -}); +after(() => registry.restore()); -test("provides enabled page intents and revalidates their owned buttons", () => { - const clipRoot = {}; - let clicks = 0; - const first = { - dataset: { pageKind: "first", page: "1" }, - disabled: true, - }; - const previous = { - dataset: { pageKind: "previous", page: "1" }, - disabled: false, - getAttribute: () => "Previous page", - focus() {}, - click() {}, - }; - const next = { - dataset: { pageKind: "next", page: "3" }, - disabled: false, - getAttribute(name) { - return name === "aria-label" ? "Next page" : null; +test("combines the scopes its page buttons provide and watches its own button set", () => { + const received = []; + const button = (id) => ({ + actionHintScope(options) { + received.push(options); + return { + blocked: false, + targets: [{ id }], + mutationRoots: [this], + scrollRoots: [], + }; }, - focus() {}, - click() { - clicks += 1; - }, - }; - const last = { - dataset: { pageKind: "last", page: "4" }, - disabled: false, - getAttribute: () => "Last page", - focus() {}, - click() {}, - }; - const controls = new Map([ - ["first", first], - ["previous", previous], - ["next", next], - ["last", last], + }); + const previous = button("issues:page:page:previous:1"); + const next = button("issues:page:page:next:3"); + const owner = paginationWith([previous, next]); + const options = { scopeId: "issues:page", actionId: "navigation.page", clipRoots: [] }; + + const scope = owner.actionHintScope(options); + + assert.deepEqual(received, [options, options]); + assert.deepEqual(scope.targets.map(({ id }) => id), [ + "issues:page:page:previous:1", + "issues:page:page:next:3", ]); - const owner = { - hidden: false, - isConnected: true, - querySelectorAll() { - return [...controls.values()]; - }, - querySelector(selector) { - const kind = selector.match(/data-page-kind="([^"]+)"/)?.[1]; - return controls.get(kind) ?? null; + assert.deepEqual(scope.mutationRoots, [owner, previous, next]); +}); + +test("provides nothing while hidden or outside an Action Hint scope", () => { + const button = { + actionHintScope() { + throw new Error("a hidden pagination does not ask its buttons"); }, }; - const scope = pagination.actionHintScope.call(owner, { - scopeId: "issues:page", - actionId: "navigation.page", - clipRoots: [clipRoot], - }); - assert.deepEqual( - scope.targets.map(({ id }) => id), - [ - "issues:page:page:previous:1", - "issues:page:page:next:3", - "issues:page:page:last:4", - ], + paginationWith([button], { hidden: true }).actionHintScope({ scopeId: "issues:page", actionId: "navigation.page" }).targets, + [], ); - const target = scope.targets[1]; - assert.deepEqual( - { - id: target.id, - actionId: target.actionId, - label: target.label, - controlKind: target.controlKind, - }, - { - id: "issues:page:page:next:3", - actionId: "navigation.page", - label: "Next page", - controlKind: "button", - }, - ); - assert.deepEqual(scope.mutationRoots, [owner]); - assert.deepEqual(target.clipRoots, [clipRoot]); - assert.equal(target.isActionable(), true); - target.activate(); - assert.equal(clicks, 1); - - controls.set("next", { ...next }); - assert.equal(target.isActionable(), false); + assert.deepEqual(paginationWith([button]).actionHintScope({}).targets, []); }); + +function paginationWith(buttons, { hidden = false } = {}) { + return Object.assign(Object.create(pagination), { + hidden, + querySelectorAll(selector) { + assert.equal(selector, ":scope > .pagination-panel > caffold-pagination-button"); + return buttons; + }, + }); +} diff --git a/frontend/components/pagination.ts b/frontend/components/pagination.ts new file mode 100644 index 00000000..fccfd618 --- /dev/null +++ b/frontend/components/pagination.ts @@ -0,0 +1,128 @@ +import { html, LitElement, nothing } from "lit"; +import { + type ActionHintScope, + type ActionHintScopeOptions, + emptyActionHintScope, + mergeActionHintScopes, +} from "../action-hint-scope.js"; +import { warmIcons } from "./icons.js"; +import "./pagination/components/button.ts"; +import type { CaffoldPaginationButton } from "./pagination/components/button.ts"; + +class CaffoldPagination extends LitElement { + static properties = { + page: { type: Number }, + totalPages: { type: Number, attribute: "total-pages" }, + hasPrevious: { type: Boolean, attribute: "has-previous" }, + hasNext: { type: Boolean, attribute: "has-next" }, + firstLabel: { attribute: "first-label" }, + previousLabel: { attribute: "previous-label" }, + nextLabel: { attribute: "next-label" }, + lastLabel: { attribute: "last-label" }, + }; + + declare page: number; + declare totalPages: number; + declare hasPrevious: boolean; + declare hasNext: boolean; + declare firstLabel: string | null | undefined; + declare previousLabel: string | null | undefined; + declare nextLabel: string | null | undefined; + declare lastLabel: string | null | undefined; + + private readonly iconsReady = () => this.requestUpdate(); + + constructor() { + super(); + this.page = 1; + this.totalPages = 0; + this.hasPrevious = false; + this.hasNext = false; + } + + createRenderRoot() { + return this; + } + + connectedCallback() { + super.connectedCallback(); + window.addEventListener("caffold:icons-ready", this.iconsReady); + void warmIcons(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + window.removeEventListener("caffold:icons-ready", this.iconsReady); + } + + render() { + const page = finiteOr(this.page, 1); + const totalPages = finiteOr(this.totalPages, 0); + if (totalPages <= 1) { + return nothing; + } + + return html` + + `; + } + + // The page buttons provide their own targets; this adds itself so that a + // rendered or removed button set revalidates the session. + actionHintScope(options: ActionHintScopeOptions = {}): ActionHintScope { + if (!options.scopeId || !options.actionId || this.hidden) { + return emptyActionHintScope(); + } + const pageButtons = this.querySelectorAll( + ":scope > .pagination-panel > caffold-pagination-button", + ); + return mergeActionHintScopes( + { ...emptyActionHintScope(), mutationRoots: [this] }, + ...[...pageButtons].map((button) => button.actionHintScope(options)), + ); + } +} + +function finiteOr(value: number, fallback: number) { + return Number.isFinite(value) ? value : fallback; +} + +customElements.define("caffold-pagination", CaffoldPagination); diff --git a/frontend/components/pagination/components/button.css b/frontend/components/pagination/components/button.css new file mode 100644 index 00000000..ee8b27d8 --- /dev/null +++ b/frontend/components/pagination/components/button.css @@ -0,0 +1,38 @@ +caffold-pagination-button { + display: block; + + & > .pagination-button { + margin-block: 0; + color: var(--control-fg); + } + + & > .pagination-button:hover::before, + & > .pagination-button:focus-visible::before { + border-color: var(--control-emphasis-border); + background: var(--control-subtle-hover-bg); + } + + & > .pagination-button:disabled { + color: var(--disabled-text); + cursor: default; + opacity: 0.55; + } + + & > .pagination-button:disabled:hover { + background: transparent; + } + + & > .pagination-button:disabled::before, + & > .pagination-button:disabled:hover::before { + border-color: var(--border); + background: var(--surface); + } + + & .pagination-icon { + position: relative; + display: block; + width: var(--interface-icon-small-size); + height: var(--interface-icon-small-size); + stroke-width: 2.2; + } +} diff --git a/frontend/components/pagination/components/button.test.js b/frontend/components/pagination/components/button.test.js new file mode 100644 index 00000000..ffe9c463 --- /dev/null +++ b/frontend/components/pagination/components/button.test.js @@ -0,0 +1,94 @@ +import assert from "node:assert/strict"; +import test, { after } from "node:test"; + +import { + installCustomElementUnitRegistry, +} from "../../../tests/support/custom-element-unit.js"; + +const registry = installCustomElementUnitRegistry(); +await import("./button.ts"); +const pageButton = registry.element("caffold-pagination-button").prototype; +after(() => registry.restore()); + +const options = { + scopeId: "issues:page", + actionId: "navigation.page", +}; + +test("provides its enabled page button and revalidates that same button and page", () => { + const clipRoot = {}; + let clicks = 0; + const control = { + dataset: { pageKind: "next", page: "3" }, + disabled: false, + getAttribute(name) { + return name === "aria-label" ? "Next page" : null; + }, + focus() {}, + click() { + clicks += 1; + }, + }; + const owner = renderedButton(control); + + const scope = owner.actionHintScope({ ...options, clipRoots: [clipRoot] }); + + assert.equal(scope.targets.length, 1); + const [target] = scope.targets; + assert.deepEqual( + { + id: target.id, + actionId: target.actionId, + label: target.label, + controlKind: target.controlKind, + invalidationOwner: target.invalidationOwner, + clipRoots: target.clipRoots, + }, + { + id: "issues:page:page:next:3", + actionId: "navigation.page", + label: "Next page", + controlKind: "button", + invalidationOwner: owner, + clipRoots: [clipRoot], + }, + ); + assert.deepEqual(scope.mutationRoots, [owner]); + assert.equal(target.isActionable(), true); + target.activate(); + assert.equal(clicks, 1); + + control.dataset.page = "4"; + assert.equal(target.isActionable(), false); + control.dataset.page = "3"; + owner.rendered = { ...control }; + assert.equal(target.isActionable(), false); +}); + +test("provides nothing while disabled, hidden, or outside an Action Hint scope", () => { + const control = { + dataset: { pageKind: "first", page: "1" }, + disabled: true, + getAttribute: () => "First page", + }; + + assert.deepEqual(renderedButton(control).actionHintScope(options).targets, []); + assert.deepEqual( + renderedButton({ ...control, disabled: false }, { hidden: true }).actionHintScope(options).targets, + [], + ); + assert.deepEqual(renderedButton({ ...control, disabled: false }).actionHintScope({}).targets, []); +}); + +function renderedButton(control, { hidden = false } = {}) { + const owner = Object.create(pageButton); + return Object.assign(owner, { + hidden, + isConnected: true, + rendered: control, + querySelector(selector) { + assert.equal(selector, ":scope > .pagination-button"); + return this.rendered; + }, + }); +} diff --git a/frontend/components/pagination/components/button.ts b/frontend/components/pagination/components/button.ts new file mode 100644 index 00000000..444069cf --- /dev/null +++ b/frontend/components/pagination/components/button.ts @@ -0,0 +1,115 @@ +import { html, LitElement } from "lit"; +import { + type ActionHintScope, + type ActionHintScopeOptions, + buttonActionHintTarget, + emptyActionHintScope, +} from "#app/action-hint-scope.js"; +import { compactIconButton } from "#app/component-styles.js"; +import { inlineIcon } from "../../icons.js"; + +export type PageKind = "first" | "previous" | "next" | "last"; + +export class CaffoldPaginationButton extends LitElement { + static properties = { + kind: {}, + page: { type: Number }, + label: {}, + icon: {}, + disabled: { type: Boolean }, + }; + + declare kind: PageKind; + declare page: number; + declare label: string; + declare icon: string; + declare disabled: boolean; + + constructor() { + super(); + this.page = 0; + this.label = ""; + this.icon = ""; + this.disabled = false; + } + + createRenderRoot() { + return this; + } + + render() { + return html` + + `; + } + + actionHintScope({ + scopeId = "", + actionId = "", + clipRoots = [], + }: ActionHintScopeOptions = {}): ActionHintScope { + const control = this.querySelector( + ":scope > .pagination-button", + ); + if (!scopeId || !actionId || this.hidden || !control || control.disabled) { + return emptyActionHintScope(); + } + const kind = `${control.dataset.pageKind ?? ""}`; + const page = `${control.dataset.page ?? ""}`; + if (!kind || !page) { + return emptyActionHintScope(); + } + return { + ...emptyActionHintScope(), + targets: [ + buttonActionHintTarget({ + invalidationOwner: this, + id: `${scopeId}:page:${kind}:${encodeURIComponent(page)}`, + actionId, + label: control.getAttribute("aria-label") || `Open page ${page}`, + control, + clipRoots: [...clipRoots], + isActionable: () => + this.isConnected && + !this.hidden && + this.querySelector(":scope > .pagination-button") === control && + control.dataset.pageKind === kind && + control.dataset.page === page && + !control.disabled, + }), + ], + mutationRoots: [this], + }; + } + + choosePage() { + if (!Number.isFinite(this.page) || this.page < 1) { + return; + } + + this.dispatchEvent( + new CustomEvent("caffold:change-page", { + bubbles: true, + detail: { page: this.page }, + }), + ); + } +} + +await compactIconButton.register( + "caffold-pagination-button", + "> .pagination-button", +); + +customElements.define("caffold-pagination-button", CaffoldPaginationButton); diff --git a/frontend/index.html b/frontend/index.html index 81fdfe4f..9d47ca55 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -69,7 +69,13 @@ "imports": { "#app/": "/assets/", "#components/": "/assets/components/", - "#tasks/": "/assets/pages/(task-workspace)/tasks/" + "#tasks/": "/assets/pages/(task-workspace)/tasks/", + "lit": "/assets/lit/index.js", + "lit/": "/assets/lit/", + "lit-html": "/assets/lit-html/lit-html.js", + "lit-html/": "/assets/lit-html/", + "lit-element/": "/assets/lit-element/", + "@lit/reactive-element": "/assets/@lit/reactive-element/reactive-element.js" } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index da00e8ea..174448fa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,20 +1,25 @@ { "name": "caffold", - "version": "0.16.1", + "version": "0.19.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "caffold", - "version": "0.16.1", + "version": "0.19.8", + "dependencies": { + "lit": "3.3.3" + }, "devDependencies": { "@aiden0z/pptx-renderer": "1.3.0", + "@biomejs/biome": "2.6.2", "@playwright/test": "^1.53.0", "@xterm/addon-fit": "0.11.0", "@xterm/xterm": "6.0.0", "docx-preview": "0.4.1", "pdfjs-dist": "6.3.289", "pptxgenjs": "4.0.1", + "typescript": "5.9.3", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" } }, @@ -38,6 +43,232 @@ } } }, + "node_modules/@biomejs/biome": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.6.2.tgz", + "integrity": "sha512-JLTFAFF9vfJcWwLhwuL4ZHflLg+XfTEC4IUnAlJRX1cMRt57++Xd1Ysb9Fdmlh9YUrvyCXfPEHjjEIqKJGdPKw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-android-arm64": "2.6.2", + "@biomejs/cli-android-x64": "2.6.2", + "@biomejs/cli-darwin-arm64": "2.6.2", + "@biomejs/cli-darwin-x64": "2.6.2", + "@biomejs/cli-linux-arm64": "2.6.2", + "@biomejs/cli-linux-arm64-musl": "2.6.2", + "@biomejs/cli-linux-x64": "2.6.2", + "@biomejs/cli-linux-x64-musl": "2.6.2", + "@biomejs/cli-win32-arm64": "2.6.2", + "@biomejs/cli-win32-x64": "2.6.2" + } + }, + "node_modules/@biomejs/cli-android-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-android-arm64/-/cli-android-arm64-2.6.2.tgz", + "integrity": "sha512-dMpdpH7zFEL5FxuIA9LJW+RoNyC3oeDB7PJyjqVVwtMNoKB5uo8OZYmrVSBikn71txzYwy/3xga7UME6bJQZ6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-android-x64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-android-x64/-/cli-android-x64-2.6.2.tgz", + "integrity": "sha512-XCwEhPBGYzuRA502dRkCcQ+5cefQwuxQR7f9wa4do92dvIcuBL6VMLMFB/fL9hlpZ4dGSfHdpGFSIDlp52mg0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.6.2.tgz", + "integrity": "sha512-rpXtabRgyZNz3aCjd/cRoSZGYi92R9eAXdUUe1sigIsRDyfMwgThaa98Yxt1fiZ7mxquqkAyIHIo08N/e1ToBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.6.2.tgz", + "integrity": "sha512-7GGzlm1GxUKoQ9CNV8tQZ+XXad4sz+v+uzNsWlhaGjsnZqUOVBw1OmAMpG3uNV/Mr0UrCpRJvSK0qvfuqSQOjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.6.2.tgz", + "integrity": "sha512-Qs4CofCE8MdkjZFfEjswh2NAcbyQ7IVYtGSzQBvLqFlla/CSXroGbktUFndWG/kVSTywadauzAspYWtSfqAc2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.6.2.tgz", + "integrity": "sha512-I+FYCLVSmR3cYfKcQ+KNeLOZ5w0wRd9fQFlw3JbSvy7bkegvTqdHaBRRcJzHQB/1ldldTLIcG21ka6X2cTWmJw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.6.2.tgz", + "integrity": "sha512-FJKsb95418139xDdbv9XnNJsIIwvsgR21Of0aE9B9U+nQlOtRq5gJl2ps0jZUmYBxTaOlP9BKo8N+Ff8+Rui1g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.6.2.tgz", + "integrity": "sha512-wnjEXAK2pDd2j8r9gKI3L+44sIxjkYzS5aHmD1+K57RNJ3sFyT4n22NH6QFPqqeVdRBxN54H3kckUUCrFKCcJw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.6.2.tgz", + "integrity": "sha512-/PEOo6evlgrQOfKhMNtC/4Gk8ai3hySXXEtjUzCqZe/4iAbMdqelpp7qvZEDyLxlZS+BjOMQFpq2gXI/BPaAqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.6.2.tgz", + "integrity": "sha512-FVlWD+mDDz8nN5LWKRWUhhb7wKIK0VE3oXQvQBX7Fwwj00CvqMVhMyiz1iz8O5aZGmnkhFlKSwJPrUvMh5prQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz", + "integrity": "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", + "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0" + } + }, "node_modules/@napi-rs/canvas": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.9.tgz", @@ -326,6 +557,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, "node_modules/@xterm/addon-fit": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", @@ -453,6 +690,37 @@ "immediate": "~3.0.5" } }, + "node_modules/lit": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz", + "integrity": "sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", + "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.5.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz", + "integrity": "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/mtx-decompressor": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/mtx-decompressor/-/mtx-decompressor-1.8.0.tgz", @@ -589,6 +857,20 @@ "dev": true, "license": "0BSD" }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3c0e1915..28a40ff6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,17 +11,24 @@ "scripts": { "test:unit": "node --test \"*.test.js\" \"action-hints/**/*.test.js\" \"chunked-fetch/**/*.test.js\" \"components/**/*.test.js\" \"keyboard-navigation/**/*.test.js\" \"pages/**/*.test.js\"", "test:contract": "node --test \"tests/contracts/*.test.mjs\"", + "typecheck": "tsc --noEmit", + "lint": "biome check", "test:e2e": "playwright test", "test:codex-live": "playwright test --config=playwright.live.config.mjs" }, "devDependencies": { "@aiden0z/pptx-renderer": "1.3.0", + "@biomejs/biome": "2.6.2", "@playwright/test": "^1.53.0", "@xterm/addon-fit": "0.11.0", "@xterm/xterm": "6.0.0", "docx-preview": "0.4.1", "pdfjs-dist": "6.3.289", "pptxgenjs": "4.0.1", + "typescript": "5.9.3", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" + }, + "dependencies": { + "lit": "3.3.3" } } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.js b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.js index d5ec8aad..31612ca5 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.js @@ -1,7 +1,7 @@ import { escapeHtml } from "#components/dom.js"; import { renderInlineIcon, warmIcons } from "#components/icons.js"; import "#components/loading-text.js"; -import "#components/pagination.js"; +import "#components/pagination.ts"; import { ACTION_HINT_ACTION, buttonActionHintTarget, diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.js index 2aff8a9d..39c325f0 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.js @@ -1,6 +1,6 @@ import { escapeHtml } from "#components/dom.js"; import "#components/loading-text.js"; -import "#components/pagination.js"; +import "#components/pagination.ts"; import { ACTION_HINT_ACTION, buttonActionHintTarget, diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.js index 6873138f..2cea0739 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.js @@ -1,7 +1,7 @@ import { escapeHtml } from "#components/dom.js"; import { renderInlineIcon, warmIcons } from "#components/icons.js"; import "#components/loading-text.js"; -import "#components/pagination.js"; +import "#components/pagination.ts"; import { ACTION_HINT_ACTION, buttonActionHintTarget, diff --git a/frontend/service-worker.js b/frontend/service-worker.js index 3292f025..24cbbecb 100644 --- a/frontend/service-worker.js +++ b/frontend/service-worker.js @@ -20,6 +20,15 @@ const APP_SHELL_ASSETS = [ "/assets/styles.css", "/assets/app.js", "/assets/build-info.js", + "/assets/lit/index.js", + "/assets/lit/directives/unsafe-html.js", + "/assets/lit-html/lit-html.js", + "/assets/lit-html/directive.js", + "/assets/lit-html/directives/unsafe-html.js", + "/assets/lit-html/is-server.js", + "/assets/lit-element/lit-element.js", + "/assets/@lit/reactive-element/reactive-element.js", + "/assets/@lit/reactive-element/css-tag.js", "/assets/api.js", "/assets/action-hint-scope.js", "/assets/component-styles.js", @@ -429,7 +438,9 @@ const APP_SHELL_ASSETS = [ "/assets/components/loading-text.css", "/assets/components/loading-text.js", "/assets/components/pagination.css", - "/assets/components/pagination.js", + "/assets/components/pagination.ts", + "/assets/components/pagination/components/button.css", + "/assets/components/pagination/components/button.ts", "/assets/components/segmented-control.css", "/assets/components/segmented-control.js", "/assets/components/terminal-view.css", diff --git a/frontend/strip-types.mjs b/frontend/strip-types.mjs new file mode 100644 index 00000000..6a95fe33 --- /dev/null +++ b/frontend/strip-types.mjs @@ -0,0 +1,30 @@ +// Writes each production TypeScript module under the frontend to the output +// directory, at the same path, as the JavaScript the browser loads: the module +// with its type syntax replaced by spaces, so every line and column stays +// where the source has it. Node applies the same transform when tests import +// the module. +import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { stripTypeScriptTypes } from "node:module"; +import path from "node:path"; + +const [sourceRoot, outputRoot] = process.argv.slice(2); + +for (const module of productionTypeScriptModules(sourceRoot)) { + const output = path.join(outputRoot, module); + mkdirSync(path.dirname(output), { recursive: true }); + writeFileSync(output, stripTypeScriptTypes(readFileSync(path.join(sourceRoot, module), "utf8"))); +} + +function productionTypeScriptModules(root, prefix = "") { + return readdirSync(path.join(root, prefix), { withFileTypes: true }).flatMap((entry) => { + const relative = path.join(prefix, entry.name); + if (entry.isDirectory()) { + return entry.name === "node_modules" || entry.name === "tests" + ? [] + : productionTypeScriptModules(root, relative); + } + return entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".d.ts") + ? [relative] + : []; + }); +} diff --git a/frontend/styles.css b/frontend/styles.css index af7233a4..18577664 100644 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -3,6 +3,7 @@ @import "./pages/components/update-dialog.css"; @import "./pages/components/update-result-dialog.css"; @import "./components/pagination.css"; +@import "./components/pagination/components/button.css"; @import "./components/file-tree.css"; @import "./components/file-navigator.css"; @import "./components/file-viewer.css"; diff --git a/frontend/tests/contracts/appearance-ownership.test.mjs b/frontend/tests/contracts/appearance-ownership.test.mjs index e400781b..3ebe79a6 100644 --- a/frontend/tests/contracts/appearance-ownership.test.mjs +++ b/frontend/tests/contracts/appearance-ownership.test.mjs @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { readdirSync, readFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; import { relative, resolve } from "node:path"; import test from "node:test"; import { fileURLToPath } from "node:url"; @@ -793,7 +793,7 @@ test("visible inline icons have explicit block geometry from their UI owner", () ["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"], - ["components/pagination.css", ".pagination-icon", "--interface-icon-small-size"], + ["components/pagination/components/button.css", ".pagination-icon", "--interface-icon-small-size"], ]; for (const [path, selector, token] of owners) { @@ -974,12 +974,12 @@ test("compact icon controls declare shared styles at their own component boundar ["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/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/pagination/components/button", "caffold-pagination-button", "> .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"], ]; for (const [path, owner, target] of consumers) { - const javascript = readFrontend(`${path}.js`); + const javascript = readFrontendModule(path); const declarations = [...javascript.matchAll(/await compactIconButton\.register\(\s*"([^"]+)",\s*"([^"]+)"\s*,?\s*\)/g)]; assert.ok(declarations.some(([, name, selector]) => name === owner && selector === target), path); } @@ -1474,6 +1474,13 @@ function readFrontend(path) { return readFileSync(resolve(frontendRoot, path), "utf8"); } +// A module still in JavaScript or already moved to TypeScript, by its stem. +function readFrontendModule(stem) { + return existsSync(resolve(frontendRoot, `${stem}.ts`)) + ? readFrontend(`${stem}.ts`) + : readFrontend(`${stem}.js`); +} + function cssBlock(source, selector) { const start = source.indexOf(selector); assert.notEqual(start, -1, `missing selector ${selector}`); diff --git a/frontend/tests/contracts/css-ownership.test.mjs b/frontend/tests/contracts/css-ownership.test.mjs index 34077052..89712766 100644 --- a/frontend/tests/contracts/css-ownership.test.mjs +++ b/frontend/tests/contracts/css-ownership.test.mjs @@ -52,6 +52,7 @@ const ownership = new Map([ ["components/pptx-viewer.css", ["caffold-pptx-viewer"]], ["components/xlsx-viewer.css", ["caffold-xlsx-viewer"]], ["components/pagination.css", ["caffold-pagination"]], + ["components/pagination/components/button.css", ["caffold-pagination-button"]], [ "components/pane-resizer.css", ["caffold-pane-resizer"], diff --git a/frontend/tests/contracts/frontend-test-placement.test.mjs b/frontend/tests/contracts/frontend-test-placement.test.mjs index 5d3f15d7..4936a9f4 100644 --- a/frontend/tests/contracts/frontend-test-placement.test.mjs +++ b/frontend/tests/contracts/frontend-test-placement.test.mjs @@ -47,18 +47,18 @@ test("the frontend unit-test command reaches every colocated test", () => { test("focused frontend Node tests stay beside exact same-stem owners", () => { assert.ok(unitTestPaths.length > 0); for (const testPath of unitTestPaths) { - const ownerPath = testPath.replace(/[.]test[.]js$/, ".js"); + const ownerPaths = [".js", ".ts"].map((extension) => testPath.replace(/[.]test[.]js$/, extension)); assert.equal( - existsSync(join(frontendRoot, ownerPath)), + ownerPaths.some((ownerPath) => existsSync(join(frontendRoot, ownerPath))), true, - `${testPath} must be owned by ${ownerPath}`, + `${testPath} must be owned by ${ownerPaths.join(" or ")}`, ); } }); test("colocated tests stay outside production imports and runtime assets", () => { for (const path of frontendFiles) { - if (!path.endsWith(".js") || path.endsWith(".test.js")) { + if (!/[.][jt]s$/.test(path) || path.endsWith(".test.js")) { continue; } assert.doesNotMatch( diff --git a/frontend/tests/contracts/lit-library.test.mjs b/frontend/tests/contracts/lit-library.test.mjs new file mode 100644 index 00000000..fb1986ae --- /dev/null +++ b/frontend/tests/contracts/lit-library.test.mjs @@ -0,0 +1,125 @@ +import assert from "node:assert/strict"; +import { readdirSync, readFileSync } from "node:fs"; +import test from "node:test"; + +const frontendUrl = new URL("../../", import.meta.url); +const nodeModulesUrl = new URL("node_modules/", frontendUrl); +const importMap = JSON.parse( + read(frontendUrl, "index.html").match(/ - + diff --git a/frontend/service-worker.js b/frontend/service-worker.js index e81595d5..e69d4647 100644 --- a/frontend/service-worker.js +++ b/frontend/service-worker.js @@ -18,7 +18,7 @@ const APP_SHELL_ASSETS = [ "/", "/assets/manifest.webmanifest", "/assets/styles.css", - "/assets/app.js", + "/assets/app.ts", "/assets/build-info.js", "/assets/lit/index.js", "/assets/lit/async-directive.js", diff --git a/frontend/service-worker.test.js b/frontend/service-worker.test.js index 5d93a8da..fd90bfe0 100644 --- a/frontend/service-worker.test.js +++ b/frontend/service-worker.test.js @@ -199,16 +199,16 @@ test("copies a file the server calls unchanged from an earlier build's cache", a const harness = createHarness({ cacheEntries: { "caffold-shell-older-build": new Map([ - ["/assets/app.js", taggedResponse("older app", '"app-older"')], + ["/assets/app.ts", taggedResponse("older app", '"app-older"')], ]), "caffold-shell-current-build": new Map([ - ["/assets/app.js", taggedResponse("current app", '"app-current"')], + ["/assets/app.ts", taggedResponse("current app", '"app-current"')], ]), "caffold-shell-test-build": new Map(), }, fetch: async (path, init) => { requests.push({ path, init }); - return path === "/assets/app.js" + return path === "/assets/app.ts" ? new Response(null, { status: 304 }) : new Response(`network ${path}`); }, @@ -216,12 +216,12 @@ test("copies a file the server calls unchanged from an earlier build's cache", a await harness.dispatchExtendable("install"); - assert.equal(await installedResponse(harness, "/assets/app.js").text(), "current app"); + assert.equal(await installedResponse(harness, "/assets/app.ts").text(), "current app"); // The worker builds the request options in its own context. assert.deepEqual( - JSON.parse(JSON.stringify(requests.filter(({ path }) => path === "/assets/app.js"))), + JSON.parse(JSON.stringify(requests.filter(({ path }) => path === "/assets/app.ts"))), [{ - path: "/assets/app.js", + path: "/assets/app.ts", init: { cache: "no-store", headers: { "If-None-Match": '"app-current"' } }, }], ); @@ -235,18 +235,18 @@ test("downloads a file the server says changed since the earlier build", async ( const harness = createHarness({ cacheEntries: { "caffold-shell-current-build": new Map([ - ["/assets/app.js", taggedResponse("current app", '"app-current"')], + ["/assets/app.ts", taggedResponse("current app", '"app-current"')], ]), "caffold-shell-test-build": new Map(), }, fetch: async (path) => - new Response(path === "/assets/app.js" ? "replacement app" : `network ${path}`), + new Response(path === "/assets/app.ts" ? "replacement app" : `network ${path}`), }); await harness.dispatchExtendable("install"); assert.equal( - await installedResponse(harness, "/assets/app.js").text(), + await installedResponse(harness, "/assets/app.ts").text(), "replacement app", ); }); @@ -257,7 +257,7 @@ test("downloads a file without asking when no earlier copy carries a tag", async cacheEntries: { "caffold-shell-current-build": new Map([["/", new Response("current shell")]]), "other-cache": new Map([ - ["/assets/app.js", taggedResponse("other app", '"app-other"')], + ["/assets/app.ts", taggedResponse("other app", '"app-other"')], ]), "caffold-shell-test-build": new Map([ ["/assets/styles.css", taggedResponse("partial styles", '"styles-partial"')], @@ -271,7 +271,7 @@ test("downloads a file without asking when no earlier copy carries a tag", async await harness.dispatchExtendable("install"); - for (const path of ["/", "/assets/app.js", "/assets/styles.css"]) { + for (const path of ["/", "/assets/app.ts", "/assets/styles.css"]) { assert.deepEqual(requests.filter((request) => request.path === path), [ { path, init: undefined }, ]); @@ -283,7 +283,7 @@ test("discards the build's cache when any shell file cannot be fetched", async ( const harness = createHarness({ cacheEntries: { "caffold-shell-current-build": new Map([ - ["/assets/app.js", taggedResponse("current app", '"app-current"')], + ["/assets/app.ts", taggedResponse("current app", '"app-current"')], ]), "caffold-shell-test-build": new Map(), }, @@ -291,7 +291,7 @@ test("discards the build's cache when any shell file cannot be fetched", async ( if (path === "/assets/styles.css") { return new Response("unavailable", { status: 503 }); } - return path === "/assets/app.js" + return path === "/assets/app.ts" ? new Response(null, { status: 304 }) : new Response(`network ${path}`); }, @@ -303,7 +303,7 @@ test("discards the build's cache when any shell file cannot be fetched", async ( for (const [name, url, cachePath] of [ ["navigation", "https://caffold.test/tasks/83", "/"], - ["asset", "https://caffold.test/assets/app.js", "/assets/app.js"], + ["asset", "https://caffold.test/assets/app.ts", "/assets/app.ts"], ]) { test(`serves cached ${name} without waiting for a stalled network`, async () => { let fetchCalls = 0; @@ -340,7 +340,7 @@ test("fails a missing shell request explicitly after the network bound", async ( const response = await harness.dispatchFetch({ method: "GET", - url: "https://caffold.test/assets/app.js", + url: "https://caffold.test/assets/app.ts", }); assert.equal(response.status, 504); assert.equal(await response.text(), "Caffold app shell is unavailable."); diff --git a/frontend/tests/contracts/appearance-ownership.test.mjs b/frontend/tests/contracts/appearance-ownership.test.mjs index d654696c..f05aa390 100644 --- a/frontend/tests/contracts/appearance-ownership.test.mjs +++ b/frontend/tests/contracts/appearance-ownership.test.mjs @@ -837,7 +837,7 @@ test("control paint layers do not pull screen-reader text into layout", () => { test("the browser fixture renders every shared inline icon used by production", () => { const usedNames = new Set( frontendSources().flatMap(([, source]) => - [...source.matchAll(/renderInlineIcon\("([A-Za-z0-9]+)"/g)].map( + [...source.matchAll(/\binlineIcon\("([A-Za-z0-9]+)"/g)].map( (match) => match[1], ), ), From 48f8c0850c4a73efd78e92720c8bedb0bf3bde90 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Tue, 27 Oct 2026 12:49:40 +0900 Subject: [PATCH 15/19] Draw on Lit's update where drawing at once served nothing Components no longer draw when they connect, and those that only render their state keep it in reactive properties instead of drawing by hand. Immediate draws stay where the same call uses what it drew and in the routed layouts that hand their children a route right after drawing. --- docs/architecture/frontend.md | 12 +++-- frontend/action-hints/components/dialog.ts | 6 --- frontend/components/file-tree.ts | 1 - frontend/components/markdown-preview.ts | 1 - frontend/components/pane-resizer.ts | 1 - frontend/components/segmented-control.ts | 1 - frontend/components/terminal-view.ts | 3 +- .../keyboard-navigation/components/hud.ts | 1 - .../components/selector.ts | 6 --- .../components/shortcut-dialog.ts | 2 - .../components/reset-credit-dialog.ts | 6 --- .../components/runtime-restart-dialog.ts | 5 -- .../components/runtime-update-dialog.ts | 5 -- .../(task-workspace)/components/navigation.ts | 27 ++++++----- .../components/update-task-dialog.ts | 5 -- .../components/workspace-brand.ts | 5 -- .../notes/components/navigator.ts | 1 - .../components/runtime-restart-dialog.ts | 5 -- .../settings/components/navigator.ts | 5 -- .../components/conversation-shortcuts.ts | 42 ++++++++--------- .../components/fork-dialog.ts | 47 ++++++++++--------- .../(section)/components/github-shortcuts.ts | 24 ++++------ .../(detail)/(section)/components/summary.ts | 13 +++-- .../components/broken-delete-dialog.test.js | 22 +++++---- .../(task)/components/broken-delete-dialog.ts | 34 +++++++------- .../(task)/components/command-dialog.ts | 1 - .../(task)/components/current-plan.ts | 41 ++++++++-------- .../components/document-dialog.ts | 1 - .../components/markdown-preview-dialog.ts | 1 - .../permission-instructions-dialog.ts | 35 +++++--------- .../(detail)/(task)/components/summary.ts | 16 ++++--- .../tasks/(detail)/components/git-menu.ts | 12 ++--- .../tasks/(detail)/components/github-menu.ts | 12 ++--- .../(detail)/components/terminal-button.ts | 17 +++++-- .../terminal/components/special-keys.ts | 17 ++++--- .../components/archived-delete-dialog.ts | 6 --- .../composer/components/context-usage.test.js | 12 +++-- .../composer/components/context-usage.ts | 1 - .../tasks/components/image-preview-dialog.ts | 35 +++++++------- .../tasks/components/task-absence.ts | 14 ++++-- .../tasks/components/task-create.ts | 3 +- .../tasks/components/task-store-recovery.ts | 17 ++++--- .../tasks/components/task-switcher-dialog.ts | 11 ++--- .../(task-workspace)/tasks/recovery/page.ts | 4 +- frontend/pages/components/update-dialog.ts | 5 -- .../pages/components/update-result-dialog.ts | 6 --- frontend/tests/e2e/component-styles.spec.js | 18 ++++--- 47 files changed, 259 insertions(+), 306 deletions(-) diff --git a/docs/architecture/frontend.md b/docs/architecture/frontend.md index 0fc4e7cd..e7d2ee12 100644 --- a/docs/architecture/frontend.md +++ b/docs/architecture/frontend.md @@ -103,10 +103,14 @@ rendered DOM, such as scroll restoration, focus, or measurement, runs in owner that needs a child's rendered DOM awaits the child's `updateComplete`. A component that calls methods on a child its own template draws renders its pending update, with `performUpdate()`, before it looks the child up, so the -child is there even before the component is in the document. A component also -renders at once when it connects and when one of its public methods changes -what it shows, so the owner that mounts or calls it can read its DOM right -away. +child is there even before the component is in the document. A public method +renders at once in the same way where the same call goes on to use what it +drew, to measure or place it, to open or focus an element the change drew, or +to hand values to the children it draws, and where a component changes its +state in place rather than through reactive properties. The routed layouts +render at once whenever they change what they show, because each hands the +next layout or page its route right after drawing it. Other components, +including when they connect, render on Lit's update. The Task conversation renders the components its entries mount at once in the same way, because Task Detail reads the whole transcript right after it changes. diff --git a/frontend/action-hints/components/dialog.ts b/frontend/action-hints/components/dialog.ts index 3a8493f0..52a427a8 100644 --- a/frontend/action-hints/components/dialog.ts +++ b/frontend/action-hints/components/dialog.ts @@ -65,11 +65,6 @@ export class CaffoldActionHintDialog extends LitElement { return this; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); - } - disconnectedCallback() { super.disconnectedCallback(); const dialog = this.dialog.value; @@ -118,7 +113,6 @@ export class CaffoldActionHintDialog extends LitElement { } open(targets: readonly ActionHintSessionTarget[], viewportRect: Rect | null) { - this.performUpdate(); const dialog = this.dialog.value; if (!dialog) { return; diff --git a/frontend/components/file-tree.ts b/frontend/components/file-tree.ts index fe05d3a4..7d257003 100644 --- a/frontend/components/file-tree.ts +++ b/frontend/components/file-tree.ts @@ -143,7 +143,6 @@ export class CaffoldFileTree extends LitElement { window.addEventListener("caffold:icons-ready", this.iconsReady); window.addEventListener("caffold:settings-change", this.settingsChanged); this.globalFileSortMode = normalizeFileSortMode(getSettings().fileSortMode); - this.performUpdate(); void warmIcons(); } diff --git a/frontend/components/markdown-preview.ts b/frontend/components/markdown-preview.ts index 8282f96c..73cc7352 100644 --- a/frontend/components/markdown-preview.ts +++ b/frontend/components/markdown-preview.ts @@ -116,7 +116,6 @@ export class CaffoldMarkdownPreview extends LitElement { super.connectedCallback(); this.setAttribute("role", "region"); this.setAttribute("aria-label", "Markdown preview"); - this.performUpdate(); if (this.markdown !== undefined && this.renderState === "loading") { this.startRender(); } else if (this.renderState === "markdown") { diff --git a/frontend/components/pane-resizer.ts b/frontend/components/pane-resizer.ts index 2416e6f6..57069d7d 100644 --- a/frontend/components/pane-resizer.ts +++ b/frontend/components/pane-resizer.ts @@ -47,7 +47,6 @@ export class CaffoldPaneResizer extends LitElement { connectedCallback() { super.connectedCallback(); - this.performUpdate(); this.setAttribute("role", "separator"); this.setAttribute("aria-orientation", "vertical"); if (!this.hasAttribute("aria-label")) { diff --git a/frontend/components/segmented-control.ts b/frontend/components/segmented-control.ts index 0be69223..fbde7c82 100644 --- a/frontend/components/segmented-control.ts +++ b/frontend/components/segmented-control.ts @@ -101,7 +101,6 @@ export class CaffoldSegmentedControl extends LitElement { choices, actionVerb: `${snapshot.actionVerb ?? "Select"}`, }; - this.performUpdate(); } actionHintScope(pull: ActionHintPull = {}) { diff --git a/frontend/components/terminal-view.ts b/frontend/components/terminal-view.ts index d17acced..63f79172 100644 --- a/frontend/components/terminal-view.ts +++ b/frontend/components/terminal-view.ts @@ -92,7 +92,6 @@ export class CaffoldTerminalView extends LitElement { connectedCallback() { super.connectedCallback(); - this.performUpdate(); window.addEventListener("caffold:theme-change", this.appearanceChanged); window.addEventListener("caffold:settings-change", this.appearanceChanged); this.resizeObserver.observe(this); @@ -119,7 +118,7 @@ export class CaffoldTerminalView extends LitElement { /** Opens a terminal unless one is open. Resolves with its size once it has a box. */ async open() { - this.performUpdate(); + await this.updateComplete; if (!this.terminal) { await this.createTerminal(); } diff --git a/frontend/keyboard-navigation/components/hud.ts b/frontend/keyboard-navigation/components/hud.ts index d2013d38..e5f526c4 100644 --- a/frontend/keyboard-navigation/components/hud.ts +++ b/frontend/keyboard-navigation/components/hud.ts @@ -25,7 +25,6 @@ export class CaffoldScrollModeHud extends LitElement { connectedCallback() { super.connectedCallback(); this.hidden = !this.surfaceRect; - this.performUpdate(); } render() { diff --git a/frontend/keyboard-navigation/components/selector.ts b/frontend/keyboard-navigation/components/selector.ts index 16a96a0f..06c55137 100644 --- a/frontend/keyboard-navigation/components/selector.ts +++ b/frontend/keyboard-navigation/components/selector.ts @@ -55,11 +55,6 @@ export class CaffoldScrollSurfaceSelector extends LitElement { return this; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); - } - disconnectedCallback() { super.disconnectedCallback(); this.close(); @@ -118,7 +113,6 @@ export class CaffoldScrollSurfaceSelector extends LitElement { surfaces: readonly SelectableScrollSurface[], viewportRect: Rect | null, ) { - this.performUpdate(); const dialog = this.dialog.value; if (!dialog) { return; diff --git a/frontend/keyboard-navigation/components/shortcut-dialog.ts b/frontend/keyboard-navigation/components/shortcut-dialog.ts index 4854cce2..274b426f 100644 --- a/frontend/keyboard-navigation/components/shortcut-dialog.ts +++ b/frontend/keyboard-navigation/components/shortcut-dialog.ts @@ -34,7 +34,6 @@ export class CaffoldKeyboardShortcutDialog extends LitElement { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); - this.performUpdate(); } disconnectedCallback() { @@ -94,7 +93,6 @@ export class CaffoldKeyboardShortcutDialog extends LitElement { } open() { - this.performUpdate(); const dialog = this.dialog.value; if (!dialog || dialog.open) { return false; diff --git a/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts b/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts index dca89cf5..6edf845e 100644 --- a/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts +++ b/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts @@ -33,11 +33,6 @@ export class CaffoldCodexResetCreditDialog extends LitElement { return this; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); - } - render() { const { creditId, title, expiry, retry = false } = this.request; const heading = retry @@ -94,7 +89,6 @@ export class CaffoldCodexResetCreditDialog extends LitElement { open(request: ResetCreditRequest = {}) { this.request = request; - this.performUpdate(); const dialog = this.dialog.value; if (!dialog || dialog.open) { return; diff --git a/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts b/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts index ffb78be1..3c589c6e 100644 --- a/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts +++ b/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts @@ -16,11 +16,6 @@ export class CaffoldCodexRuntimeRestartDialog extends LitElement { return this; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); - } - render() { return html` this.requestUpdate(); private readonly hints = new ActionHints(this); + constructor() { + super(); + this.mode = "tasks"; + this.updateAvailable = false; + this.reloadReady = false; + } + createRenderRoot() { return this; } @@ -43,12 +56,10 @@ export class CaffoldTaskWorkspaceNavigation extends LitElement { setMode(mode: string | null | undefined) { this.mode = MODES.find((entry) => entry.mode === mode)?.mode ?? "tasks"; - this.draw(); } setCaffoldUpdate(snapshot: CaffoldUpdateSnapshot | null | undefined) { this.updateAvailable = snapshot?.status?.updateAvailable === true; - this.draw(); } /** Whether this window has a new build ready to load. */ @@ -56,7 +67,6 @@ export class CaffoldTaskWorkspaceNavigation extends LitElement { status: { preparedUpdate?: { ready?: boolean } | null } | null | undefined, ) { this.reloadReady = status?.preparedUpdate?.ready === true; - this.draw(); } actionHintScope({ scopeId = "workspace", ...pull }: ActionHintPull = {}) { @@ -110,11 +120,6 @@ export class CaffoldTaskWorkspaceNavigation extends LitElement { }), ); } - - private draw() { - this.requestUpdate(); - this.performUpdate(); - } } customElements.define( diff --git a/frontend/pages/(task-workspace)/components/update-task-dialog.ts b/frontend/pages/(task-workspace)/components/update-task-dialog.ts index 3e0c2d5d..dca52b1c 100644 --- a/frontend/pages/(task-workspace)/components/update-task-dialog.ts +++ b/frontend/pages/(task-workspace)/components/update-task-dialog.ts @@ -61,11 +61,6 @@ export class CaffoldUpdateTaskDialog extends LitElement { return this; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); - } - /** * Opens for an update the server offers: `status` is its update status with * an `updateTask`, and `composerSettings` are the update Section's last diff --git a/frontend/pages/(task-workspace)/components/workspace-brand.ts b/frontend/pages/(task-workspace)/components/workspace-brand.ts index cb796fa4..b7ec541c 100644 --- a/frontend/pages/(task-workspace)/components/workspace-brand.ts +++ b/frontend/pages/(task-workspace)/components/workspace-brand.ts @@ -5,11 +5,6 @@ class CaffoldWorkspaceBrand extends LitElement { return this; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); - } - render() { return html` { const about = entry.section === "about"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts index f355da8e..fdb1fadf 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts @@ -32,26 +32,36 @@ export interface ConversationShortcutsContext { * that already exists elsewhere: today, a Codex thread. * * It shows only while active and once Codex is known to be installed, and - * asks Codex each time it is shown. The Section screen hands it its context - * and reads it right after, so it draws at once wherever its state changes. + * asks Codex each time it is shown. */ export class CaffoldSectionConversationShortcuts extends LitElement { - private active = false; - private context: ConversationShortcutsContext = { - key: "", - sectionId: "", - path: "", + static properties = { + context: { state: true }, + transportAvailable: { state: true }, + codexReadiness: { state: true }, + taskStoreStatusSnapshot: { state: true }, }; - private transportAvailable = true; + + declare private context: ConversationShortcutsContext; + declare private transportAvailable: boolean; + declare private codexReadiness: CodexStatusSnapshot; + declare private taskStoreStatusSnapshot: TaskStoreStatusSnapshot | null; + private active = false; // Asked for whenever the row is shown: whether Codex is installed, and why // it is blocked. The previous answer stays on show while the next is asked. - private codexReadiness: CodexStatusSnapshot = createCodexStatusSnapshot(); private codexReadinessRequest = 0; private readonly hints = new ActionHints(this, { clipRoots: () => [this], }); private readonly dialog = new DrawnRef(this); - private taskStoreStatusSnapshot: TaskStoreStatusSnapshot | null = null; + + constructor() { + super(); + this.context = { key: "", sectionId: "", path: "" }; + this.transportAvailable = true; + this.codexReadiness = createCodexStatusSnapshot(); + this.taskStoreStatusSnapshot = null; + } createRenderRoot() { return this; @@ -61,7 +71,6 @@ export class CaffoldSectionConversationShortcuts extends LitElement { super.connectedCallback(); setAttribute(this, "role", "complementary"); setAttribute(this, "aria-label", "Existing conversations"); - this.draw(); } disconnectedCallback() { @@ -86,7 +95,6 @@ export class CaffoldSectionConversationShortcuts extends LitElement { void this.askCodexReadiness(); } } - this.draw(); } setTransportAvailable(available: boolean) { @@ -97,12 +105,10 @@ export class CaffoldSectionConversationShortcuts extends LitElement { if (regained && this.active && this.codexReadiness.phase === "failed") { void this.askCodexReadiness(); } - this.draw(); } setTaskStoreStatusSnapshot(snapshot: TaskStoreStatusSnapshot | null) { this.taskStoreStatusSnapshot = snapshot ?? null; - this.draw(); } activate() { @@ -111,14 +117,12 @@ export class CaffoldSectionConversationShortcuts extends LitElement { if (shown) { void this.askCodexReadiness(); } - this.draw(); } deactivate() { this.active = false; this.codexReadinessRequest += 1; this.forkDialog()?.deactivate(); - this.draw(); } actionHintScope({ scopeId = "section", ...pull }: ActionHintPull = {}) { @@ -197,7 +201,6 @@ export class CaffoldSectionConversationShortcuts extends LitElement { return; } this.codexReadiness = answer; - this.draw(); } private disabledReason() { @@ -239,11 +242,6 @@ export class CaffoldSectionConversationShortcuts extends LitElement { private forkDialog() { return this.dialog.value; } - - private draw() { - this.requestUpdate(); - this.performUpdate(); - } } customElements.define( diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts index e3d7c0a4..dc373e48 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts @@ -36,18 +36,24 @@ const STALE_SOURCE_CODES = [ /** * Starts a Task in a Section from a Codex thread: the person names the * thread, previews it, and forks it. - * - * The Section's conversation shortcuts open it and read its keyboard context - * right after, so it draws at once wherever its state changes. */ export class CaffoldConversationForkDialog extends LitElement { - private context = { sectionId: "", sectionPath: "" }; + static properties = { + context: { state: true }, + threadIdText: { state: true }, + preview: { state: true }, + previewPending: { state: true }, + forkPending: { state: true }, + error: { state: true }, + }; + + declare private context: { sectionId: string; sectionPath: string }; + declare private threadIdText: string; + declare private preview: TaskForkSourcePreview | null; + declare private previewPending: boolean; + declare private forkPending: boolean; + declare private error: TaskResponseError | null; private opener: HTMLElement | null = null; - private threadIdText = ""; - private preview: TaskForkSourcePreview | null = null; - private previewPending = false; - private forkPending = false; - private error: TaskResponseError | null = null; private previewRequestId = 0; private forkRequestId = 0; private previewController: AbortController | null = null; @@ -60,6 +66,16 @@ export class CaffoldConversationForkDialog extends LitElement { private readonly threadIdInput = new DrawnRef(this); private readonly cancelButton = new DrawnRef(this); + constructor() { + super(); + this.context = { sectionId: "", sectionPath: "" }; + this.threadIdText = ""; + this.preview = null; + this.previewPending = false; + this.forkPending = false; + this.error = null; + } + createRenderRoot() { return this; } @@ -107,7 +123,6 @@ export class CaffoldConversationForkDialog extends LitElement { this.error = null; this.restoreFocus = true; this.threadIdText = ""; - this.draw(); const dialog = this.dialog; if (!dialog) { @@ -323,7 +338,6 @@ export class CaffoldConversationForkDialog extends LitElement { if (!sourceId) { this.preview = null; this.error = new Error("Enter a Codex Thread ID."); - this.draw(); this.threadIdInput.value?.focus(); return; } @@ -335,7 +349,6 @@ export class CaffoldConversationForkDialog extends LitElement { this.preview = null; this.previewPending = true; this.error = null; - this.draw(); try { const preview = await previewTaskForkSource( { provider: "codex", sourceId }, @@ -347,7 +360,6 @@ export class CaffoldConversationForkDialog extends LitElement { this.preview = normalizePreview(preview, sourceId); this.previewPending = false; this.previewController = null; - this.draw(); } catch (error) { if (requestId !== this.previewRequestId) { return; @@ -357,7 +369,6 @@ export class CaffoldConversationForkDialog extends LitElement { if ((error as Error | null)?.name !== "AbortError") { this.error = error instanceof Error ? error : new Error(`${error}`); } - this.draw(); } } @@ -372,7 +383,6 @@ export class CaffoldConversationForkDialog extends LitElement { this.abortPreview(); this.forkPending = true; this.error = null; - this.draw(); try { const created = await createTaskFork({ provider: "codex", @@ -419,7 +429,6 @@ export class CaffoldConversationForkDialog extends LitElement { if (STALE_SOURCE_CODES.includes(`${failure.code}`)) { this.preview = null; } - this.draw(); } } @@ -428,7 +437,6 @@ export class CaffoldConversationForkDialog extends LitElement { this.abortPreview(); this.preview = null; this.error = null; - this.draw(); } private handleSubmit(event: Event) { @@ -480,11 +488,6 @@ export class CaffoldConversationForkDialog extends LitElement { } } - private draw() { - this.requestUpdate(); - this.performUpdate(); - } - private get dialog() { return this.modal.value; } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts index 6af2eab0..0a1802da 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts @@ -28,21 +28,28 @@ type GitHubKind = "issues" | "pulls"; * shown only while the repository is on GitHub. * * It asks GitHub about the repository while active, once for each directory. - * The Section screen hands it its context and reads it right after, so it - * draws at once wherever its state changes. */ export class CaffoldSectionGithubShortcuts extends LitElement { + static properties = { + githubStatus: { state: true }, + }; + + declare private githubStatus: GitHubStatus | null; private active = false; private context: GitHubShortcutsContext = { key: "", path: "", repository: false, }; - private githubStatus: GitHubStatus | null = null; private statusContextKey = ""; private statusRequestId = 0; private readonly hints = new ActionHints(this); + constructor() { + super(); + this.githubStatus = null; + } + createRenderRoot() { return this; } @@ -51,14 +58,12 @@ export class CaffoldSectionGithubShortcuts extends LitElement { super.connectedCallback(); setAttribute(this, "role", "complementary"); setAttribute(this, "aria-label", "GitHub shortcuts"); - this.draw(); } disconnectedCallback() { super.disconnectedCallback(); this.active = false; this.invalidateStatus(); - this.draw(); } setContext({ @@ -143,17 +148,14 @@ export class CaffoldSectionGithubShortcuts extends LitElement { const contextKey = this.requestContextKey(); if (!this.context.repository) { this.invalidateStatus(); - this.draw(); return; } if (!this.active || this.statusContextKey === contextKey) { - this.draw(); return; } this.githubStatus = null; this.statusContextKey = contextKey; - this.draw(); const requestId = ++this.statusRequestId; void this.loadStatus(contextKey, this.context.path, requestId); } @@ -176,7 +178,6 @@ export class CaffoldSectionGithubShortcuts extends LitElement { this.githubStatus = null; this.statusContextKey = ""; } - this.draw(); } private isCurrentRequest(contextKey: string, requestId: number) { @@ -206,11 +207,6 @@ export class CaffoldSectionGithubShortcuts extends LitElement { }), ); } - - private draw() { - this.requestUpdate(); - this.performUpdate(); - } } customElements.define( diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts index d6dd5e14..a01f239e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts @@ -6,7 +6,16 @@ import { sectionLabel } from "#tasks/task-format.ts"; * whole directory as its title. */ export class CaffoldSectionDetailSummary extends LitElement { - private section: { id: string; name: string } | null = null; + static properties = { + section: { state: true }, + }; + + declare private section: { id: string; name: string } | null; + + constructor() { + super(); + this.section = null; + } createRenderRoot() { return this; @@ -18,8 +27,6 @@ export class CaffoldSectionDetailSummary extends LitElement { section?: { id: string; name: string } | null; } = {}) { this.section = section; - this.requestUpdate(); - this.performUpdate(); } render() { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.test.js index a48eb9a2..74f0bafc 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.test.js @@ -16,14 +16,13 @@ afterEach(() => { } }); -// A dialog whose drawing is kept as markup, with a native dialog stand-in. +// A dialog with a native dialog stand-in. function dialogFixture() { const dialog = { open: false, showModal() { this.open = true; }, close() { this.open = false; } }; const owner = Object.create(Dialog.prototype, Object.getOwnPropertyDescriptors({ phase: "idle", generation: 0, context: null, target: null, failure: null, - isConnected: true, intents: [], drawn: "", + isConnected: true, intents: [], dialog: { value: dialog }, - draw() { this.drawn = templateHtml(this.render()).replace(/\s+/g, " "); }, intent(detail) { this.intents.push(detail); }, })); owner.hints = new ActionHints(owner); @@ -43,13 +42,12 @@ test("all declared deletion edges and rejected events use one phase authority", for (const event of ["open", "cancel", "confirm", "success", "failure", "refused", "reset", "stale"]) { const { owner } = dialogFixture(); owner.phase = phase; - owner.drawn = ""; assert.equal(owner.transition(event), event in allowed, `${phase}/${event}`); assert.equal(owner.phase, allowed[event] ?? phase); if (event in allowed) { const deleting = owner.phase === "deleting"; - assert.match(owner.drawn, new RegExp(`closedby="${deleting ? "none" : "any"}"`)); - assert.equal((owner.drawn.match(/\?disabled="true"/g) ?? []).length, deleting ? 2 : 0); + assert.match(drawn(owner), new RegExp(`closedby="${deleting ? "none" : "any"}"`)); + assert.equal((drawn(owner).match(/\?disabled="true"/g) ?? []).length, deleting ? 2 : 0); } } } @@ -58,8 +56,8 @@ test("all declared deletion edges and rejected events use one phase authority", test("cancel and revoked confirmation have no mutation and invalidate the captured target", () => { const { owner, dialog } = dialogFixture(); assert.equal(owner.openTask(), true); - assert.match(owner.drawn, /Delete task and remaining worktree files\?/); - assert.match(owner.drawn, /data-broken-delete-path >\/managed\/uuid\/managed\/uuidDeleting…<\/button>/); + assert.match(drawn(owner), />Deleting…<\/button>/); owner.setContext({ threadId: "other", error: { allowedActions: ["deleteTask"], worktreeId: "other-uuid" } }); release(); await request; @@ -114,6 +112,10 @@ test("current success publishes deletion, while a partial failure requires a fre assert.equal(owner.phase, eligible ? "confirming" : "idle"); assert.equal(dialog.open, eligible); assert.equal(owner.intents.at(-1).type, "broken-delete-error"); - if (eligible) assert.match(owner.drawn, /role="alert" \?hidden="false"\s*>native delete failednative delete failed>>> = { * deletes it with the worktree its diagnosis named. */ export class CaffoldBrokenTaskDeleteDialog extends LitElement { - private phase: Phase = "idle"; + static properties = { + phase: { state: true }, + target: { state: true }, + failure: { state: true }, + }; + + declare private phase: Phase; + declare private target: BrokenTaskContext | null; + declare private failure: Error | null; private generation = 0; private context: BrokenTaskContext | null = null; - private target: BrokenTaskContext | null = null; - private failure: Error | null = null; private readonly hints = new ActionHints(this); private readonly keyboard = new KeyboardContexts(this); private readonly dialog = new DrawnRef(this); - createRenderRoot() { - return this; + constructor() { + super(); + this.phase = "idle"; + this.target = null; + this.failure = null; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); + createRenderRoot() { + return this; } disconnectedCallback() { @@ -84,7 +92,6 @@ export class CaffoldBrokenTaskDeleteDialog extends LitElement { this.context = next; if (this.phase === "confirming" && next) { this.target = { ...next }; - this.draw(); } } @@ -94,7 +101,6 @@ export class CaffoldBrokenTaskDeleteDialog extends LitElement { } this.target = { ...this.context }; this.failure = null; - this.draw(); this.dialog.value?.showModal(); return true; } @@ -187,18 +193,12 @@ export class CaffoldBrokenTaskDeleteDialog extends LitElement { `; } - private draw() { - this.requestUpdate(); - this.performUpdate(); - } - private transition(event: PhaseEvent) { const next = EDGES[this.phase][event]; if (!next) { return false; } this.phase = next; - this.draw(); return true; } @@ -233,7 +233,6 @@ export class CaffoldBrokenTaskDeleteDialog extends LitElement { } const generation = ++this.generation; this.failure = null; - this.draw(); const current = () => this.isConnected && this.generation === generation && @@ -270,7 +269,6 @@ export class CaffoldBrokenTaskDeleteDialog extends LitElement { if (eligible) { this.transition("failure"); this.failure = error instanceof Error ? error : new Error(`${error}`); - this.draw(); this.intent({ type: "broken-delete-error", error: diagnostic }); } else { this.transition("refused"); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts index 2d23f76c..0a4c5bdd 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts @@ -55,7 +55,6 @@ export class CaffoldTaskCommandDialog extends LitElement { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); - this.performUpdate(); } disconnectedCallback() { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts index 483a46b0..0b57bf26 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts @@ -44,19 +44,27 @@ let currentPlanInstanceId = 0; * cannot be read. It reads the plan again whenever its files change. */ export class CaffoldTaskCurrentPlan extends LitElement { + static properties = { + node: { state: true }, + context: { state: true }, + projection: { state: true }, + readError: { state: true }, + watchError: { state: true }, + }; + + declare private node: CurrentPlanNode; + declare private context: CurrentPlanContext | null; + declare private projection: CurrentPlanProjection | null; + declare private readError: Error | null; + declare private watchError: Error | null; private readonly statusPopoverId = `task-current-plan-status-${++currentPlanInstanceId}`; - private node: CurrentPlanNode = CURRENT_PLAN_NODE.INACTIVE; - private context: CurrentPlanContext | null = null; private contextGeneration = 0; private requestId = 0; private requestController: AbortController | null = null; private liveUpdates: WorkspaceLiveUpdates | null = null; private watchPath = ""; private watchUnsubscribe: (() => void) | null = null; - private projection: CurrentPlanProjection | null = null; - private readError: Error | null = null; - private watchError: Error | null = null; // Whether focus sat on the status controls before a drawing hid them. private statusHadFocus = false; private readonly iconsReady = () => this.requestUpdate(); @@ -71,6 +79,11 @@ export class CaffoldTaskCurrentPlan extends LitElement { constructor() { super(); + this.node = CURRENT_PLAN_NODE.INACTIVE; + this.context = null; + this.projection = null; + this.readError = null; + this.watchError = null; this.addEventListener(KEYBOARD_SESSION_DISMISS_EVENT, (event) => this.handleDismiss(event), ); @@ -84,7 +97,6 @@ export class CaffoldTaskCurrentPlan extends LitElement { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); - this.performUpdate(); if (this.context) { this.requestRead(); } @@ -128,12 +140,10 @@ export class CaffoldTaskCurrentPlan extends LitElement { this.clearActivation(); this.context = next; if (this.node !== CURRENT_PLAN_NODE.INACTIVE) { - this.transition(CURRENT_PLAN_NODE.INACTIVE, { draw: false }); + this.transition(CURRENT_PLAN_NODE.INACTIVE); } if (this.isConnected) { this.requestRead(); - } else { - this.draw(); } } @@ -142,8 +152,6 @@ export class CaffoldTaskCurrentPlan extends LitElement { this.context = null; if (this.node !== CURRENT_PLAN_NODE.INACTIVE) { this.transition(CURRENT_PLAN_NODE.INACTIVE); - } else { - this.draw(); } } @@ -354,11 +362,6 @@ export class CaffoldTaskCurrentPlan extends LitElement { } } - private draw() { - this.requestUpdate(); - this.performUpdate(); - } - private clearActivation() { this.contextGeneration += 1; this.invalidateRequest(); @@ -484,7 +487,6 @@ export class CaffoldTaskCurrentPlan extends LitElement { } this.watchError = error instanceof Error ? error : new Error(`${error}`); - this.draw(); }, }); } @@ -509,14 +511,11 @@ export class CaffoldTaskCurrentPlan extends LitElement { this.requestController = null; } - private transition(next: CurrentPlanNode, { draw = true } = {}) { + private transition(next: CurrentPlanNode) { if (!currentPlanTransitionAllowed(this.node, next)) { return false; } this.node = next; - if (draw) { - this.draw(); - } return true; } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts index fbfc62dc..9c411aff 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts @@ -62,7 +62,6 @@ export class CaffoldCurrentPlanDocumentDialog extends LitElement { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); - this.performUpdate(); } disconnectedCallback() { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts index cb64660a..65fb8cc5 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts @@ -49,7 +49,6 @@ export class CaffoldTaskMarkdownPreviewDialog extends LitElement { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); - this.performUpdate(); } disconnectedCallback() { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts index 74093317..b28547d2 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts @@ -39,16 +39,13 @@ interface RecordState { export class CaffoldTaskPermissionInstructionsDialog extends LitElement { static properties = { threadId: { state: true }, + state: { state: true }, }; declare private threadId: string; + declare private state: RecordState; private opener: HTMLElement | null = null; private request = 0; - private state: RecordState = { - loading: false, - instructions: null, - error: "", - }; private readonly iconsReady = () => this.requestUpdate(); private readonly hints = new ActionHints(this); private readonly keyboard = new KeyboardContexts(this); @@ -57,6 +54,7 @@ export class CaffoldTaskPermissionInstructionsDialog extends LitElement { constructor() { super(); this.threadId = ""; + this.state = { loading: false, instructions: null, error: "" }; } createRenderRoot() { @@ -67,7 +65,6 @@ export class CaffoldTaskPermissionInstructionsDialog extends LitElement { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); - this.performUpdate(); } disconnectedCallback() { @@ -206,26 +203,26 @@ export class CaffoldTaskPermissionInstructionsDialog extends LitElement { return; } const request = ++this.request; - this.show({ loading: true, instructions: null, error: "" }); + this.state = { loading: true, instructions: null, error: "" }; try { const payload = await getTaskPermissionInstructions(threadId); if (this.isCurrent(request, threadId)) { - this.show({ + this.state = { loading: false, instructions: instructionsOf(payload), error: "", - }); + }; } } catch (error) { if (this.isCurrent(request, threadId)) { - this.show({ + this.state = { loading: false, instructions: null, error: messageOf( error, "Caffold could not read what this Task settled.", ), - }); + }; this.reportRequestFailure(threadId, error); } } @@ -237,37 +234,31 @@ export class CaffoldTaskPermissionInstructionsDialog extends LitElement { return; } const request = ++this.request; - this.show({ ...this.state, loading: true, error: "" }); + this.state = { ...this.state, loading: true, error: "" }; try { const payload = await forgetTaskPermissionInstructions(threadId); if (this.isCurrent(request, threadId)) { - this.show({ + this.state = { loading: false, instructions: instructionsOf(payload), error: "", - }); + }; } } catch (error) { if (this.isCurrent(request, threadId)) { - this.show({ + this.state = { ...this.state, loading: false, error: messageOf( error, "Caffold could not forget what this Task settled.", ), - }); + }; this.reportRequestFailure(threadId, error); } } } - private show(state: RecordState) { - this.state = state; - this.requestUpdate(); - this.performUpdate(); - } - private reportRequestFailure(threadId: string, error: unknown) { this.dispatchEvent( new CustomEvent("caffold:task-detail-intent", { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts index 7b6a06db..a69b52c6 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts @@ -19,14 +19,20 @@ export type TaskSummaryIntent = TaskInfoIntent; /** * The open Task's heading: its title, and Task Info beside it. - * - * The Tasks Detail layout hands it the Task's snapshot and reads its Action - * Hints and keyboard context right after, so it draws at once. */ export class CaffoldTaskDetailSummary extends LitElement { - private snapshot: TaskInfoSnapshot = taskInfoSnapshot(); + static properties = { + snapshot: { state: true }, + }; + + declare private snapshot: TaskInfoSnapshot; private readonly info = new DrawnRef(this); + constructor() { + super(); + this.snapshot = taskInfoSnapshot(); + } + createRenderRoot() { return this; } @@ -38,8 +44,6 @@ export class CaffoldTaskDetailSummary extends LitElement { setSnapshot(snapshot: Partial = {}) { this.snapshot = taskInfoSnapshot(snapshot); - this.requestUpdate(); - this.performUpdate(); } deactivate() { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts b/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts index 5c082ba0..f0d41c9a 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts @@ -23,12 +23,13 @@ let taskDetailGitInstanceId = 0; /** * The Detail header's Git button, and the popover of Git work it opens: * Compare and Log. - * - * The Detail layout hands it whether Git is available and reads it right - * after, so it draws at once. */ export class CaffoldTaskDetailGit extends LitElement { - private available = false; + static properties = { + available: { state: true }, + }; + + declare private available: boolean; private readonly popoverId = `task-git-actions-${++taskDetailGitInstanceId}`; private readonly hints = new ActionHints(this); private readonly keyboard = new KeyboardContexts(this); @@ -36,6 +37,7 @@ export class CaffoldTaskDetailGit extends LitElement { constructor() { super(); + this.available = false; this.addEventListener(KEYBOARD_SESSION_DISMISS_EVENT, (event) => this.handleDismiss(event), ); @@ -52,8 +54,6 @@ export class CaffoldTaskDetailGit extends LitElement { setSnapshot({ available = false }: { available?: boolean } = {}) { this.available = Boolean(available); - this.requestUpdate(); - this.performUpdate(); if (!this.available) { this.deactivate(); } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts b/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts index 9f91f85b..1d085ef6 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts @@ -23,12 +23,13 @@ let taskDetailGithubInstanceId = 0; /** * The Detail header's GitHub button, and the popover of GitHub work it * opens: Pull Requests and Issues. - * - * The Detail layout hands it whether GitHub is available and reads it right - * after, so it draws at once. */ export class CaffoldTaskDetailGithub extends LitElement { - private available = false; + static properties = { + available: { state: true }, + }; + + declare private available: boolean; private readonly popoverId = `task-github-actions-${++taskDetailGithubInstanceId}`; private readonly hints = new ActionHints(this); @@ -37,6 +38,7 @@ export class CaffoldTaskDetailGithub extends LitElement { constructor() { super(); + this.available = false; this.addEventListener(KEYBOARD_SESSION_DISMISS_EVENT, (event) => this.handleDismiss(event), ); @@ -53,8 +55,6 @@ export class CaffoldTaskDetailGithub extends LitElement { setSnapshot({ available = false }: { available?: boolean } = {}) { this.available = Boolean(available); - this.requestUpdate(); - this.performUpdate(); if (!this.available) { this.deactivate(); } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts b/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts index 814bba49..46871588 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts @@ -13,11 +13,22 @@ export const TERMINAL_BUTTON_INTENT_EVENT = // The Detail header's terminal toggle. It only reports the press; the Detail // layout decides whether that enters the terminal or returns from it. export class CaffoldTaskDetailTerminal extends LitElement { - private available = false; - private pressed = false; + static properties = { + available: { state: true }, + pressed: { state: true }, + }; + + declare private available: boolean; + declare private pressed: boolean; private readonly iconsReady = () => this.requestUpdate(); private readonly hints = new ActionHints(this); + constructor() { + super(); + this.available = false; + this.pressed = false; + } + createRenderRoot() { return this; } @@ -41,8 +52,6 @@ export class CaffoldTaskDetailTerminal extends LitElement { } = {}) { this.available = Boolean(available); this.pressed = Boolean(pressed); - this.requestUpdate(); - this.performUpdate(); } actionHintScope(pull: ActionHintPull = {}) { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.ts b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.ts index 73f099e9..3f72c652 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.ts @@ -22,10 +22,15 @@ const KEYS = Object.freeze([ ]); export class CaffoldTerminalSpecialKeys extends LitElement { - private controlPending = false; + static properties = { + controlPending: { state: true }, + }; + + declare private controlPending: boolean; constructor() { super(); + this.controlPending = false; // The keys must not take focus from the terminal, or a phone would close // its keyboard on every press. this.addEventListener("pointerdown", (event) => { @@ -48,7 +53,7 @@ export class CaffoldTerminalSpecialKeys extends LitElement { /** Whether Ctrl was waiting for this key; it stops waiting either way. */ consumeControl() { const pending = this.controlPending; - this.setControlPending(false); + this.controlPending = false; return pending; } @@ -70,7 +75,7 @@ export class CaffoldTerminalSpecialKeys extends LitElement { private pressKey(event: Event) { const key = `${(event.currentTarget as HTMLButtonElement).dataset.terminalSpecialKey}`; if (key === "control") { - this.setControlPending(!this.controlPending); + this.controlPending = !this.controlPending; return; } this.dispatchEvent( @@ -80,12 +85,6 @@ export class CaffoldTerminalSpecialKeys extends LitElement { }), ); } - - private setControlPending(pending: boolean) { - this.controlPending = pending; - this.requestUpdate(); - this.performUpdate(); - } } customElements.define( diff --git a/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts b/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts index 5363bec5..02a71517 100644 --- a/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts @@ -30,11 +30,6 @@ export class CaffoldTaskArchivedDeleteDialog extends LitElement { return this; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); - } - render() { return html` } }); -test("a new count is drawn at once, and the same count draws nothing", () => { +test("a new count is drawn, and the same count leaves the pie as it is", () => { const owner = pie(); owner.setSnapshot({ usedTokens: 32_147, windowTokens: 200_000 }); assert.equal(owner.updates, 1); @@ -111,13 +111,19 @@ function wedge(d) { // A pie whose drawing is its `render()` alone; each drawing it asks for is // counted. +// A pie that counts how often its reactive usage is set, each a request to +// draw it again. function pie(properties = {}) { + let usage = null; const owner = Object.create(ContextUsage.prototype, { ...Object.getOwnPropertyDescriptors({ - usage: null, popoverId: "task-context-usage-1", updates: 0, - performUpdate() { + get usage() { + return usage; + }, + set usage(next) { + usage = next; this.updates += 1; }, }), diff --git a/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts b/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts index 21e0318d..74d9454a 100644 --- a/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts +++ b/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts @@ -47,7 +47,6 @@ export class CaffoldTaskContextUsage extends LitElement { return; } this.usage = next; - this.performUpdate(); } render() { diff --git a/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts b/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts index 43db6cf4..f941631f 100644 --- a/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts @@ -40,15 +40,17 @@ export function requestTaskImagePreview( // cannot be shown. type PictureView = "image" | "loading" | "unavailable"; -/** - * One picture from a Task, larger, in a dialog over the Tasks screen. - * - * The Tasks screen opens it and shows it at once, so it draws in place. - */ +/** One picture from a Task, larger, in a dialog over the Tasks screen. */ export class CaffoldTaskImagePreviewDialog extends LitElement { - private name = ""; - private src = ""; - private view: PictureView = "image"; + static properties = { + name: { state: true }, + src: { state: true }, + view: { state: true }, + }; + + declare private name: string; + declare private src: string; + declare private view: PictureView; private picture: { fetching: AbortController; objectUrl: string } | null = null; private readonly iconsReady = () => this.requestUpdate(); @@ -56,6 +58,13 @@ export class CaffoldTaskImagePreviewDialog extends LitElement { private readonly keyboard = new KeyboardContexts(this); private readonly dialog = new DrawnRef(this); + constructor() { + super(); + this.name = ""; + this.src = ""; + this.view = "image"; + } + createRenderRoot() { return this; } @@ -89,7 +98,6 @@ export class CaffoldTaskImagePreviewDialog extends LitElement { // showing an empty image the browser would draw as broken. this.src = inline ? src : ""; this.view = inline ? "image" : "loading"; - this.draw(); if (!inline) { this.showFetchedPicture(src); } @@ -183,7 +191,6 @@ export class CaffoldTaskImagePreviewDialog extends LitElement { } picture.objectUrl = URL.createObjectURL(file); this.src = picture.objectUrl; - this.draw(); }, (error) => { if ( @@ -210,12 +217,10 @@ export class CaffoldTaskImagePreviewDialog extends LitElement { private showLoaded() { this.view = "image"; - this.draw(); } private showUnavailable() { this.view = "unavailable"; - this.draw(); } private reset() { @@ -223,12 +228,6 @@ export class CaffoldTaskImagePreviewDialog extends LitElement { this.name = ""; this.src = ""; this.view = "image"; - this.draw(); - } - - private draw() { - this.requestUpdate(); - this.performUpdate(); } } diff --git a/frontend/pages/(task-workspace)/tasks/components/task-absence.ts b/frontend/pages/(task-workspace)/tasks/components/task-absence.ts index a3da0cf2..28273fab 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-absence.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-absence.ts @@ -49,12 +49,21 @@ const COPY: Readonly< * Restore; its owner sends the request and hands back how it went. */ export class CaffoldTaskAbsence extends LitElement { - private snapshot: TaskAbsenceSnapshot | null = null; + static properties = { + snapshot: { state: true }, + }; + + declare private snapshot: TaskAbsenceSnapshot | null; private readonly iconsReady = () => this.requestUpdate(); private readonly hints = new ActionHints(this, { clipRoots: () => [this], }); + constructor() { + super(); + this.snapshot = null; + } + createRenderRoot() { return this; } @@ -63,7 +72,6 @@ export class CaffoldTaskAbsence extends LitElement { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); - this.performUpdate(); } disconnectedCallback() { @@ -73,8 +81,6 @@ export class CaffoldTaskAbsence extends LitElement { setSnapshot(snapshot: TaskAbsenceSnapshot) { this.snapshot = snapshot; - this.requestUpdate(); - this.performUpdate(); } actionHintScope(pull: ActionHintPull = {}) { diff --git a/frontend/pages/(task-workspace)/tasks/components/task-create.ts b/frontend/pages/(task-workspace)/tasks/components/task-create.ts index 8c68e3a6..4e990702 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-create.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-create.ts @@ -70,7 +70,8 @@ export type TaskCreateIntentDetail = * * The surface that mounts it hands it the directory, the server's paths, and * whether Tasks can be made now, and starts the Task it asks for. It draws at - * once wherever its state changes, since that surface reads it right after. + * once wherever its state changes, since it hands its directory field and + * Composer their context right after drawing them. */ export class CaffoldTaskCreate extends LitElement { private cwd = "."; diff --git a/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts b/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts index 7fba681c..b5556e9d 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts @@ -24,19 +24,24 @@ const FAILED_INSTRUCTION = /** * What the Tasks screen shows instead of Tasks while the Task store is being * upgraded, or after its upgrade failed and can be retried. - * - * The Tasks screen hands it each store snapshot and reads its Action Hints - * and scroll surface right after, so it draws at once. */ export class CaffoldTaskStoreRecovery extends LitElement { - private snapshot: TaskStoreStatusSnapshot = - INITIAL_TASK_STORE_STATUS_SNAPSHOT; + static properties = { + snapshot: { state: true }, + }; + + declare private snapshot: TaskStoreStatusSnapshot; private readonly surface = new DrawnRef(this); private readonly hints: ActionHints = new ActionHints(this, { clipRoots: () => [this, this.surface.value], }); private readonly surfaces = new ScrollSurfaces(this); + constructor() { + super(); + this.snapshot = INITIAL_TASK_STORE_STATUS_SNAPSHOT; + } + createRenderRoot() { return this; } @@ -47,8 +52,6 @@ export class CaffoldTaskStoreRecovery extends LitElement { return; } this.snapshot = next; - this.requestUpdate(); - this.performUpdate(); } actionHintScope({ diff --git a/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts b/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts index d09c2879..7195fcc4 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts @@ -78,7 +78,7 @@ export class CaffoldTaskSwitcherDialog extends LitElement { /** Show the given Tasks, most recently finished first. */ open({ rows = [], loaded = true }: TaskSwitcherSnapshot = {}) { const dialog = this.dialog; - if (dialog.open) { + if (!dialog || dialog.open) { return false; } this.tasksLoaded = loaded; @@ -93,7 +93,7 @@ export class CaffoldTaskSwitcherDialog extends LitElement { close() { const dialog = this.dialog; - if (!dialog.open) { + if (!dialog?.open) { return false; } dialog.close(); @@ -109,7 +109,7 @@ export class CaffoldTaskSwitcherDialog extends LitElement { * Task list arrived has no order to protect yet. */ updateTasks({ rows = [], loaded = true }: TaskSwitcherSnapshot = {}) { - if (!this.dialog.open) { + if (!this.dialog?.open) { return false; } const wasLoaded = this.tasksLoaded; @@ -260,11 +260,8 @@ export class CaffoldTaskSwitcherDialog extends LitElement { .map(({ task }) => taskThreadId(task)); } - // The dialog is drawn the first time it is needed, which is when the - // workspace first opens it or asks for its keyboard contexts. private get dialog() { - this.performUpdate(); - return this.modal.value as HTMLDialogElement; + return this.modal.value ?? null; } } diff --git a/frontend/pages/(task-workspace)/tasks/recovery/page.ts b/frontend/pages/(task-workspace)/tasks/recovery/page.ts index f7c2c28f..f3b15476 100644 --- a/frontend/pages/(task-workspace)/tasks/recovery/page.ts +++ b/frontend/pages/(task-workspace)/tasks/recovery/page.ts @@ -62,8 +62,8 @@ export type TaskRecoveryIntent = * A listed Task Caffold cannot open as it is: why, and what can be done with * it, each action answered by the server. * - * The Tasks screen opens it for a Task and reads its Action Hints and scroll - * surface right after, so it draws at once. + * It draws at once wherever its state changes, since it hands the notice it + * draws what to show right after drawing it. */ export class CaffoldTaskRecovery extends LitElement { private recovery: ActiveTaskRecovery | null = null; diff --git a/frontend/pages/components/update-dialog.ts b/frontend/pages/components/update-dialog.ts index 6d67c586..9e2d51cf 100644 --- a/frontend/pages/components/update-dialog.ts +++ b/frontend/pages/components/update-dialog.ts @@ -17,11 +17,6 @@ export class CaffoldUpdateDialog extends LitElement { return this; } - connectedCallback() { - super.connectedCallback(); - this.performUpdate(); - } - render() { return html` customElements.whenDefined("caffold-segmented-control")); - const result = await page.evaluate(() => { + // Each pull waits for the next frame, as keyboard navigation does. + const result = await page.evaluate(async () => { const control = document.createElement("caffold-segmented-control"); document.body.append(control); const choices = [ { value: "changes", label: "Changes" }, { value: "file path", label: "Files" }, ]; - const targets = () => control.actionHintScope({ - scopeId: "review:navigator-axis", - actionId: "navigation.review.axis", - }).targets.map((target) => [target.id, target.label]); + const targets = async () => { + await new Promise(requestAnimationFrame); + return control.actionHintScope({ + scopeId: "review:navigator-axis", + actionId: "navigation.review.axis", + }).targets.map((target) => [target.id, target.label]); + }; control.setSnapshot({ label: "Review navigator", selected: "changes", choices, actionVerb: "Show" }); - const onChanges = targets(); + const onChanges = await targets(); control.setSnapshot({ label: "Review navigator", selected: "file path", choices }); - const onFiles = targets(); + const onFiles = await targets(); return { onChanges, onFiles }; }); expect(result).toEqual({ From f42004f66d54c44b9a51cc708971dc8e97fe76e6 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Tue, 27 Oct 2026 14:25:28 +0900 Subject: [PATCH 16/19] Build the frontend with tsc alone Node's type stripping limited the frontend to `.ts` import paths and erasable syntax, behind a separate script. tsc now compiles each module, the service worker included, to the `.js` path it is served at with a source map beside it, stops the server build on a type error, and writes the build unit tests run from. Keyboard navigation imports Action Hints through its public entry again, which the boundary contract checks now that imports end in `.js`. --- .github/workflows/macos-release.yml | 6 +- .github/workflows/rust-checks.yml | 6 +- .gitignore | 1 + caffold/build.rs | 135 +- caffold/src/app/shell.rs | 22 +- caffold/src/static_assets.rs | 1423 +++++++++-------- docs/architecture/frontend.md | 43 +- docs/development/testing.md | 9 +- docs/review/frontend.md | 10 +- frontend/README.md | 17 +- frontend/action-hint-scope.test.js | 2 +- frontend/action-hints.test.js | 2 +- frontend/action-hints.ts | 11 +- .../action-hints/components/dialog.test.js | 4 +- frontend/action-hints/components/dialog.ts | 8 +- frontend/action-hints/control.test.js | 2 +- frontend/action-hints/declarations.test.js | 4 +- frontend/action-hints/declarations.ts | 6 +- frontend/action-hints/model.test.js | 2 +- frontend/action-hints/model.ts | 2 +- frontend/api.test.js | 4 +- frontend/api.ts | 2 +- frontend/app.ts | 4 +- frontend/chunked-fetch.ts | 6 +- frontend/chunked-fetch/cell-scheduler.test.js | 4 +- frontend/chunked-fetch/cell-scheduler.ts | 4 +- .../chunked-fetch/in-flight-limit.test.js | 2 +- frontend/chunked-fetch/transfer.test.js | 2 +- frontend/component-styles.test.js | 2 +- frontend/components/docx-viewer.test.js | 2 +- frontend/components/docx-viewer.ts | 6 +- frontend/components/file-navigator.test.js | 2 +- frontend/components/file-navigator.ts | 14 +- .../file-navigator/components/list.test.js | 2 +- .../file-navigator/components/list.ts | 18 +- frontend/components/file-tree.ts | 14 +- .../file-viewer-presentation.test.js | 2 +- .../components/file-viewer-presentation.ts | 4 +- frontend/components/file-viewer.test.js | 2 +- frontend/components/file-viewer.ts | 46 +- .../file-viewer/components/code-viewer.ts | 8 +- .../file-viewer/components/diff-viewer.ts | 10 +- .../file-viewer/syntax-spans.test.js | 2 +- .../components/file-viewer/syntax-spans.ts | 2 +- .../components/git-compare-browser.test.js | 2 +- frontend/components/git-compare-browser.ts | 24 +- frontend/components/git-compare-tree.test.js | 2 +- frontend/components/git-compare-tree.ts | 14 +- frontend/components/loading-text.test.js | 2 +- frontend/components/markdown-preview.test.js | 2 +- frontend/components/markdown-preview.ts | 16 +- frontend/components/pagination.test.js | 2 +- frontend/components/pagination.ts | 10 +- .../pagination/components/button.ts | 6 +- frontend/components/pane-resizer.test.js | 2 +- frontend/components/pane-resizer.ts | 2 +- frontend/components/pdf-viewer.test.js | 2 +- frontend/components/pdf-viewer.ts | 6 +- frontend/components/pptx-viewer.test.js | 2 +- frontend/components/pptx-viewer.ts | 10 +- frontend/components/review-responsive.test.js | 2 +- frontend/components/segmented-control.ts | 2 +- frontend/components/terminal-view.ts | 2 +- frontend/components/xlsx-viewer.test.js | 2 +- frontend/components/xlsx-viewer.ts | 20 +- .../components/xlsx-viewer/window.test.js | 2 +- .../components/xlsx-viewer/workbook.test.js | 2 +- frontend/components/xlsx-viewer/workbook.ts | 2 +- frontend/element-declarations.test.js | 2 +- frontend/file-status.test.js | 2 +- frontend/fonts.test.js | 41 +- frontend/index.html | 2 +- frontend/keyboard-navigation.test.js | 4 +- frontend/keyboard-navigation.ts | 22 +- .../components/hud.test.js | 2 +- .../keyboard-navigation/components/hud.ts | 4 +- .../components/presentation.test.js | 2 +- .../components/presentation.ts | 14 +- .../components/selector.test.js | 4 +- .../components/selector.ts | 4 +- .../components/shortcut-dialog.test.js | 8 +- .../components/shortcut-dialog.ts | 16 +- .../components/shortcut-list.test.js | 4 +- .../components/shortcut-list.ts | 2 +- frontend/keyboard-navigation/context.test.js | 2 +- frontend/keyboard-navigation/context.ts | 10 +- frontend/keyboard-navigation/control.test.js | 2 +- frontend/keyboard-navigation/model.test.js | 2 +- frontend/keyboard-navigation/model.ts | 6 +- .../keyboard-navigation/shortcuts.test.js | 4 +- frontend/navigation-routes.test.js | 2 +- frontend/package-lock.json | 5 +- frontend/package.json | 9 +- .../pages/(task-workspace)/codex-status.ts | 10 +- .../components/reset-credit-dialog.ts | 8 +- .../components/runtime-restart-dialog.ts | 8 +- .../components/runtime-update-dialog.ts | 8 +- .../codex-status/lifecycle.test.js | 4 +- .../codex-status/lifecycle.ts | 8 +- .../codex-status/model.test.js | 2 +- .../(task-workspace)/codex-status/model.ts | 2 +- .../codex-status/runtime-restart-lifecycle.ts | 4 +- .../runtime-update-lifecycle.test.js | 2 +- .../codex-status/runtime-update-lifecycle.ts | 4 +- .../components/navigation.test.js | 4 +- .../(task-workspace)/components/navigation.ts | 6 +- .../components/update-task-dialog.test.js | 8 +- .../components/update-task-dialog.ts | 18 +- .../pages/(task-workspace)/layout.test.js | 4 +- frontend/pages/(task-workspace)/layout.ts | 90 +- .../(task-workspace)/live-updates.test.js | 2 +- .../pages/(task-workspace)/live-updates.ts | 6 +- .../live-updates/lifecycle.test.js | 2 +- .../notes/components/document.test.js | 2 +- .../notes/components/document.ts | 24 +- .../notes/components/info.test.js | 2 +- .../(task-workspace)/notes/components/info.ts | 24 +- .../info/components/copy-markdown.test.js | 2 +- .../info/components/copy-markdown.ts | 4 +- .../info/components/copy-path.test.js | 2 +- .../components/info/components/copy-path.ts | 4 +- .../notes/components/navigator.test.js | 2 +- .../notes/components/navigator.ts | 20 +- .../(task-workspace)/notes/layout.test.js | 6 +- .../pages/(task-workspace)/notes/layout.ts | 18 +- .../notes/layout/selection.test.js | 2 +- .../pages/(task-workspace)/notes/tree.test.js | 2 +- frontend/pages/(task-workspace)/notes/tree.ts | 4 +- .../settings/about/page.test.js | 4 +- .../(task-workspace)/settings/about/page.ts | 14 +- .../settings/appearance/page.ts | 10 +- .../components/runtime-restart-dialog.ts | 8 +- .../settings/claude/display.test.js | 2 +- .../settings/claude/display.ts | 2 +- .../(task-workspace)/settings/claude/page.ts | 18 +- .../(task-workspace)/settings/codex/page.ts | 20 +- .../settings/components/navigator.test.js | 2 +- .../settings/components/navigator.ts | 12 +- .../navigator/components/item.test.js | 2 +- .../components/navigator/components/item.ts | 4 +- .../settings/components/refresh-button.ts | 6 +- .../(task-workspace)/settings/files/page.ts | 8 +- .../settings/grok/display.test.js | 2 +- .../(task-workspace)/settings/grok/display.ts | 2 +- .../settings/grok/page.test.js | 2 +- .../(task-workspace)/settings/grok/page.ts | 18 +- .../settings/jev/lifecycle.test.js | 2 +- .../settings/jev/lifecycle.ts | 2 +- .../(task-workspace)/settings/jev/page.ts | 10 +- .../settings/keyboard/page.ts | 10 +- .../(task-workspace)/settings/layout.test.js | 2 +- .../pages/(task-workspace)/settings/layout.ts | 44 +- .../settings/notifications/lifecycle.test.js | 2 +- .../settings/notifications/lifecycle.ts | 2 +- .../settings/notifications/page.test.js | 5 +- .../settings/notifications/page.ts | 10 +- .../settings/remote-access/page.ts | 8 +- .../settings/remote-access/tailscale.test.js | 2 +- .../settings/remote-access/tailscale.ts | 2 +- .../settings/voice/lifecycle.test.js | 2 +- .../settings/voice/lifecycle.ts | 2 +- .../settings/voice/page.test.js | 2 +- .../(task-workspace)/settings/voice/page.ts | 10 +- .../(task-workspace)/task-store-status.ts | 8 +- .../task-store-status/lifecycle.test.js | 2 +- .../task-store-status/lifecycle.ts | 4 +- .../task-store-status/model.test.js | 2 +- .../task-store-status/model.ts | 2 +- .../commit/components/changes-tree.test.js | 2 +- .../(log)/commit/components/changes-tree.ts | 12 +- .../(detail)/(git)/(log)/commit/page.test.js | 2 +- .../tasks/(detail)/(git)/(log)/commit/page.ts | 24 +- .../tasks/(detail)/(git)/(log)/layout.ts | 16 +- .../tasks/(detail)/(git)/(log)/list/page.ts | 16 +- .../tasks/(detail)/(git)/compare/page.ts | 10 +- .../(detail)/(git)/components/controls.ts | 12 +- .../tasks/(detail)/(git)/layout.test.js | 2 +- .../tasks/(detail)/(git)/layout.ts | 28 +- .../(github)/(issues)/detail/page.test.js | 2 +- .../(detail)/(github)/(issues)/detail/page.ts | 16 +- .../(detail)/(github)/(issues)/layout.test.js | 2 +- .../(detail)/(github)/(issues)/layout.ts | 16 +- .../(detail)/(github)/(issues)/list/page.ts | 14 +- .../(github)/(pulls)/detail/page.test.js | 2 +- .../(detail)/(github)/(pulls)/detail/page.ts | 16 +- .../(pulls)/files/components/tree.test.js | 2 +- .../(github)/(pulls)/files/components/tree.ts | 12 +- .../(github)/(pulls)/files/page.test.js | 2 +- .../(detail)/(github)/(pulls)/files/page.ts | 24 +- .../(detail)/(github)/(pulls)/layout.test.js | 2 +- .../tasks/(detail)/(github)/(pulls)/layout.ts | 20 +- .../(detail)/(github)/(pulls)/list/page.ts | 16 +- .../(github)/components/markdown.test.js | 2 +- .../(detail)/(github)/components/markdown.ts | 8 +- .../components/task-start-dialog.test.js | 2 +- .../(github)/components/task-start-dialog.ts | 24 +- .../components/github-issue.ts | 6 +- .../components/github-pull.ts | 8 +- .../tasks/(detail)/(github)/layout.test.js | 2 +- .../tasks/(detail)/(github)/layout.ts | 28 +- .../(detail)/(review)/changes-reading.test.js | 2 +- .../components/branch-changes.test.js | 4 +- .../(review)/components/branch-changes.ts | 16 +- .../(review)/components/selected-file.test.js | 2 +- .../(review)/components/selected-file.ts | 22 +- .../components/selected-file/content.test.js | 2 +- .../components/selected-file/content.ts | 6 +- .../components/working-tree-changes.test.js | 4 +- .../components/working-tree-changes.ts | 16 +- .../components/changes-tree.test.js | 2 +- .../components/changes-tree.ts | 16 +- .../tasks/(detail)/(review)/layout.test.js | 2 +- .../tasks/(detail)/(review)/layout.ts | 46 +- .../tasks/(detail)/(review)/layout/paths.ts | 2 +- .../tasks/(detail)/(review)/layout/route.ts | 6 +- .../(detail)/(review)/representations.ts | 2 +- .../components/conversation-shortcuts.test.js | 6 +- .../components/conversation-shortcuts.ts | 14 +- .../components/fork-dialog.ts | 16 +- .../(section)/components/github-shortcuts.ts | 4 +- .../(detail)/(section)/components/summary.ts | 2 +- .../tasks/(detail)/(section)/layout.test.js | 2 +- .../tasks/(detail)/(section)/layout.ts | 26 +- .../components/broken-delete-dialog.test.js | 6 +- .../(task)/components/broken-delete-dialog.ts | 10 +- .../(task)/components/command-dialog.test.js | 8 +- .../(task)/components/command-dialog.ts | 16 +- .../(task)/components/conversation.test.js | 2 +- .../(task)/components/conversation.ts | 46 +- .../conversation/active-turn.test.js | 2 +- .../components/conversation/active-turn.ts | 6 +- .../conversation/command-runs.test.js | 2 +- .../components/conversation/command-runs.ts | 4 +- .../conversation/components/active-turn.ts | 4 +- .../conversation/components/approval.test.js | 6 +- .../conversation/components/approval.ts | 8 +- .../components/assistant-message.test.js | 4 +- .../components/assistant-message.ts | 26 +- .../components/copy-button.test.js | 2 +- .../components/copy-button.ts | 4 +- .../components/suggested-prompts.test.js | 4 +- .../components/suggested-prompts.ts | 6 +- .../conversation/components/changed-files.ts | 2 +- .../components/command-group.test.js | 6 +- .../conversation/components/command-group.ts | 20 +- .../components/command-group/model.test.js | 2 +- .../components/command-group/model.ts | 4 +- .../conversation/components/command.ts | 12 +- .../components/command/model.test.js | 2 +- .../conversation/components/command/model.ts | 8 +- .../conversation/components/markdown.test.js | 2 +- .../conversation/components/markdown.ts | 14 +- .../markdown/components/code-block.test.js | 6 +- .../markdown/components/code-block.ts | 8 +- .../components/message-attachments.test.js | 4 +- .../components/message-attachments.ts | 12 +- .../components/older-history.test.js | 4 +- .../conversation/components/older-history.ts | 4 +- .../components/user-message.test.js | 2 +- .../conversation/components/user-message.ts | 12 +- .../components/work-details.test.js | 2 +- .../conversation/components/work-details.ts | 30 +- .../components/conversation/entries.test.js | 2 +- .../(task)/components/conversation/entries.ts | 14 +- .../(task)/components/current-plan.test.js | 6 +- .../(task)/components/current-plan.ts | 22 +- .../components/document-dialog.test.js | 2 +- .../components/document-dialog.ts | 22 +- .../components/current-plan/model.test.js | 2 +- .../(task)/components/current-plan/model.ts | 2 +- .../markdown-preview-dialog.test.js | 2 +- .../components/markdown-preview-dialog.ts | 16 +- .../permission-instructions-dialog.test.js | 6 +- .../permission-instructions-dialog.ts | 16 +- .../(detail)/(task)/components/summary.ts | 8 +- .../summary/components/info.test.js | 4 +- .../components/summary/components/info.ts | 26 +- .../components/info/components/actions.ts | 6 +- .../tasks/(detail)/(task)/layout.test.js | 4 +- .../tasks/(detail)/(task)/layout.ts | 70 +- .../(task)/layout/conversation.test.js | 2 +- .../(detail)/(task)/layout/conversation.ts | 4 +- .../(detail)/(task)/layout/history.test.js | 2 +- .../tasks/(detail)/(task)/layout/history.ts | 6 +- .../(task)/layout/prompt-attachments.test.js | 2 +- .../(task)/layout/prompt-submission.test.js | 2 +- .../(task)/layout/upload-discards.test.js | 2 +- .../tasks/(detail)/(task)/session.test.js | 4 +- .../tasks/(detail)/(task)/session.ts | 8 +- .../(detail)/components/git-menu.test.js | 2 +- .../tasks/(detail)/components/git-menu.ts | 10 +- .../(detail)/components/github-menu.test.js | 2 +- .../tasks/(detail)/components/github-menu.ts | 10 +- .../(detail)/components/terminal-button.ts | 6 +- .../tasks/(detail)/layout.test.js | 2 +- .../(task-workspace)/tasks/(detail)/layout.ts | 74 +- .../tasks/(detail)/terminal/page.test.js | 6 +- .../tasks/(detail)/terminal/page.ts | 26 +- .../(detail)/terminal/page/connection.test.js | 2 +- .../(detail)/terminal/page/connection.ts | 2 +- .../terminal/page/keyboard-inset.test.js | 2 +- .../tasks/(detail)/terminal/page/keys.test.js | 2 +- .../(detail)/terminal/page/model.test.js | 2 +- .../tasks/components/active-task-list.test.js | 2 +- .../tasks/components/active-task-list.ts | 26 +- .../components/section.test.js | 4 +- .../active-task-list/components/section.ts | 16 +- .../components/section/components/row.test.js | 4 +- .../components/section/components/row.ts | 18 +- .../components/archived-delete-dialog.test.js | 2 +- .../components/archived-delete-dialog.ts | 10 +- .../components/archived-task-list.test.js | 4 +- .../tasks/components/archived-task-list.ts | 20 +- .../tasks/components/composer.test.js | 4 +- .../tasks/components/composer.ts | 30 +- .../composer/components/attachments.ts | 6 +- .../composer/components/context-usage.test.js | 4 +- .../composer/components/context-usage.ts | 2 +- .../composer/voice-recorder.test.js | 2 +- .../components/composer/voice-recorder.ts | 2 +- .../components/image-preview-dialog.test.js | 6 +- .../tasks/components/image-preview-dialog.ts | 14 +- .../tasks/components/navigator.test.js | 8 +- .../tasks/components/navigator.ts | 34 +- .../tasks/components/task-absence.test.js | 4 +- .../tasks/components/task-absence.ts | 6 +- .../tasks/components/task-create.ts | 24 +- .../task-create/components/directory-field.ts | 18 +- .../directory-field/control.test.js | 2 +- .../components/directory-field/control.ts | 4 +- .../task-create/directory-path.test.js | 2 +- .../components/task-create/directory-path.ts | 2 +- .../tasks/components/task-status-chip.test.js | 2 +- .../tasks/components/task-status-chip.ts | 8 +- .../components/task-store-recovery.test.js | 8 +- .../tasks/components/task-store-recovery.ts | 8 +- .../components/task-switcher-dialog.test.js | 10 +- .../tasks/components/task-switcher-dialog.ts | 22 +- .../components/task-turn-options.test.js | 2 +- .../tasks/components/task-turn-options.ts | 20 +- .../(task-workspace)/tasks/layout.test.js | 2 +- .../pages/(task-workspace)/tasks/layout.ts | 46 +- .../(task-workspace)/tasks/new/page.test.js | 2 +- .../pages/(task-workspace)/tasks/new/page.ts | 16 +- .../tasks/recovery/page.test.js | 6 +- .../(task-workspace)/tasks/recovery/page.ts | 18 +- .../tasks/runtime-state.test.js | 2 +- .../(task-workspace)/tasks/runtime-state.ts | 2 +- .../(task-workspace)/tasks/stream.test.js | 4 +- .../pages/(task-workspace)/tasks/stream.ts | 8 +- .../tasks/stream/machine.test.js | 4 +- .../(task-workspace)/tasks/stream/machine.ts | 4 +- .../tasks/task-events.test.js | 2 +- .../(task-workspace)/tasks/task-events.ts | 6 +- .../tasks/task-format.test.js | 2 +- .../(task-workspace)/tasks/task-format.ts | 2 +- .../tasks/task-list-model.test.js | 2 +- .../(task-workspace)/tasks/task-list-model.ts | 4 +- .../pages/components/build-mismatch-alert.ts | 2 +- frontend/pages/components/update-dialog.ts | 8 +- .../components/update-result-dialog.test.js | 2 +- .../pages/components/update-result-dialog.ts | 10 +- frontend/pages/foreground-recovery.test.js | 2 +- frontend/pages/foreground-recovery.ts | 8 +- .../browser-signals.test.js | 2 +- .../foreground-recovery/browser-signals.ts | 2 +- .../foreground-recovery/diagnostics.test.js | 2 +- .../pages/foreground-recovery/diagnostics.ts | 6 +- .../foreground-recovery/lifecycle.test.js | 6 +- .../pages/foreground-recovery/lifecycle.ts | 4 +- .../pages/foreground-recovery/machine.test.js | 4 +- frontend/pages/layout.test.js | 6 +- frontend/pages/layout.ts | 46 +- frontend/pages/navigation-history.test.js | 4 +- frontend/pages/navigation-history.ts | 2 +- frontend/pages/pwa-update-lifecycle.ts | 2 +- .../pwa-update-lifecycle/machine.test.js | 2 +- .../pages/pwa-update-lifecycle/runtime.ts | 2 +- frontend/scroll-scope.test.js | 2 +- frontend/scroll-scope.ts | 2 +- frontend/service-worker.test.js | 52 +- .../{service-worker.js => service-worker.ts} | 625 ++++---- frontend/settings.test.js | 2 +- frontend/settings.ts | 6 +- frontend/strip-types.mjs | 30 - .../tests/contracts/appearance-theme.test.mjs | 2 +- .../contracts/docx-viewer-library.test.mjs | 6 +- .../tests/contracts/font-presets.test.mjs | 38 +- .../frontend-test-placement.test.mjs | 24 +- frontend/tests/contracts/lit-library.test.mjs | 4 +- .../tests/contracts/native-imports.test.mjs | 8 +- .../contracts/pdf-viewer-library.test.mjs | 6 +- .../contracts/pptx-viewer-library.test.mjs | 6 +- .../tests/contracts/service-worker.test.mjs | 69 +- .../task-workspace-structure.test.mjs | 70 +- .../contracts/terminal-view-library.test.mjs | 6 +- .../contracts/typescript-modules.test.mjs | 20 +- .../contracts/xlsx-viewer-library.test.mjs | 6 +- frontend/tests/e2e/app-shell-update.spec.js | 43 +- frontend/tests/e2e/app-shell.spec.js | 48 +- frontend/tests/e2e/component-styles.spec.js | 6 +- frontend/tests/e2e/file-tree.spec.js | 18 +- frontend/tests/e2e/loading-text.spec.js | 2 +- frontend/tests/e2e/native-imports.spec.js | 50 +- frontend/tests/e2e/notes.spec.js | 8 +- frontend/tests/e2e/settings.spec.js | 2 +- frontend/tests/e2e/task-workspace.spec.js | 2 +- frontend/tests/e2e/tasks/action-hints.spec.js | 2 +- .../e2e/tasks/code-block-controls.spec.js | 2 +- frontend/tests/e2e/tasks/composer.spec.js | 4 +- frontend/tests/e2e/tasks/git-review.spec.js | 4 +- .../tests/e2e/tasks/github-review.spec.js | 2 +- .../tests/e2e/tasks/interface-scaling.spec.js | 2 +- frontend/tests/e2e/tasks/lifecycle.spec.js | 2 +- frontend/tests/e2e/tasks/review-files.spec.js | 8 +- frontend/tests/e2e/tasks/review-git.spec.js | 2 +- .../tests/e2e/tasks/review-responsive.spec.js | 4 +- frontend/tests/e2e/tasks/scroll-mode.spec.js | 2 +- frontend/tests/support/custom-element-unit.js | 4 +- frontend/tests/support/source-url.js | 11 + frontend/theme.test.js | 2 +- frontend/tsconfig.json | 10 +- frontend/tsconfig.test.json | 10 + frontend/tsconfig.worker.json | 12 + frontend/watch.test.js | 2 +- frontend/watch.ts | 2 +- 426 files changed, 3021 insertions(+), 2926 deletions(-) rename frontend/{service-worker.js => service-worker.ts} (71%) delete mode 100644 frontend/strip-types.mjs create mode 100644 frontend/tests/support/source-url.js create mode 100644 frontend/tsconfig.test.json create mode 100644 frontend/tsconfig.worker.json diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml index f5b14fe3..ddcff385 100644 --- a/.github/workflows/macos-release.yml +++ b/.github/workflows/macos-release.yml @@ -84,9 +84,9 @@ jobs: node-version: 24 package-manager-cache: false - # The server embeds Lit from frontend/node_modules and builds its - # TypeScript modules with Node.js. - - name: Install frontend runtime dependencies + # The server embeds Lit from frontend/node_modules and compiles its + # TypeScript modules with the TypeScript installed there. + - name: Install frontend build dependencies run: npm ci --omit=dev working-directory: frontend diff --git a/.github/workflows/rust-checks.yml b/.github/workflows/rust-checks.yml index bb8e2d62..139cf32b 100644 --- a/.github/workflows/rust-checks.yml +++ b/.github/workflows/rust-checks.yml @@ -37,9 +37,9 @@ jobs: cache: npm cache-dependency-path: frontend/package-lock.json - # The server embeds Lit from frontend/node_modules and builds its - # TypeScript modules with Node.js. - - name: Install frontend runtime dependencies + # The server embeds Lit from frontend/node_modules and compiles its + # TypeScript modules with the TypeScript installed there. + - name: Install frontend build dependencies run: npm ci --omit=dev working-directory: frontend diff --git a/.gitignore b/.gitignore index c959584e..2100dc2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /target /frontend/node_modules +/frontend/tests/unit-build /playwright-report /test-results /frontend/tests/e2e/*-snapshots diff --git a/caffold/build.rs b/caffold/build.rs index 18b1be51..59f9fefe 100644 --- a/caffold/build.rs +++ b/caffold/build.rs @@ -2,7 +2,7 @@ use serde_json::Value; use std::{ env, fs, path::{Path, PathBuf}, - process::Command, + process::{Command, Stdio}, time::{SystemTime, UNIX_EPOCH}, }; @@ -13,14 +13,20 @@ fn main() { let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("Cargo OUT_DIR must be set")); - if let Err(problem) = check_installed_lit() { - println!("cargo::error={problem}; run `npm ci` in frontend/ before building"); - return; + for package in ["lit", "typescript"] { + if let Err(problem) = check_installed_package(package) { + println!("cargo::error={problem}; run `npm ci` in frontend/ before building"); + return; + } } - if let Err(problem) = strip_frontend_types(&out_dir.join("frontend")) { - println!("cargo::error={problem}"); + let frontend = out_dir.join("frontend"); + if let Err(problem) = compile_frontend(&frontend) { + for line in problem.lines() { + println!("cargo::error={line}"); + } return; } + write_source_map_table(&out_dir, &frontend); let commit = git_output(&["rev-parse", "--short=8", "HEAD"]).unwrap_or_else(|| "unknown".to_string()); @@ -47,45 +53,108 @@ fn main() { println!("cargo:rustc-env=CAFFOLD_BUILD_NUMBER={build_number}"); } -// The static asset table embeds Lit from frontend/node_modules, which must -// hold the version frontend/package.json names. -fn check_installed_lit() -> Result<(), String> { +// The static asset table embeds Lit from frontend/node_modules, and the +// TypeScript there compiles the frontend modules; each must be the version +// frontend/package.json names. +fn check_installed_package(name: &str) -> Result<(), String> { let wanted = read_json("../frontend/package.json") - .and_then(|package| package["dependencies"]["lit"].as_str().map(str::to_owned)) - .ok_or("frontend/package.json does not name a Lit version")?; - let installed = read_json("../frontend/node_modules/lit/package.json") + .and_then(|package| package["dependencies"][name].as_str().map(str::to_owned)) + .ok_or_else(|| format!("frontend/package.json does not name a {name} version"))?; + let installed = read_json(&format!("../frontend/node_modules/{name}/package.json")) .and_then(|package| package["version"].as_str().map(str::to_owned)); match installed { Some(installed) if installed == wanted => Ok(()), Some(installed) => Err(format!( - "frontend/node_modules holds lit {installed}, but frontend/package.json needs {wanted}" + "frontend/node_modules holds {name} {installed}, but frontend/package.json needs {wanted}" + )), + None => Err(format!( + "frontend/node_modules does not hold {name} {wanted}" )), - None => Err(format!("frontend/node_modules does not hold lit {wanted}")), } } // The static asset table embeds each frontend TypeScript module as the -// JavaScript this writes to the same path under `output`. -fn strip_frontend_types(output: &Path) -> Result<(), String> { - let status = Command::new("node") - .args([ - "--disable-warning=ExperimentalWarning", - "../frontend/strip-types.mjs", - "../frontend", - ]) - .arg(output) - .status() - .map_err(|error| { - format!( - "Node.js 24 or newer must be on PATH to build the frontend's TypeScript modules: {error}" - ) - })?; - if status.success() { +// JavaScript and source map tsc writes to the same path under `output`. The +// page modules and the service worker are typed for different globals, so +// each has its own project; a type error in either stops the build. +fn compile_frontend(output: &Path) -> Result<(), String> { + if output.exists() { + fs::remove_dir_all(output) + .map_err(|error| format!("{} could not be cleared: {error}", output.display()))?; + } + let compilers = ["tsconfig.json", "tsconfig.worker.json"].map(|project| { + let child = Command::new("node") + .arg("../frontend/node_modules/typescript/bin/tsc") + .arg("--project") + .arg(format!("../frontend/{project}")) + .args(["--noEmit", "false", "--outDir"]) + .arg(output) + .stdout(Stdio::piped()) + .spawn(); + (project, child) + }); + let mut problems = Vec::new(); + for (project, child) in compilers { + let compiled = child + .and_then(|child| child.wait_with_output()) + .map_err(|error| { + format!( + "Node.js must be on PATH to compile the frontend's TypeScript modules: {error}" + ) + })?; + if !compiled.status.success() { + problems.push(format!( + "tsc could not compile frontend/{project} ({})", + compiled.status + )); + problems.extend( + String::from_utf8_lossy(&compiled.stdout) + .lines() + .map(str::to_owned), + ); + } + } + if problems.is_empty() { Ok(()) } else { - Err(format!( - "frontend/strip-types.mjs could not write the frontend's TypeScript modules ({status})" - )) + Err(problems.join("\n")) + } +} + +// The static asset table answers `.js.map` from this table of every +// source map tsc wrote. +fn write_source_map_table(out_dir: &Path, frontend: &Path) { + let mut maps = Vec::new(); + collect_source_maps(frontend, frontend, &mut maps); + maps.sort(); + let arms: String = maps + .iter() + .map(|map| { + let file = format!("/frontend/{map}"); + format!( + " {map:?} => Some(include_bytes!(concat!(env!(\"OUT_DIR\"), {file:?}))),\n" + ) + }) + .collect(); + let table = format!( + "fn compiled_source_map(path: &str) -> Option<&'static [u8]> {{\n match path {{\n{arms} _ => None,\n }}\n}}\n" + ); + fs::write(out_dir.join("source_maps.rs"), table).expect("source map table must be written"); +} + +fn collect_source_maps(root: &Path, directory: &Path, maps: &mut Vec) { + for entry in fs::read_dir(directory).expect("compiled frontend must be readable") { + let path = entry + .expect("compiled frontend entry must be readable") + .path(); + if path.is_dir() { + collect_source_maps(root, &path, maps); + } else if path.to_string_lossy().ends_with(".js.map") { + let relative = path + .strip_prefix(root) + .expect("map must be under the compiled frontend"); + maps.push(relative.to_string_lossy().into_owned()); + } } } diff --git a/caffold/src/app/shell.rs b/caffold/src/app/shell.rs index 644e3144..3061ea0e 100644 --- a/caffold/src/app/shell.rs +++ b/caffold/src/app/shell.rs @@ -276,8 +276,12 @@ mod tests { let first = render_service_worker(source, "first-build").unwrap(); let second = render_service_worker(source, "second\"\\\nbuild").unwrap(); - assert!(first.starts_with("const CACHE_NAME = \"caffold-shell-first-build\";")); - assert!(second.starts_with("const CACHE_NAME = \"caffold-shell-second\\\"\\\\\\nbuild\";")); + assert!( + first.starts_with("\"use strict\";\nconst CACHE_NAME = \"caffold-shell-first-build\";") + ); + assert!(second.starts_with( + "\"use strict\";\nconst CACHE_NAME = \"caffold-shell-second\\\"\\\\\\nbuild\";" + )); assert_ne!(first, second); assert!(!first.contains("__CAFFOLD_BUILD_ID__")); assert!(!second.contains("__CAFFOLD_BUILD_ID__")); @@ -304,9 +308,9 @@ mod tests { #[tokio::test] async fn assets_are_tagged_by_their_contents_and_revalidated_before_use() { let (_root, router) = shell_router(); - let script = static_assets::get("app.ts").unwrap(); + let script = static_assets::get("app.js").unwrap(); - let response = get_asset(&router, "app.ts", None).await; + let response = get_asset(&router, "app.js", None).await; assert_eq!(response.status(), StatusCode::OK); assert_eq!( @@ -326,7 +330,7 @@ mod tests { ) ); assert_eq!(body(response).await, script.body); - let again = get_asset(&router, "app.ts", None).await; + let again = get_asset(&router, "app.js", None).await; assert_eq!(again.headers()[header::ETAG], tag.as_str()); let font = get_asset(&router, "fonts/GeistSans-Variable.woff2", None).await; assert_ne!(font.headers()[header::ETAG], tag.as_str()); @@ -335,7 +339,7 @@ mod tests { #[tokio::test] async fn an_asset_the_browser_already_holds_is_answered_without_its_body() { let (_root, router) = shell_router(); - let tag = get_asset(&router, "app.ts", None).await.headers()[header::ETAG] + let tag = get_asset(&router, "app.js", None).await.headers()[header::ETAG] .to_str() .unwrap() .to_string(); @@ -346,18 +350,18 @@ mod tests { format!("\"elsewhere,1\", {tag}"), "*".to_string(), ] { - let response = get_asset(&router, "app.ts", Some(&held)).await; + let response = get_asset(&router, "app.js", Some(&held)).await; assert_eq!(response.status(), StatusCode::NOT_MODIFIED, "{held}"); assert_eq!(response.headers()[header::ETAG], tag.as_str()); assert_eq!(response.headers()[header::CACHE_CONTROL], "no-cache"); assert!(body(response).await.is_empty()); } - let changed = get_asset(&router, "app.ts", Some("\"an-older-build\"")).await; + let changed = get_asset(&router, "app.js", Some("\"an-older-build\"")).await; assert_eq!(changed.status(), StatusCode::OK); assert_eq!( body(changed).await, - static_assets::get("app.ts").unwrap().body + static_assets::get("app.js").unwrap().body ); } diff --git a/caffold/src/static_assets.rs b/caffold/src/static_assets.rs index b73697d6..63ce5c87 100644 --- a/caffold/src/static_assets.rs +++ b/caffold/src/static_assets.rs @@ -10,15 +10,18 @@ pub(crate) fn get(path: &str) -> Option { "manifest.webmanifest" => Some(manifest(include_str!( "../../frontend/manifest.webmanifest" ))), - "service-worker.js" => Some(js(include_str!("../../frontend/service-worker.js"))), + "service-worker.js" => Some(js(include_str!(concat!( + env!("OUT_DIR"), + "/frontend/service-worker.js" + )))), "build-info.js" => Some(js_bytes(include_bytes!(concat!( env!("OUT_DIR"), "/build-info.js" )))), "styles.css" => Some(css(include_str!("../../frontend/styles.css"))), - "app.ts" => Some(js(include_str!(concat!( + "app.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/app.ts" + "/frontend/app.js" )))), "lit/index.js" => Some(js(include_str!("../../frontend/node_modules/lit/index.js"))), "lit/async-directive.js" => Some(js(include_str!( @@ -72,21 +75,21 @@ pub(crate) fn get(path: &str) -> Option { "@lit/reactive-element/css-tag.js" => Some(js(include_str!( "../../frontend/node_modules/@lit/reactive-element/css-tag.js" ))), - "api.ts" => Some(js(include_str!(concat!( + "api.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/api.ts" + "/frontend/api.js" )))), - "action-hint-scope.ts" => Some(js(include_str!(concat!( + "action-hint-scope.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/action-hint-scope.ts" + "/frontend/action-hint-scope.js" )))), - "element-declarations.ts" => Some(js(include_str!(concat!( + "element-declarations.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/element-declarations.ts" + "/frontend/element-declarations.js" )))), - "component-styles.ts" => Some(js(include_str!(concat!( + "component-styles.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/component-styles.ts" + "/frontend/component-styles.js" )))), "component-styles/compact-icon-button.css" => Some(css(include_str!( "../../frontend/component-styles/compact-icon-button.css" @@ -94,45 +97,45 @@ pub(crate) fn get(path: &str) -> Option { "component-styles/busy-spin.css" => Some(css(include_str!( "../../frontend/component-styles/busy-spin.css" ))), - "origin-reachability.ts" => Some(js(include_str!(concat!( + "origin-reachability.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/origin-reachability.ts" + "/frontend/origin-reachability.js" )))), - "file-status.ts" => Some(js(include_str!(concat!( + "file-status.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/file-status.ts" + "/frontend/file-status.js" )))), - "chunked-fetch.ts" => Some(js(include_str!(concat!( + "chunked-fetch.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/chunked-fetch.ts" + "/frontend/chunked-fetch.js" )))), - "chunked-fetch/cell-scheduler.ts" => Some(js(include_str!(concat!( + "chunked-fetch/cell-scheduler.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/chunked-fetch/cell-scheduler.ts" + "/frontend/chunked-fetch/cell-scheduler.js" )))), - "chunked-fetch/in-flight-limit.ts" => Some(js(include_str!(concat!( + "chunked-fetch/in-flight-limit.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/chunked-fetch/in-flight-limit.ts" + "/frontend/chunked-fetch/in-flight-limit.js" )))), - "chunked-fetch/transfer.ts" => Some(js(include_str!(concat!( + "chunked-fetch/transfer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/chunked-fetch/transfer.ts" + "/frontend/chunked-fetch/transfer.js" )))), - "fonts.ts" => Some(js(include_str!(concat!( + "fonts.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/fonts.ts" + "/frontend/fonts.js" )))), - "navigation-routes.ts" => Some(js(include_str!(concat!( + "navigation-routes.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/navigation-routes.ts" + "/frontend/navigation-routes.js" )))), - "settings.ts" => Some(js(include_str!(concat!( + "settings.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/settings.ts" + "/frontend/settings.js" )))), - "theme.ts" => Some(js(include_str!(concat!( + "theme.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/theme.ts" + "/frontend/theme.js" )))), "fonts/GeistSans-Variable.woff2" => Some(woff2(include_bytes!( "../../frontend/assets/fonts/GeistSans-Variable.woff2" @@ -306,177 +309,177 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/assets/brand/grok-template.png" ))), "pages/layout.css" => Some(css(include_str!("../../frontend/pages/layout.css"))), - "pages/layout.ts" => Some(js(include_str!(concat!( + "pages/layout.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/layout.ts" + "/frontend/pages/layout.js" )))), - "pages/navigation-history.ts" => Some(js(include_str!(concat!( + "pages/navigation-history.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/navigation-history.ts" + "/frontend/pages/navigation-history.js" )))), - "pages/foreground-recovery.ts" => Some(js(include_str!(concat!( + "pages/foreground-recovery.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/foreground-recovery.ts" + "/frontend/pages/foreground-recovery.js" )))), - "pages/foreground-recovery/browser-signals.ts" => Some(js(include_str!(concat!( + "pages/foreground-recovery/browser-signals.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/foreground-recovery/browser-signals.ts" + "/frontend/pages/foreground-recovery/browser-signals.js" )))), - "pages/foreground-recovery/lifecycle.ts" => Some(js(include_str!(concat!( + "pages/foreground-recovery/lifecycle.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/foreground-recovery/lifecycle.ts" + "/frontend/pages/foreground-recovery/lifecycle.js" )))), - "pages/foreground-recovery/machine.ts" => Some(js(include_str!(concat!( + "pages/foreground-recovery/machine.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/foreground-recovery/machine.ts" + "/frontend/pages/foreground-recovery/machine.js" )))), - "pages/foreground-recovery/diagnostics.ts" => Some(js(include_str!(concat!( + "pages/foreground-recovery/diagnostics.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/foreground-recovery/diagnostics.ts" + "/frontend/pages/foreground-recovery/diagnostics.js" )))), - "pages/pwa-update-lifecycle.ts" => Some(js(include_str!(concat!( + "pages/pwa-update-lifecycle.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/pwa-update-lifecycle.ts" + "/frontend/pages/pwa-update-lifecycle.js" )))), - "pages/pwa-update-lifecycle/machine.ts" => Some(js(include_str!(concat!( + "pages/pwa-update-lifecycle/machine.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/pwa-update-lifecycle/machine.ts" + "/frontend/pages/pwa-update-lifecycle/machine.js" )))), - "pages/pwa-update-lifecycle/runtime.ts" => Some(js(include_str!(concat!( + "pages/pwa-update-lifecycle/runtime.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/pwa-update-lifecycle/runtime.ts" + "/frontend/pages/pwa-update-lifecycle/runtime.js" )))), "pages/components/build-mismatch-alert.css" => Some(css(include_str!( "../../frontend/pages/components/build-mismatch-alert.css" ))), - "pages/components/build-mismatch-alert.ts" => Some(js(include_str!(concat!( + "pages/components/build-mismatch-alert.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/components/build-mismatch-alert.ts" + "/frontend/pages/components/build-mismatch-alert.js" )))), "pages/components/update-dialog.css" => Some(css(include_str!( "../../frontend/pages/components/update-dialog.css" ))), - "pages/components/update-dialog.ts" => Some(js(include_str!(concat!( + "pages/components/update-dialog.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/components/update-dialog.ts" + "/frontend/pages/components/update-dialog.js" )))), "pages/components/update-result-dialog.css" => Some(css(include_str!( "../../frontend/pages/components/update-result-dialog.css" ))), - "pages/components/update-result-dialog.ts" => Some(js(include_str!(concat!( + "pages/components/update-result-dialog.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/components/update-result-dialog.ts" + "/frontend/pages/components/update-result-dialog.js" )))), "components/file-tree.css" => { Some(css(include_str!("../../frontend/components/file-tree.css"))) } - "components/file-tree.ts" => Some(js(include_str!(concat!( + "components/file-tree.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/file-tree.ts" + "/frontend/components/file-tree.js" )))), "components/file-navigator.css" => Some(css(include_str!( "../../frontend/components/file-navigator.css" ))), - "components/file-navigator.ts" => Some(js(include_str!(concat!( + "components/file-navigator.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/file-navigator.ts" + "/frontend/components/file-navigator.js" )))), "components/file-navigator/components/list.css" => Some(css(include_str!( "../../frontend/components/file-navigator/components/list.css" ))), - "components/file-navigator/components/list.ts" => Some(js(include_str!(concat!( + "components/file-navigator/components/list.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/file-navigator/components/list.ts" + "/frontend/components/file-navigator/components/list.js" )))), "components/pane-resizer.css" => Some(css(include_str!( "../../frontend/components/pane-resizer.css" ))), - "components/pane-resizer.ts" => Some(js(include_str!(concat!( + "components/pane-resizer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/pane-resizer.ts" + "/frontend/components/pane-resizer.js" )))), - "components/review-responsive.ts" => Some(js(include_str!(concat!( + "components/review-responsive.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/review-responsive.ts" + "/frontend/components/review-responsive.js" )))), - "watch.ts" => Some(js(include_str!(concat!( + "watch.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/watch.ts" + "/frontend/watch.js" )))), "pages/(task-workspace)/settings/appearance/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/appearance/page.css" ))), - "pages/(task-workspace)/settings/appearance/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/appearance/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/appearance/page.ts" + "/frontend/pages/(task-workspace)/settings/appearance/page.js" )))), "pages/(task-workspace)/settings/keyboard/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/keyboard/page.css" ))), - "pages/(task-workspace)/settings/keyboard/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/keyboard/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/keyboard/page.ts" + "/frontend/pages/(task-workspace)/settings/keyboard/page.js" )))), "pages/(task-workspace)/settings/files/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/files/page.css" ))), - "pages/(task-workspace)/settings/files/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/files/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/files/page.ts" + "/frontend/pages/(task-workspace)/settings/files/page.js" )))), "pages/(task-workspace)/settings/notifications/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/notifications/page.css" ))), - "pages/(task-workspace)/settings/notifications/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/notifications/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/notifications/page.ts" + "/frontend/pages/(task-workspace)/settings/notifications/page.js" )))), - "pages/(task-workspace)/settings/notifications/lifecycle.ts" => { + "pages/(task-workspace)/settings/notifications/lifecycle.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/notifications/lifecycle.ts" + "/frontend/pages/(task-workspace)/settings/notifications/lifecycle.js" )))) } "pages/(task-workspace)/settings/remote-access/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/remote-access/page.css" ))), - "pages/(task-workspace)/settings/remote-access/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/remote-access/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/remote-access/page.ts" + "/frontend/pages/(task-workspace)/settings/remote-access/page.js" )))), - "pages/(task-workspace)/settings/remote-access/tailscale.ts" => { + "pages/(task-workspace)/settings/remote-access/tailscale.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/remote-access/tailscale.ts" + "/frontend/pages/(task-workspace)/settings/remote-access/tailscale.js" )))) } "pages/(task-workspace)/notes/components/document.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/notes/components/document.css" ))), - "pages/(task-workspace)/notes/components/document.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/notes/components/document.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/notes/components/document.ts" + "/frontend/pages/(task-workspace)/notes/components/document.js" )))), - "pages/(task-workspace)/notes/layout/selection.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/notes/layout/selection.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/notes/layout/selection.ts" + "/frontend/pages/(task-workspace)/notes/layout/selection.js" )))), "pages/(task-workspace)/notes/components/info.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/notes/components/info.css" ))), - "pages/(task-workspace)/notes/components/info.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/notes/components/info.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/notes/components/info.ts" + "/frontend/pages/(task-workspace)/notes/components/info.js" )))), "pages/(task-workspace)/notes/components/info/components/copy-markdown.css" => { Some(css(include_str!( "../../frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.css" ))) } - "pages/(task-workspace)/notes/components/info/components/copy-markdown.ts" => { + "pages/(task-workspace)/notes/components/info/components/copy-markdown.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.ts" + "/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.js" )))) } "pages/(task-workspace)/notes/components/info/components/copy-path.css" => { @@ -484,44 +487,44 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/notes/components/info/components/copy-path.css" ))) } - "pages/(task-workspace)/notes/components/info/components/copy-path.ts" => { + "pages/(task-workspace)/notes/components/info/components/copy-path.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.ts" + "/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.js" )))) } "pages/(task-workspace)/notes/components/navigator.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/notes/components/navigator.css" ))), - "pages/(task-workspace)/notes/components/navigator.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/notes/components/navigator.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/notes/components/navigator.ts" + "/frontend/pages/(task-workspace)/notes/components/navigator.js" )))), "pages/(task-workspace)/notes/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/notes/layout.css" ))), - "pages/(task-workspace)/notes/layout.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/notes/layout.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/notes/layout.ts" + "/frontend/pages/(task-workspace)/notes/layout.js" )))), - "pages/(task-workspace)/notes/tree.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/notes/tree.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/notes/tree.ts" + "/frontend/pages/(task-workspace)/notes/tree.js" )))), "pages/(task-workspace)/settings/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/layout.css" ))), - "pages/(task-workspace)/settings/layout.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/layout.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/layout.ts" + "/frontend/pages/(task-workspace)/settings/layout.js" )))), "pages/(task-workspace)/settings/components/navigator.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/components/navigator.css" ))), - "pages/(task-workspace)/settings/components/navigator.ts" => { + "pages/(task-workspace)/settings/components/navigator.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/components/navigator.ts" + "/frontend/pages/(task-workspace)/settings/components/navigator.js" )))) } "pages/(task-workspace)/settings/components/navigator/components/item.css" => { @@ -529,247 +532,247 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/settings/components/navigator/components/item.css" ))) } - "pages/(task-workspace)/settings/components/navigator/components/item.ts" => { + "pages/(task-workspace)/settings/components/navigator/components/item.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/components/navigator/components/item.ts" + "/frontend/pages/(task-workspace)/settings/components/navigator/components/item.js" )))) } "pages/(task-workspace)/settings/components/detail-list.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/components/detail-list.css" ))), - "pages/(task-workspace)/settings/components/detail-list.ts" => { + "pages/(task-workspace)/settings/components/detail-list.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/components/detail-list.ts" + "/frontend/pages/(task-workspace)/settings/components/detail-list.js" )))) } "pages/(task-workspace)/settings/components/refresh-button.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/components/refresh-button.css" ))), - "pages/(task-workspace)/settings/components/refresh-button.ts" => { + "pages/(task-workspace)/settings/components/refresh-button.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/components/refresh-button.ts" + "/frontend/pages/(task-workspace)/settings/components/refresh-button.js" )))) } "pages/(task-workspace)/settings/codex/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/codex/page.css" ))), - "pages/(task-workspace)/settings/codex/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/codex/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/codex/page.ts" + "/frontend/pages/(task-workspace)/settings/codex/page.js" )))), "pages/(task-workspace)/settings/claude/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/claude/page.css" ))), - "pages/(task-workspace)/settings/claude/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/claude/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/claude/page.ts" + "/frontend/pages/(task-workspace)/settings/claude/page.js" )))), - "pages/(task-workspace)/settings/claude/display.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/claude/display.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/claude/display.ts" + "/frontend/pages/(task-workspace)/settings/claude/display.js" )))), "pages/(task-workspace)/settings/voice/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/voice/page.css" ))), - "pages/(task-workspace)/settings/voice/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/voice/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/voice/page.ts" + "/frontend/pages/(task-workspace)/settings/voice/page.js" )))), - "pages/(task-workspace)/settings/voice/lifecycle.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/voice/lifecycle.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/voice/lifecycle.ts" + "/frontend/pages/(task-workspace)/settings/voice/lifecycle.js" )))), "pages/(task-workspace)/settings/jev/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/jev/page.css" ))), - "pages/(task-workspace)/settings/jev/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/jev/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/jev/page.ts" + "/frontend/pages/(task-workspace)/settings/jev/page.js" )))), - "pages/(task-workspace)/settings/jev/lifecycle.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/jev/lifecycle.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/jev/lifecycle.ts" + "/frontend/pages/(task-workspace)/settings/jev/lifecycle.js" )))), "pages/(task-workspace)/settings/grok/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/grok/page.css" ))), - "pages/(task-workspace)/settings/grok/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/grok/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/grok/page.ts" + "/frontend/pages/(task-workspace)/settings/grok/page.js" )))), - "pages/(task-workspace)/settings/grok/display.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/grok/display.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/grok/display.ts" + "/frontend/pages/(task-workspace)/settings/grok/display.js" )))), "pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.css" => { Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.css" ))) } - "pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.ts" => { + "pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.ts" + "/frontend/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.js" )))) } "pages/(task-workspace)/settings/about/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/settings/about/page.css" ))), - "pages/(task-workspace)/settings/about/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/settings/about/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/settings/about/page.ts" + "/frontend/pages/(task-workspace)/settings/about/page.js" )))), "pages/(task-workspace)/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/layout.css" ))), - "pages/(task-workspace)/layout.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/layout.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/layout.ts" + "/frontend/pages/(task-workspace)/layout.js" )))), - "keyboard-navigation.ts" => Some(js(include_str!(concat!( + "keyboard-navigation.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation.ts" + "/frontend/keyboard-navigation.js" )))), - "scroll-scope.ts" => Some(js(include_str!(concat!( + "scroll-scope.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/scroll-scope.ts" + "/frontend/scroll-scope.js" )))), - "keyboard-navigation/control.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/control.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/control.ts" + "/frontend/keyboard-navigation/control.js" )))), - "keyboard-navigation/context.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/context.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/context.ts" + "/frontend/keyboard-navigation/context.js" )))), - "keyboard-navigation/model.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/model.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/model.ts" + "/frontend/keyboard-navigation/model.js" )))), - "keyboard-navigation/shortcuts.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/shortcuts.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/shortcuts.ts" + "/frontend/keyboard-navigation/shortcuts.js" )))), "keyboard-navigation/components/hud.css" => Some(css(include_str!( "../../frontend/keyboard-navigation/components/hud.css" ))), - "keyboard-navigation/components/hud.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/components/hud.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/components/hud.ts" + "/frontend/keyboard-navigation/components/hud.js" )))), "keyboard-navigation/components/selector.css" => Some(css(include_str!( "../../frontend/keyboard-navigation/components/selector.css" ))), - "keyboard-navigation/components/selector.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/components/selector.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/components/selector.ts" + "/frontend/keyboard-navigation/components/selector.js" )))), "keyboard-navigation/components/presentation.css" => Some(css(include_str!( "../../frontend/keyboard-navigation/components/presentation.css" ))), - "keyboard-navigation/components/presentation.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/components/presentation.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/components/presentation.ts" + "/frontend/keyboard-navigation/components/presentation.js" )))), "keyboard-navigation/components/shortcut-list.css" => Some(css(include_str!( "../../frontend/keyboard-navigation/components/shortcut-list.css" ))), - "keyboard-navigation/components/shortcut-list.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/components/shortcut-list.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/components/shortcut-list.ts" + "/frontend/keyboard-navigation/components/shortcut-list.js" )))), "keyboard-navigation/components/shortcut-dialog.css" => Some(css(include_str!( "../../frontend/keyboard-navigation/components/shortcut-dialog.css" ))), - "keyboard-navigation/components/shortcut-dialog.ts" => Some(js(include_str!(concat!( + "keyboard-navigation/components/shortcut-dialog.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/keyboard-navigation/components/shortcut-dialog.ts" + "/frontend/keyboard-navigation/components/shortcut-dialog.js" )))), - "action-hints.ts" => Some(js(include_str!(concat!( + "action-hints.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/action-hints.ts" + "/frontend/action-hints.js" )))), - "action-hints/control.ts" => Some(js(include_str!(concat!( + "action-hints/control.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/action-hints/control.ts" + "/frontend/action-hints/control.js" )))), - "action-hints/declarations.ts" => Some(js(include_str!(concat!( + "action-hints/declarations.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/action-hints/declarations.ts" + "/frontend/action-hints/declarations.js" )))), - "action-hints/model.ts" => Some(js(include_str!(concat!( + "action-hints/model.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/action-hints/model.ts" + "/frontend/action-hints/model.js" )))), "action-hints/components/dialog.css" => Some(css(include_str!( "../../frontend/action-hints/components/dialog.css" ))), - "action-hints/components/dialog.ts" => Some(js(include_str!(concat!( + "action-hints/components/dialog.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/action-hints/components/dialog.ts" + "/frontend/action-hints/components/dialog.js" )))), - "pages/(task-workspace)/live-updates.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/live-updates.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/live-updates.ts" + "/frontend/pages/(task-workspace)/live-updates.js" )))), - "pages/(task-workspace)/live-updates/lifecycle.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/live-updates/lifecycle.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/live-updates/lifecycle.ts" + "/frontend/pages/(task-workspace)/live-updates/lifecycle.js" )))), "pages/(task-workspace)/components/navigation.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/components/navigation.css" ))), - "pages/(task-workspace)/components/navigation.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/components/navigation.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/components/navigation.ts" + "/frontend/pages/(task-workspace)/components/navigation.js" )))), "pages/(task-workspace)/components/update-task-dialog.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/components/update-task-dialog.css" ))), - "pages/(task-workspace)/components/update-task-dialog.ts" => { + "pages/(task-workspace)/components/update-task-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/components/update-task-dialog.ts" + "/frontend/pages/(task-workspace)/components/update-task-dialog.js" )))) } "pages/(task-workspace)/components/workspace-brand.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/components/workspace-brand.css" ))), - "pages/(task-workspace)/components/workspace-brand.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/components/workspace-brand.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/components/workspace-brand.ts" + "/frontend/pages/(task-workspace)/components/workspace-brand.js" )))), - "pages/(task-workspace)/codex-status.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/codex-status.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/codex-status.ts" + "/frontend/pages/(task-workspace)/codex-status.js" )))), - "pages/(task-workspace)/codex-status/model.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/codex-status/model.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/codex-status/model.ts" + "/frontend/pages/(task-workspace)/codex-status/model.js" )))), - "pages/(task-workspace)/codex-status/lifecycle.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/codex-status/lifecycle.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/codex-status/lifecycle.ts" + "/frontend/pages/(task-workspace)/codex-status/lifecycle.js" )))), - "pages/(task-workspace)/task-store-status.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/task-store-status.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/task-store-status.ts" + "/frontend/pages/(task-workspace)/task-store-status.js" )))), - "pages/(task-workspace)/task-store-status/model.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/task-store-status/model.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/task-store-status/model.ts" + "/frontend/pages/(task-workspace)/task-store-status/model.js" )))), - "pages/(task-workspace)/task-store-status/lifecycle.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/task-store-status/lifecycle.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/task-store-status/lifecycle.ts" + "/frontend/pages/(task-workspace)/task-store-status/lifecycle.js" )))), - "pages/(task-workspace)/codex-status/runtime-restart-lifecycle.ts" => { + "pages/(task-workspace)/codex-status/runtime-restart-lifecycle.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.ts" + "/frontend/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.js" )))) } "pages/(task-workspace)/codex-status/components/runtime-restart-dialog.css" => { @@ -777,10 +780,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.css" ))) } - "pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts" => { + "pages/(task-workspace)/codex-status/components/runtime-restart-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts" + "/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.js" )))) } "pages/(task-workspace)/codex-status/components/reset-credit-dialog.css" => { @@ -788,16 +791,16 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.css" ))) } - "pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts" => { + "pages/(task-workspace)/codex-status/components/reset-credit-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts" + "/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.js" )))) } - "pages/(task-workspace)/codex-status/runtime-update-lifecycle.ts" => { + "pages/(task-workspace)/codex-status/runtime-update-lifecycle.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.ts" + "/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.js" )))) } "pages/(task-workspace)/codex-status/components/runtime-update-dialog.css" => { @@ -805,10 +808,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/codex-status/components/runtime-update-dialog.css" ))) } - "pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts" => { + "pages/(task-workspace)/codex-status/components/runtime-update-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts" + "/frontend/pages/(task-workspace)/codex-status/components/runtime-update-dialog.js" )))) } "pages/(task-workspace)/tasks/layout.css" => Some(css(include_str!( @@ -817,24 +820,24 @@ pub(crate) fn get(path: &str) -> Option { "pages/(task-workspace)/tasks/controls.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/controls.css" ))), - "pages/(task-workspace)/tasks/layout.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/layout.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/layout.ts" + "/frontend/pages/(task-workspace)/tasks/layout.js" )))), "pages/(task-workspace)/tasks/(detail)/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/layout.css" ))), - "pages/(task-workspace)/tasks/(detail)/layout.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/(detail)/layout.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/layout.js" )))), "pages/(task-workspace)/tasks/(detail)/(section)/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.css" ))), - "pages/(task-workspace)/tasks/(detail)/(section)/layout.ts" => { + "pages/(task-workspace)/tasks/(detail)/(section)/layout.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.js" )))) } "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.css" => { @@ -842,10 +845,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts" => { + "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.js" )))) } "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.css" => { @@ -853,10 +856,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts" => { + "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.js" )))) } "pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.css" => { @@ -864,10 +867,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts" => { + "pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.js" )))) } "pages/(task-workspace)/tasks/(detail)/(section)/components/summary.css" => { @@ -875,52 +878,52 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts" => { + "pages/(task-workspace)/tasks/(detail)/(section)/components/summary.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.js" )))) } - "pages/(task-workspace)/tasks/runtime-state.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/runtime-state.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/runtime-state.ts" + "/frontend/pages/(task-workspace)/tasks/runtime-state.js" )))), - "pages/(task-workspace)/tasks/stream.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/stream.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/stream.ts" + "/frontend/pages/(task-workspace)/tasks/stream.js" )))), - "pages/(task-workspace)/tasks/stream/machine.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/stream/machine.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/stream/machine.ts" + "/frontend/pages/(task-workspace)/tasks/stream/machine.js" )))), - "pages/(task-workspace)/tasks/task-events.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/task-events.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/task-events.ts" + "/frontend/pages/(task-workspace)/tasks/task-events.js" )))), - "pages/(task-workspace)/tasks/task-format.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/task-format.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/task-format.ts" + "/frontend/pages/(task-workspace)/tasks/task-format.js" )))), - "pages/(task-workspace)/tasks/task-list-model.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/task-list-model.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/task-list-model.ts" + "/frontend/pages/(task-workspace)/tasks/task-list-model.js" )))), "pages/(task-workspace)/tasks/components/composer.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/composer.css" ))), - "pages/(task-workspace)/tasks/components/composer.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/components/composer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/composer.ts" + "/frontend/pages/(task-workspace)/tasks/components/composer.js" )))), "pages/(task-workspace)/tasks/components/composer/components/attachments.css" => { Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/composer/components/attachments.css" ))) } - "pages/(task-workspace)/tasks/components/composer/components/attachments.ts" => { + "pages/(task-workspace)/tasks/components/composer/components/attachments.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/composer/components/attachments.ts" + "/frontend/pages/(task-workspace)/tasks/components/composer/components/attachments.js" )))) } "pages/(task-workspace)/tasks/components/composer/components/context-usage.css" => { @@ -928,19 +931,19 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.css" ))) } - "pages/(task-workspace)/tasks/components/composer/components/context-usage.ts" => { + "pages/(task-workspace)/tasks/components/composer/components/context-usage.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts" + "/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.js" )))) } "pages/(task-workspace)/tasks/components/task-turn-options.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/task-turn-options.css" ))), - "pages/(task-workspace)/tasks/components/task-turn-options.ts" => { + "pages/(task-workspace)/tasks/components/task-turn-options.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-turn-options.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-turn-options.js" )))) } "pages/(task-workspace)/tasks/components/task-create/components/directory-field.css" => { @@ -948,22 +951,22 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field.css" ))) } - "pages/(task-workspace)/tasks/components/task-create/components/directory-field.ts" => { + "pages/(task-workspace)/tasks/components/task-create/components/directory-field.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field.js" )))) } - "pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.ts" => { + "pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.js" )))) } - "pages/(task-workspace)/tasks/components/task-create/directory-path.ts" => { + "pages/(task-workspace)/tasks/components/task-create/directory-path.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.js" )))) } "pages/(task-workspace)/tasks/components/archived-delete-dialog.css" => { @@ -971,10 +974,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.css" ))) } - "pages/(task-workspace)/tasks/components/archived-delete-dialog.ts" => { + "pages/(task-workspace)/tasks/components/archived-delete-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.js" )))) } "pages/(task-workspace)/tasks/components/task-switcher-dialog.css" => { @@ -982,10 +985,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.css" ))) } - "pages/(task-workspace)/tasks/components/task-switcher-dialog.ts" => { + "pages/(task-workspace)/tasks/components/task-switcher-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.js" )))) } "pages/(task-workspace)/tasks/components/image-preview-dialog.css" => { @@ -993,10 +996,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.css" ))) } - "pages/(task-workspace)/tasks/components/image-preview-dialog.ts" => { + "pages/(task-workspace)/tasks/components/image-preview-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.js" )))) } "pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.css" => { @@ -1004,67 +1007,67 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.css" ))) } - "pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.ts" => { + "pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.ts" + "/frontend/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.js" )))) } - "pages/(task-workspace)/tasks/components/composer/voice-recorder.ts" => { + "pages/(task-workspace)/tasks/components/composer/voice-recorder.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.ts" + "/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.js" )))) } - "pages/(task-workspace)/tasks/components/composer/voice-worklet.ts" => { + "pages/(task-workspace)/tasks/components/composer/voice-worklet.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/composer/voice-worklet.ts" + "/frontend/pages/(task-workspace)/tasks/components/composer/voice-worklet.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.css" ))), - "pages/(task-workspace)/tasks/(detail)/(task)/layout.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/layout.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/layout/history.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/layout/history.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/session.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/session.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.css" => { @@ -1072,16 +1075,16 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.css" => { @@ -1089,10 +1092,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/summary.css" => { @@ -1100,19 +1103,19 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/summary.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.js" )))) } "pages/(task-workspace)/tasks/(detail)/components/git-menu.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.css" ))), - "pages/(task-workspace)/tasks/(detail)/components/git-menu.ts" => { + "pages/(task-workspace)/tasks/(detail)/components/git-menu.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.js" )))) } "pages/(task-workspace)/tasks/(detail)/components/terminal-button.css" => { @@ -1120,43 +1123,43 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.css" ))) } - "pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts" => { + "pages/(task-workspace)/tasks/(detail)/components/terminal-button.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.js" )))) } "pages/(task-workspace)/tasks/(detail)/terminal/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.css" ))), - "pages/(task-workspace)/tasks/(detail)/terminal/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/terminal/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.js" )))) } - "pages/(task-workspace)/tasks/(detail)/terminal/page/connection.ts" => { + "pages/(task-workspace)/tasks/(detail)/terminal/page/connection.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.js" )))) } - "pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.ts" => { + "pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.js" )))) } - "pages/(task-workspace)/tasks/(detail)/terminal/page/keys.ts" => { + "pages/(task-workspace)/tasks/(detail)/terminal/page/keys.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keys.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keys.js" )))) } - "pages/(task-workspace)/tasks/(detail)/terminal/page/model.ts" => { + "pages/(task-workspace)/tasks/(detail)/terminal/page/model.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/model.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/model.js" )))) } "pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.css" => { @@ -1164,10 +1167,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.css" ))) } - "pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.ts" => { + "pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.js" )))) } "pages/(task-workspace)/tasks/(detail)/components/github-menu.css" => { @@ -1175,10 +1178,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.css" ))) } - "pages/(task-workspace)/tasks/(detail)/components/github-menu.ts" => { + "pages/(task-workspace)/tasks/(detail)/components/github-menu.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.css" => { @@ -1186,10 +1189,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.css" => { @@ -1197,10 +1200,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.css" => { @@ -1208,10 +1211,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.css" => { @@ -1219,10 +1222,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.css" => { @@ -1230,16 +1233,16 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.css" => { @@ -1247,10 +1250,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.css" => { @@ -1258,10 +1261,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.css" => { @@ -1269,10 +1272,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.css" => { @@ -1280,10 +1283,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.css" => { @@ -1291,10 +1294,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.css" => { @@ -1302,10 +1305,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.css" => { @@ -1313,10 +1316,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.css" => { @@ -1324,16 +1327,16 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.css" => { @@ -1341,22 +1344,22 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.css" => { @@ -1364,10 +1367,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.css" => { @@ -1375,10 +1378,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.css" => { @@ -1386,10 +1389,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.css" => { @@ -1397,16 +1400,16 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.css" => { @@ -1414,10 +1417,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.css" => { @@ -1425,10 +1428,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.js" )))) } "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.css" => { @@ -1436,43 +1439,43 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts" => { + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.js" )))) } "pages/(task-workspace)/tasks/(detail)/(review)/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.css" ))), - "pages/(task-workspace)/tasks/(detail)/(review)/layout.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/layout.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(review)/layout/route.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/layout/route.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/route.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/route.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(review)/representations.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/representations.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/representations.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/representations.js" )))) } "pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.css" => { @@ -1480,10 +1483,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.js" )))) } "pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.css" => { @@ -1491,10 +1494,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.js" )))) } "pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.css" => { @@ -1502,16 +1505,16 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.js" )))) } "pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.css" => { @@ -1519,25 +1522,25 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts" => { + "pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.js" )))) } "pages/(task-workspace)/tasks/components/active-task-list.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/active-task-list.css" ))), - "pages/(task-workspace)/tasks/components/active-task-list.ts" => { + "pages/(task-workspace)/tasks/components/active-task-list.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/active-task-list.ts" + "/frontend/pages/(task-workspace)/tasks/components/active-task-list.js" )))) } - "pages/(task-workspace)/tasks/components/active-task-list/move-list-item.ts" => { + "pages/(task-workspace)/tasks/components/active-task-list/move-list-item.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/active-task-list/move-list-item.ts" + "/frontend/pages/(task-workspace)/tasks/components/active-task-list/move-list-item.js" )))) } "pages/(task-workspace)/tasks/components/active-task-list/components/section.css" => { @@ -1545,10 +1548,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.css" ))) } - "pages/(task-workspace)/tasks/components/active-task-list/components/section.ts" => { + "pages/(task-workspace)/tasks/components/active-task-list/components/section.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.ts" + "/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.js" )))) } "pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.css" => { @@ -1556,10 +1559,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.css" ))) } - "pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts" => { + "pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts" + "/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.js" )))) } "pages/(task-workspace)/tasks/components/archived-task-list.css" => { @@ -1567,119 +1570,119 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/components/archived-task-list.css" ))) } - "pages/(task-workspace)/tasks/components/archived-task-list.ts" => { + "pages/(task-workspace)/tasks/components/archived-task-list.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/archived-task-list.ts" + "/frontend/pages/(task-workspace)/tasks/components/archived-task-list.js" )))) } "pages/(task-workspace)/tasks/recovery/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/recovery/page.css" ))), - "pages/(task-workspace)/tasks/recovery/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/recovery/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/recovery/page.ts" + "/frontend/pages/(task-workspace)/tasks/recovery/page.js" )))), "pages/(task-workspace)/tasks/components/task-store-recovery.css" => { Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/task-store-recovery.css" ))) } - "pages/(task-workspace)/tasks/components/task-store-recovery.ts" => { + "pages/(task-workspace)/tasks/components/task-store-recovery.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.js" )))) } "pages/(task-workspace)/tasks/components/task-absence.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/task-absence.css" ))), - "pages/(task-workspace)/tasks/components/task-absence.ts" => { + "pages/(task-workspace)/tasks/components/task-absence.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-absence.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-absence.js" )))) } "pages/(task-workspace)/tasks/components/navigator.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/navigator.css" ))), - "pages/(task-workspace)/tasks/components/navigator.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/components/navigator.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/navigator.ts" + "/frontend/pages/(task-workspace)/tasks/components/navigator.js" )))), "pages/(task-workspace)/tasks/components/task-create.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/task-create.css" ))), - "pages/(task-workspace)/tasks/components/task-create.ts" => { + "pages/(task-workspace)/tasks/components/task-create.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-create.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-create.js" )))) } "pages/(task-workspace)/tasks/new/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/new/page.css" ))), - "pages/(task-workspace)/tasks/new/page.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/new/page.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/new/page.ts" + "/frontend/pages/(task-workspace)/tasks/new/page.js" )))), "pages/(task-workspace)/tasks/components/task-status-chip.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/components/task-status-chip.css" ))), - "pages/(task-workspace)/tasks/components/task-status-chip.ts" => { + "pages/(task-workspace)/tasks/components/task-status-chip.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/components/task-status-chip.ts" + "/frontend/pages/(task-workspace)/tasks/components/task-status-chip.js" )))) } "pages/(task-workspace)/tasks/(detail)/(git)/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.css" ))), - "pages/(task-workspace)/tasks/(detail)/(git)/layout.ts" => Some(js(include_str!(concat!( + "pages/(task-workspace)/tasks/(detail)/(git)/layout.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.js" )))), "pages/(task-workspace)/tasks/(detail)/(git)/components/controls.css" => { Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(git)/components/controls.ts" => { + "pages/(task-workspace)/tasks/(detail)/(git)/components/controls.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.js" )))) } "pages/(task-workspace)/tasks/(detail)/(git)/compare/page.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.css" ))), - "pages/(task-workspace)/tasks/(detail)/(git)/compare/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/(git)/compare/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.js" )))) } "components/git-compare-browser.css" => Some(css(include_str!( "../../frontend/components/git-compare-browser.css" ))), - "components/git-compare-browser.ts" => Some(js(include_str!(concat!( + "components/git-compare-browser.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/git-compare-browser.ts" + "/frontend/components/git-compare-browser.js" )))), "components/git-compare-tree.css" => Some(css(include_str!( "../../frontend/components/git-compare-tree.css" ))), - "components/git-compare-tree.ts" => Some(js(include_str!(concat!( + "components/git-compare-tree.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/git-compare-tree.ts" + "/frontend/components/git-compare-tree.js" )))), "pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.css" ))), - "pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.ts" => { + "pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.js" )))) } "pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.css" => { @@ -1687,10 +1690,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.js" )))) } "pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.css" => { @@ -1698,10 +1701,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.js" )))) } "pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.css" => { @@ -1709,25 +1712,25 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.ts" => { + "pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/layout.css" => Some(css(include_str!( "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.css" ))), - "pages/(task-workspace)/tasks/(detail)/(github)/layout.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/layout.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.js" )))) } - "pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.css" => { @@ -1735,10 +1738,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.css" => { @@ -1746,10 +1749,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.css" => { @@ -1757,10 +1760,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.css" => { @@ -1768,10 +1771,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.css" => { @@ -1779,10 +1782,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.css" => { @@ -1790,10 +1793,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.css" => { @@ -1801,10 +1804,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.css" => { @@ -1812,10 +1815,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.css" => { @@ -1823,10 +1826,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.css" => { @@ -1834,10 +1837,10 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.js" )))) } "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.css" => { @@ -1845,135 +1848,137 @@ pub(crate) fn get(path: &str) -> Option { "../../frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.css" ))) } - "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.ts" => { + "pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.js" => { Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.ts" + "/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.js" )))) } "components/file-viewer/components/code-viewer.css" => Some(css(include_str!( "../../frontend/components/file-viewer/components/code-viewer.css" ))), - "components/file-viewer/components/code-viewer.ts" => Some(js(include_str!(concat!( + "components/file-viewer/components/code-viewer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/file-viewer/components/code-viewer.ts" + "/frontend/components/file-viewer/components/code-viewer.js" )))), "components/file-viewer/components/diff-viewer.css" => Some(css(include_str!( "../../frontend/components/file-viewer/components/diff-viewer.css" ))), - "components/file-viewer/components/diff-viewer.ts" => Some(js(include_str!(concat!( + "components/file-viewer/components/diff-viewer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/file-viewer/components/diff-viewer.ts" + "/frontend/components/file-viewer/components/diff-viewer.js" )))), - "components/file-viewer/syntax-spans.ts" => Some(js(include_str!(concat!( + "components/file-viewer/syntax-spans.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/file-viewer/syntax-spans.ts" + "/frontend/components/file-viewer/syntax-spans.js" )))), - "components/dom.ts" => Some(js(include_str!(concat!( + "components/dom.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/dom.ts" + "/frontend/components/dom.js" )))), "components/file-viewer.css" => Some(css(include_str!( "../../frontend/components/file-viewer.css" ))), - "components/file-viewer.ts" => Some(js(include_str!(concat!( + "components/file-viewer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/file-viewer.ts" + "/frontend/components/file-viewer.js" )))), "components/markdown-preview.css" => Some(css(include_str!( "../../frontend/components/markdown-preview.css" ))), - "components/markdown-preview.ts" => Some(js(include_str!(concat!( + "components/markdown-preview.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/markdown-preview.ts" + "/frontend/components/markdown-preview.js" )))), "components/pdf-viewer.css" => Some(css(include_str!( "../../frontend/components/pdf-viewer.css" ))), - "components/pdf-viewer.ts" => Some(js(include_str!(concat!( + "components/pdf-viewer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/pdf-viewer.ts" + "/frontend/components/pdf-viewer.js" )))), "components/docx-viewer.css" => Some(css(include_str!( "../../frontend/components/docx-viewer.css" ))), - "components/docx-viewer.ts" => Some(js(include_str!(concat!( + "components/docx-viewer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/docx-viewer.ts" + "/frontend/components/docx-viewer.js" )))), "components/pptx-viewer.css" => Some(css(include_str!( "../../frontend/components/pptx-viewer.css" ))), - "components/pptx-viewer.ts" => Some(js(include_str!(concat!( + "components/pptx-viewer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/pptx-viewer.ts" + "/frontend/components/pptx-viewer.js" )))), "components/xlsx-viewer.css" => Some(css(include_str!( "../../frontend/components/xlsx-viewer.css" ))), - "components/xlsx-viewer.ts" => Some(js(include_str!(concat!( + "components/xlsx-viewer.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/xlsx-viewer.ts" + "/frontend/components/xlsx-viewer.js" )))), - "components/xlsx-viewer/window.ts" => Some(js(include_str!(concat!( + "components/xlsx-viewer/window.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/xlsx-viewer/window.ts" + "/frontend/components/xlsx-viewer/window.js" )))), - "components/xlsx-viewer/workbook.ts" => Some(js(include_str!(concat!( + "components/xlsx-viewer/workbook.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/xlsx-viewer/workbook.ts" + "/frontend/components/xlsx-viewer/workbook.js" )))), - "components/file-viewer-presentation.ts" => Some(js(include_str!(concat!( + "components/file-viewer-presentation.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/file-viewer-presentation.ts" + "/frontend/components/file-viewer-presentation.js" )))), - "components/icons.ts" => Some(js(include_str!(concat!( + "components/icons.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/icons.ts" + "/frontend/components/icons.js" )))), - "components/drawn-refs.ts" => Some(js(include_str!(concat!( + "components/drawn-refs.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/drawn-refs.ts" + "/frontend/components/drawn-refs.js" )))), "components/loading-text.css" => Some(css(include_str!( "../../frontend/components/loading-text.css" ))), - "components/loading-text.ts" => Some(js(include_str!(concat!( + "components/loading-text.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/loading-text.ts" + "/frontend/components/loading-text.js" )))), "components/pagination.css" => Some(css(include_str!( "../../frontend/components/pagination.css" ))), - "components/pagination.ts" => Some(js(include_str!(concat!( + "components/pagination.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/pagination.ts" + "/frontend/components/pagination.js" )))), "components/pagination/components/button.css" => Some(css(include_str!( "../../frontend/components/pagination/components/button.css" ))), - "components/pagination/components/button.ts" => Some(js(include_str!(concat!( + "components/pagination/components/button.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/pagination/components/button.ts" + "/frontend/components/pagination/components/button.js" )))), "components/terminal-view.css" => Some(css(include_str!( "../../frontend/components/terminal-view.css" ))), - "components/terminal-view.ts" => Some(js(include_str!(concat!( + "components/terminal-view.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/terminal-view.ts" + "/frontend/components/terminal-view.js" )))), "components/segmented-control.css" => Some(css(include_str!( "../../frontend/components/segmented-control.css" ))), - "components/segmented-control.ts" => Some(js(include_str!(concat!( + "components/segmented-control.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), - "/frontend/components/segmented-control.ts" + "/frontend/components/segmented-control.js" )))), - _ => None, + _ => compiled_source_map(path).map(source_map), } } +include!(concat!(env!("OUT_DIR"), "/source_maps.rs")); + fn css(body: &'static str) -> StaticAsset { StaticAsset { content_type: "text/css; charset=utf-8", @@ -1995,6 +2000,13 @@ fn js_bytes(body: &'static [u8]) -> StaticAsset { } } +fn source_map(body: &'static [u8]) -> StaticAsset { + StaticAsset { + content_type: "application/json; charset=utf-8", + body, + } +} + fn manifest(body: &'static str) -> StaticAsset { StaticAsset { content_type: "application/manifest+json; charset=utf-8", @@ -2033,11 +2045,12 @@ fn plain_text(body: &'static str) -> StaticAsset { #[cfg(test)] mod tests { use super::get; + use serde_json::Value; #[test] fn serves_component_style_registration_assets() { for (path, content_type) in [ - ("component-styles.ts", "text/javascript; charset=utf-8"), + ("component-styles.js", "text/javascript; charset=utf-8"), ( "component-styles/compact-icon-button.css", "text/css; charset=utf-8", @@ -2086,14 +2099,14 @@ mod tests { } #[test] - fn serves_typescript_modules_as_javascript_without_their_types() { + fn serves_typescript_modules_as_the_javascript_and_source_maps_tsc_writes() { for (path, class) in [ ( - "components/pagination.ts", + "components/pagination.js", "class CaffoldPagination extends LitElement", ), ( - "components/pagination/components/button.ts", + "components/pagination/components/button.js", "class CaffoldPaginationButton extends LitElement", ), ] { @@ -2102,9 +2115,25 @@ mod tests { let source = std::str::from_utf8(module.body).expect("UTF-8 module"); assert!(source.contains(class)); assert!(!source.contains("declare page: number")); + let map_name = format!("{}.map", path.rsplit('/').next().unwrap()); + assert!(source.ends_with(&format!("//# sourceMappingURL={map_name}"))); + + let map = get(&format!("{path}.map")) + .unwrap_or_else(|| panic!("missing source map for {path}")); + assert_eq!(map.content_type, "application/json; charset=utf-8"); + let map: Value = serde_json::from_slice(map.body).expect("JSON source map"); + assert_eq!(map["sourceRoot"], "/assets/"); + assert_eq!(map["sources"][0], path.replace(".js", ".ts")); + assert!( + map["sourcesContent"][0] + .as_str() + .unwrap() + .contains("declare page") + ); } - assert!(get("components/pagination.js").is_none()); + assert!(get("components/pagination.ts").is_none()); assert!(get("action-hint-scope.d.ts").is_none()); + assert!(get("service-worker.js.map").is_none()); } #[test] @@ -2121,7 +2150,11 @@ mod tests { service_worker.content_type, "text/javascript; charset=utf-8" ); - assert!(service_worker.body.starts_with(b"const CACHE_NAME")); + assert!( + service_worker + .body + .starts_with(b"\"use strict\";\nconst CACHE_NAME") + ); let build_info = get("build-info.js").expect("build info asset"); assert_eq!(build_info.content_type, "text/javascript; charset=utf-8"); @@ -2148,32 +2181,22 @@ mod tests { } let codex_status_model = - get("pages/(task-workspace)/codex-status/model.ts").expect("Codex status model asset"); + get("pages/(task-workspace)/codex-status/model.js").expect("Codex status model asset"); assert_eq!( codex_status_model.content_type, "text/javascript; charset=utf-8" ); - assert!( - codex_status_model - .body - .trim_ascii_start() - .starts_with(b"export ") - ); - let codex_status_lifecycle = get("pages/(task-workspace)/codex-status/lifecycle.ts") + assert!(codex_status_model.body.starts_with(b"export ")); + let codex_status_lifecycle = get("pages/(task-workspace)/codex-status/lifecycle.js") .expect("Codex status lifecycle asset"); assert_eq!( codex_status_lifecycle.content_type, "text/javascript; charset=utf-8" ); - assert!( - codex_status_lifecycle - .body - .trim_ascii_start() - .starts_with(b"import ") - ); + assert!(codex_status_lifecycle.body.starts_with(b"import ")); let review_responsive_js = - get("components/review-responsive.ts").expect("review responsive js asset"); + get("components/review-responsive.js").expect("review responsive js asset"); assert_eq!( review_responsive_js.content_type, "text/javascript; charset=utf-8" @@ -2185,10 +2208,10 @@ mod tests { ); for path in [ - "pages/(task-workspace)/tasks/(detail)/components/git-menu.ts", - "pages/(task-workspace)/tasks/(detail)/components/github-menu.ts", - "pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts", - "pages/(task-workspace)/tasks/(detail)/terminal/page.ts", + "pages/(task-workspace)/tasks/(detail)/components/git-menu.js", + "pages/(task-workspace)/tasks/(detail)/components/github-menu.js", + "pages/(task-workspace)/tasks/(detail)/components/terminal-button.js", + "pages/(task-workspace)/tasks/(detail)/terminal/page.js", ] { let task_button_js = get(path).expect("Task button js asset"); assert_eq!( @@ -2234,7 +2257,7 @@ mod tests { assert!(get("pages/components/pathbar.js").is_none()); let build_mismatch_alert = - get("pages/components/build-mismatch-alert.ts").expect("build mismatch alert js asset"); + get("pages/components/build-mismatch-alert.js").expect("build mismatch alert js asset"); assert_eq!( build_mismatch_alert.content_type, "text/javascript; charset=utf-8" @@ -2242,7 +2265,7 @@ mod tests { assert!(build_mismatch_alert.body.starts_with(b"import ")); let pwa_update_lifecycle = - get("pages/pwa-update-lifecycle.ts").expect("PWA update lifecycle js asset"); + get("pages/pwa-update-lifecycle.js").expect("PWA update lifecycle js asset"); assert_eq!( pwa_update_lifecycle.content_type, "text/javascript; charset=utf-8" @@ -2250,21 +2273,21 @@ mod tests { assert!(pwa_update_lifecycle.body.starts_with(b"import ")); for path in [ - "pages/pwa-update-lifecycle/machine.ts", - "pages/pwa-update-lifecycle/runtime.ts", + "pages/pwa-update-lifecycle/machine.js", + "pages/pwa-update-lifecycle/runtime.js", ] { let asset = get(path).expect("PWA update private module asset"); assert_eq!(asset.content_type, "text/javascript; charset=utf-8"); } let update_dialog = - get("pages/components/update-dialog.ts").expect("update dialog js asset"); + get("pages/components/update-dialog.js").expect("update dialog js asset"); assert_eq!(update_dialog.content_type, "text/javascript; charset=utf-8"); assert!(update_dialog.body.starts_with(b"import ")); for path in [ - "pages/components/update-result-dialog.ts", - "pages/(task-workspace)/components/update-task-dialog.ts", + "pages/components/update-result-dialog.js", + "pages/(task-workspace)/components/update-task-dialog.js", ] { let dialog = get(path).expect("update dialog js asset"); assert_eq!(dialog.content_type, "text/javascript; charset=utf-8"); @@ -2280,25 +2303,20 @@ mod tests { assert!(get("pages/components/about-dialog.js").is_none()); assert!(get("pages/components/about-dialog.css").is_none()); - let settings_module = get("settings.ts").expect("settings module asset"); + let settings_module = get("settings.js").expect("settings module asset"); assert_eq!( settings_module.content_type, "text/javascript; charset=utf-8" ); assert!(settings_module.body.starts_with(b"import ")); - let theme_module = get("theme.ts").expect("theme module asset"); + let theme_module = get("theme.js").expect("theme module asset"); assert_eq!(theme_module.content_type, "text/javascript; charset=utf-8"); assert!(theme_module.body.starts_with(b"const ")); - let fonts_module = get("fonts.ts").expect("fonts module asset"); + let fonts_module = get("fonts.js").expect("fonts module asset"); assert_eq!(fonts_module.content_type, "text/javascript; charset=utf-8"); - assert!( - fonts_module - .body - .trim_ascii_start() - .starts_with(b"export const") - ); + assert!(fonts_module.body.starts_with(b"export const")); for family in ["GeistSans", "Inter", "Pretendard"] { let path = format!("fonts/{family}-Variable.woff2"); @@ -2357,73 +2375,71 @@ mod tests { ); } - let settings_page = get("pages/(task-workspace)/settings/appearance/page.ts") + let settings_page = get("pages/(task-workspace)/settings/appearance/page.js") .expect("settings appearance page js asset"); assert_eq!(settings_page.content_type, "text/javascript; charset=utf-8"); assert!(settings_page.body.starts_with(b"import ")); for path in [ - "keyboard-navigation.ts", - "keyboard-navigation/control.ts", - "keyboard-navigation/context.ts", - "keyboard-navigation/model.ts", - "keyboard-navigation/shortcuts.ts", - "keyboard-navigation/components/hud.ts", - "keyboard-navigation/components/selector.ts", - "keyboard-navigation/components/presentation.ts", - "keyboard-navigation/components/shortcut-list.ts", - "keyboard-navigation/components/shortcut-dialog.ts", - "chunked-fetch.ts", - "chunked-fetch/cell-scheduler.ts", - "chunked-fetch/in-flight-limit.ts", - "chunked-fetch/transfer.ts", - "components/file-viewer/syntax-spans.ts", - "action-hints.ts", - "action-hints/control.ts", - "action-hints/declarations.ts", - "action-hints/model.ts", - "action-hints/components/dialog.ts", - "pages/(task-workspace)/notes/layout.ts", - "pages/(task-workspace)/notes/layout/selection.ts", - "pages/(task-workspace)/notes/components/document.ts", - "pages/(task-workspace)/notes/components/info.ts", - "pages/(task-workspace)/notes/components/info/components/copy-markdown.ts", - "pages/(task-workspace)/notes/components/info/components/copy-path.ts", - "pages/(task-workspace)/notes/components/navigator.ts", - "pages/(task-workspace)/notes/tree.ts", - "pages/(task-workspace)/settings/layout.ts", - "pages/(task-workspace)/settings/components/navigator.ts", - "pages/(task-workspace)/settings/components/navigator/components/item.ts", - "pages/(task-workspace)/settings/keyboard/page.ts", - "pages/(task-workspace)/settings/files/page.ts", - "pages/(task-workspace)/settings/notifications/page.ts", - "pages/(task-workspace)/settings/remote-access/page.ts", - "pages/(task-workspace)/settings/remote-access/tailscale.ts", - "pages/(task-workspace)/settings/voice/page.ts", - "pages/(task-workspace)/settings/voice/lifecycle.ts", - "pages/(task-workspace)/settings/jev/page.ts", - "pages/(task-workspace)/settings/jev/lifecycle.ts", - "pages/(task-workspace)/settings/codex/page.ts", - "pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts", - "pages/(task-workspace)/settings/claude/page.ts", - "pages/(task-workspace)/settings/claude/display.ts", - "pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.ts", - "pages/(task-workspace)/settings/grok/page.ts", - "pages/(task-workspace)/settings/grok/display.ts", - "pages/(task-workspace)/settings/about/page.ts", - "pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.ts", - "pages/(task-workspace)/tasks/components/task-create/directory-path.ts", + "keyboard-navigation.js", + "keyboard-navigation/control.js", + "keyboard-navigation/context.js", + "keyboard-navigation/model.js", + "keyboard-navigation/shortcuts.js", + "keyboard-navigation/components/hud.js", + "keyboard-navigation/components/selector.js", + "keyboard-navigation/components/presentation.js", + "keyboard-navigation/components/shortcut-list.js", + "keyboard-navigation/components/shortcut-dialog.js", + "chunked-fetch.js", + "chunked-fetch/cell-scheduler.js", + "chunked-fetch/in-flight-limit.js", + "chunked-fetch/transfer.js", + "components/file-viewer/syntax-spans.js", + "action-hints.js", + "action-hints/control.js", + "action-hints/declarations.js", + "action-hints/model.js", + "action-hints/components/dialog.js", + "pages/(task-workspace)/notes/layout.js", + "pages/(task-workspace)/notes/layout/selection.js", + "pages/(task-workspace)/notes/components/document.js", + "pages/(task-workspace)/notes/components/info.js", + "pages/(task-workspace)/notes/components/info/components/copy-markdown.js", + "pages/(task-workspace)/notes/components/info/components/copy-path.js", + "pages/(task-workspace)/notes/components/navigator.js", + "pages/(task-workspace)/notes/tree.js", + "pages/(task-workspace)/settings/layout.js", + "pages/(task-workspace)/settings/components/navigator.js", + "pages/(task-workspace)/settings/components/navigator/components/item.js", + "pages/(task-workspace)/settings/keyboard/page.js", + "pages/(task-workspace)/settings/files/page.js", + "pages/(task-workspace)/settings/notifications/page.js", + "pages/(task-workspace)/settings/remote-access/page.js", + "pages/(task-workspace)/settings/remote-access/tailscale.js", + "pages/(task-workspace)/settings/voice/page.js", + "pages/(task-workspace)/settings/voice/lifecycle.js", + "pages/(task-workspace)/settings/jev/page.js", + "pages/(task-workspace)/settings/jev/lifecycle.js", + "pages/(task-workspace)/settings/codex/page.js", + "pages/(task-workspace)/codex-status/components/reset-credit-dialog.js", + "pages/(task-workspace)/settings/claude/page.js", + "pages/(task-workspace)/settings/claude/display.js", + "pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.js", + "pages/(task-workspace)/settings/grok/page.js", + "pages/(task-workspace)/settings/grok/display.js", + "pages/(task-workspace)/settings/about/page.js", + "pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.js", + "pages/(task-workspace)/tasks/components/task-create/directory-path.js", ] { let asset = get(path).unwrap_or_else(|| panic!("missing frontend asset {path}")); assert_eq!(asset.content_type, "text/javascript; charset=utf-8"); // The body is the module itself, not an HTML error page. A module - // with no imports of its own opens with what it exports, and a - // TypeScript module may open with type syntax stripped to spaces. - let body = asset.body.trim_ascii_start(); - assert!(body.starts_with(b"import ") || body.starts_with(b"export ")); + // with no imports of its own opens with what it exports. + assert!(asset.body.starts_with(b"import ") || asset.body.starts_with(b"export ")); } - let scroll_scope = get("scroll-scope.ts").expect("Scroll scope module asset"); + let scroll_scope = get("scroll-scope.js").expect("Scroll scope module asset"); assert_eq!(scroll_scope.content_type, "text/javascript; charset=utf-8"); assert!(scroll_scope.body.starts_with(b"import {")); @@ -2451,21 +2467,16 @@ mod tests { } let notification_lifecycle = - get("pages/(task-workspace)/settings/notifications/lifecycle.ts") + get("pages/(task-workspace)/settings/notifications/lifecycle.js") .expect("settings notification lifecycle asset"); assert_eq!( notification_lifecycle.content_type, "text/javascript; charset=utf-8" ); - assert!( - notification_lifecycle - .body - .trim_ascii_start() - .starts_with(b"export const") - ); + assert!(notification_lifecycle.body.starts_with(b"export const")); let runtime_restart_dialog = - get("pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts") + get("pages/(task-workspace)/codex-status/components/runtime-restart-dialog.js") .expect("Codex runtime restart dialog asset"); assert_eq!( runtime_restart_dialog.content_type, @@ -2474,7 +2485,7 @@ mod tests { assert!(runtime_restart_dialog.body.starts_with(b"import ")); let runtime_update_dialog = - get("pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts") + get("pages/(task-workspace)/codex-status/components/runtime-update-dialog.js") .expect("Codex runtime update dialog asset"); assert_eq!( runtime_update_dialog.content_type, @@ -2482,7 +2493,7 @@ mod tests { ); assert!(runtime_update_dialog.body.starts_with(b"import ")); - let app_shell_layout = get("pages/layout.ts").expect("app shell layout js asset"); + let app_shell_layout = get("pages/layout.js").expect("app shell layout js asset"); assert_eq!( app_shell_layout.content_type, "text/javascript; charset=utf-8" @@ -2491,7 +2502,7 @@ mod tests { assert!(get("pages/(review-workspace)/layout.css").is_none()); - let git_review_layout = get("pages/(task-workspace)/tasks/(detail)/(git)/layout.ts") + let git_review_layout = get("pages/(task-workspace)/tasks/(detail)/(git)/layout.js") .expect("git review layout js"); assert_eq!( git_review_layout.content_type, @@ -2505,7 +2516,7 @@ mod tests { .any(|window| window == b"caffold-task-git-layout") ); let git_review_controls = - get("pages/(task-workspace)/tasks/(detail)/(git)/components/controls.ts") + get("pages/(task-workspace)/tasks/(detail)/(git)/components/controls.js") .expect("git review controls js"); assert_eq!( git_review_controls.content_type, @@ -2529,7 +2540,7 @@ mod tests { .body .starts_with(b"caffold-git-review-controls") ); - let pane_resizer = get("components/pane-resizer.ts").expect("pane resizer js"); + let pane_resizer = get("components/pane-resizer.js").expect("pane resizer js"); assert_eq!(pane_resizer.content_type, "text/javascript; charset=utf-8"); assert!( pane_resizer @@ -2541,7 +2552,7 @@ mod tests { assert_eq!(pane_resizer_css.content_type, "text/css; charset=utf-8"); assert!(pane_resizer_css.body.starts_with(b"caffold-pane-resizer")); - let compare_page = get("pages/(task-workspace)/tasks/(detail)/(git)/compare/page.ts") + let compare_page = get("pages/(task-workspace)/tasks/(detail)/(git)/compare/page.js") .expect("compare page js"); assert_eq!(compare_page.content_type, "text/javascript; charset=utf-8"); assert!(compare_page.body.starts_with(b"import ")); @@ -2551,7 +2562,7 @@ mod tests { .windows(b"caffold-git-compare-page".len()) .any(|window| window == b"caffold-git-compare-page") ); - let compare_browser = get("components/git-compare-browser.ts").expect("compare browser js"); + let compare_browser = get("components/git-compare-browser.js").expect("compare browser js"); assert_eq!( compare_browser.content_type, "text/javascript; charset=utf-8" @@ -2562,7 +2573,7 @@ mod tests { .windows(b"caffold-git-compare-browser".len()) .any(|window| window == b"caffold-git-compare-browser") ); - let compare_tree = get("components/git-compare-tree.ts").expect("compare tree js"); + let compare_tree = get("components/git-compare-tree.js").expect("compare tree js"); assert_eq!(compare_tree.content_type, "text/javascript; charset=utf-8"); assert!( compare_tree @@ -2576,7 +2587,7 @@ mod tests { ); let task_workspace_layout = - get("pages/(task-workspace)/layout.ts").expect("task workspace layout js"); + get("pages/(task-workspace)/layout.js").expect("task workspace layout js"); assert_eq!( task_workspace_layout.content_type, "text/javascript; charset=utf-8" @@ -2590,11 +2601,11 @@ mod tests { ); for (path, marker) in [ ( - "pages/(task-workspace)/live-updates.ts", + "pages/(task-workspace)/live-updates.js", b"WorkspaceLiveUpdates".as_slice(), ), ( - "pages/(task-workspace)/live-updates/lifecycle.ts", + "pages/(task-workspace)/live-updates/lifecycle.js", b"LIVE_CONNECTION_NODE".as_slice(), ), ] { @@ -2618,7 +2629,7 @@ mod tests { .body .starts_with(b"caffold-task-workspace") ); - let task_workspace_navigation = get("pages/(task-workspace)/components/navigation.ts") + let task_workspace_navigation = get("pages/(task-workspace)/components/navigation.js") .expect("task workspace navigation js"); assert_eq!( task_workspace_navigation.content_type, @@ -2641,7 +2652,7 @@ mod tests { .body .starts_with(b"caffold-task-workspace-navigation") ); - let workspace_brand = get("pages/(task-workspace)/components/workspace-brand.ts") + let workspace_brand = get("pages/(task-workspace)/components/workspace-brand.js") .expect("workspace brand js"); assert_eq!( workspace_brand.content_type, @@ -2662,7 +2673,7 @@ mod tests { .starts_with(b"caffold-workspace-brand") ); - let tasks_page = get("pages/(task-workspace)/tasks/layout.ts").expect("tasks layout js"); + let tasks_page = get("pages/(task-workspace)/tasks/layout.js").expect("tasks layout js"); assert_eq!(tasks_page.content_type, "text/javascript; charset=utf-8"); assert!(tasks_page.body.starts_with(b"import ")); assert!( @@ -2672,27 +2683,21 @@ mod tests { .any(|window| window == b"caffold-tasks-page") ); let tasks_runtime_state = - get("pages/(task-workspace)/tasks/runtime-state.ts").expect("tasks runtime state js"); + get("pages/(task-workspace)/tasks/runtime-state.js").expect("tasks runtime state js"); assert_eq!( tasks_runtime_state.content_type, "text/javascript; charset=utf-8" ); - assert!( - tasks_runtime_state - .body - .trim_ascii_start() - .starts_with(b"export ") - ); + assert!(tasks_runtime_state.body.starts_with(b"export ")); for path in [ - "pages/(task-workspace)/tasks/task-events.ts", - "pages/(task-workspace)/tasks/task-format.ts", - "pages/(task-workspace)/tasks/task-list-model.ts", - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.ts", + "pages/(task-workspace)/tasks/task-events.js", + "pages/(task-workspace)/tasks/task-format.js", + "pages/(task-workspace)/tasks/task-list-model.js", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.js", ] { let asset = get(path).expect("tasks state module"); assert_eq!(asset.content_type, "text/javascript; charset=utf-8"); - let body = asset.body.trim_ascii_start(); - assert!(body.starts_with(b"export ") || body.starts_with(b"import ")); + assert!(asset.body.starts_with(b"export ") || asset.body.starts_with(b"import ")); } let tasks_page_css = get("pages/(task-workspace)/tasks/layout.css").expect("tasks layout css"); @@ -2703,7 +2708,7 @@ mod tests { assert_eq!(tasks_controls_css.content_type, "text/css; charset=utf-8"); assert!(tasks_controls_css.body.starts_with(b"caffold-tasks-page")); let tasks_markdown = - get("pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.ts") + get("pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.js") .expect("tasks markdown component js"); assert_eq!( tasks_markdown.content_type, @@ -2725,7 +2730,7 @@ mod tests { .starts_with(b"caffold-task-markdown") ); let code_block = - get("pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.ts") + get("pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.js") .expect("tasks Markdown code-block component js"); assert_eq!(code_block.content_type, "text/javascript; charset=utf-8"); assert!( @@ -2743,7 +2748,7 @@ mod tests { .body .starts_with(b"caffold-task-markdown-code-block") ); - let tasks_navigator = get("pages/(task-workspace)/tasks/components/navigator.ts") + let tasks_navigator = get("pages/(task-workspace)/tasks/components/navigator.js") .expect("tasks navigator component js"); assert_eq!( tasks_navigator.content_type, @@ -2757,27 +2762,27 @@ mod tests { ); for (path, marker) in [ ( - "pages/(task-workspace)/tasks/components/active-task-list.ts", + "pages/(task-workspace)/tasks/components/active-task-list.js", b"caffold-active-task-list".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/active-task-list/move-list-item.ts", + "pages/(task-workspace)/tasks/components/active-task-list/move-list-item.js", b"moveBefore".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts", + "pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.js", b"caffold-active-task-row".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/active-task-list/components/section.ts", + "pages/(task-workspace)/tasks/components/active-task-list/components/section.js", b"caffold-active-task-section".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/archived-task-list.ts", + "pages/(task-workspace)/tasks/components/archived-task-list.js", b"caffold-archived-task-list".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/task-status-chip.ts", + "pages/(task-workspace)/tasks/components/task-status-chip.js", b"caffold-task-status-chip".as_slice(), ), ] { @@ -2824,16 +2829,16 @@ mod tests { assert!(task_list_css.body.starts_with(prefix)); } for path in [ - "pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.ts", - "pages/(task-workspace)/tasks/(detail)/(task)/layout/history.ts", - "pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.ts", - "pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.ts", - "pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.js", + "pages/(task-workspace)/tasks/(detail)/(task)/layout/history.js", + "pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.js", + "pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.js", + "pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.js", ] { let asset = get(path).expect("task conversation owner asset"); assert_eq!(asset.content_type, "text/javascript; charset=utf-8"); } - let task_detail_session = get("pages/(task-workspace)/tasks/(detail)/(task)/session.ts") + let task_detail_session = get("pages/(task-workspace)/tasks/(detail)/(task)/session.js") .expect("task detail session js"); assert_eq!( task_detail_session.content_type, @@ -2847,11 +2852,11 @@ mod tests { ); for (path, marker) in [ ( - "pages/(task-workspace)/tasks/stream.ts", + "pages/(task-workspace)/tasks/stream.js", b"TaskStreamLifecycle".as_slice(), ), ( - "pages/(task-workspace)/tasks/stream/machine.ts", + "pages/(task-workspace)/tasks/stream/machine.js", b"TASK_STREAM_NODE".as_slice(), ), ] { @@ -2866,171 +2871,171 @@ mod tests { } for (path, tag) in [ ( - "pages/(task-workspace)/tasks/components/composer.ts", + "pages/(task-workspace)/tasks/components/composer.js", b"caffold-task-composer".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/task-turn-options.ts", + "pages/(task-workspace)/tasks/components/task-turn-options.js", b"caffold-task-turn-options".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/task-create/components/directory-field.ts", + "pages/(task-workspace)/tasks/components/task-create/components/directory-field.js", b"caffold-task-directory-field".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/archived-delete-dialog.ts", + "pages/(task-workspace)/tasks/components/archived-delete-dialog.js", b"caffold-task-archived-delete-dialog".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/task-switcher-dialog.ts", + "pages/(task-workspace)/tasks/components/task-switcher-dialog.js", b"caffold-task-switcher-dialog".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/image-preview-dialog.ts", + "pages/(task-workspace)/tasks/components/image-preview-dialog.js", b"caffold-task-image-preview-dialog".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/task-store-recovery.ts", + "pages/(task-workspace)/tasks/components/task-store-recovery.js", b"caffold-task-store-recovery".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/task-absence.ts", + "pages/(task-workspace)/tasks/components/task-absence.js", b"caffold-task-absence".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.js", b"caffold-task-conversation".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.js", b"caffold-task-older-history".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.js", b"caffold-task-active-turn".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.js", b"caffold-task-assistant-message".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.js", b"caffold-task-assistant-message-copy-button".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.js", b"caffold-task-assistant-message-suggested-prompts".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.js", b"caffold-task-message-attachments".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.js", b"caffold-task-user-message".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.js", b"caffold-task-changed-files".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.js", b"caffold-task-command-dialog".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.js", b"caffold-task-markdown-preview-dialog".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.js", b"caffold-task-permission-instructions-dialog".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.js", b"caffold-task-approval".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.js", b"caffold-task-command".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.js", b"caffold-task-command-group".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.js", b"caffold-task-work-details".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/layout.ts", + "pages/(task-workspace)/tasks/(detail)/layout.js", b"caffold-detail-layout".as_slice(), ), ( - "components/segmented-control.ts", + "components/segmented-control.js", b"caffold-segmented-control".as_slice(), ), ( - "components/loading-text.ts", + "components/loading-text.js", b"caffold-loading-text".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(section)/layout.ts", + "pages/(task-workspace)/tasks/(detail)/(section)/layout.js", b"caffold-section-detail".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts", + "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.js", b"caffold-section-conversation-shortcuts".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts", + "pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.js", b"caffold-conversation-fork-dialog".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts", + "pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.js", b"caffold-section-github-shortcuts".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts", + "pages/(task-workspace)/tasks/(detail)/(section)/components/summary.js", b"caffold-section-detail-summary".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/task-create.ts", + "pages/(task-workspace)/tasks/components/task-create.js", b"caffold-task-create".as_slice(), ), ( - "pages/(task-workspace)/tasks/new/page.ts", + "pages/(task-workspace)/tasks/new/page.js", b"caffold-task-new".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/layout.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/layout.js", b"caffold-task-detail".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/summary.js", b"caffold-task-detail-summary".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.js", b"caffold-task-detail-info".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts", + "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.js", b"caffold-task-detail-info-actions".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(review)/layout.ts", + "pages/(task-workspace)/tasks/(detail)/(review)/layout.js", b"caffold-task-review".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.ts", + "pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.js", b"caffold-review-working-tree-changes".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.ts", + "pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.js", b"caffold-review-branch-changes".as_slice(), ), ( - "pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.ts", + "pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.js", b"caffold-review-selected-file".as_slice(), ), ] { @@ -3040,23 +3045,23 @@ mod tests { } for (path, marker) in [ ( - "pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.ts", + "pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.js", b"caffold-voice-level-meter".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/composer/components/attachments.ts", + "pages/(task-workspace)/tasks/components/composer/components/attachments.js", b"caffold-task-composer-attachments".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/composer/components/context-usage.ts", + "pages/(task-workspace)/tasks/components/composer/components/context-usage.js", b"caffold-task-context-usage".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/composer/voice-recorder.ts", + "pages/(task-workspace)/tasks/components/composer/voice-recorder.js", b"VoiceRecorder".as_slice(), ), ( - "pages/(task-workspace)/tasks/components/composer/voice-worklet.ts", + "pages/(task-workspace)/tasks/components/composer/voice-worklet.js", b"caffold-voice-capture".as_slice(), ), ] { @@ -3070,37 +3075,37 @@ mod tests { ); } for path in [ - "/assets/pages/(task-workspace)/live-updates.ts", - "/assets/pages/(task-workspace)/live-updates/lifecycle.ts", - "/assets/pages/(task-workspace)/tasks/stream.ts", - "/assets/pages/(task-workspace)/tasks/stream/machine.ts", + "/assets/pages/(task-workspace)/live-updates.js", + "/assets/pages/(task-workspace)/live-updates/lifecycle.js", + "/assets/pages/(task-workspace)/tasks/stream.js", + "/assets/pages/(task-workspace)/tasks/stream/machine.js", "/assets/pages/(task-workspace)/tasks/components/active-task-list.css", - "/assets/pages/(task-workspace)/tasks/components/active-task-list.ts", - "/assets/pages/(task-workspace)/tasks/components/active-task-list/move-list-item.ts", + "/assets/pages/(task-workspace)/tasks/components/active-task-list.js", + "/assets/pages/(task-workspace)/tasks/components/active-task-list/move-list-item.js", "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section.css", - "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section.ts", + "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section.js", "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.css", - "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts", + "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.js", "/assets/pages/(task-workspace)/tasks/components/archived-task-list.css", - "/assets/pages/(task-workspace)/tasks/components/archived-task-list.ts", + "/assets/pages/(task-workspace)/tasks/components/archived-task-list.js", "/assets/pages/(task-workspace)/tasks/components/task-status-chip.css", - "/assets/pages/(task-workspace)/tasks/components/task-status-chip.ts", + "/assets/pages/(task-workspace)/tasks/components/task-status-chip.js", "/assets/pages/(task-workspace)/tasks/components/task-store-recovery.css", - "/assets/pages/(task-workspace)/tasks/components/task-store-recovery.ts", + "/assets/pages/(task-workspace)/tasks/components/task-store-recovery.js", "/assets/pages/(task-workspace)/tasks/components/task-absence.css", - "/assets/pages/(task-workspace)/tasks/components/task-absence.ts", - "/assets/pages/(task-workspace)/task-store-status.ts", - "/assets/pages/(task-workspace)/task-store-status/model.ts", - "/assets/pages/(task-workspace)/task-store-status/lifecycle.ts", + "/assets/pages/(task-workspace)/tasks/components/task-absence.js", + "/assets/pages/(task-workspace)/task-store-status.js", + "/assets/pages/(task-workspace)/task-store-status/model.js", + "/assets/pages/(task-workspace)/task-store-status/lifecycle.js", "/assets/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.css", - "/assets/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.ts", + "/assets/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.js", "/assets/pages/(task-workspace)/tasks/components/composer/components/attachments.css", - "/assets/pages/(task-workspace)/tasks/components/composer/components/attachments.ts", + "/assets/pages/(task-workspace)/tasks/components/composer/components/attachments.js", "/assets/pages/(task-workspace)/tasks/components/composer/components/context-usage.css", - "/assets/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts", - "/assets/pages/(task-workspace)/codex-status/lifecycle.ts", + "/assets/pages/(task-workspace)/tasks/components/composer/components/context-usage.js", + "/assets/pages/(task-workspace)/codex-status/lifecycle.js", "/assets/pages/(task-workspace)/codex-status/components/reset-credit-dialog.css", - "/assets/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts", + "/assets/pages/(task-workspace)/codex-status/components/reset-credit-dialog.js", ] { assert!( service_worker @@ -3288,7 +3293,7 @@ mod tests { assert!(get("pages/tasks/page.css").is_none()); let diff_changes_tree = - get("pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts") + get("pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.js") .expect("integrated review changes tree js"); assert_eq!( diff_changes_tree.content_type, @@ -3303,7 +3308,7 @@ mod tests { assert!(get("components/git-diff-browser.js").is_none()); assert!(get("pages/(task-workspace)/tasks/(detail)/(git)/diff/page.js").is_none()); - let github_review_layout = get("pages/(task-workspace)/tasks/(detail)/(github)/layout.ts") + let github_review_layout = get("pages/(task-workspace)/tasks/(detail)/(github)/layout.js") .expect("github review layout js"); assert_eq!( github_review_layout.content_type, @@ -3317,7 +3322,7 @@ mod tests { .any(|window| window == b"caffold-task-github-layout") ); let github_markdown = - get("pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.js") .expect("github markdown component js"); assert_eq!( github_markdown.content_type, @@ -3332,7 +3337,7 @@ mod tests { ); let issues_layout = - get("pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.js") .expect("issues layout js"); assert_eq!(issues_layout.content_type, "text/javascript; charset=utf-8"); assert!(issues_layout.body.starts_with(b"import ")); @@ -3344,7 +3349,7 @@ mod tests { ); let issues_list_page = - get("pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.js") .expect("issues list page js"); assert_eq!( issues_list_page.content_type, @@ -3359,7 +3364,7 @@ mod tests { ); let issue_detail_page = - get("pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.js") .expect("issue detail page js"); assert_eq!( issue_detail_page.content_type, @@ -3373,7 +3378,7 @@ mod tests { .any(|window| window == b"caffold-github-issue-detail-page") ); let github_task_start_dialog = - get("pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.js") .expect("github task start dialog js"); assert_eq!( github_task_start_dialog.content_type, @@ -3386,7 +3391,7 @@ mod tests { .any(|window| window == b"caffold-github-task-start-dialog") ); let github_issue_task_source = get( - "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.ts", + "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.js", ) .expect("github issue task source js"); assert!( @@ -3396,7 +3401,7 @@ mod tests { .any(|window| window == b"caffold-github-issue-task-source") ); let github_pull_task_source = get( - "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.ts", + "pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.js", ) .expect("github pull task source js"); assert!( @@ -3409,7 +3414,7 @@ mod tests { assert!(get("components/github-issue-viewer.js").is_none()); assert!(get("components/github-markdown.js").is_none()); - let pulls_layout = get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.ts") + let pulls_layout = get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.js") .expect("pulls layout js"); assert_eq!(pulls_layout.content_type, "text/javascript; charset=utf-8"); assert!(pulls_layout.body.starts_with(b"import ")); @@ -3421,7 +3426,7 @@ mod tests { ); let pulls_list_page = - get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.js") .expect("pulls list page js"); assert_eq!( pulls_list_page.content_type, @@ -3436,7 +3441,7 @@ mod tests { ); let pull_detail_page = - get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.js") .expect("pull detail page js"); assert_eq!( pull_detail_page.content_type, @@ -3451,7 +3456,7 @@ mod tests { ); let pull_files_page = - get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.js") .expect("pull files page js"); assert_eq!( pull_files_page.content_type, @@ -3465,7 +3470,7 @@ mod tests { .any(|window| window == b"caffold-github-pull-files-page") ); let pull_files_tree = - get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.ts") + get("pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.js") .expect("pull files tree js"); assert_eq!( pull_files_tree.content_type, @@ -3486,7 +3491,7 @@ mod tests { assert!(get("components/file-browser.js").is_none()); let file_navigator_component = - get("components/file-navigator.ts").expect("file navigator component js asset"); + get("components/file-navigator.js").expect("file navigator component js asset"); assert_eq!( file_navigator_component.content_type, "text/javascript; charset=utf-8" @@ -3507,7 +3512,7 @@ mod tests { .starts_with(b"@import \"./file-navigator/components/list.css\"") ); - let file_list_component = get("components/file-navigator/components/list.ts") + 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, @@ -3521,7 +3526,7 @@ mod tests { .any(|window| window == b"caffold-file-list") ); let file_tree_component = - get("components/file-tree.ts").expect("file tree component js asset"); + get("components/file-tree.js").expect("file tree component js asset"); assert_eq!( file_tree_component.content_type, "text/javascript; charset=utf-8" @@ -3533,7 +3538,7 @@ mod tests { .windows(b"caffold-file-tree".len()) .any(|window| window == b"caffold-file-tree") ); - let file_status_module = get("file-status.ts").expect("file status js asset"); + let file_status_module = get("file-status.js").expect("file status js asset"); assert_eq!( file_status_module.content_type, "text/javascript; charset=utf-8" @@ -3549,7 +3554,7 @@ mod tests { assert!(file_tree_css.body.starts_with(b"caffold-file-tree")); assert!(get("pages/files/components/list.js").is_none()); assert!(get("pages/files/components/list.css").is_none()); - let watch_module = get("watch.ts").expect("watch js asset"); + let watch_module = get("watch.js").expect("watch js asset"); assert_eq!(watch_module.content_type, "text/javascript; charset=utf-8"); assert!( watch_module @@ -3561,7 +3566,7 @@ mod tests { assert!(get("watch.test.js").is_none()); assert!(get("pages/(task-workspace)/codex-status/model.test.js").is_none()); assert!(get("pages/(task-workspace)/tasks/task-format.test.js").is_none()); - let log_layout = get("pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.ts") + let log_layout = get("pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.js") .expect("git log layout js asset"); assert_eq!(log_layout.content_type, "text/javascript; charset=utf-8"); assert!(log_layout.body.starts_with(b"import ")); @@ -3571,7 +3576,7 @@ mod tests { .windows(b"caffold-git-log-layout".len()) .any(|window| window == b"caffold-git-log-layout") ); - let log_list_page = get("pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.ts") + let log_list_page = get("pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.js") .expect("log list page js asset"); assert_eq!(log_list_page.content_type, "text/javascript; charset=utf-8"); assert!(log_list_page.body.starts_with(b"import ")); @@ -3582,7 +3587,7 @@ mod tests { .any(|window| window == b"caffold-git-log-list-page") ); let log_commit_page = - get("pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.ts") + get("pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.js") .expect("log commit page js asset"); assert_eq!( log_commit_page.content_type, @@ -3596,7 +3601,7 @@ mod tests { .any(|window| window == b"caffold-git-log-commit-page") ); let commit_tree_component = get( - "pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.ts", + "pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.js", ) .expect("commit changes tree component js asset"); assert_eq!( diff --git a/docs/architecture/frontend.md b/docs/architecture/frontend.md index e7d2ee12..ba7bfc2d 100644 --- a/docs/architecture/frontend.md +++ b/docs/architecture/frontend.md @@ -28,7 +28,7 @@ their owner. A reusable component does not become a page merely because it occupies most of a surface. An expanded non-page module uses a same-stem file and directory pair. The root -`name.js` is its public feature entry point; `name/` owns private model and +`name.ts` is its public feature entry point; `name/` owns private model and lifecycle implementation, plus a `components/` directory for Web Components mounted by the feature's outside owner. Non-visual consumers import only the explicit public API from `name.js`. A mounting owner imports the component path @@ -37,28 +37,29 @@ directories, an expanded module directory contains no `page.ts` or `layout.ts`. ## Native module imports -Modules run in the browser without a bundler. A TypeScript module (`.ts`) loses -only its type syntax: when the server is built, `frontend/strip-types.mjs` -replaces that syntax with spaces, and the server serves the result at the -module's own path as JavaScript, so its lines and columns match the source. -Node runs the same `.ts` files directly in unit tests. Because the build only -removes types, modules use TypeScript syntax that erases cleanly, without -`enum`, `namespace`, or decorators. - -A module moves to TypeScript whole: `name.ts` is typed throughout under the -strict check, and `name.js` has not moved yet. While a JavaScript module has -TypeScript callers, a same-stem `name.d.ts` beside it declares the part they -use, and it goes when the module becomes `name.ts`. The TypeScript-modules -contract keeps each `.d.ts` beside its `.js` and no module in both languages; -`build-info.d.ts` alone declares a module the server build writes instead. +Modules run in the browser without a bundler, one file per module. Each is +TypeScript, `name.ts`, under the strict check. When the server is built, tsc +compiles it to `name.js` with a source map beside it, and the server serves +both under `/assets/`. tsc keeps import specifiers as written, so an import +names the `.js` file that runs, and the type check reads the `.ts` source +beside it. The service worker is typed for its own global scope, so +`frontend/tsconfig.worker.json` compiles it apart from the page modules. A type +error in either stops the server build. + +A source map carries its TypeScript source, so browser developer tools show +`/assets/name.ts` without fetching it; the service worker does not cache source +maps. `build-info.d.ts` is the one type declaration file: it declares the +module the server build writes. Unit tests run from tsc's output too: +`npm run test:unit` compiles the modules and their colocated tests into +`frontend/tests/unit-build/` and runs them there. Shared imports can use three native aliases: | Prefix | Frontend-relative directory | Example | | --- | --- | --- | -| `#app/` | `./` | `#app/api.ts` | -| `#components/` | `components/` | `#components/icons.ts` | -| `#tasks/` | `pages/(task-workspace)/tasks/` | `#tasks/task-format.ts` | +| `#app/` | `./` | `#app/api.js` | +| `#components/` | `components/` | `#components/icons.js` | +| `#tasks/` | `pages/(task-workspace)/tasks/` | `#tasks/task-format.js` | `frontend/index.html` declares the browser import map before the app module. `frontend/package.json` declares matching `imports` patterns for Node unit @@ -67,7 +68,7 @@ the browser's `/assets/` URL and the frontend package directory respectively. Use relative imports within the same owner and aliases for shared dependencies. Use `#components/` for shared components and `#tasks/` for Task-owned shared -modules; `#app/` covers other app-wide modules. Retain explicit `.js` or `.ts` +modules; `#app/` covers other app-wide modules. Retain explicit `.js` extensions and the existing public-entry-point and private-implementation boundaries. Aliases only change path resolution, not module ownership or registration. @@ -2210,14 +2211,14 @@ Every production JavaScript/CSS asset must be registered consistently in: - module imports; - `frontend/styles.css`, or the consuming component's explicit style declaration for reusable `:scope` sources; -- `frontend/service-worker.js`; +- `frontend/service-worker.ts`; - `caffold/src/static_assets.rs`; - static asset and CSS ownership tests. The service-worker cache and Rust static-asset table are exact manifests of the assets imported by the active application hierarchy. Lit's modules are part of both even though they come from `frontend/node_modules`, and a TypeScript -module is listed at its `.ts` path. pdf.js, +module is listed at the `.js` path it is served at. pdf.js, docx-preview, pptx-renderer, SheetJS, and xterm.js with its fit addon are the exceptions: their owners import pinned releases at first use, and the browser suite answers diff --git a/docs/development/testing.md b/docs/development/testing.md index 8171dbf3..2e255f7f 100644 --- a/docs/development/testing.md +++ b/docs/development/testing.md @@ -18,10 +18,11 @@ npm ci npx playwright install chromium ``` -`npm ci` also installs the Lit release the server embeds, so it comes before -the Rust checks as well as the browser suites. Cargo builds run Node.js to -remove the type syntax of the frontend's TypeScript modules, and Node runs -those modules directly in unit tests. +`npm ci` also installs the Lit release the server embeds and the TypeScript +compiler, so it comes before the Rust checks as well as the browser suites. +Cargo builds run tsc on Node.js to compile the frontend's TypeScript modules +and stop on a type error, and `npm run test:unit` runs the unit tests from +tsc's output. Rust checks use the 1.96 toolchain pinned by `rust-toolchain.toml` and bounded by `Cargo.toml`'s minimum supported version. diff --git a/docs/review/frontend.md b/docs/review/frontend.md index 0ff49657..1340434a 100644 --- a/docs/review/frontend.md +++ b/docs/review/frontend.md @@ -142,12 +142,10 @@ Apply the common [Source Module Ownership](policy.md#source-module-ownership) rules when deciding whether frontend implementation remains private to its current owner or moves to a shared boundary. -A module may be written in TypeScript as `name.ts` instead of `name.js`; the -rules below apply to it the same way. A module moves to TypeScript whole, not -partly to publish its types. A type belongs to the module that owns the -contract it describes, and other modules import it rather than declaring it -again. While a JavaScript module has TypeScript callers, a same-stem -`name.d.ts` declares the part they use until the module becomes `name.ts`. +Frontend modules are TypeScript files, `name.ts`, imported as `name.js`, the +file tsc writes for them; the paths below are those import paths. A type +belongs to the module that owns the contract it describes, and other modules +import it rather than declaring it again. When one frontend module grows beyond a single file, use an adjacent same-stem directory: `name.js` and `name/` form one ownership boundary. `name.js` is the diff --git a/frontend/README.md b/frontend/README.md index 2093e0a6..4b3bfa02 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -41,13 +41,12 @@ working directory. Browser sources are embedded by `caffold/src/static_assets.rs` at build time, so a new runtime asset must be added there and precached by -`service-worker.js`. Nothing in `tests/` is part of that inventory. From -`node_modules/`, only the Lit modules a page reaches are: the server embeds the -Lit release `package.json` pins under `dependencies`, so a Cargo build needs -`npm ci` here first. `npm ci --omit=dev` installs that dependency alone when the -browser suites and type checks are not needed. To change the release, change -the pin and run `npm install`. A `.ts` module is embedded as -[`strip-types.mjs`](strip-types.mjs) writes it, which needs Node.js on `PATH` -during the build; the +`service-worker.ts`. Nothing in `tests/` is part of that inventory. From +`node_modules/`, only the Lit modules a page reaches are. The server embeds the +Lit release `package.json` pins under `dependencies` and compiles each `.ts` +module with the TypeScript release pinned beside it, so a Cargo build needs +`npm ci` here first and Node.js on `PATH`. `npm ci --omit=dev` installs those +two alone when the browser suites and lint are not needed. To change a release, +change its pin and run `npm install`. The [frontend architecture](../docs/architecture/frontend.md#native-module-imports) -describes how pages and unit tests load both. +describes how pages and unit tests load the compiled modules. diff --git a/frontend/action-hint-scope.test.js b/frontend/action-hint-scope.test.js index c824d8ea..14f25cb6 100644 --- a/frontend/action-hint-scope.test.js +++ b/frontend/action-hint-scope.test.js @@ -19,7 +19,7 @@ import { switchActionHintTarget, textboxActionHintTarget, withActionHintOcclusionRoots, -} from "./action-hint-scope.ts"; +} from "./action-hint-scope.js"; test("button Action Hint targets preserve owner state and native activation", () => { const calls = []; diff --git a/frontend/action-hints.test.js b/frontend/action-hints.test.js index 2e1b2d3f..751e92fd 100644 --- a/frontend/action-hints.test.js +++ b/frontend/action-hints.test.js @@ -4,7 +4,7 @@ import test from "node:test"; import { ActionHintController, normalizeMutationRootList, -} from "./action-hints.ts"; +} from "./action-hints.js"; test("normalizes Action Hint mutation roots at the public boundary", () => { const restoreGlobals = installDomGlobals(); diff --git a/frontend/action-hints.ts b/frontend/action-hints.ts index 6347a118..6d550d4e 100644 --- a/frontend/action-hints.ts +++ b/frontend/action-hints.ts @@ -18,11 +18,11 @@ import { switchActionHintTarget, textboxActionHintTarget, withActionHintOcclusionRoots, -} from "./action-hint-scope.ts"; +} from "./action-hint-scope.js"; import { ACTION_HINT_ACTIVATE_EVENT, ACTION_HINT_CANCEL_EVENT, -} from "./action-hints/control.ts"; +} from "./action-hints/control.js"; import { ACTION_HINT_ACTION, type ActionHintViewport, @@ -44,19 +44,20 @@ import { TASK_HINT_ALPHABET, taskHintSuffix, visibleTargetRect, -} from "./action-hints/model.ts"; +} from "./action-hints/model.js"; export { type ActionHintDeclaration, type ActionHintPull, ActionHints, -} from "./action-hints/declarations.ts"; +} from "./action-hints/declarations.js"; export type { ActionFor, ActionHintAction, HintProgress, Rect, -} from "./action-hints/model.ts"; + RectLike, +} from "./action-hints/model.js"; export { ACTION_HINT_ACTION, diff --git a/frontend/action-hints/components/dialog.test.js b/frontend/action-hints/components/dialog.test.js index d6e40a76..be2c7d52 100644 --- a/frontend/action-hints/components/dialog.test.js +++ b/frontend/action-hints/components/dialog.test.js @@ -7,10 +7,10 @@ import { import { templateHtml } from "../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./dialog.ts"); +await import("./dialog.js"); const ActionHintDialog = registry.element("caffold-action-hint-dialog"); const dialog = ActionHintDialog.prototype; -const { DrawnRefs } = await import("../../components/drawn-refs.ts"); +const { DrawnRefs } = await import("../../components/drawn-refs.js"); after(() => registry.restore()); // A badge dialog with the given state, which draws only when a test asks. diff --git a/frontend/action-hints/components/dialog.ts b/frontend/action-hints/components/dialog.ts index 52a427a8..371da737 100644 --- a/frontend/action-hints/components/dialog.ts +++ b/frontend/action-hints/components/dialog.ts @@ -1,12 +1,12 @@ import { html, LitElement } from "lit"; import { repeat } from "lit/directives/repeat.js"; -import type { ActionHintSessionTarget } from "../../action-hints.ts"; -import { DrawnRef, DrawnRefs, drawn } from "../../components/drawn-refs.ts"; +import type { ActionHintSessionTarget } from "../../action-hints.js"; +import { DrawnRef, DrawnRefs, drawn } from "../../components/drawn-refs.js"; import { ACTION_HINT_ACTIVATE_EVENT, ACTION_HINT_CANCEL_EVENT, -} from "../control.ts"; -import { clampBadgePosition, type HintProgress, type Rect } from "../model.ts"; +} from "../control.js"; +import { clampBadgePosition, type HintProgress, type Rect } from "../model.js"; let actionHintDialogInstanceId = 0; diff --git a/frontend/action-hints/control.test.js b/frontend/action-hints/control.test.js index 892d935f..9aa7fc1d 100644 --- a/frontend/action-hints/control.test.js +++ b/frontend/action-hints/control.test.js @@ -4,7 +4,7 @@ import test from "node:test"; import { ACTION_HINT_ACTIVATE_EVENT, ACTION_HINT_CANCEL_EVENT, -} from "./control.ts"; +} from "./control.js"; test("keeps Action Hint presentation events inside the Action Hint boundary", () => { assert.equal(ACTION_HINT_ACTIVATE_EVENT, "caffold:action-hint-activate"); diff --git a/frontend/action-hints/declarations.test.js b/frontend/action-hints/declarations.test.js index 77e3f539..3339c7b7 100644 --- a/frontend/action-hints/declarations.test.js +++ b/frontend/action-hints/declarations.test.js @@ -4,9 +4,9 @@ import test, { after } from "node:test"; const previousNode = globalThis.Node; globalThis.Node = { DOCUMENT_POSITION_PRECEDING: 2 }; const { ActionHints, declaredActionHintScope } = await import( - "./declarations.ts" + "./declarations.js" ); -const { ACTION_HINT_ACTION } = await import("./model.ts"); +const { ACTION_HINT_ACTION } = await import("./model.js"); after(() => restoreGlobal("Node", previousNode)); diff --git a/frontend/action-hints/declarations.ts b/frontend/action-hints/declarations.ts index 69094598..46664a87 100644 --- a/frontend/action-hints/declarations.ts +++ b/frontend/action-hints/declarations.ts @@ -14,12 +14,12 @@ import { separatorActionHintTarget, switchActionHintTarget, textboxActionHintTarget, -} from "../action-hint-scope.ts"; +} from "../action-hint-scope.js"; import { type DeclaredElement, ElementDeclarations, -} from "../element-declarations.ts"; -import type { ActionFor } from "./model.ts"; +} from "../element-declarations.js"; +import type { ActionFor } from "./model.js"; // One target as its owner's template declares it, on the control itself. A // link may leave its label out and be named from what it shows and where it diff --git a/frontend/action-hints/model.test.js b/frontend/action-hints/model.test.js index 1dad916d..06f252d6 100644 --- a/frontend/action-hints/model.test.js +++ b/frontend/action-hints/model.test.js @@ -16,7 +16,7 @@ import { sameActionHintViewport, sortByVisualOrder, visibleTargetRect, -} from "./model.ts"; +} from "./model.js"; function target(id, actionId, controlKind = "button") { return { id, actionId, controlKind }; diff --git a/frontend/action-hints/model.ts b/frontend/action-hints/model.ts index 6a6b48d0..16bc417c 100644 --- a/frontend/action-hints/model.ts +++ b/frontend/action-hints/model.ts @@ -1,4 +1,4 @@ -import type { ActionHintControlKind } from "../action-hint-scope.ts"; +import type { ActionHintControlKind } from "../action-hint-scope.js"; export const TASK_HINT_ALPHABET = "ASDFGHJKLQWERTYUIOPZXCVBNM"; diff --git a/frontend/api.test.js b/frontend/api.test.js index ecba40d9..c6f8713b 100644 --- a/frontend/api.test.js +++ b/frontend/api.test.js @@ -24,8 +24,8 @@ import { terminalSocketUrl, updateLiveSubscriptions, uploadTaskFile, -} from "./api.ts"; -import { CAFFOLD_ORIGIN_REACHABLE_EVENT } from "./origin-reachability.ts"; +} from "./api.js"; +import { CAFFOLD_ORIGIN_REACHABLE_EVENT } from "./origin-reachability.js"; const originalBrowserGlobals = { fetch: globalThis.fetch, diff --git a/frontend/api.ts b/frontend/api.ts index 47045f63..fe95e60e 100644 --- a/frontend/api.ts +++ b/frontend/api.ts @@ -1,4 +1,4 @@ -import { reportOriginReachable } from "./origin-reachability.ts"; +import { reportOriginReachable } from "./origin-reachability.js"; // The limit for requests that recovery and live updates wait on. On a slow // link their answers queue behind a large live update; whether the link still diff --git a/frontend/app.ts b/frontend/app.ts index 29700e95..22c6e053 100644 --- a/frontend/app.ts +++ b/frontend/app.ts @@ -1,2 +1,2 @@ -import "./settings.ts"; -import "./pages/layout.ts"; +import "./settings.js"; +import "./pages/layout.js"; diff --git a/frontend/chunked-fetch.ts b/frontend/chunked-fetch.ts index a13c917c..2277d63c 100644 --- a/frontend/chunked-fetch.ts +++ b/frontend/chunked-fetch.ts @@ -1,7 +1,7 @@ -import { CellScheduler } from "./chunked-fetch/cell-scheduler.ts"; -import type { FetchedFile } from "./chunked-fetch/transfer.ts"; +import { CellScheduler } from "./chunked-fetch/cell-scheduler.js"; +import type { FetchedFile } from "./chunked-fetch/transfer.js"; -export type { FetchedFile } from "./chunked-fetch/transfer.ts"; +export type { FetchedFile } from "./chunked-fetch/transfer.js"; // A large file a viewer shows is fetched in cells of 64 KB rather than in one // response, so the bytes of an image or document never wait in line ahead of diff --git a/frontend/chunked-fetch/cell-scheduler.test.js b/frontend/chunked-fetch/cell-scheduler.test.js index 633a40b7..c10f9660 100644 --- a/frontend/chunked-fetch/cell-scheduler.test.js +++ b/frontend/chunked-fetch/cell-scheduler.test.js @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { CellScheduler } from "./cell-scheduler.ts"; -import { CELL_BYTES } from "./transfer.ts"; +import { CellScheduler } from "./cell-scheduler.js"; +import { CELL_BYTES } from "./transfer.js"; const ORIGIN = "https://caffold.test"; diff --git a/frontend/chunked-fetch/cell-scheduler.ts b/frontend/chunked-fetch/cell-scheduler.ts index be855ff0..41d175a6 100644 --- a/frontend/chunked-fetch/cell-scheduler.ts +++ b/frontend/chunked-fetch/cell-scheduler.ts @@ -1,11 +1,11 @@ -import { InFlightLimit } from "./in-flight-limit.ts"; +import { InFlightLimit } from "./in-flight-limit.js"; import { abortError, type Cell, type CellAnswer, type FetchedFile, Transfer, -} from "./transfer.ts"; +} from "./transfer.js"; type FetchCell = (url: string, init: RequestInit) => Promise; diff --git a/frontend/chunked-fetch/in-flight-limit.test.js b/frontend/chunked-fetch/in-flight-limit.test.js index 8d32f37f..799b4da2 100644 --- a/frontend/chunked-fetch/in-flight-limit.test.js +++ b/frontend/chunked-fetch/in-flight-limit.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { InFlightLimit } from "./in-flight-limit.ts"; +import { InFlightLimit } from "./in-flight-limit.js"; function observeRounds(limit, waitMs, rounds) { for (let count = 0; count < rounds; count += 1) { diff --git a/frontend/chunked-fetch/transfer.test.js b/frontend/chunked-fetch/transfer.test.js index 48004b12..299f21cd 100644 --- a/frontend/chunked-fetch/transfer.test.js +++ b/frontend/chunked-fetch/transfer.test.js @@ -6,7 +6,7 @@ import { TRANSFER_EVENT, TRANSFER_NODE, Transfer, -} from "./transfer.ts"; +} from "./transfer.js"; const URL_ASKED = "https://caffold.test/api/image?path=photo.png"; diff --git a/frontend/component-styles.test.js b/frontend/component-styles.test.js index 1267b5ec..36039f2b 100644 --- a/frontend/component-styles.test.js +++ b/frontend/component-styles.test.js @@ -117,5 +117,5 @@ async function styleEnvironment(t, fetchSource) { this.cssRules = [{ cssRules: [{ cssRules: [{}] }] }]; } }; - return await import(`./component-styles.ts?environment=${++environmentId}`); + return await import(`./component-styles.js?environment=${++environmentId}`); } diff --git a/frontend/components/docx-viewer.test.js b/frontend/components/docx-viewer.test.js index c652b585..571e2419 100644 --- a/frontend/components/docx-viewer.test.js +++ b/frontend/components/docx-viewer.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./docx-viewer.ts"); +await import("./docx-viewer.js"); const docxViewer = registry.element("caffold-docx-viewer").prototype; after(() => registry.restore()); diff --git a/frontend/components/docx-viewer.ts b/frontend/components/docx-viewer.ts index 2878c9b6..fc3507a4 100644 --- a/frontend/components/docx-viewer.ts +++ b/frontend/components/docx-viewer.ts @@ -1,7 +1,7 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; -import { type ScrollSurfacePull, ScrollSurfaces } from "../scroll-scope.ts"; -import type { DocumentPreviewFile } from "./dom.ts"; -import "./loading-text.ts"; +import { type ScrollSurfacePull, ScrollSurfaces } from "../scroll-scope.js"; +import type { DocumentPreviewFile } from "./dom.js"; +import "./loading-text.js"; const DOCX_PREVIEW_VERSION = "0.4.1"; const DOCX_PREVIEW_IMPORT = `https://cdn.jsdelivr.net/npm/docx-preview@${DOCX_PREVIEW_VERSION}/+esm`; diff --git a/frontend/components/file-navigator.test.js b/frontend/components/file-navigator.test.js index baa61fd0..a8b2e791 100644 --- a/frontend/components/file-navigator.test.js +++ b/frontend/components/file-navigator.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./file-navigator.ts"); +await import("./file-navigator.js"); const navigator = registry.element("caffold-file-navigator").prototype; after(() => registry.restore()); diff --git a/frontend/components/file-navigator.ts b/frontend/components/file-navigator.ts index 99d0be9a..79aa55c6 100644 --- a/frontend/components/file-navigator.ts +++ b/frontend/components/file-navigator.ts @@ -1,20 +1,20 @@ import { html, LitElement } from "lit"; -import type { ActionHintPull } from "../action-hints.ts"; -import { type DirectoryListing, listDirectory } from "../api.ts"; -import type { ScrollSurfacePull } from "../scroll-scope.ts"; +import type { ActionHintPull } from "../action-hints.js"; +import { type DirectoryListing, listDirectory } from "../api.js"; +import type { ScrollSurfacePull } from "../scroll-scope.js"; import { createRefreshCoordinator, type RefreshCoordinator, type RefreshState, subscribeToWatch, type WatchChange, -} from "../watch.ts"; -import "./file-navigator/components/list.ts"; -import { DrawnRef, drawn } from "./drawn-refs.ts"; +} from "../watch.js"; +import "./file-navigator/components/list.js"; +import { DrawnRef, drawn } from "./drawn-refs.js"; import type { CaffoldFileList, RefreshButtonState, -} from "./file-navigator/components/list.ts"; +} from "./file-navigator/components/list.js"; const LOADING_DELAY_MS = 180; diff --git a/frontend/components/file-navigator/components/list.test.js b/frontend/components/file-navigator/components/list.test.js index 39aea108..bfaf87e6 100644 --- a/frontend/components/file-navigator/components/list.test.js +++ b/frontend/components/file-navigator/components/list.test.js @@ -6,7 +6,7 @@ import { } from "../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./list.ts"); +await import("./list.js"); const list = registry.element("caffold-file-list").prototype; after(() => registry.restore()); diff --git a/frontend/components/file-navigator/components/list.ts b/frontend/components/file-navigator/components/list.ts index 60de0088..08caf896 100644 --- a/frontend/components/file-navigator/components/list.ts +++ b/frontend/components/file-navigator/components/list.ts @@ -3,19 +3,19 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type DirectoryEntry, type DirectoryListing, listDirectory, -} from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; +} from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { entryKindLabel } from "../../dom.ts"; +} from "#app/scroll-scope.js"; +import { entryKindLabel } from "../../dom.js"; import { type CaffoldFileTree, errorFileTreeChildren, @@ -25,10 +25,10 @@ import { loadingFileTreeChildren, readyFileTreeChildren, unloadedFileTreeChildren, -} from "../../file-tree.ts"; -import { inlineIcon, warmIcons } from "../../icons.ts"; -import "../../loading-text.ts"; -import { DrawnRef, drawn } from "../../drawn-refs.ts"; +} from "../../file-tree.js"; +import { inlineIcon, warmIcons } from "../../icons.js"; +import "../../loading-text.js"; +import { DrawnRef, drawn } from "../../drawn-refs.js"; const TREE_LOADING_DELAY_MS = 180; diff --git a/frontend/components/file-tree.ts b/frontend/components/file-tree.ts index 7d257003..faabb6fd 100644 --- a/frontend/components/file-tree.ts +++ b/frontend/components/file-tree.ts @@ -1,24 +1,24 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; import { repeat } from "lit/directives/repeat.js"; -import { type ActionHintPull, ActionHints } from "../action-hints.ts"; +import { type ActionHintPull, ActionHints } from "../action-hints.js"; import { type FileStatusContext, fileStatusPresentation, -} from "../file-status.ts"; +} from "../file-status.js"; import { type ScrollSurfacePull, ScrollSurfaces, scrollBackToTop, -} from "../scroll-scope.ts"; +} from "../scroll-scope.js"; import { FILE_SORT_MODES, type FileSortMode, getSettings, normalizeFileSortMode, -} from "../settings.ts"; -import { entryIcon, warmIcons } from "./icons.ts"; -import "./loading-text.ts"; -import { DrawnRef, DrawnRefs, drawn } from "./drawn-refs.ts"; +} from "../settings.js"; +import { entryIcon, warmIcons } from "./icons.js"; +import "./loading-text.js"; +import { DrawnRef, DrawnRefs, drawn } from "./drawn-refs.js"; export const FILE_TREE_SELECT_EVENT = "caffold:file-tree-select"; export const FILE_TREE_LOAD_EVENT = "caffold:file-tree-load-children"; diff --git a/frontend/components/file-viewer-presentation.test.js b/frontend/components/file-viewer-presentation.test.js index ae257718..62341afc 100644 --- a/frontend/components/file-viewer-presentation.test.js +++ b/frontend/components/file-viewer-presentation.test.js @@ -3,7 +3,7 @@ import test from "node:test"; import { diffViewerPresentation, sourceViewerPresentation, -} from "./file-viewer-presentation.ts"; +} from "./file-viewer-presentation.js"; test("source presentation keeps a stable basename while metadata becomes available", () => { const loading = sourceViewerPresentation({ path: "src/planner/mod.rs" }); diff --git a/frontend/components/file-viewer-presentation.ts b/frontend/components/file-viewer-presentation.ts index a2083fd8..95730d57 100644 --- a/frontend/components/file-viewer-presentation.ts +++ b/frontend/components/file-viewer-presentation.ts @@ -1,5 +1,5 @@ -import { fileStatusPresentation } from "../file-status.ts"; -import { fileNameFromPath, formatBytes, formatModified } from "./dom.ts"; +import { fileStatusPresentation } from "../file-status.js"; +import { fileNameFromPath, formatBytes, formatModified } from "./dom.js"; export interface ViewerMetadataItem { field: string; diff --git a/frontend/components/file-viewer.test.js b/frontend/components/file-viewer.test.js index 7f29e39c..91651ddf 100644 --- a/frontend/components/file-viewer.test.js +++ b/frontend/components/file-viewer.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./file-viewer.ts"); +await import("./file-viewer.js"); const fileViewer = registry.element("caffold-review-file-viewer").prototype; after(() => registry.restore()); diff --git a/frontend/components/file-viewer.ts b/frontend/components/file-viewer.ts index 732ebb27..fbb9cef8 100644 --- a/frontend/components/file-viewer.ts +++ b/frontend/components/file-viewer.ts @@ -1,61 +1,61 @@ import { html, LitElement, nothing, type TemplateResult } from "lit"; -import type { ActionHintScope } from "../action-hint-scope.ts"; +import type { ActionHintScope } from "../action-hint-scope.js"; import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "../action-hints.ts"; -import { documentUrl, downloadUrl, imageUrl } from "../api.ts"; -import { type FetchedFile, fetchInChunks } from "../chunked-fetch.ts"; -import { compactIconButton } from "../component-styles.ts"; +} from "../action-hints.js"; +import { documentUrl, downloadUrl, imageUrl } from "../api.js"; +import { type FetchedFile, fetchInChunks } from "../chunked-fetch.js"; +import { compactIconButton } from "../component-styles.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, -} from "../keyboard-navigation.ts"; -import "../keyboard-navigation/components/presentation.ts"; +} from "../keyboard-navigation.js"; +import "../keyboard-navigation/components/presentation.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, type ScrollSurfaceScope, ScrollSurfaces, -} from "../scroll-scope.ts"; -import "./docx-viewer.ts"; +} from "../scroll-scope.js"; +import "./docx-viewer.js"; import { type DocumentPreviewFile, type DocumentPreviewKind, formatBytes, formatLineStats, formatModified, -} from "./dom.ts"; -import "./file-viewer/components/code-viewer.ts"; +} from "./dom.js"; +import "./file-viewer/components/code-viewer.js"; import type { CaffoldCodeViewer, CodeFile, ScrollPosition, ViewerPosition, ViewerPositionOptions, -} from "./file-viewer/components/code-viewer.ts"; -import "./file-viewer/components/diff-viewer.ts"; +} from "./file-viewer/components/code-viewer.js"; +import "./file-viewer/components/diff-viewer.js"; import type { CaffoldDiffViewer, FileDiff, -} from "./file-viewer/components/diff-viewer.ts"; +} from "./file-viewer/components/diff-viewer.js"; import { diffViewerPresentation, sourceViewerPresentation, type ViewerDiff, type ViewerPresentation, type ViewerSource, -} from "./file-viewer-presentation.ts"; -import { inlineIcon, warmIcons } from "./icons.ts"; -import "./loading-text.ts"; -import "./markdown-preview.ts"; -import type { CaffoldMarkdownPreview } from "./markdown-preview.ts"; -import "./pdf-viewer.ts"; -import "./pptx-viewer.ts"; -import "./xlsx-viewer.ts"; -import { DrawnRef, drawn } from "./drawn-refs.ts"; +} from "./file-viewer-presentation.js"; +import { inlineIcon, warmIcons } from "./icons.js"; +import "./loading-text.js"; +import "./markdown-preview.js"; +import type { CaffoldMarkdownPreview } from "./markdown-preview.js"; +import "./pdf-viewer.js"; +import "./pptx-viewer.js"; +import "./xlsx-viewer.js"; +import { DrawnRef, drawn } from "./drawn-refs.js"; // How long switching to another file keeps showing the previous one before // the loading state replaces it. diff --git a/frontend/components/file-viewer/components/code-viewer.ts b/frontend/components/file-viewer/components/code-viewer.ts index 764aa705..9cc27a2b 100644 --- a/frontend/components/file-viewer/components/code-viewer.ts +++ b/frontend/components/file-viewer/components/code-viewer.ts @@ -1,8 +1,8 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; -import type { SyntaxSpan } from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "../../drawn-refs.ts"; -import { syntaxLine } from "../syntax-spans.ts"; +import type { SyntaxSpan } from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "../../drawn-refs.js"; +import { syntaxLine } from "../syntax-spans.js"; export interface CodeFile { content: string; diff --git a/frontend/components/file-viewer/components/diff-viewer.ts b/frontend/components/file-viewer/components/diff-viewer.ts index 7d6e5f0a..6982fe38 100644 --- a/frontend/components/file-viewer/components/diff-viewer.ts +++ b/frontend/components/file-viewer/components/diff-viewer.ts @@ -1,9 +1,9 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; -import type { SyntaxSpan } from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "../../drawn-refs.ts"; -import { syntaxLine } from "../syntax-spans.ts"; -import type { ScrollPosition, ViewerPosition } from "./code-viewer.ts"; +import type { SyntaxSpan } from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "../../drawn-refs.js"; +import { syntaxLine } from "../syntax-spans.js"; +import type { ScrollPosition, ViewerPosition } from "./code-viewer.js"; export interface FileDiff { diff?: string; diff --git a/frontend/components/file-viewer/syntax-spans.test.js b/frontend/components/file-viewer/syntax-spans.test.js index cbd92598..f9c4318c 100644 --- a/frontend/components/file-viewer/syntax-spans.test.js +++ b/frontend/components/file-viewer/syntax-spans.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { syntaxLine } from "./syntax-spans.ts"; +import { syntaxLine } from "./syntax-spans.js"; test("wraps each span in its color class between the plain text around it", () => { assert.deepEqual( diff --git a/frontend/components/file-viewer/syntax-spans.ts b/frontend/components/file-viewer/syntax-spans.ts index 2100379e..9544b9d6 100644 --- a/frontend/components/file-viewer/syntax-spans.ts +++ b/frontend/components/file-viewer/syntax-spans.ts @@ -1,5 +1,5 @@ import { html, type TemplateResult } from "lit"; -import type { SyntaxSpan } from "#app/api.ts"; +import type { SyntaxSpan } from "#app/api.js"; // The class for each color number, in the order `SyntaxColor` lists them. const SYNTAX_CLASSES = [ diff --git a/frontend/components/git-compare-browser.test.js b/frontend/components/git-compare-browser.test.js index c1dece1b..b5e40b7b 100644 --- a/frontend/components/git-compare-browser.test.js +++ b/frontend/components/git-compare-browser.test.js @@ -9,7 +9,7 @@ const previousWindow = globalThis.window; let singlePane = false; globalThis.window = { matchMedia: () => ({ matches: singlePane }) }; const registry = installCustomElementUnitRegistry(); -await import("./git-compare-browser.ts"); +await import("./git-compare-browser.js"); const browser = registry.element("caffold-git-compare-browser").prototype; after(() => { registry.restore(); diff --git a/frontend/components/git-compare-browser.ts b/frontend/components/git-compare-browser.ts index 6d221754..7743d9ba 100644 --- a/frontend/components/git-compare-browser.ts +++ b/frontend/components/git-compare-browser.ts @@ -1,5 +1,5 @@ import { html, LitElement } from "lit"; -import { type ActionHintPull, mergeActionHintScopes } from "../action-hints.ts"; +import { type ActionHintPull, mergeActionHintScopes } from "../action-hints.js"; import { type GitCompare, type GitDiff, @@ -9,20 +9,20 @@ import { getGitCompare, getGitCompareDiff, getGitRefs, -} from "../api.ts"; +} from "../api.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, -} from "../scroll-scope.ts"; -import "./file-viewer.ts"; -import type { CaffoldReviewFileViewer } from "./file-viewer.ts"; -import { diffViewerPresentation } from "./file-viewer-presentation.ts"; -import "./git-compare-tree.ts"; -import type { CaffoldGitCompareTree } from "./git-compare-tree.ts"; -import "./pane-resizer.ts"; -import { DrawnRef, drawn } from "./drawn-refs.ts"; -import type { CaffoldPaneResizer } from "./pane-resizer.ts"; -import { REVIEW_SINGLE_PANE_MEDIA_QUERY } from "./review-responsive.ts"; +} from "../scroll-scope.js"; +import "./file-viewer.js"; +import type { CaffoldReviewFileViewer } from "./file-viewer.js"; +import { diffViewerPresentation } from "./file-viewer-presentation.js"; +import "./git-compare-tree.js"; +import type { CaffoldGitCompareTree } from "./git-compare-tree.js"; +import "./pane-resizer.js"; +import { DrawnRef, drawn } from "./drawn-refs.js"; +import type { CaffoldPaneResizer } from "./pane-resizer.js"; +import { REVIEW_SINGLE_PANE_MEDIA_QUERY } from "./review-responsive.js"; const LOADING_DELAY_MS = 180; diff --git a/frontend/components/git-compare-tree.test.js b/frontend/components/git-compare-tree.test.js index 89131002..db7d2266 100644 --- a/frontend/components/git-compare-tree.test.js +++ b/frontend/components/git-compare-tree.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./git-compare-tree.ts"); +await import("./git-compare-tree.js"); const compareTree = registry.element("caffold-git-compare-tree").prototype; after(() => registry.restore()); diff --git a/frontend/components/git-compare-tree.ts b/frontend/components/git-compare-tree.ts index 9809ba75..38f0cacc 100644 --- a/frontend/components/git-compare-tree.ts +++ b/frontend/components/git-compare-tree.ts @@ -3,22 +3,22 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "../action-hints.ts"; -import type { GitCompare, GitCompareFile, GitRepository } from "../api.ts"; +} from "../action-hints.js"; +import type { GitCompare, GitCompareFile, GitRepository } from "../api.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "../scroll-scope.ts"; -import { formatLineStats } from "./dom.ts"; +} from "../scroll-scope.js"; +import { formatLineStats } from "./dom.js"; import { buildFileTreeNodes, type CaffoldFileTree, type FileTreeNode, type FileTreeSelectDetail, -} from "./file-tree.ts"; -import "./loading-text.ts"; -import { DrawnRef, drawn } from "./drawn-refs.ts"; +} from "./file-tree.js"; +import "./loading-text.js"; +import { DrawnRef, drawn } from "./drawn-refs.js"; type CompareState = | { status: "idle" } diff --git a/frontend/components/loading-text.test.js b/frontend/components/loading-text.test.js index 4be0f4b6..b760f21d 100644 --- a/frontend/components/loading-text.test.js +++ b/frontend/components/loading-text.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./loading-text.ts"); +await import("./loading-text.js"); const loadingText = registry.element("caffold-loading-text").prototype; after(() => registry.restore()); diff --git a/frontend/components/markdown-preview.test.js b/frontend/components/markdown-preview.test.js index 84350a5d..9b19b230 100644 --- a/frontend/components/markdown-preview.test.js +++ b/frontend/components/markdown-preview.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./markdown-preview.ts"); +await import("./markdown-preview.js"); const markdownPreview = registry.element("caffold-markdown-preview").prototype; after(() => registry.restore()); diff --git a/frontend/components/markdown-preview.ts b/frontend/components/markdown-preview.ts index 73cc7352..7d05b6b0 100644 --- a/frontend/components/markdown-preview.ts +++ b/frontend/components/markdown-preview.ts @@ -8,20 +8,20 @@ import { linkActionHintLabel, linkActionHintTarget, matchesLinkActionHintBinding, -} from "../action-hint-scope.ts"; -import type { ActionHintPull } from "../action-hints.ts"; -import { imageUrl } from "../api.ts"; -import { fetchInChunks } from "../chunked-fetch.ts"; -import { parseLinkRoute } from "../navigation-routes.ts"; +} from "../action-hint-scope.js"; +import type { ActionHintPull } from "../action-hints.js"; +import { imageUrl } from "../api.js"; +import { fetchInChunks } from "../chunked-fetch.js"; +import { parseLinkRoute } from "../navigation-routes.js"; import { hasScrollLayoutBox, mergeScrollSurfaceScopes, type ScrollSurfacePull, type ScrollSurfaceScope, ScrollSurfaces, -} from "../scroll-scope.ts"; -import "./loading-text.ts"; -import { DrawnRef, drawn } from "./drawn-refs.ts"; +} from "../scroll-scope.js"; +import "./loading-text.js"; +import { DrawnRef, drawn } from "./drawn-refs.js"; const MARKED_IMPORT = "https://esm.sh/marked@15.0.12"; diff --git a/frontend/components/pagination.test.js b/frontend/components/pagination.test.js index d0239c15..22a33379 100644 --- a/frontend/components/pagination.test.js +++ b/frontend/components/pagination.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./pagination.ts"); +await import("./pagination.js"); const pagination = registry.element("caffold-pagination").prototype; after(() => registry.restore()); diff --git a/frontend/components/pagination.ts b/frontend/components/pagination.ts index bc5895f5..69bcea66 100644 --- a/frontend/components/pagination.ts +++ b/frontend/components/pagination.ts @@ -3,14 +3,14 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "../action-hints.ts"; -import { warmIcons } from "./icons.ts"; -import "./pagination/components/button.ts"; -import { DrawnRefs, drawn } from "./drawn-refs.ts"; +} from "../action-hints.js"; +import { warmIcons } from "./icons.js"; +import "./pagination/components/button.js"; +import { DrawnRefs, drawn } from "./drawn-refs.js"; import type { CaffoldPaginationButton, PageKind, -} from "./pagination/components/button.ts"; +} from "./pagination/components/button.js"; const PAGE_KINDS: readonly PageKind[] = ["first", "previous", "next", "last"]; diff --git a/frontend/components/pagination/components/button.ts b/frontend/components/pagination/components/button.ts index 6f29217e..b58ea584 100644 --- a/frontend/components/pagination/components/button.ts +++ b/frontend/components/pagination/components/button.ts @@ -1,7 +1,7 @@ import { html, LitElement } from "lit"; -import { type ActionHintPull, ActionHints } from "#app/action-hints.ts"; -import { compactIconButton } from "#app/component-styles.ts"; -import { inlineIcon } from "../../icons.ts"; +import { type ActionHintPull, ActionHints } from "#app/action-hints.js"; +import { compactIconButton } from "#app/component-styles.js"; +import { inlineIcon } from "../../icons.js"; export type PageKind = "first" | "previous" | "next" | "last"; diff --git a/frontend/components/pane-resizer.test.js b/frontend/components/pane-resizer.test.js index 6238f962..4b55b7bf 100644 --- a/frontend/components/pane-resizer.test.js +++ b/frontend/components/pane-resizer.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./pane-resizer.ts"); +await import("./pane-resizer.js"); const resizer = registry.element("caffold-pane-resizer").prototype; after(() => registry.restore()); diff --git a/frontend/components/pane-resizer.ts b/frontend/components/pane-resizer.ts index 57069d7d..967473be 100644 --- a/frontend/components/pane-resizer.ts +++ b/frontend/components/pane-resizer.ts @@ -1,5 +1,5 @@ import { html, LitElement } from "lit"; -import { type ActionHintPull, ActionHints } from "../action-hints.ts"; +import { type ActionHintPull, ActionHints } from "../action-hints.js"; const START_DEFAULT_WIDTH = 320; const START_MIN_WIDTH = 180; diff --git a/frontend/components/pdf-viewer.test.js b/frontend/components/pdf-viewer.test.js index dcc7d749..3768b61e 100644 --- a/frontend/components/pdf-viewer.test.js +++ b/frontend/components/pdf-viewer.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./pdf-viewer.ts"); +await import("./pdf-viewer.js"); const pdfViewer = registry.element("caffold-pdf-viewer").prototype; after(() => registry.restore()); diff --git a/frontend/components/pdf-viewer.ts b/frontend/components/pdf-viewer.ts index e6b21e40..4843bacf 100644 --- a/frontend/components/pdf-viewer.ts +++ b/frontend/components/pdf-viewer.ts @@ -1,7 +1,7 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; -import { type ScrollSurfacePull, ScrollSurfaces } from "../scroll-scope.ts"; -import type { DocumentPreviewFile } from "./dom.ts"; -import "./loading-text.ts"; +import { type ScrollSurfacePull, ScrollSurfaces } from "../scroll-scope.js"; +import type { DocumentPreviewFile } from "./dom.js"; +import "./loading-text.js"; const PDFJS_VERSION = "6.3.289"; const PDFJS_BASE = `https://cdn.jsdelivr.net/npm/pdfjs-dist@${PDFJS_VERSION}`; diff --git a/frontend/components/pptx-viewer.test.js b/frontend/components/pptx-viewer.test.js index a8374fe4..0b7e0cc4 100644 --- a/frontend/components/pptx-viewer.test.js +++ b/frontend/components/pptx-viewer.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./pptx-viewer.ts"); +await import("./pptx-viewer.js"); const pptxViewer = registry.element("caffold-pptx-viewer").prototype; after(() => registry.restore()); diff --git a/frontend/components/pptx-viewer.ts b/frontend/components/pptx-viewer.ts index 5ab6a388..101f4c29 100644 --- a/frontend/components/pptx-viewer.ts +++ b/frontend/components/pptx-viewer.ts @@ -5,11 +5,11 @@ import { hasActionHintLayoutBox, linkActionHintLabel, linkActionHintTarget, -} from "../action-hint-scope.ts"; -import type { ActionHintPull } from "../action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "../scroll-scope.ts"; -import type { DocumentPreviewFile } from "./dom.ts"; -import "./loading-text.ts"; +} from "../action-hint-scope.js"; +import type { ActionHintPull } from "../action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "../scroll-scope.js"; +import type { DocumentPreviewFile } from "./dom.js"; +import "./loading-text.js"; const PPTX_RENDERER_VERSION = "1.3.0"; const PPTX_RENDERER_IMPORT = diff --git a/frontend/components/review-responsive.test.js b/frontend/components/review-responsive.test.js index 2689a142..a565daa2 100644 --- a/frontend/components/review-responsive.test.js +++ b/frontend/components/review-responsive.test.js @@ -4,7 +4,7 @@ import test from "node:test"; import { REVIEW_SINGLE_PANE_MAX_WIDTH_PX, REVIEW_SINGLE_PANE_MEDIA_QUERY, -} from "./review-responsive.ts"; +} from "./review-responsive.js"; test("defines one canonical review single-pane boundary", () => { assert.equal(REVIEW_SINGLE_PANE_MAX_WIDTH_PX, 860); diff --git a/frontend/components/segmented-control.ts b/frontend/components/segmented-control.ts index fbde7c82..8786c457 100644 --- a/frontend/components/segmented-control.ts +++ b/frontend/components/segmented-control.ts @@ -1,6 +1,6 @@ import { html, LitElement, nothing } from "lit"; import { repeat } from "lit/directives/repeat.js"; -import { type ActionHintPull, ActionHints } from "../action-hints.ts"; +import { type ActionHintPull, ActionHints } from "../action-hints.js"; export interface SegmentedChoice { value: string; diff --git a/frontend/components/terminal-view.ts b/frontend/components/terminal-view.ts index 63f79172..41e80e8a 100644 --- a/frontend/components/terminal-view.ts +++ b/frontend/components/terminal-view.ts @@ -5,7 +5,7 @@ // the first time a terminal opens. import { html, LitElement } from "lit"; -import { DrawnRef, drawn } from "./drawn-refs.ts"; +import { DrawnRef, drawn } from "./drawn-refs.js"; const XTERM_VERSION = "6.0.0"; const FIT_ADDON_VERSION = "0.11.0"; diff --git a/frontend/components/xlsx-viewer.test.js b/frontend/components/xlsx-viewer.test.js index 8b6715ec..1c8f58ab 100644 --- a/frontend/components/xlsx-viewer.test.js +++ b/frontend/components/xlsx-viewer.test.js @@ -6,7 +6,7 @@ import { } from "../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./xlsx-viewer.ts"); +await import("./xlsx-viewer.js"); const xlsxViewer = registry.element("caffold-xlsx-viewer").prototype; after(() => registry.restore()); diff --git a/frontend/components/xlsx-viewer.ts b/frontend/components/xlsx-viewer.ts index 2d6ab7f7..9948251e 100644 --- a/frontend/components/xlsx-viewer.ts +++ b/frontend/components/xlsx-viewer.ts @@ -3,31 +3,31 @@ import { hasActionHintLayoutBox, linkActionHintLabel, linkActionHintTarget, -} from "../action-hint-scope.ts"; +} from "../action-hint-scope.js"; import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "../action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "../scroll-scope.ts"; -import type { DocumentPreviewFile } from "./dom.ts"; -import "./loading-text.ts"; -import "./segmented-control.ts"; -import { DrawnRef, drawn } from "./drawn-refs.ts"; -import type { CaffoldSegmentedControl } from "./segmented-control.ts"; +} from "../action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "../scroll-scope.js"; +import type { DocumentPreviewFile } from "./dom.js"; +import "./loading-text.js"; +import "./segmented-control.js"; +import { DrawnRef, drawn } from "./drawn-refs.js"; +import type { CaffoldSegmentedControl } from "./segmented-control.js"; import { containsRange, coverMerges, type TrackRange, trackOffsets, visibleTracks, -} from "./xlsx-viewer/window.ts"; +} from "./xlsx-viewer/window.js"; import { readWorkbook, type SheetJs, type SheetModel, type WorkbookModel, -} from "./xlsx-viewer/workbook.ts"; +} from "./xlsx-viewer/workbook.js"; const SHEETJS_VERSION = "0.20.3"; const SHEETJS_IMPORT = `https://cdn.sheetjs.com/xlsx-${SHEETJS_VERSION}/package/xlsx.mjs`; diff --git a/frontend/components/xlsx-viewer/window.test.js b/frontend/components/xlsx-viewer/window.test.js index d40aff13..b8d328f1 100644 --- a/frontend/components/xlsx-viewer/window.test.js +++ b/frontend/components/xlsx-viewer/window.test.js @@ -6,7 +6,7 @@ import { coverMerges, trackOffsets, visibleTracks, -} from "./window.ts"; +} from "./window.js"; test("sums the sizes of the tracks after the frozen ones", () => { assert.deepEqual([...trackOffsets([10, 20, 30])], [0, 10, 30, 60]); diff --git a/frontend/components/xlsx-viewer/workbook.test.js b/frontend/components/xlsx-viewer/workbook.test.js index bf1dbb61..d9684e99 100644 --- a/frontend/components/xlsx-viewer/workbook.test.js +++ b/frontend/components/xlsx-viewer/workbook.test.js @@ -3,7 +3,7 @@ import test from "node:test"; import * as XLSX from "xlsx"; import { spreadsheetWorkbook } from "../../tests/support/spreadsheet-fixture.js"; -import { colorValue, readWorkbook } from "./workbook.ts"; +import { colorValue, readWorkbook } from "./workbook.js"; // Office's default theme, in the order a theme declares its colors: dark 1, // light 1, dark 2, light 2, six accents, and the two link colors. diff --git a/frontend/components/xlsx-viewer/workbook.ts b/frontend/components/xlsx-viewer/workbook.ts index 46300dec..8ac23cfb 100644 --- a/frontend/components/xlsx-viewer/workbook.ts +++ b/frontend/components/xlsx-viewer/workbook.ts @@ -4,7 +4,7 @@ // which style each cell uses, the borders, and frozen panes come from the // workbook's own XML, which SheetJS hands over unparsed. -import type { TrackRange } from "./window.ts"; +import type { TrackRange } from "./window.js"; // The parts of SheetJS and of the workbook it reads that the viewer uses. export interface SheetJs { diff --git a/frontend/element-declarations.test.js b/frontend/element-declarations.test.js index 84870eb2..01c21313 100644 --- a/frontend/element-declarations.test.js +++ b/frontend/element-declarations.test.js @@ -3,7 +3,7 @@ import test, { after } from "node:test"; const previousNode = globalThis.Node; globalThis.Node = { DOCUMENT_POSITION_PRECEDING: 2 }; -const { ElementDeclarations } = await import("./element-declarations.ts"); +const { ElementDeclarations } = await import("./element-declarations.js"); after(() => restoreGlobal("Node", previousNode)); diff --git a/frontend/file-status.test.js b/frontend/file-status.test.js index 950616ea..cd3bee82 100644 --- a/frontend/file-status.test.js +++ b/frontend/file-status.test.js @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { fileStatusPresentation } from "./file-status.ts"; +import { fileStatusPresentation } from "./file-status.js"; test("normalizes Working Tree porcelain status by its owning section", () => { const cases = [ diff --git a/frontend/fonts.test.js b/frontend/fonts.test.js index cf84ba6c..2fea9893 100644 --- a/frontend/fonts.test.js +++ b/frontend/fonts.test.js @@ -3,7 +3,9 @@ import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import test from "node:test"; -const moduleUrl = new URL("./fonts.ts", import.meta.url); +import { sourceUrl } from "./tests/support/source-url.js"; + +const moduleUrl = new URL("./fonts.js", import.meta.url); async function importFreshFonts(label) { const url = new URL(moduleUrl); @@ -13,7 +15,7 @@ async function importFreshFonts(label) { async function readFontFaces() { const stylesheet = await readFile( - new URL("./styles.css", import.meta.url), + sourceUrl(new URL("./styles.css", import.meta.url)), "utf8", ); return [...stylesheet.matchAll(/@font-face\s*\{([^}]*)\}/g)].map( @@ -29,7 +31,7 @@ function bundledFamily(preset) { function assertBundledFile(face, family) { const [, file] = face.match(/url\("\.\/fonts\/([^"]+)"\)/); assert.ok( - existsSync(new URL(`./assets/fonts/${file}`, import.meta.url)), + existsSync(sourceUrl(new URL(`./assets/fonts/${file}`, import.meta.url))), `${family} names ${file}, which is not bundled`, ); } @@ -155,6 +157,39 @@ test("backs every bundled code preset with static regular and bold faces", async } }); +test("the index preloads exactly the faces the default presets name", async () => { + const { + CODE_TYPEFACE_PRESETS, + DEFAULT_CODE_TYPEFACE_PRESET, + DEFAULT_UI_TYPEFACE_PRESET, + UI_TYPEFACE_PRESETS, + } = await importFreshFonts("preloads"); + const index = await readFile( + sourceUrl(new URL("./index.html", import.meta.url)), + "utf8", + ); + const faces = new Map(); + for (const body of await readFontFaces()) { + const family = body.match(/font-family:\s*("[^"]+")/)?.[1]; + const file = body.match(/url\("\.\/fonts\/([^"]+)"\)/)?.[1]; + if (family && file) { + faces.set(family, [...(faces.get(family) ?? []), file]); + } + } + const expected = [ + UI_TYPEFACE_PRESETS[DEFAULT_UI_TYPEFACE_PRESET], + CODE_TYPEFACE_PRESETS[DEFAULT_CODE_TYPEFACE_PRESET], + ].flatMap((preset) => faces.get(preset.stack.split(", ")[0]) ?? []); + + assert.ok(expected.length > 0, "default presets must name bundled faces"); + assert.deepEqual( + [...index.matchAll(/href="\/assets\/fonts\/([^"]+)"/g)].map( + ([, name]) => name, + ), + expected, + ); +}); + test("applies each axis to its own token without touching the other", async () => { const { applyCodeTypefacePreset, applyUiTypefacePreset } = await importFreshFonts("roles"); diff --git a/frontend/index.html b/frontend/index.html index 6f6b7845..9d47ca55 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -79,7 +79,7 @@ } } - + diff --git a/frontend/keyboard-navigation.test.js b/frontend/keyboard-navigation.test.js index 46b96472..b8f04256 100644 --- a/frontend/keyboard-navigation.test.js +++ b/frontend/keyboard-navigation.test.js @@ -8,11 +8,11 @@ import { const registry = installCustomElementUnitRegistry(); const previousElement = globalThis.Element; globalThis.Element = globalThis.HTMLElement; -const { KeyboardNavigationController } = await import("./keyboard-navigation.ts"); +const { KeyboardNavigationController } = await import("./keyboard-navigation.js"); const { KEYBOARD_NAVIGATION_EVENT, KEYBOARD_NAVIGATION_NODE, -} = await import("./keyboard-navigation/control.ts"); +} = await import("./keyboard-navigation/control.js"); after(() => { restoreGlobal("Element", previousElement); registry.restore(); diff --git a/frontend/keyboard-navigation.ts b/frontend/keyboard-navigation.ts index 1781c84c..5e0a9f58 100644 --- a/frontend/keyboard-navigation.ts +++ b/frontend/keyboard-navigation.ts @@ -1,4 +1,4 @@ -import type { CaffoldActionHintDialog } from "./action-hints/components/dialog.ts"; +import type { CaffoldActionHintDialog } from "./action-hints/components/dialog.js"; import { type ActionHintBinding, ActionHintController, @@ -10,27 +10,27 @@ import { normalizeRect, type Rect, rectsEqual, -} from "./action-hints.ts"; -import type { CaffoldScrollModeHud } from "./keyboard-navigation/components/hud.ts"; +} from "./action-hints.js"; +import type { CaffoldScrollModeHud } from "./keyboard-navigation/components/hud.js"; import { type CaffoldScrollSurfaceSelector, SCROLL_SURFACE_CANCEL_EVENT, SCROLL_SURFACE_SELECT_EVENT, -} from "./keyboard-navigation/components/selector.ts"; -import type { CaffoldKeyboardShortcutDialog } from "./keyboard-navigation/components/shortcut-dialog.ts"; +} from "./keyboard-navigation/components/selector.js"; +import type { CaffoldKeyboardShortcutDialog } from "./keyboard-navigation/components/shortcut-dialog.js"; import { type ContextScrollSurface, type KeyboardNavigationContext, type NormalizedKeyboardNavigationContext, normalizeKeyboardNavigationContexts, -} from "./keyboard-navigation/context.ts"; +} from "./keyboard-navigation/context.js"; import { KEYBOARD_NAVIGATION_EVENT, KEYBOARD_NAVIGATION_NODE, type KeyboardNavigationEvent, type KeyboardNavigationNode, transitionKeyboardNavigation, -} from "./keyboard-navigation/control.ts"; +} from "./keyboard-navigation/control.js"; import { allocateScrollSurfaceCodes, normalizedContextRect, @@ -40,21 +40,21 @@ import { sameScrollSelectionSnapshot, scrollCommandPosition, visibleScrollSurfaceRect, -} from "./keyboard-navigation/model.ts"; +} from "./keyboard-navigation/model.js"; import { KEY_COMBINATION_ACTION, KEYBOARD_NAVIGATION_KEY, KEYBOARD_SHORTCUT_CLOSE_EVENT, keyCombinationAction, matchesKeyboardNavigationKey, -} from "./keyboard-navigation/shortcuts.ts"; -import { availableScrollAxes, type ScrollAxis } from "./scroll-scope.ts"; +} from "./keyboard-navigation/shortcuts.js"; +import { availableScrollAxes, type ScrollAxis } from "./scroll-scope.js"; export { KeyboardContexts, type KeyboardNavigationContext, mergeKeyboardNavigationContexts, -} from "./keyboard-navigation/context.ts"; +} from "./keyboard-navigation/context.js"; export const KEYBOARD_SESSION_DISMISS_EVENT = "caffold:keyboard-session-dismiss"; diff --git a/frontend/keyboard-navigation/components/hud.test.js b/frontend/keyboard-navigation/components/hud.test.js index 0411a3d0..0ab68e68 100644 --- a/frontend/keyboard-navigation/components/hud.test.js +++ b/frontend/keyboard-navigation/components/hud.test.js @@ -7,7 +7,7 @@ import { import { templateHtml } from "../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./hud.ts"); +await import("./hud.js"); const hud = registry.element("caffold-scroll-mode-hud").prototype; after(() => registry.restore()); diff --git a/frontend/keyboard-navigation/components/hud.ts b/frontend/keyboard-navigation/components/hud.ts index e5f526c4..59b1ae6d 100644 --- a/frontend/keyboard-navigation/components/hud.ts +++ b/frontend/keyboard-navigation/components/hud.ts @@ -4,8 +4,8 @@ import { normalizeRect, type Rect, type RectLike, -} from "../../action-hints/model.ts"; -import { DrawnRef, drawn } from "../../components/drawn-refs.ts"; +} from "../../action-hints.js"; +import { DrawnRef, drawn } from "../../components/drawn-refs.js"; /** * The outline around the surface Scroll mode moves, and the status at its diff --git a/frontend/keyboard-navigation/components/presentation.test.js b/frontend/keyboard-navigation/components/presentation.test.js index e1da0741..a0dc6b9c 100644 --- a/frontend/keyboard-navigation/components/presentation.test.js +++ b/frontend/keyboard-navigation/components/presentation.test.js @@ -7,7 +7,7 @@ import { import { templateHtml } from "../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./presentation.ts"); +await import("./presentation.js"); const presentation = registry.element( "caffold-keyboard-navigation-presentation", ).prototype; diff --git a/frontend/keyboard-navigation/components/presentation.ts b/frontend/keyboard-navigation/components/presentation.ts index c8279648..dfcf9ed4 100644 --- a/frontend/keyboard-navigation/components/presentation.ts +++ b/frontend/keyboard-navigation/components/presentation.ts @@ -1,11 +1,11 @@ import { html, LitElement } from "lit"; -import "../../action-hints/components/dialog.ts"; -import type { CaffoldActionHintDialog } from "../../action-hints/components/dialog.ts"; -import { DrawnRef, drawn } from "../../components/drawn-refs.ts"; -import "./hud.ts"; -import type { CaffoldScrollModeHud } from "./hud.ts"; -import "./selector.ts"; -import type { CaffoldScrollSurfaceSelector } from "./selector.ts"; +import "../../action-hints/components/dialog.js"; +import type { CaffoldActionHintDialog } from "../../action-hints/components/dialog.js"; +import { DrawnRef, drawn } from "../../components/drawn-refs.js"; +import "./hud.js"; +import type { CaffoldScrollModeHud } from "./hud.js"; +import "./selector.js"; +import type { CaffoldScrollSurfaceSelector } from "./selector.js"; /** * What one keyboard context shows its modes with: the Action Hint badges, diff --git a/frontend/keyboard-navigation/components/selector.test.js b/frontend/keyboard-navigation/components/selector.test.js index 247985b9..5ba996ea 100644 --- a/frontend/keyboard-navigation/components/selector.test.js +++ b/frontend/keyboard-navigation/components/selector.test.js @@ -11,10 +11,10 @@ const previousElement = globalThis.Element; const previousButton = globalThis.HTMLButtonElement; globalThis.Element = globalThis.HTMLElement; globalThis.HTMLButtonElement = class TestButton extends globalThis.HTMLElement {}; -await import("./selector.ts"); +await import("./selector.js"); const Selector = registry.element("caffold-scroll-surface-selector"); const selector = Selector.prototype; -const { DrawnRefs } = await import("../../components/drawn-refs.ts"); +const { DrawnRefs } = await import("../../components/drawn-refs.js"); after(() => { restoreGlobal("Element", previousElement); restoreGlobal("HTMLButtonElement", previousButton); diff --git a/frontend/keyboard-navigation/components/selector.ts b/frontend/keyboard-navigation/components/selector.ts index 06c55137..6811832b 100644 --- a/frontend/keyboard-navigation/components/selector.ts +++ b/frontend/keyboard-navigation/components/selector.ts @@ -1,7 +1,7 @@ import { html, LitElement } from "lit"; import { repeat } from "lit/directives/repeat.js"; -import { clampBadgePosition, type Rect } from "../../action-hints/model.ts"; -import { DrawnRef, DrawnRefs, drawn } from "../../components/drawn-refs.ts"; +import { clampBadgePosition, type Rect } from "../../action-hints.js"; +import { DrawnRef, DrawnRefs, drawn } from "../../components/drawn-refs.js"; export const SCROLL_SURFACE_SELECT_EVENT = "caffold:scroll-surface-select"; export const SCROLL_SURFACE_CANCEL_EVENT = "caffold:scroll-surface-cancel"; diff --git a/frontend/keyboard-navigation/components/shortcut-dialog.test.js b/frontend/keyboard-navigation/components/shortcut-dialog.test.js index 16538b6b..a16a7efc 100644 --- a/frontend/keyboard-navigation/components/shortcut-dialog.test.js +++ b/frontend/keyboard-navigation/components/shortcut-dialog.test.js @@ -7,11 +7,11 @@ import { import { templateHtml } from "../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./shortcut-dialog.ts"); +await import("./shortcut-dialog.js"); const dialog = registry.element("caffold-keyboard-shortcut-dialog").prototype; -const { ActionHints } = await import("../../action-hints.ts"); -const { ScrollSurfaces } = await import("../../scroll-scope.ts"); -const { KeyboardContexts } = await import("../context.ts"); +const { ActionHints } = await import("../../action-hints.js"); +const { ScrollSurfaces } = await import("../../scroll-scope.js"); +const { KeyboardContexts } = await import("../context.js"); after(() => registry.restore()); test("draws the shared shortcut map in one native dialog", () => { diff --git a/frontend/keyboard-navigation/components/shortcut-dialog.ts b/frontend/keyboard-navigation/components/shortcut-dialog.ts index 274b426f..9fc5b457 100644 --- a/frontend/keyboard-navigation/components/shortcut-dialog.ts +++ b/frontend/keyboard-navigation/components/shortcut-dialog.ts @@ -1,12 +1,12 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "../../action-hints.ts"; -import { DrawnRef, drawn } from "../../components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "../../components/icons.ts"; -import { ScrollSurfaces } from "../../scroll-scope.ts"; -import { KeyboardContexts } from "../context.ts"; -import { KEYBOARD_SHORTCUT_CLOSE_EVENT } from "../shortcuts.ts"; -import "./presentation.ts"; -import "./shortcut-list.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "../../action-hints.js"; +import { DrawnRef, drawn } from "../../components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "../../components/icons.js"; +import { ScrollSurfaces } from "../../scroll-scope.js"; +import { KeyboardContexts } from "../context.js"; +import { KEYBOARD_SHORTCUT_CLOSE_EVENT } from "../shortcuts.js"; +import "./presentation.js"; +import "./shortcut-list.js"; const CLOSE_LABEL = "Close keyboard shortcuts"; diff --git a/frontend/keyboard-navigation/components/shortcut-list.test.js b/frontend/keyboard-navigation/components/shortcut-list.test.js index aaa14004..ab5cbdd6 100644 --- a/frontend/keyboard-navigation/components/shortcut-list.test.js +++ b/frontend/keyboard-navigation/components/shortcut-list.test.js @@ -5,10 +5,10 @@ import { installCustomElementUnitRegistry, } from "../../tests/support/custom-element-unit.js"; import { templateHtml } from "../../tests/support/lit-template.js"; -import { KEYBOARD_SHORTCUT_HELP_SECTIONS } from "../shortcuts.ts"; +import { KEYBOARD_SHORTCUT_HELP_SECTIONS } from "../shortcuts.js"; const registry = installCustomElementUnitRegistry(); -await import("./shortcut-list.ts"); +await import("./shortcut-list.js"); const shortcutList = registry.element( "caffold-keyboard-shortcut-list", ).prototype; diff --git a/frontend/keyboard-navigation/components/shortcut-list.ts b/frontend/keyboard-navigation/components/shortcut-list.ts index 55050244..954ff973 100644 --- a/frontend/keyboard-navigation/components/shortcut-list.ts +++ b/frontend/keyboard-navigation/components/shortcut-list.ts @@ -1,5 +1,5 @@ import { html, LitElement, nothing } from "lit"; -import { KEYBOARD_SHORTCUT_HELP_SECTIONS } from "../shortcuts.ts"; +import { KEYBOARD_SHORTCUT_HELP_SECTIONS } from "../shortcuts.js"; /** Every keyboard shortcut, in the sections the help dialog shows. */ export class CaffoldKeyboardShortcutList extends LitElement { diff --git a/frontend/keyboard-navigation/context.test.js b/frontend/keyboard-navigation/context.test.js index 28d52659..ee64f372 100644 --- a/frontend/keyboard-navigation/context.test.js +++ b/frontend/keyboard-navigation/context.test.js @@ -10,7 +10,7 @@ const { KeyboardContexts, mergeKeyboardNavigationContexts, normalizeKeyboardNavigationContexts, -} = await import("./context.ts"); +} = await import("./context.js"); const previousElement = globalThis.Element; const previousShadowRoot = globalThis.ShadowRoot; const previousNode = globalThis.Node; diff --git a/frontend/keyboard-navigation/context.ts b/frontend/keyboard-navigation/context.ts index c9b63a50..e53c4222 100644 --- a/frontend/keyboard-navigation/context.ts +++ b/frontend/keyboard-navigation/context.ts @@ -1,12 +1,12 @@ import { type ActionHintScope, mergeActionHintScopes, -} from "../action-hint-scope.ts"; +} from "../action-hint-scope.js"; import { type ActionHintPull, normalizeMutationRootList, -} from "../action-hints.ts"; -import { ElementDeclarations } from "../element-declarations.ts"; +} from "../action-hints.js"; +import { ElementDeclarations } from "../element-declarations.js"; import { mergeScrollSurfaceScopes, normalizeScrollAxes, @@ -14,8 +14,8 @@ import { type ScrollSurface, type ScrollSurfacePull, type ScrollSurfaceScope, -} from "../scroll-scope.ts"; -import type { CaffoldKeyboardNavigationPresentation } from "./components/presentation.ts"; +} from "../scroll-scope.js"; +import type { CaffoldKeyboardNavigationPresentation } from "./components/presentation.js"; export type KeyboardNavigationContextKind = "workspace" | "modal" | "popover"; diff --git a/frontend/keyboard-navigation/control.test.js b/frontend/keyboard-navigation/control.test.js index ef1f43b1..1a4d1bb6 100644 --- a/frontend/keyboard-navigation/control.test.js +++ b/frontend/keyboard-navigation/control.test.js @@ -6,7 +6,7 @@ import { KEYBOARD_NAVIGATION_GRAPH, KEYBOARD_NAVIGATION_NODE, transitionKeyboardNavigation, -} from "./control.ts"; +} from "./control.js"; test("declares every keyboard navigation edge and reaches every node", () => { assert.deepEqual(KEYBOARD_NAVIGATION_GRAPH, { diff --git a/frontend/keyboard-navigation/model.test.js b/frontend/keyboard-navigation/model.test.js index c1d8e4e9..e9266e7d 100644 --- a/frontend/keyboard-navigation/model.test.js +++ b/frontend/keyboard-navigation/model.test.js @@ -8,7 +8,7 @@ import { sameScrollSelectionSnapshot, scrollCommandPosition, visibleScrollSurfaceRect, -} from "./model.ts"; +} from "./model.js"; test("allocates same-width prefix-free Scroll codes in ASDF order", () => { const surfaces = Array.from({ length: 28 }, (_, index) => ({ diff --git a/frontend/keyboard-navigation/model.ts b/frontend/keyboard-navigation/model.ts index 1f6b9a6d..12a94e2c 100644 --- a/frontend/keyboard-navigation/model.ts +++ b/frontend/keyboard-navigation/model.ts @@ -7,9 +7,9 @@ import { sortByVisualOrder, TASK_HINT_ALPHABET, taskHintSuffix, -} from "../action-hints/model.ts"; -import type { ScrollAxis } from "../scroll-scope.ts"; -import { KEYBOARD_NAVIGATION_KEY } from "./shortcuts.ts"; +} from "../action-hints.js"; +import type { ScrollAxis } from "../scroll-scope.js"; +import { KEYBOARD_NAVIGATION_KEY } from "./shortcuts.js"; interface ScrollCommand { axis: ScrollAxis; diff --git a/frontend/keyboard-navigation/shortcuts.test.js b/frontend/keyboard-navigation/shortcuts.test.js index cfd1bc60..de57b9b3 100644 --- a/frontend/keyboard-navigation/shortcuts.test.js +++ b/frontend/keyboard-navigation/shortcuts.test.js @@ -1,14 +1,14 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { SCROLL_COMMAND } from "./model.ts"; +import { SCROLL_COMMAND } from "./model.js"; import { KEYBOARD_NAVIGATION_KEY, KEYBOARD_SHORTCUT_HELP_SECTIONS, KEY_COMBINATION_ACTION, keyCombinationAction, matchesKeyboardNavigationKey, -} from "./shortcuts.ts"; +} from "./shortcuts.js"; test("keeps the displayed Scroll keys aligned with executable commands", () => { assert.deepEqual(Object.keys(SCROLL_COMMAND), ["J", "K", "D", "U", "H", "L"]); diff --git a/frontend/navigation-routes.test.js b/frontend/navigation-routes.test.js index a3503c7e..00e25fe6 100644 --- a/frontend/navigation-routes.test.js +++ b/frontend/navigation-routes.test.js @@ -14,7 +14,7 @@ import { routeTab, routeTarget, routeUrl, -} from "./navigation-routes.ts"; +} from "./navigation-routes.js"; globalThis.window = { location: { origin: "http://caffold.test" }, diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 174448fa..80a199d8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,7 +8,8 @@ "name": "caffold", "version": "0.19.8", "dependencies": { - "lit": "3.3.3" + "lit": "3.3.3", + "typescript": "5.9.3" }, "devDependencies": { "@aiden0z/pptx-renderer": "1.3.0", @@ -19,7 +20,6 @@ "docx-preview": "0.4.1", "pdfjs-dist": "6.3.289", "pptxgenjs": "4.0.1", - "typescript": "5.9.3", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" } }, @@ -861,7 +861,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/frontend/package.json b/frontend/package.json index 28a40ff6..24b0bcc9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,9 +9,10 @@ "#tasks/*": "./pages/(task-workspace)/tasks/*" }, "scripts": { - "test:unit": "node --test \"*.test.js\" \"action-hints/**/*.test.js\" \"chunked-fetch/**/*.test.js\" \"components/**/*.test.js\" \"keyboard-navigation/**/*.test.js\" \"pages/**/*.test.js\"", + "test:unit": "npm run build:unit-tests && node --test \"tests/unit-build/**/*.test.js\"", + "build:unit-tests": "rm -rf tests/unit-build && tsc -p tsconfig.test.json && tsc -p tsconfig.worker.json --noEmit false --outDir tests/unit-build", "test:contract": "node --test \"tests/contracts/*.test.mjs\"", - "typecheck": "tsc --noEmit", + "typecheck": "tsc && tsc -p tsconfig.worker.json", "lint": "biome check", "test:e2e": "playwright test", "test:codex-live": "playwright test --config=playwright.live.config.mjs" @@ -25,10 +26,10 @@ "docx-preview": "0.4.1", "pdfjs-dist": "6.3.289", "pptxgenjs": "4.0.1", - "typescript": "5.9.3", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "dependencies": { - "lit": "3.3.3" + "lit": "3.3.3", + "typescript": "5.9.3" } } diff --git a/frontend/pages/(task-workspace)/codex-status.ts b/frontend/pages/(task-workspace)/codex-status.ts index b43876c9..44b61457 100644 --- a/frontend/pages/(task-workspace)/codex-status.ts +++ b/frontend/pages/(task-workspace)/codex-status.ts @@ -3,11 +3,11 @@ import { getCodexStatus, restartCodexRuntime, updateCodexRuntime, -} from "../../api.ts"; +} from "../../api.js"; import { CodexStatusLifecycle, type CodexStatusLifecycleOptions, -} from "./codex-status/lifecycle.ts"; +} from "./codex-status/lifecycle.js"; import { codexBlocksTaskOperations, codexRateWindows, @@ -25,13 +25,13 @@ import { formatUsedPercent, INITIAL_CODEX_STATUS_SNAPSHOT, resetCreditExpiry, -} from "./codex-status/model.ts"; +} from "./codex-status/model.js"; -export type { CodexResetCreditRequestState } from "./codex-status/lifecycle.ts"; +export type { CodexResetCreditRequestState } from "./codex-status/lifecycle.js"; export type { CodexRuntimeRequestState, CodexStatusSnapshot, -} from "./codex-status/model.ts"; +} from "./codex-status/model.js"; export const CODEX_STATUS_REFRESH_REQUEST_EVENT = "caffold:refresh-codex-status"; diff --git a/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts b/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts index 6edf845e..6bb016a2 100644 --- a/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts +++ b/frontend/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts @@ -1,8 +1,8 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; export const CODEX_RESET_CREDIT_CONFIRMED_EVENT = "caffold:codex-reset-credit-confirmed"; diff --git a/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts b/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts index 3c589c6e..3a72174e 100644 --- a/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts +++ b/frontend/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts @@ -1,8 +1,8 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; export const CODEX_RUNTIME_RESTART_CONFIRMED_EVENT = "caffold:codex-runtime-restart-confirmed"; diff --git a/frontend/pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts b/frontend/pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts index 587e61f4..cbbe372c 100644 --- a/frontend/pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts +++ b/frontend/pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts @@ -1,8 +1,8 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; export const CODEX_RUNTIME_UPDATE_CONFIRMED_EVENT = "caffold:codex-runtime-update-confirmed"; diff --git a/frontend/pages/(task-workspace)/codex-status/lifecycle.test.js b/frontend/pages/(task-workspace)/codex-status/lifecycle.test.js index 509ebb65..f0272b51 100644 --- a/frontend/pages/(task-workspace)/codex-status/lifecycle.test.js +++ b/frontend/pages/(task-workspace)/codex-status/lifecycle.test.js @@ -1,10 +1,10 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { CodexStatusLifecycle } from "./lifecycle.ts"; +import { CodexStatusLifecycle } from "./lifecycle.js"; import { codexBlocksTaskOperations, createCodexStatusSnapshot, -} from "./model.ts"; +} from "./model.js"; function deferred() { let resolve; diff --git a/frontend/pages/(task-workspace)/codex-status/lifecycle.ts b/frontend/pages/(task-workspace)/codex-status/lifecycle.ts index 48c01cce..fce081c3 100644 --- a/frontend/pages/(task-workspace)/codex-status/lifecycle.ts +++ b/frontend/pages/(task-workspace)/codex-status/lifecycle.ts @@ -2,7 +2,7 @@ import type { CodexResetCreditOutcome, CodexStatus, CodexUpdateOutcome, -} from "#app/api.ts"; +} from "#app/api.js"; import { type CodexRuntimeRequestState, type CodexStatusSnapshot, @@ -12,9 +12,9 @@ import { createCodexStatusSnapshot, INITIAL_CODEX_STATUS_SNAPSHOT, sameCodexStatusSnapshot, -} from "./model.ts"; -import { CodexRuntimeRestartLifecycle } from "./runtime-restart-lifecycle.ts"; -import { CodexRuntimeUpdateLifecycle } from "./runtime-update-lifecycle.ts"; +} from "./model.js"; +import { CodexRuntimeRestartLifecycle } from "./runtime-restart-lifecycle.js"; +import { CodexRuntimeUpdateLifecycle } from "./runtime-update-lifecycle.js"; /** * Restarting and updating both replace the shared Codex runtime, so at most diff --git a/frontend/pages/(task-workspace)/codex-status/model.test.js b/frontend/pages/(task-workspace)/codex-status/model.test.js index 235a8199..144f21b0 100644 --- a/frontend/pages/(task-workspace)/codex-status/model.test.js +++ b/frontend/pages/(task-workspace)/codex-status/model.test.js @@ -11,7 +11,7 @@ import { formatResetCredits, resetCreditExpiry, sameCodexStatus, -} from "./model.ts"; +} from "./model.js"; function codexStatus(state, blocksTaskOperations = state !== "ready") { return { diff --git a/frontend/pages/(task-workspace)/codex-status/model.ts b/frontend/pages/(task-workspace)/codex-status/model.ts index 19267925..906f32a3 100644 --- a/frontend/pages/(task-workspace)/codex-status/model.ts +++ b/frontend/pages/(task-workspace)/codex-status/model.ts @@ -3,7 +3,7 @@ import type { CodexRateWindow, CodexResetCredit, CodexStatus, -} from "#app/api.ts"; +} from "#app/api.js"; type Status = CodexStatus | null | undefined; diff --git a/frontend/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.ts b/frontend/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.ts index 259e3dd3..8c6cb099 100644 --- a/frontend/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.ts +++ b/frontend/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.ts @@ -1,5 +1,5 @@ -import type { CodexStatus } from "#app/api.ts"; -import type { CodexRuntimeRequestState } from "./model.ts"; +import type { CodexStatus } from "#app/api.js"; +import type { CodexRuntimeRequestState } from "./model.js"; const IDLE_RESTART_STATE = Object.freeze({ state: "idle", diff --git a/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.test.js b/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.test.js index 87c960d8..984f0976 100644 --- a/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.test.js +++ b/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.test.js @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { CodexRuntimeUpdateLifecycle } from "./runtime-update-lifecycle.ts"; +import { CodexRuntimeUpdateLifecycle } from "./runtime-update-lifecycle.js"; const RESTARTED = "The managed installation is ready and the running daemon was restarted. Active or queued work may have been interrupted."; diff --git a/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.ts b/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.ts index 24ae37d2..8d977e48 100644 --- a/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.ts +++ b/frontend/pages/(task-workspace)/codex-status/runtime-update-lifecycle.ts @@ -1,5 +1,5 @@ -import type { CodexStatus, CodexUpdateOutcome } from "#app/api.ts"; -import type { CodexRuntimeRequestState } from "./model.ts"; +import type { CodexStatus, CodexUpdateOutcome } from "#app/api.js"; +import type { CodexRuntimeRequestState } from "./model.js"; const IDLE_UPDATE_STATE = Object.freeze({ state: "idle", diff --git a/frontend/pages/(task-workspace)/components/navigation.test.js b/frontend/pages/(task-workspace)/components/navigation.test.js index 4e671f53..40ea59e7 100644 --- a/frontend/pages/(task-workspace)/components/navigation.test.js +++ b/frontend/pages/(task-workspace)/components/navigation.test.js @@ -7,9 +7,9 @@ import { import { templateHtml } from "../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./navigation.ts"); +await import("./navigation.js"); const Navigation = registry.element("caffold-task-workspace-navigation"); -const { ActionHints } = await import("#app/action-hints.ts"); +const { ActionHints } = await import("#app/action-hints.js"); after(() => registry.restore()); test("marks exactly the chosen workspace mode as current", () => { diff --git a/frontend/pages/(task-workspace)/components/navigation.ts b/frontend/pages/(task-workspace)/components/navigation.ts index 4d2100a8..43c92f33 100644 --- a/frontend/pages/(task-workspace)/components/navigation.ts +++ b/frontend/pages/(task-workspace)/components/navigation.ts @@ -3,9 +3,9 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import type { CaffoldUpdateSnapshot } from "../settings/about/page.ts"; +} from "#app/action-hints.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import type { CaffoldUpdateSnapshot } from "../settings/about/page.js"; type WorkspaceMode = "tasks" | "notes" | "settings"; diff --git a/frontend/pages/(task-workspace)/components/update-task-dialog.test.js b/frontend/pages/(task-workspace)/components/update-task-dialog.test.js index c9d9ee97..ff00ff52 100644 --- a/frontend/pages/(task-workspace)/components/update-task-dialog.test.js +++ b/frontend/pages/(task-workspace)/components/update-task-dialog.test.js @@ -7,12 +7,12 @@ import { import { templateHtml } from "../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -const { updateTaskFrom, updateTaskPrompt } = await import("./update-task-dialog.ts"); +const { updateTaskFrom, updateTaskPrompt } = await import("./update-task-dialog.js"); const Dialog = registry.element("caffold-update-task-dialog"); const dialog = Dialog.prototype; -const { ActionHints } = await import("#app/action-hints.ts"); -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); -const { ScrollSurfaces } = await import("#app/scroll-scope.ts"); +const { ActionHints } = await import("#app/action-hints.js"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); +const { ScrollSurfaces } = await import("#app/scroll-scope.js"); after(() => registry.restore()); const COMMAND = diff --git a/frontend/pages/(task-workspace)/components/update-task-dialog.ts b/frontend/pages/(task-workspace)/components/update-task-dialog.ts index dca52b1c..cba1808c 100644 --- a/frontend/pages/(task-workspace)/components/update-task-dialog.ts +++ b/frontend/pages/(task-workspace)/components/update-task-dialog.ts @@ -4,21 +4,21 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { KeyboardContexts, mergeKeyboardNavigationContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import type { TaskStartIntent } from "#tasks/components/task-create.ts"; -import "#tasks/components/task-turn-options.ts"; -import type { CaffoldUpdateStatus } from "#app/api.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import type { TaskStartIntent } from "#tasks/components/task-create.js"; +import "#tasks/components/task-turn-options.js"; +import type { CaffoldUpdateStatus } from "#app/api.js"; import type { CaffoldTaskTurnOptions, TurnOptionsSelection, -} from "#tasks/components/task-turn-options.ts"; +} from "#tasks/components/task-turn-options.js"; const RESTART_NOTICE = "Running Tasks keep going while Caffold restarts. Claude sessions stop if it takes more than 10 minutes. Open terminals close."; diff --git a/frontend/pages/(task-workspace)/layout.test.js b/frontend/pages/(task-workspace)/layout.test.js index 9d7c77f4..2f94be87 100644 --- a/frontend/pages/(task-workspace)/layout.test.js +++ b/frontend/pages/(task-workspace)/layout.test.js @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { registerHooks } from "node:module"; import test, { after } from "node:test"; -import { parseRoute } from "../../navigation-routes.ts"; +import { parseRoute } from "../../navigation-routes.js"; import { installCustomElementUnitRegistry, } from "../../tests/support/custom-element-unit.js"; @@ -19,7 +19,7 @@ const buildInfoHook = registerHooks({ }, }); const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const workspace = registry.element("caffold-task-workspace").prototype; after(() => { registry.restore(); diff --git a/frontend/pages/(task-workspace)/layout.ts b/frontend/pages/(task-workspace)/layout.ts index c5874b71..fd792e1a 100644 --- a/frontend/pages/(task-workspace)/layout.ts +++ b/frontend/pages/(task-workspace)/layout.ts @@ -6,21 +6,21 @@ import { hasActionHintLayoutBox, mergeActionHintScopes, withActionHintOcclusionRoots, -} from "#app/action-hints.ts"; -import { type Health, restartClaudeRuntime } from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; -import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.ts"; -import { routeDomain, routeTab, routeTarget } from "#app/navigation-routes.ts"; +} from "#app/action-hints.js"; +import { type Health, restartClaudeRuntime } from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; +import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.js"; +import { routeDomain, routeTab, routeTarget } from "#app/navigation-routes.js"; import { emptyScrollSurfaceScope, hasScrollLayoutBox, mergeScrollSurfaceScopes, type ScrollSurfaceScope, -} from "#app/scroll-scope.ts"; -import { DrawnRefs, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/pane-resizer.ts"; -import type { CaffoldPaneResizer } from "#components/pane-resizer.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRefs, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/pane-resizer.js"; +import type { CaffoldPaneResizer } from "#components/pane-resizer.js"; import { CODEX_RESET_CREDIT_REQUEST_EVENT, CODEX_RUNTIME_RESTART_REQUEST_EVENT, @@ -33,57 +33,57 @@ import { codexRuntimeUpdateAvailable, createCodexStatusLifecycle, resetCreditExpiry, -} from "./codex-status.ts"; -import "./codex-status/components/reset-credit-dialog.ts"; -import type { CaffoldCodexResetCreditDialog } from "./codex-status/components/reset-credit-dialog.ts"; -import "./codex-status/components/runtime-restart-dialog.ts"; -import type { CaffoldCodexRuntimeRestartDialog } from "./codex-status/components/runtime-restart-dialog.ts"; -import "./codex-status/components/runtime-update-dialog.ts"; -import type { CaffoldCodexRuntimeUpdateDialog } from "./codex-status/components/runtime-update-dialog.ts"; -import "./components/navigation.ts"; -import type { CaffoldTaskWorkspaceNavigation } from "./components/navigation.ts"; -import "./components/update-task-dialog.ts"; -import type { CaffoldUpdateTaskDialog } from "./components/update-task-dialog.ts"; +} from "./codex-status.js"; +import "./codex-status/components/reset-credit-dialog.js"; +import type { CaffoldCodexResetCreditDialog } from "./codex-status/components/reset-credit-dialog.js"; +import "./codex-status/components/runtime-restart-dialog.js"; +import type { CaffoldCodexRuntimeRestartDialog } from "./codex-status/components/runtime-restart-dialog.js"; +import "./codex-status/components/runtime-update-dialog.js"; +import type { CaffoldCodexRuntimeUpdateDialog } from "./codex-status/components/runtime-update-dialog.js"; +import "./components/navigation.js"; +import type { CaffoldTaskWorkspaceNavigation } from "./components/navigation.js"; +import "./components/update-task-dialog.js"; +import type { CaffoldUpdateTaskDialog } from "./components/update-task-dialog.js"; import { LIVE_CONNECTION_REPORT_EVENT, WorkspaceLiveUpdates, -} from "./live-updates.ts"; -import "./notes/components/navigator.ts"; -import type { CaffoldNotesNavigator } from "./notes/components/navigator.ts"; -import "./notes/layout.ts"; -import type { CaffoldNotesWorkspace } from "./notes/layout.ts"; +} from "./live-updates.js"; +import "./notes/components/navigator.js"; +import type { CaffoldNotesNavigator } from "./notes/components/navigator.js"; +import "./notes/layout.js"; +import type { CaffoldNotesWorkspace } from "./notes/layout.js"; import { CAFFOLD_UPDATE_TASK_REQUEST_EVENT, type CaffoldUpdateSnapshot, -} from "./settings/about/page.ts"; -import "./settings/claude/components/runtime-restart-dialog.ts"; -import type { CaffoldClaudeRuntimeRestartDialog } from "./settings/claude/components/runtime-restart-dialog.ts"; -import { CLAUDE_RUNTIME_RESTART_REQUEST_EVENT } from "./settings/claude/page.ts"; -import "./settings/components/navigator.ts"; -import type { CaffoldSettingsNavigator } from "./settings/components/navigator.ts"; -import "./settings/layout.ts"; -import type { CaffoldSettingsWorkspace } from "./settings/layout.ts"; +} from "./settings/about/page.js"; +import "./settings/claude/components/runtime-restart-dialog.js"; +import type { CaffoldClaudeRuntimeRestartDialog } from "./settings/claude/components/runtime-restart-dialog.js"; +import { CLAUDE_RUNTIME_RESTART_REQUEST_EVENT } from "./settings/claude/page.js"; +import "./settings/components/navigator.js"; +import type { CaffoldSettingsNavigator } from "./settings/components/navigator.js"; +import "./settings/layout.js"; +import type { CaffoldSettingsWorkspace } from "./settings/layout.js"; import { createTaskStoreStatusLifecycle, TASK_STORE_RETRY_REQUEST_EVENT, type TaskStoreStatusSnapshot, -} from "./task-store-status.ts"; -import "./tasks/components/archived-delete-dialog.ts"; -import type { CaffoldTaskArchivedDeleteDialog } from "./tasks/components/archived-delete-dialog.ts"; -import type { ArchivedTaskListIntent } from "./tasks/components/archived-task-list.ts"; -import "./tasks/components/navigator.ts"; +} from "./task-store-status.js"; +import "./tasks/components/archived-delete-dialog.js"; +import type { CaffoldTaskArchivedDeleteDialog } from "./tasks/components/archived-delete-dialog.js"; +import type { ArchivedTaskListIntent } from "./tasks/components/archived-task-list.js"; +import "./tasks/components/navigator.js"; import type { CaffoldTaskNavigator, TaskNavigatorIntent, -} from "./tasks/components/navigator.ts"; -import type { TaskStartIntent } from "./tasks/components/task-create.ts"; -import "./tasks/components/task-switcher-dialog.ts"; +} from "./tasks/components/navigator.js"; +import type { TaskStartIntent } from "./tasks/components/task-create.js"; +import "./tasks/components/task-switcher-dialog.js"; import type { CaffoldTaskSwitcherDialog, TaskSwitcherSelectDetail, -} from "./tasks/components/task-switcher-dialog.ts"; -import "./tasks/layout.ts"; -import type { CaffoldTasksPage } from "./tasks/layout.ts"; +} from "./tasks/components/task-switcher-dialog.js"; +import "./tasks/layout.js"; +import type { CaffoldTasksPage } from "./tasks/layout.js"; const WORKSPACE_MASTER_DETAIL_MEDIA_QUERY = "(min-width: 900px)"; const ROUTE_CONTROL_ICON = "task-workspace-route-control-icon"; diff --git a/frontend/pages/(task-workspace)/live-updates.test.js b/frontend/pages/(task-workspace)/live-updates.test.js index 15e0dadc..7a019a8f 100644 --- a/frontend/pages/(task-workspace)/live-updates.test.js +++ b/frontend/pages/(task-workspace)/live-updates.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test, { afterEach } from "node:test"; -import { WorkspaceLiveUpdates } from "./live-updates.ts"; +import { WorkspaceLiveUpdates } from "./live-updates.js"; const originalGlobals = { document: globalThis.document, diff --git a/frontend/pages/(task-workspace)/live-updates.ts b/frontend/pages/(task-workspace)/live-updates.ts index 1cab1fde..a19cd0f8 100644 --- a/frontend/pages/(task-workspace)/live-updates.ts +++ b/frontend/pages/(task-workspace)/live-updates.ts @@ -1,5 +1,5 @@ -import { liveUpdatesUrl, updateLiveSubscriptions } from "../../api.ts"; -import { reportOriginReachable } from "../../origin-reachability.ts"; +import { liveUpdatesUrl, updateLiveSubscriptions } from "../../api.js"; +import { reportOriginReachable } from "../../origin-reachability.js"; import { LIVE_CONNECTION_EFFECT, LIVE_CONNECTION_EVENT, @@ -8,7 +8,7 @@ import { type LiveConnectionEvent, type LiveConnectionNode, transitionLiveConnection, -} from "./live-updates/lifecycle.ts"; +} from "./live-updates/lifecycle.js"; // What the workspace raises for each connection report, for the App Shell's // foreground recovery diagnostics. diff --git a/frontend/pages/(task-workspace)/live-updates/lifecycle.test.js b/frontend/pages/(task-workspace)/live-updates/lifecycle.test.js index 704313e8..f1c20419 100644 --- a/frontend/pages/(task-workspace)/live-updates/lifecycle.test.js +++ b/frontend/pages/(task-workspace)/live-updates/lifecycle.test.js @@ -7,7 +7,7 @@ import { LIVE_CONNECTION_EVENT, LIVE_CONNECTION_NODE, transitionLiveConnection, -} from "./lifecycle.ts"; +} from "./lifecycle.js"; const EXPECTED_EFFECTS = Object.freeze({ [LIVE_CONNECTION_EVENT.CONNECT]: [LIVE_CONNECTION_EFFECT.OPEN], diff --git a/frontend/pages/(task-workspace)/notes/components/document.test.js b/frontend/pages/(task-workspace)/notes/components/document.test.js index eaf234bb..ca3098ef 100644 --- a/frontend/pages/(task-workspace)/notes/components/document.test.js +++ b/frontend/pages/(task-workspace)/notes/components/document.test.js @@ -3,7 +3,7 @@ import test, { after } from "node:test"; import { installCustomElementUnitRegistry } from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./document.ts"); +await import("./document.js"); const documentView = registry.element("caffold-note-document").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/notes/components/document.ts b/frontend/pages/(task-workspace)/notes/components/document.ts index 0af54c19..4dce3e30 100644 --- a/frontend/pages/(task-workspace)/notes/components/document.ts +++ b/frontend/pages/(task-workspace)/notes/components/document.ts @@ -1,25 +1,25 @@ import { html, LitElement, type PropertyValues } from "lit"; -import { hasActionHintLayoutBox } from "#app/action-hint-scope.ts"; +import { hasActionHintLayoutBox } from "#app/action-hint-scope.js"; import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { Note } from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; +} from "#app/action-hints.js"; +import type { Note } from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import "#components/markdown-preview.ts"; -import type { CaffoldMarkdownPreview } from "#components/markdown-preview.ts"; -import "./info.ts"; -import type { CaffoldNotesInfo } from "./info.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import "#components/markdown-preview.js"; +import type { CaffoldMarkdownPreview } from "#components/markdown-preview.js"; +import "./info.js"; +import type { CaffoldNotesInfo } from "./info.js"; export const NOTE_DOCUMENT_INTENT_EVENT = "caffold:note-document-intent"; diff --git a/frontend/pages/(task-workspace)/notes/components/info.test.js b/frontend/pages/(task-workspace)/notes/components/info.test.js index aa02676d..d6a41524 100644 --- a/frontend/pages/(task-workspace)/notes/components/info.test.js +++ b/frontend/pages/(task-workspace)/notes/components/info.test.js @@ -6,7 +6,7 @@ import { } from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./info.ts"); +await import("./info.js"); const info = registry.element("caffold-notes-info").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/notes/components/info.ts b/frontend/pages/(task-workspace)/notes/components/info.ts index 72cb0fc0..a9f3c8ab 100644 --- a/frontend/pages/(task-workspace)/notes/components/info.ts +++ b/frontend/pages/(task-workspace)/notes/components/info.ts @@ -4,21 +4,21 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { Note, NoteTask } from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; +} from "#app/action-hints.js"; +import type { Note, NoteTask } from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { formatModified } from "#components/dom.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "./info/components/copy-markdown.ts"; -import type { CaffoldNotesInfoCopyMarkdown } from "./info/components/copy-markdown.ts"; -import "./info/components/copy-path.ts"; -import type { CaffoldNotesInfoCopyPath } from "./info/components/copy-path.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { formatModified } from "#components/dom.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "./info/components/copy-markdown.js"; +import type { CaffoldNotesInfoCopyMarkdown } from "./info/components/copy-markdown.js"; +import "./info/components/copy-path.js"; +import type { CaffoldNotesInfoCopyPath } from "./info/components/copy-path.js"; let notesInfoInstanceId = 0; diff --git a/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.test.js b/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.test.js index 530f5b4d..6ffbd035 100644 --- a/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.test.js +++ b/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./copy-markdown.ts"); +await import("./copy-markdown.js"); const copyMarkdown = registry.element("caffold-notes-info-copy-markdown").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.ts b/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.ts index f59dff89..55ea0581 100644 --- a/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.ts +++ b/frontend/pages/(task-workspace)/notes/components/info/components/copy-markdown.ts @@ -3,8 +3,8 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import type { Note } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { Note } from "#app/api.js"; const COPY_FEEDBACK_DURATION_MS = 1_800; diff --git a/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.test.js b/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.test.js index ee1e2905..028356ee 100644 --- a/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.test.js +++ b/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./copy-path.ts"); +await import("./copy-path.js"); const copyPath = registry.element("caffold-notes-info-copy-path").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.ts b/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.ts index 975d2d0e..4a4c1444 100644 --- a/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.ts +++ b/frontend/pages/(task-workspace)/notes/components/info/components/copy-path.ts @@ -3,8 +3,8 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import type { Note } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { Note } from "#app/api.js"; const COPY_FEEDBACK_DURATION_MS = 1_800; diff --git a/frontend/pages/(task-workspace)/notes/components/navigator.test.js b/frontend/pages/(task-workspace)/notes/components/navigator.test.js index a2e9b10e..55823261 100644 --- a/frontend/pages/(task-workspace)/notes/components/navigator.test.js +++ b/frontend/pages/(task-workspace)/notes/components/navigator.test.js @@ -6,7 +6,7 @@ import { } from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./navigator.ts"); +await import("./navigator.js"); const navigator = registry.element("caffold-notes-navigator").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/notes/components/navigator.ts b/frontend/pages/(task-workspace)/notes/components/navigator.ts index f9c0602e..1dcd401d 100644 --- a/frontend/pages/(task-workspace)/notes/components/navigator.ts +++ b/frontend/pages/(task-workspace)/notes/components/navigator.ts @@ -4,29 +4,29 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import { compactIconButton } from "#app/component-styles.ts"; +} from "#app/action-hints.js"; +import { compactIconButton } from "#app/component-styles.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import type { CaffoldFileTree, FileTreeSelectDetail, -} from "#components/file-tree.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import "../../components/workspace-brand.ts"; -import type { NotesPicker } from "../layout/selection.ts"; +} from "#components/file-tree.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import "../../components/workspace-brand.js"; +import type { NotesPicker } from "../layout/selection.js"; import { directoryIdFromKey, type NotesLevels, noteIdOf, noteKey, notesTreeNodes, -} from "../tree.ts"; +} from "../tree.js"; export const NOTES_NAVIGATOR_INTENT_EVENT = "caffold:notes-navigator-intent"; diff --git a/frontend/pages/(task-workspace)/notes/layout.test.js b/frontend/pages/(task-workspace)/notes/layout.test.js index 33f475e0..0d3d0163 100644 --- a/frontend/pages/(task-workspace)/notes/layout.test.js +++ b/frontend/pages/(task-workspace)/notes/layout.test.js @@ -8,7 +8,7 @@ import { const apiHook = registerHooks({ resolve(specifier, context, nextResolve) { - if (specifier === "#app/api.ts" && context.parentURL === new URL("./layout.ts", import.meta.url).href) { + if (specifier === "#app/api.js" && context.parentURL === new URL("./layout.js", import.meta.url).href) { return { shortCircuit: true, url: "data:text/javascript,export const getNotes=(...a)=>globalThis.notesApi.getNotes(...a);export const getNote=(...a)=>globalThis.notesApi.getNote(...a);", @@ -18,8 +18,8 @@ const apiHook = registerHooks({ }, }); const registry = installCustomElementUnitRegistry(); -const { NotesSelection } = await import("./layout/selection.ts"); -await import("./layout.ts"); +const { NotesSelection } = await import("./layout/selection.js"); +await import("./layout.js"); const workspace = registry.element("caffold-notes-workspace").prototype; const timers = new Map(); let nextTimerId = 0; diff --git a/frontend/pages/(task-workspace)/notes/layout.ts b/frontend/pages/(task-workspace)/notes/layout.ts index 5d037bb1..671ff7bd 100644 --- a/frontend/pages/(task-workspace)/notes/layout.ts +++ b/frontend/pages/(task-workspace)/notes/layout.ts @@ -2,33 +2,33 @@ import { html, LitElement } from "lit"; import { emptyActionHintScope, mergeActionHintScopes, -} from "#app/action-hint-scope.ts"; -import { getNote, getNotes } from "#app/api.ts"; +} from "#app/action-hint-scope.js"; +import { getNote, getNotes } from "#app/api.js"; import { emptyScrollSurfaceScope, mergeScrollSurfaceScopes, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "./components/document.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "./components/document.js"; import type { CaffoldNoteDocument, NoteDocumentIntent, NoteDocumentSnapshot, NoteRead, NoteSide, -} from "./components/document.ts"; +} from "./components/document.js"; import { type CaffoldNotesNavigator, NOTES_NAVIGATOR_INTENT_EVENT, type NotesNavigatorIntent, type NotesNavigatorSnapshot, -} from "./components/navigator.ts"; -import { NotesSelection } from "./layout/selection.ts"; +} from "./components/navigator.js"; +import { NotesSelection } from "./layout/selection.js"; import { type NotesLevelRead, type NotesLevels, noteDirectoryKey, -} from "./tree.ts"; +} from "./tree.js"; // A level with nothing to show yet is marked loading only once its read // outlasts this wait, so a quick read never flashes a loading row. diff --git a/frontend/pages/(task-workspace)/notes/layout/selection.test.js b/frontend/pages/(task-workspace)/notes/layout/selection.test.js index 773115df..0ec6b538 100644 --- a/frontend/pages/(task-workspace)/notes/layout/selection.test.js +++ b/frontend/pages/(task-workspace)/notes/layout/selection.test.js @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { NotesSelection } from "./selection.ts"; +import { NotesSelection } from "./selection.js"; test("only a readable primary document can start a companion picker; cancel restores single", () => { const selection = new NotesSelection(); diff --git a/frontend/pages/(task-workspace)/notes/tree.test.js b/frontend/pages/(task-workspace)/notes/tree.test.js index 4386460b..a7f1027b 100644 --- a/frontend/pages/(task-workspace)/notes/tree.test.js +++ b/frontend/pages/(task-workspace)/notes/tree.test.js @@ -11,7 +11,7 @@ const { noteDirectoryKey, noteKey, notesTreeNodes, -} = await import("./tree.ts"); +} = await import("./tree.js"); after(() => registry.restore()); function ready(directories, notes = []) { diff --git a/frontend/pages/(task-workspace)/notes/tree.ts b/frontend/pages/(task-workspace)/notes/tree.ts index afad9128..78222024 100644 --- a/frontend/pages/(task-workspace)/notes/tree.ts +++ b/frontend/pages/(task-workspace)/notes/tree.ts @@ -1,4 +1,4 @@ -import type { NoteDirectorySummary, NotesLevel } from "#app/api.ts"; +import type { NoteDirectorySummary, NotesLevel } from "#app/api.js"; import { errorFileTreeChildren, type FileTreeChildren, @@ -6,7 +6,7 @@ import { loadingFileTreeChildren, readyFileTreeChildren, unloadedFileTreeChildren, -} from "#components/file-tree.ts"; +} from "#components/file-tree.js"; // The latest read of one level: the `listing` that arrived last, and the // read's `state` and `message`. diff --git a/frontend/pages/(task-workspace)/settings/about/page.test.js b/frontend/pages/(task-workspace)/settings/about/page.test.js index cf2a837d..ab69ca8c 100644 --- a/frontend/pages/(task-workspace)/settings/about/page.test.js +++ b/frontend/pages/(task-workspace)/settings/about/page.test.js @@ -18,13 +18,13 @@ const buildInfoHook = registerHooks({ }, }); const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); after(() => { registry.restore(); buildInfoHook.deregister(); }); -const { caffoldUpdatesView, lastUpdateValue } = await import("./page.ts"); +const { caffoldUpdatesView, lastUpdateValue } = await import("./page.js"); function answered(status) { return { checking: false, status, error: null }; diff --git a/frontend/pages/(task-workspace)/settings/about/page.ts b/frontend/pages/(task-workspace)/settings/about/page.ts index 18a8a9e3..fe437370 100644 --- a/frontend/pages/(task-workspace)/settings/about/page.ts +++ b/frontend/pages/(task-workspace)/settings/about/page.ts @@ -4,20 +4,20 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type CaffoldUpdateAttempt, type CaffoldUpdateStatus, type CodexMcpDiagnostics, getCodexMcpDiagnostics, type Health, -} from "#app/api.ts"; +} from "#app/api.js"; import { BUILD_INFO } from "#app/build-info.js"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/loading-text.ts"; -import "../components/detail-list.ts"; -import type { SettingsDetailRow } from "../components/detail-list.ts"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/loading-text.js"; +import "../components/detail-list.js"; +import type { SettingsDetailRow } from "../components/detail-list.js"; /** Asks the workspace to open the update Task dialog. */ export const CAFFOLD_UPDATE_TASK_REQUEST_EVENT = diff --git a/frontend/pages/(task-workspace)/settings/appearance/page.ts b/frontend/pages/(task-workspace)/settings/appearance/page.ts index 1d7dbbf8..b56e1b31 100644 --- a/frontend/pages/(task-workspace)/settings/appearance/page.ts +++ b/frontend/pages/(task-workspace)/settings/appearance/page.ts @@ -3,8 +3,8 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; +} from "#app/action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; import { APPEARANCE_RANGE_SETTINGS, type AppearanceRangeName, @@ -22,9 +22,9 @@ import { THEME_MODES, type TypefacePreset, UI_TYPEFACE_PRESETS, -} from "#app/settings.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/settings.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; type TypefaceSetting = "uiTypefacePreset" | "codeTypefacePreset"; diff --git a/frontend/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.ts b/frontend/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.ts index 6b55fe2c..3ab3b3f2 100644 --- a/frontend/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.ts +++ b/frontend/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.ts @@ -1,8 +1,8 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; export const CLAUDE_RUNTIME_RESTART_CONFIRMED_EVENT = "caffold:claude-runtime-restart-confirmed"; diff --git a/frontend/pages/(task-workspace)/settings/claude/display.test.js b/frontend/pages/(task-workspace)/settings/claude/display.test.js index f0ded093..56465c7e 100644 --- a/frontend/pages/(task-workspace)/settings/claude/display.test.js +++ b/frontend/pages/(task-workspace)/settings/claude/display.test.js @@ -5,7 +5,7 @@ import { idleTimeoutValue, usageWindowLabel, usageWindowValue, -} from "./display.ts"; +} from "./display.js"; test("usage windows read in this page's words, and unknown kinds in their own", () => { assert.equal(usageWindowLabel({ kind: "session" }), "Session"); diff --git a/frontend/pages/(task-workspace)/settings/claude/display.ts b/frontend/pages/(task-workspace)/settings/claude/display.ts index 096e8496..d0f3d96b 100644 --- a/frontend/pages/(task-workspace)/settings/claude/display.ts +++ b/frontend/pages/(task-workspace)/settings/claude/display.ts @@ -1,4 +1,4 @@ -import type { ClaudeStatus } from "#app/api.ts"; +import type { ClaudeStatus } from "#app/api.js"; type UsageWindow = NonNullable["windows"][number]; diff --git a/frontend/pages/(task-workspace)/settings/claude/page.ts b/frontend/pages/(task-workspace)/settings/claude/page.ts index a52a973b..ff439094 100644 --- a/frontend/pages/(task-workspace)/settings/claude/page.ts +++ b/frontend/pages/(task-workspace)/settings/claude/page.ts @@ -4,19 +4,19 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import { type ClaudeStatus, getClaudeStatus } from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "../components/detail-list.ts"; -import type { SettingsDetailRow } from "../components/detail-list.ts"; -import "../components/refresh-button.ts"; -import type { CaffoldSettingsRefreshButton } from "../components/refresh-button.ts"; +} from "#app/action-hints.js"; +import { type ClaudeStatus, getClaudeStatus } from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "../components/detail-list.js"; +import type { SettingsDetailRow } from "../components/detail-list.js"; +import "../components/refresh-button.js"; +import type { CaffoldSettingsRefreshButton } from "../components/refresh-button.js"; import { idleTimeoutValue, usageWindowLabel, usageWindowValue, -} from "./display.ts"; +} from "./display.js"; export const CLAUDE_RUNTIME_RESTART_REQUEST_EVENT = "caffold:request-claude-runtime-restart"; diff --git a/frontend/pages/(task-workspace)/settings/codex/page.ts b/frontend/pages/(task-workspace)/settings/codex/page.ts index d18d1854..ef59f540 100644 --- a/frontend/pages/(task-workspace)/settings/codex/page.ts +++ b/frontend/pages/(task-workspace)/settings/codex/page.ts @@ -5,7 +5,7 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type CodexRateWindow, type CodexReadiness, @@ -13,10 +13,10 @@ import { type CodexStatus, type CodexUpdates, getCodexUpdates, -} from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/loading-text.ts"; +} from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/loading-text.js"; import { CODEX_RESET_CREDIT_REQUEST_EVENT, CODEX_RUNTIME_RESTART_REQUEST_EVENT, @@ -35,11 +35,11 @@ import { formatResetCredits, formatUsedPercent, resetCreditExpiry, -} from "../../codex-status.ts"; -import "../components/detail-list.ts"; -import type { SettingsDetailRow } from "../components/detail-list.ts"; -import "../components/refresh-button.ts"; -import type { CaffoldSettingsRefreshButton } from "../components/refresh-button.ts"; +} from "../../codex-status.js"; +import "../components/detail-list.js"; +import type { SettingsDetailRow } from "../components/detail-list.js"; +import "../components/refresh-button.js"; +import type { CaffoldSettingsRefreshButton } from "../components/refresh-button.js"; const CODEX_INSTALL_COMMAND = "curl -fsSL https://chatgpt.com/codex/install.sh | sh"; diff --git a/frontend/pages/(task-workspace)/settings/components/navigator.test.js b/frontend/pages/(task-workspace)/settings/components/navigator.test.js index 1b464108..3b5e0e0e 100644 --- a/frontend/pages/(task-workspace)/settings/components/navigator.test.js +++ b/frontend/pages/(task-workspace)/settings/components/navigator.test.js @@ -6,7 +6,7 @@ import { } from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./navigator.ts"); +await import("./navigator.js"); const navigator = registry.element("caffold-settings-navigator").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/settings/components/navigator.ts b/frontend/pages/(task-workspace)/settings/components/navigator.ts index 15b4ce92..62d38c02 100644 --- a/frontend/pages/(task-workspace)/settings/components/navigator.ts +++ b/frontend/pages/(task-workspace)/settings/components/navigator.ts @@ -3,19 +3,19 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type ScrollSurfacePull, ScrollSurfaces, scrollBackToTop, -} from "#app/scroll-scope.ts"; -import "../../components/workspace-brand.ts"; -import "./navigator/components/item.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; +} from "#app/scroll-scope.js"; +import "../../components/workspace-brand.js"; +import "./navigator/components/item.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; import type { CaffoldSettingsNavigatorItem, SettingsNavigatorEntry, -} from "./navigator/components/item.ts"; +} from "./navigator/components/item.js"; // Each brand mark is published in a single color so it can be tinted, and the // theme tints it through --brand-monochrome-filter. diff --git a/frontend/pages/(task-workspace)/settings/components/navigator/components/item.test.js b/frontend/pages/(task-workspace)/settings/components/navigator/components/item.test.js index 62740806..88d684d8 100644 --- a/frontend/pages/(task-workspace)/settings/components/navigator/components/item.test.js +++ b/frontend/pages/(task-workspace)/settings/components/navigator/components/item.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./item.ts"); +await import("./item.js"); const item = registry.element("caffold-settings-navigator-item").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/settings/components/navigator/components/item.ts b/frontend/pages/(task-workspace)/settings/components/navigator/components/item.ts index 7acf223f..b610129a 100644 --- a/frontend/pages/(task-workspace)/settings/components/navigator/components/item.ts +++ b/frontend/pages/(task-workspace)/settings/components/navigator/components/item.ts @@ -3,8 +3,8 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/action-hints.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; // The section an entry opens, its label, and either an icon or a brand mark. export interface SettingsNavigatorEntry { diff --git a/frontend/pages/(task-workspace)/settings/components/refresh-button.ts b/frontend/pages/(task-workspace)/settings/components/refresh-button.ts index 08a79fe1..18fc24b3 100644 --- a/frontend/pages/(task-workspace)/settings/components/refresh-button.ts +++ b/frontend/pages/(task-workspace)/settings/components/refresh-button.ts @@ -3,9 +3,9 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { busySpin } from "#app/component-styles.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/action-hints.js"; +import { busySpin } from "#app/component-styles.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; export const SETTINGS_REFRESH_INTENT_EVENT = "caffold:settings-refresh-intent"; diff --git a/frontend/pages/(task-workspace)/settings/files/page.ts b/frontend/pages/(task-workspace)/settings/files/page.ts index e97847d1..0685d166 100644 --- a/frontend/pages/(task-workspace)/settings/files/page.ts +++ b/frontend/pages/(task-workspace)/settings/files/page.ts @@ -3,15 +3,15 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; +} from "#app/action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; import { FILE_SORT_MODES, getSettings, type Settings, setFileSortMode, -} from "#app/settings.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; +} from "#app/settings.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; const SORT_OPTIONS = Object.freeze([ Object.freeze({ diff --git a/frontend/pages/(task-workspace)/settings/grok/display.test.js b/frontend/pages/(task-workspace)/settings/grok/display.test.js index 4aecb107..ba7cf7ce 100644 --- a/frontend/pages/(task-workspace)/settings/grok/display.test.js +++ b/frontend/pages/(task-workspace)/settings/grok/display.test.js @@ -6,7 +6,7 @@ import { prepaidValue, usagePeriodLabel, usagePeriodValue, -} from "./display.ts"; +} from "./display.js"; test("usage periods read in this page's words, and unknown types in their own", () => { assert.equal(usagePeriodLabel({ type: "USAGE_PERIOD_TYPE_WEEKLY" }), "Weekly"); diff --git a/frontend/pages/(task-workspace)/settings/grok/display.ts b/frontend/pages/(task-workspace)/settings/grok/display.ts index dcee711e..35f0399f 100644 --- a/frontend/pages/(task-workspace)/settings/grok/display.ts +++ b/frontend/pages/(task-workspace)/settings/grok/display.ts @@ -1,4 +1,4 @@ -import type { GrokStatus } from "#app/api.ts"; +import type { GrokStatus } from "#app/api.js"; type Usage = NonNullable; diff --git a/frontend/pages/(task-workspace)/settings/grok/page.test.js b/frontend/pages/(task-workspace)/settings/grok/page.test.js index 486584f3..102b3a37 100644 --- a/frontend/pages/(task-workspace)/settings/grok/page.test.js +++ b/frontend/pages/(task-workspace)/settings/grok/page.test.js @@ -6,7 +6,7 @@ import { } from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); const grok = registry.element("caffold-settings-grok-page").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/settings/grok/page.ts b/frontend/pages/(task-workspace)/settings/grok/page.ts index 751c52aa..4cba04ee 100644 --- a/frontend/pages/(task-workspace)/settings/grok/page.ts +++ b/frontend/pages/(task-workspace)/settings/grok/page.ts @@ -4,20 +4,20 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import { type GrokStatus, getGrokStatus } from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "../components/detail-list.ts"; -import type { SettingsDetailRow } from "../components/detail-list.ts"; -import "../components/refresh-button.ts"; -import type { CaffoldSettingsRefreshButton } from "../components/refresh-button.ts"; +} from "#app/action-hints.js"; +import { type GrokStatus, getGrokStatus } from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "../components/detail-list.js"; +import type { SettingsDetailRow } from "../components/detail-list.js"; +import "../components/refresh-button.js"; +import type { CaffoldSettingsRefreshButton } from "../components/refresh-button.js"; import { onDemandValue, prepaidValue, usagePeriodLabel, usagePeriodValue, -} from "./display.ts"; +} from "./display.js"; const GROK_SERVICE_STATUS_URL = "https://status.x.ai"; diff --git a/frontend/pages/(task-workspace)/settings/jev/lifecycle.test.js b/frontend/pages/(task-workspace)/settings/jev/lifecycle.test.js index d84a2eff..130f9c0e 100644 --- a/frontend/pages/(task-workspace)/settings/jev/lifecycle.test.js +++ b/frontend/pages/(task-workspace)/settings/jev/lifecycle.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { JevSettingsLifecycle } from "./lifecycle.ts"; +import { JevSettingsLifecycle } from "./lifecycle.js"; function settings(overrides = {}) { return { diff --git a/frontend/pages/(task-workspace)/settings/jev/lifecycle.ts b/frontend/pages/(task-workspace)/settings/jev/lifecycle.ts index 7fb1687d..b486fdd5 100644 --- a/frontend/pages/(task-workspace)/settings/jev/lifecycle.ts +++ b/frontend/pages/(task-workspace)/settings/jev/lifecycle.ts @@ -3,7 +3,7 @@ import { removeJevKey, saveJevCriteria, storeJevKey, -} from "#app/api.ts"; +} from "#app/api.js"; export interface JevSettings { model: string; diff --git a/frontend/pages/(task-workspace)/settings/jev/page.ts b/frontend/pages/(task-workspace)/settings/jev/page.ts index b39f6c18..a1a5626b 100644 --- a/frontend/pages/(task-workspace)/settings/jev/page.ts +++ b/frontend/pages/(task-workspace)/settings/jev/page.ts @@ -3,15 +3,15 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "../components/detail-list.ts"; +} from "#app/action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "../components/detail-list.js"; import { type JevSettings, JevSettingsLifecycle, type JevSettingsSnapshot, -} from "./lifecycle.ts"; +} from "./lifecycle.js"; export class CaffoldSettingsJevPage extends LitElement { static properties = { diff --git a/frontend/pages/(task-workspace)/settings/keyboard/page.ts b/frontend/pages/(task-workspace)/settings/keyboard/page.ts index a7f01241..81d81289 100644 --- a/frontend/pages/(task-workspace)/settings/keyboard/page.ts +++ b/frontend/pages/(task-workspace)/settings/keyboard/page.ts @@ -3,15 +3,15 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#app/keyboard-navigation/components/shortcut-list.ts"; +} from "#app/action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#app/keyboard-navigation/components/shortcut-list.js"; import { getSettings, type Settings, setActionHintsEnabled, -} from "#app/settings.ts"; +} from "#app/settings.js"; export class CaffoldSettingsKeyboardPage extends LitElement { static properties = { diff --git a/frontend/pages/(task-workspace)/settings/layout.test.js b/frontend/pages/(task-workspace)/settings/layout.test.js index ce781c0e..dc0158dd 100644 --- a/frontend/pages/(task-workspace)/settings/layout.test.js +++ b/frontend/pages/(task-workspace)/settings/layout.test.js @@ -18,7 +18,7 @@ const buildInfoHook = registerHooks({ }, }); const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const workspace = registry.element("caffold-settings-workspace").prototype; after(() => { registry.restore(); diff --git a/frontend/pages/(task-workspace)/settings/layout.ts b/frontend/pages/(task-workspace)/settings/layout.ts index fc38dd3e..a95296d6 100644 --- a/frontend/pages/(task-workspace)/settings/layout.ts +++ b/frontend/pages/(task-workspace)/settings/layout.ts @@ -1,40 +1,40 @@ import { html, LitElement } from "lit"; -import type { ActionHintScope } from "#app/action-hint-scope.ts"; +import type { ActionHintScope } from "#app/action-hint-scope.js"; import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, type ScrollSurfaceScope, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import type { CodexStatusSnapshot } from "../codex-status.ts"; -import "./about/page.ts"; -import type { Health } from "#app/api.ts"; -import type { CaffoldUpdateSnapshot } from "./about/page.ts"; -import "./appearance/page.ts"; -import "./claude/page.ts"; -import type { CaffoldSettingsClaudePage } from "./claude/page.ts"; -import "./codex/page.ts"; +} from "#app/scroll-scope.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import type { CodexStatusSnapshot } from "../codex-status.js"; +import "./about/page.js"; +import type { Health } from "#app/api.js"; +import type { CaffoldUpdateSnapshot } from "./about/page.js"; +import "./appearance/page.js"; +import "./claude/page.js"; +import type { CaffoldSettingsClaudePage } from "./claude/page.js"; +import "./codex/page.js"; import type { CaffoldSettingsCodexPage, ResetCreditState, RuntimeRequestState, -} from "./codex/page.ts"; -import type { CaffoldSettingsNavigator } from "./components/navigator.ts"; -import "./files/page.ts"; -import "./grok/page.ts"; -import "./jev/page.ts"; -import "./keyboard/page.ts"; -import "./notifications/page.ts"; -import "./remote-access/page.ts"; -import "./voice/page.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; +} from "./codex/page.js"; +import type { CaffoldSettingsNavigator } from "./components/navigator.js"; +import "./files/page.js"; +import "./grok/page.js"; +import "./jev/page.js"; +import "./keyboard/page.js"; +import "./notifications/page.js"; +import "./remote-access/page.js"; +import "./voice/page.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; const TITLES: Readonly> = { appearance: "Appearance", diff --git a/frontend/pages/(task-workspace)/settings/notifications/lifecycle.test.js b/frontend/pages/(task-workspace)/settings/notifications/lifecycle.test.js index e8ab26b5..3ba8eaf6 100644 --- a/frontend/pages/(task-workspace)/settings/notifications/lifecycle.test.js +++ b/frontend/pages/(task-workspace)/settings/notifications/lifecycle.test.js @@ -9,7 +9,7 @@ import { pushSupport, shortPushClientId, subscriptionPayload, -} from "./lifecycle.ts"; +} from "./lifecycle.js"; test("client ID remains stable and replaces malformed storage", () => { let stored = null; diff --git a/frontend/pages/(task-workspace)/settings/notifications/lifecycle.ts b/frontend/pages/(task-workspace)/settings/notifications/lifecycle.ts index 6a828868..90e90d93 100644 --- a/frontend/pages/(task-workspace)/settings/notifications/lifecycle.ts +++ b/frontend/pages/(task-workspace)/settings/notifications/lifecycle.ts @@ -1,4 +1,4 @@ -import type { PushSubscriptionPayload } from "#app/api.ts"; +import type { PushSubscriptionPayload } from "#app/api.js"; export const PUSH_CLIENT_ID_STORAGE_KEY = "caffold:push-client-id"; diff --git a/frontend/pages/(task-workspace)/settings/notifications/page.test.js b/frontend/pages/(task-workspace)/settings/notifications/page.test.js index ca4b4c47..dc6bbede 100644 --- a/frontend/pages/(task-workspace)/settings/notifications/page.test.js +++ b/frontend/pages/(task-workspace)/settings/notifications/page.test.js @@ -6,16 +6,17 @@ import { fileURLToPath } from "node:url"; import { installCustomElementUnitRegistry, } from "../../../../tests/support/custom-element-unit.js"; +import { sourceUrl } from "../../../../tests/support/source-url.js"; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); const notifications = registry.element( "caffold-settings-notifications-page", ).prototype; after(() => registry.restore()); test("notification permission is requested only by the explicit Enable action", () => { - const pagePath = fileURLToPath(new URL("./page.ts", import.meta.url)); + const pagePath = fileURLToPath(sourceUrl(new URL("./page.ts", import.meta.url))); const source = readFileSync(pagePath, "utf8"); const enable = source.slice( source.indexOf("private enable() {"), diff --git a/frontend/pages/(task-workspace)/settings/notifications/page.ts b/frontend/pages/(task-workspace)/settings/notifications/page.ts index a4fc33b6..a9c0c325 100644 --- a/frontend/pages/(task-workspace)/settings/notifications/page.ts +++ b/frontend/pages/(task-workspace)/settings/notifications/page.ts @@ -4,16 +4,16 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { getPushConfig, getPushInstallations, type PushInstallation, removePushInstallation, upsertPushInstallation, -} from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import { applicationServerKey, getOrCreatePushClientId, @@ -23,7 +23,7 @@ import { pushSupport, shortPushClientId, subscriptionPayload, -} from "./lifecycle.ts"; +} from "./lifecycle.js"; const STATE_COPY: Readonly< Record diff --git a/frontend/pages/(task-workspace)/settings/remote-access/page.ts b/frontend/pages/(task-workspace)/settings/remote-access/page.ts index 2d3634cd..f05c65b3 100644 --- a/frontend/pages/(task-workspace)/settings/remote-access/page.ts +++ b/frontend/pages/(task-workspace)/settings/remote-access/page.ts @@ -3,15 +3,15 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import { TailscaleLifecycle, type TailscaleSnapshot, type TailscaleState, tailscaleQrCodeUrl, -} from "./tailscale.ts"; +} from "./tailscale.js"; const STATE_COPY: Readonly< Record diff --git a/frontend/pages/(task-workspace)/settings/remote-access/tailscale.test.js b/frontend/pages/(task-workspace)/settings/remote-access/tailscale.test.js index 1c4298e6..b837ec83 100644 --- a/frontend/pages/(task-workspace)/settings/remote-access/tailscale.test.js +++ b/frontend/pages/(task-workspace)/settings/remote-access/tailscale.test.js @@ -5,7 +5,7 @@ import { canonicalTailnetUrl, normalizeTailscaleStatus, tailscaleQrCodeUrl, -} from "./tailscale.ts"; +} from "./tailscale.js"; function status(state, overrides = {}) { return { diff --git a/frontend/pages/(task-workspace)/settings/remote-access/tailscale.ts b/frontend/pages/(task-workspace)/settings/remote-access/tailscale.ts index 8daf6505..d74907e8 100644 --- a/frontend/pages/(task-workspace)/settings/remote-access/tailscale.ts +++ b/frontend/pages/(task-workspace)/settings/remote-access/tailscale.ts @@ -1,4 +1,4 @@ -import { getTailscaleStatus, setTailscaleServe } from "#app/api.ts"; +import { getTailscaleStatus, setTailscaleServe } from "#app/api.js"; export type TailscaleState = | "notInstalled" diff --git a/frontend/pages/(task-workspace)/settings/voice/lifecycle.test.js b/frontend/pages/(task-workspace)/settings/voice/lifecycle.test.js index fa872ff2..1a66bfea 100644 --- a/frontend/pages/(task-workspace)/settings/voice/lifecycle.test.js +++ b/frontend/pages/(task-workspace)/settings/voice/lifecycle.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { VoiceSettingsLifecycle, voiceReadinessKey } from "./lifecycle.ts"; +import { VoiceSettingsLifecycle, voiceReadinessKey } from "./lifecycle.js"; function settings({ selected = "whisper", whisper = {}, openai = {}, gemini = {}, grok = {} } = {}) { return { diff --git a/frontend/pages/(task-workspace)/settings/voice/lifecycle.ts b/frontend/pages/(task-workspace)/settings/voice/lifecycle.ts index 56b44d25..402ec536 100644 --- a/frontend/pages/(task-workspace)/settings/voice/lifecycle.ts +++ b/frontend/pages/(task-workspace)/settings/voice/lifecycle.ts @@ -5,7 +5,7 @@ import { selectVoiceProvider, startVoiceModelDownload, storeVoiceKey, -} from "#app/api.ts"; +} from "#app/api.js"; const VOICE_PROVIDERS = Object.freeze(["whisper", "openai", "gemini", "grok"]); export const VOICE_KEY_PROVIDERS = Object.freeze([ diff --git a/frontend/pages/(task-workspace)/settings/voice/page.test.js b/frontend/pages/(task-workspace)/settings/voice/page.test.js index 37b84d36..8cf6427c 100644 --- a/frontend/pages/(task-workspace)/settings/voice/page.test.js +++ b/frontend/pages/(task-workspace)/settings/voice/page.test.js @@ -6,7 +6,7 @@ import { } from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); const voicePage = registry.element("caffold-settings-voice-page").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/settings/voice/page.ts b/frontend/pages/(task-workspace)/settings/voice/page.ts index 16acf641..c0518284 100644 --- a/frontend/pages/(task-workspace)/settings/voice/page.ts +++ b/frontend/pages/(task-workspace)/settings/voice/page.ts @@ -4,10 +4,10 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; -import "../components/detail-list.ts"; +} from "#app/action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; +import "../components/detail-list.js"; import { VOICE_KEY_PROVIDERS, type VoiceKeyProvider, @@ -15,7 +15,7 @@ import { VoiceSettingsLifecycle, type VoiceSettingsSnapshot, voiceReadinessKey, -} from "./lifecycle.ts"; +} from "./lifecycle.js"; const PROVIDER_CHOICES = Object.freeze([ { diff --git a/frontend/pages/(task-workspace)/task-store-status.ts b/frontend/pages/(task-workspace)/task-store-status.ts index 864fe838..5519ee15 100644 --- a/frontend/pages/(task-workspace)/task-store-status.ts +++ b/frontend/pages/(task-workspace)/task-store-status.ts @@ -1,18 +1,18 @@ -import { getTaskStoreStatus, retryTaskStoreMigration } from "../../api.ts"; +import { getTaskStoreStatus, retryTaskStoreMigration } from "../../api.js"; import { TaskStoreStatusLifecycle, type TaskStoreStatusLifecycleOptions, -} from "./task-store-status/lifecycle.ts"; +} from "./task-store-status/lifecycle.js"; import { INITIAL_TASK_STORE_STATUS_SNAPSHOT, taskStoreBlocksTaskOperations, taskStoreOperationsPresentation, -} from "./task-store-status/model.ts"; +} from "./task-store-status/model.js"; export type { TaskOperationsPresentation, TaskStoreStatusSnapshot, -} from "./task-store-status/model.ts"; +} from "./task-store-status/model.js"; // What a screen asks when the person retries a failed store upgrade. export const TASK_STORE_RETRY_REQUEST_EVENT = diff --git a/frontend/pages/(task-workspace)/task-store-status/lifecycle.test.js b/frontend/pages/(task-workspace)/task-store-status/lifecycle.test.js index cfa69c13..fb66f6f1 100644 --- a/frontend/pages/(task-workspace)/task-store-status/lifecycle.test.js +++ b/frontend/pages/(task-workspace)/task-store-status/lifecycle.test.js @@ -8,7 +8,7 @@ import { TaskStoreStatusLifecycle, createTaskStoreStatusState, transitionTaskStoreStatus, -} from "./lifecycle.ts"; +} from "./lifecycle.js"; const READY = { state: "ready", blocksTaskOperations: false, diagnosticMessage: "" }; const MIGRATING = { diff --git a/frontend/pages/(task-workspace)/task-store-status/lifecycle.ts b/frontend/pages/(task-workspace)/task-store-status/lifecycle.ts index f1d4b135..cd8fc249 100644 --- a/frontend/pages/(task-workspace)/task-store-status/lifecycle.ts +++ b/frontend/pages/(task-workspace)/task-store-status/lifecycle.ts @@ -1,10 +1,10 @@ -import type { TaskStoreReadiness } from "#app/api.ts"; +import type { TaskStoreReadiness } from "#app/api.js"; import { createTaskStoreStatusSnapshot, normalizeTaskStoreReadiness, sameTaskStoreStatusSnapshot, type TaskStoreStatusSnapshot, -} from "./model.ts"; +} from "./model.js"; const RECHECK_DELAY_MS = 500; diff --git a/frontend/pages/(task-workspace)/task-store-status/model.test.js b/frontend/pages/(task-workspace)/task-store-status/model.test.js index 86ae1ed5..c302d1e1 100644 --- a/frontend/pages/(task-workspace)/task-store-status/model.test.js +++ b/frontend/pages/(task-workspace)/task-store-status/model.test.js @@ -7,7 +7,7 @@ import { sameTaskStoreStatusSnapshot, taskStoreBlocksTaskOperations, taskStoreOperationsPresentation, -} from "./model.ts"; +} from "./model.js"; function snapshot(state, diagnosticMessage = "") { return createTaskStoreStatusSnapshot({ diff --git a/frontend/pages/(task-workspace)/task-store-status/model.ts b/frontend/pages/(task-workspace)/task-store-status/model.ts index 5738b29e..8ee62dac 100644 --- a/frontend/pages/(task-workspace)/task-store-status/model.ts +++ b/frontend/pages/(task-workspace)/task-store-status/model.ts @@ -1,4 +1,4 @@ -import type { TaskStoreReadiness } from "#app/api.ts"; +import type { TaskStoreReadiness } from "#app/api.js"; // What the Task store last said about whether it can take Task operations. export interface TaskStoreStatusSnapshot { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.test.js index 8acc1f81..e7ae8eab 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./changes-tree.ts"); +await import("./changes-tree.js"); const changesTree = registry.element("caffold-commit-changes-tree").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.ts index f307dfa4..db908681 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.ts @@ -2,20 +2,20 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; import { type ActionHintPull, emptyActionHintScope, -} from "#app/action-hints.ts"; -import type { GitCommit, GitCommitFile } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { GitCommit, GitCommitFile } from "#app/api.js"; import { emptyScrollSurfaceScope, type ScrollSurfacePull, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import { buildFileTreeNodes, type CaffoldFileTree, type FileTreeNode, type FileTreeSelectDetail, -} from "#components/file-tree.ts"; -import "#components/loading-text.ts"; +} from "#components/file-tree.js"; +import "#components/loading-text.js"; type CommitState = | { status: "idle" } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.test.js index 32643c16..c8586068 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.test.js @@ -9,7 +9,7 @@ const previousWindow = globalThis.window; let singlePane = false; globalThis.window = { matchMedia: () => ({ matches: singlePane }) }; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); const page = registry.element("caffold-git-log-commit-page").prototype; after(() => { registry.restore(); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.ts index 1f68a0b7..bfe75c43 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.ts @@ -5,32 +5,32 @@ import { emptyActionHintScope, hasActionHintLayoutBox, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type GitCommit, type GitDiff, type GitRepository, getGitCommit, getGitCommitDiff, -} from "#app/api.ts"; +} from "#app/api.js"; import { emptyScrollSurfaceScope, hasScrollLayoutBox, mergeScrollSurfaceScopes, type ScrollSurfacePull, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/file-viewer.ts"; -import type { CaffoldReviewFileViewer } from "#components/file-viewer.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/file-viewer.js"; +import type { CaffoldReviewFileViewer } from "#components/file-viewer.js"; import { diffViewerPresentation, type ViewerPresentation, -} from "#components/file-viewer-presentation.ts"; -import "#components/pane-resizer.ts"; -import type { CaffoldPaneResizer } from "#components/pane-resizer.ts"; -import { REVIEW_SINGLE_PANE_MEDIA_QUERY } from "#components/review-responsive.ts"; -import "./components/changes-tree.ts"; -import type { CaffoldCommitChangesTree } from "./components/changes-tree.ts"; +} from "#components/file-viewer-presentation.js"; +import "#components/pane-resizer.js"; +import type { CaffoldPaneResizer } from "#components/pane-resizer.js"; +import { REVIEW_SINGLE_PANE_MEDIA_QUERY } from "#components/review-responsive.js"; +import "./components/changes-tree.js"; +import type { CaffoldCommitChangesTree } from "./components/changes-tree.js"; const LOADING_DELAY_MS = 180; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.ts index bc4b717b..5389fbb9 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.ts @@ -1,18 +1,18 @@ import { html, LitElement } from "lit"; -import type { ActionHintPull } from "#app/action-hints.ts"; +import type { ActionHintPull } from "#app/action-hints.js"; import { fetchGitRemote, type GitFetchResult, type GitLog, type GitRepository, getGitLog, -} from "#app/api.ts"; -import type { ScrollSurfacePull } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "./commit/page.ts"; -import type { CaffoldGitLogCommitPage } from "./commit/page.ts"; -import "./list/page.ts"; -import type { CaffoldGitLogListPage } from "./list/page.ts"; +} from "#app/api.js"; +import type { ScrollSurfacePull } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "./commit/page.js"; +import type { CaffoldGitLogCommitPage } from "./commit/page.js"; +import "./list/page.js"; +import type { CaffoldGitLogListPage } from "./list/page.js"; const LOADING_DELAY_MS = 180; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.ts index 7cfe454c..a60d7b2a 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.ts @@ -5,14 +5,14 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { GitLog } from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import "#components/pagination.ts"; -import type { CaffoldPagination } from "#components/pagination.ts"; +} from "#app/action-hints.js"; +import type { GitLog } from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import "#components/pagination.js"; +import type { CaffoldPagination } from "#components/pagination.js"; type LogState = | { status: "idle" } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.ts index d9af3e56..5867b223 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.ts @@ -1,9 +1,9 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, type ActionHintPull } from "#app/action-hints.ts"; -import type { ScrollSurfacePull } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/git-compare-browser.ts"; -import type { CaffoldGitCompareBrowser } from "#components/git-compare-browser.ts"; +import { ACTION_HINT_ACTION, type ActionHintPull } from "#app/action-hints.js"; +import type { ScrollSurfacePull } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/git-compare-browser.js"; +import type { CaffoldGitCompareBrowser } from "#components/git-compare-browser.js"; export class CaffoldGitComparePage extends LitElement { private readonly browserElement = new DrawnRef( diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.ts index 982cf5dd..aeb404a5 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.ts @@ -3,12 +3,12 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import type { GitRef } from "#app/api.ts"; -import { busySpin, compactIconButton } from "#app/component-styles.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import type { FetchState } from "../(log)/layout.ts"; +} from "#app/action-hints.js"; +import type { GitRef } from "#app/api.js"; +import { busySpin, compactIconButton } from "#app/component-styles.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import type { FetchState } from "../(log)/layout.js"; // What the Git header's controls show for the open route. export interface GitControlsSnapshot { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.test.js index 4ca5eb97..1f5f47a8 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const gitLayout = registry.element("caffold-task-git-layout").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.ts index 9bfb6a64..bf1d067f 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(git)/layout.ts @@ -4,24 +4,24 @@ import { ActionHints, emptyActionHintScope, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { GitRepository } from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; -import { routeMode } from "#app/navigation-routes.ts"; -import { emptyScrollSurfaceScope } from "#app/scroll-scope.ts"; -import { createRefreshCoordinator, subscribeToWatch } from "#app/watch.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "./(log)/layout.ts"; -import type { CaffoldGitLogLayout } from "./(log)/layout.ts"; -import "./compare/page.ts"; -import type { CaffoldGitComparePage } from "./compare/page.ts"; -import "./components/controls.ts"; +} from "#app/action-hints.js"; +import type { GitRepository } from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; +import { routeMode } from "#app/navigation-routes.js"; +import { emptyScrollSurfaceScope } from "#app/scroll-scope.js"; +import { createRefreshCoordinator, subscribeToWatch } from "#app/watch.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "./(log)/layout.js"; +import type { CaffoldGitLogLayout } from "./(log)/layout.js"; +import "./compare/page.js"; +import type { CaffoldGitComparePage } from "./compare/page.js"; +import "./components/controls.js"; import { type CaffoldGitReviewControls, type GitControlsSnapshot, IDLE_CONTROLS, -} from "./components/controls.ts"; +} from "./components/controls.js"; type GitMode = "compare" | "log"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.test.js index d6ac49fe..644ae4a1 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); const page = registry.element("caffold-github-issue-detail-page").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.ts index 815a3f4c..f4979d11 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.ts @@ -4,18 +4,18 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { GitHubIssue } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { GitHubIssue } from "#app/api.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import "../../components/markdown.ts"; -import type { CaffoldGithubMarkdown } from "../../components/markdown.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import "../../components/markdown.js"; +import type { CaffoldGithubMarkdown } from "../../components/markdown.js"; type IssueState = | { status: "empty" } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.test.js index 8387f9a6..76a5e941 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const layout = registry.element("caffold-github-issues-layout").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.ts index 7ea81aa8..4a8cc089 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.ts @@ -1,18 +1,18 @@ import { html, LitElement } from "lit"; -import type { ActionHintPull } from "#app/action-hints.ts"; +import type { ActionHintPull } from "#app/action-hints.js"; import { type GitHubIssues, type GitHubStatus, type GitRepository, getGitHubIssue, getGitHubIssues, -} from "#app/api.ts"; -import type { ScrollSurfacePull } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "./detail/page.ts"; -import type { CaffoldGithubIssueDetailPage } from "./detail/page.ts"; -import "./list/page.ts"; -import type { CaffoldGithubIssuesListPage } from "./list/page.ts"; +} from "#app/api.js"; +import type { ScrollSurfacePull } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "./detail/page.js"; +import type { CaffoldGithubIssueDetailPage } from "./detail/page.js"; +import "./list/page.js"; +import type { CaffoldGithubIssuesListPage } from "./list/page.js"; const GITHUB_ISSUES_PER_PAGE = 50; const LOADING_DELAY_MS = 180; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.ts index 9098a21a..4ee1c05b 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.ts @@ -5,13 +5,13 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { GitHubIssues, GitHubStatus } from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/loading-text.ts"; -import "#components/pagination.ts"; -import type { CaffoldPagination } from "#components/pagination.ts"; +} from "#app/action-hints.js"; +import type { GitHubIssues, GitHubStatus } from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/loading-text.js"; +import "#components/pagination.js"; +import type { CaffoldPagination } from "#components/pagination.js"; type IssuesState = | { status: "idle" } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.test.js index 3dba5807..d893119f 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); const page = registry.element("caffold-github-pull-detail-page").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.ts index d8dffce4..0d0f4d11 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.ts @@ -5,18 +5,18 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { GitHubPull } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { GitHubPull } from "#app/api.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import "../../components/markdown.ts"; -import type { CaffoldGithubMarkdown } from "../../components/markdown.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import "../../components/markdown.js"; +import type { CaffoldGithubMarkdown } from "../../components/markdown.js"; type PullState = | { status: "empty" } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.test.js index ea8d7100..62f33a83 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./tree.ts"); +await import("./tree.js"); const pullFilesTree = registry.element("caffold-github-pull-files-tree").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.ts index 3a5284db..e9c8c775 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.ts @@ -2,20 +2,20 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; import { type ActionHintPull, emptyActionHintScope, -} from "#app/action-hints.ts"; -import type { GitHubPullFile, GitHubPullFiles } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { GitHubPullFile, GitHubPullFiles } from "#app/api.js"; import { emptyScrollSurfaceScope, type ScrollSurfacePull, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import { buildFileTreeNodes, type CaffoldFileTree, type FileTreeNode, type FileTreeSelectDetail, -} from "#components/file-tree.ts"; -import "#components/loading-text.ts"; +} from "#components/file-tree.js"; +import "#components/loading-text.js"; const LOADING_FILES = "Loading pull request files..."; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.test.js index 71c9a44c..ebab7bd8 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.test.js @@ -11,7 +11,7 @@ globalThis.window = { matchMedia: () => ({ matches: singlePane }), }; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); const page = registry.element("caffold-github-pull-files-page").prototype; after(() => { registry.restore(); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.ts index 4f72067e..dc99bf39 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.ts @@ -5,32 +5,32 @@ import { emptyActionHintScope, hasActionHintLayoutBox, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type GitHubPullFileDiff, type GitHubPullFiles, type GitRepository, getGitHubPullFile, getGitHubPullFiles, -} from "#app/api.ts"; +} from "#app/api.js"; import { emptyScrollSurfaceScope, hasScrollLayoutBox, mergeScrollSurfaceScopes, type ScrollSurfacePull, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/file-viewer.ts"; -import type { CaffoldReviewFileViewer } from "#components/file-viewer.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/file-viewer.js"; +import type { CaffoldReviewFileViewer } from "#components/file-viewer.js"; import { diffViewerPresentation, type ViewerPresentation, -} from "#components/file-viewer-presentation.ts"; -import "#components/pane-resizer.ts"; -import type { CaffoldPaneResizer } from "#components/pane-resizer.ts"; -import { REVIEW_SINGLE_PANE_MEDIA_QUERY } from "#components/review-responsive.ts"; -import "./components/tree.ts"; -import type { CaffoldGithubPullFilesTree } from "./components/tree.ts"; +} from "#components/file-viewer-presentation.js"; +import "#components/pane-resizer.js"; +import type { CaffoldPaneResizer } from "#components/pane-resizer.js"; +import { REVIEW_SINGLE_PANE_MEDIA_QUERY } from "#components/review-responsive.js"; +import "./components/tree.js"; +import type { CaffoldGithubPullFilesTree } from "./components/tree.js"; const LOADING_DELAY_MS = 180; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.test.js index 2d7b4c3b..754ffe60 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const layout = registry.element("caffold-github-pulls-layout").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.ts index 6a963bf6..4dc8f9e8 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.ts @@ -1,20 +1,20 @@ import { html, LitElement } from "lit"; -import type { ActionHintPull } from "#app/action-hints.ts"; +import type { ActionHintPull } from "#app/action-hints.js"; import { type GitHubPulls, type GitHubStatus, type GitRepository, getGitHubPull, getGitHubPulls, -} from "#app/api.ts"; -import type { ScrollSurfacePull } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "./detail/page.ts"; -import type { CaffoldGithubPullDetailPage } from "./detail/page.ts"; -import "./files/page.ts"; -import type { CaffoldGithubPullFilesPage } from "./files/page.ts"; -import "./list/page.ts"; -import type { CaffoldGithubPullsListPage } from "./list/page.ts"; +} from "#app/api.js"; +import type { ScrollSurfacePull } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "./detail/page.js"; +import type { CaffoldGithubPullDetailPage } from "./detail/page.js"; +import "./files/page.js"; +import type { CaffoldGithubPullFilesPage } from "./files/page.js"; +import "./list/page.js"; +import type { CaffoldGithubPullsListPage } from "./list/page.js"; const GITHUB_PULLS_PER_PAGE = 50; const LOADING_DELAY_MS = 180; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.ts index 63aed55c..7df1bb93 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.ts @@ -5,14 +5,14 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { GitHubPulls, GitHubStatus } from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import "#components/pagination.ts"; -import type { CaffoldPagination } from "#components/pagination.ts"; +} from "#app/action-hints.js"; +import type { GitHubPulls, GitHubStatus } from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import "#components/pagination.js"; +import type { CaffoldPagination } from "#components/pagination.js"; const LOADING_PULLS = "Loading pull requests..."; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.test.js index b10450b3..748728d0 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./markdown.ts"); +await import("./markdown.js"); const markdown = registry.element("caffold-github-markdown").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.ts index 506070a5..eeb45a0e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.ts @@ -9,16 +9,16 @@ import { linkActionHintLabel, linkActionHintTarget, matchesLinkActionHintBinding, -} from "#app/action-hint-scope.ts"; -import { ACTION_HINT_ACTION, type ActionHintPull } from "#app/action-hints.ts"; +} from "#app/action-hint-scope.js"; +import { ACTION_HINT_ACTION, type ActionHintPull } from "#app/action-hints.js"; import { hasScrollLayoutBox, mergeScrollSurfaceScopes, type ScrollSurfacePull, type ScrollSurfaceScope, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; const FORBIDDEN_ELEMENTS = new Set([ "base", diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.test.js index d3684f9b..9ea9c3b3 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./task-start-dialog.ts"); +await import("./task-start-dialog.js"); const taskStart = registry.element("caffold-github-task-start-dialog").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.ts index 24bf81d9..4c013b1a 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.ts @@ -4,33 +4,33 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { createTask, type GitHubIssue, type GitHubPull, type GitRepository, -} from "#app/api.ts"; +} from "#app/api.js"; import { KeyboardContexts, mergeKeyboardNavigationContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#tasks/components/task-turn-options.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#tasks/components/task-turn-options.js"; import type { CaffoldTaskTurnOptions, TurnOptionsSelection, -} from "#tasks/components/task-turn-options.ts"; -import "./task-start-dialog/components/github-issue.ts"; -import type { CaffoldGithubIssueTaskSource } from "./task-start-dialog/components/github-issue.ts"; -import "./task-start-dialog/components/github-pull.ts"; -import type { CaffoldGithubPullTaskSource } from "./task-start-dialog/components/github-pull.ts"; +} from "#tasks/components/task-turn-options.js"; +import "./task-start-dialog/components/github-issue.js"; +import type { CaffoldGithubIssueTaskSource } from "./task-start-dialog/components/github-issue.js"; +import "./task-start-dialog/components/github-pull.js"; +import type { CaffoldGithubPullTaskSource } from "./task-start-dialog/components/github-pull.js"; // The issue or pull request a Task is started for. export type TaskStartSource = diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.ts index 4dcd582e..4fb980ef 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.ts @@ -3,14 +3,14 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type GitHubIssue, type GitRef, type GitRepository, getGitRefs, -} from "#app/api.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/api.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; interface TaskRef { name: string; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.ts index 65701e2b..9b87e29e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.ts @@ -3,7 +3,7 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type GitHubPull, type GitHubPullDetail, @@ -11,9 +11,9 @@ import { type GitRepository, getGitHubPull, prepareGitHubPullHead, -} from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; export class CaffoldGithubPullTaskSource extends LitElement { static properties = { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.test.js index 703ee982..b80eec58 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const githubLayout = registry.element("caffold-task-github-layout").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.ts index 2534542e..b6ee3e1f 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(github)/layout.ts @@ -4,27 +4,27 @@ import { ActionHints, emptyActionHintScope, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type GitHubStatus, type GitRepository, getGitHubStatus, -} from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; -import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.ts"; -import { routeMode } from "#app/navigation-routes.ts"; -import { emptyScrollSurfaceScope } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "./(issues)/layout.ts"; -import type { CaffoldGithubIssuesLayout } from "./(issues)/layout.ts"; -import "./(pulls)/layout.ts"; -import type { CaffoldGithubPullsLayout } from "./(pulls)/layout.ts"; -import "./components/task-start-dialog.ts"; +} from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; +import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.js"; +import { routeMode } from "#app/navigation-routes.js"; +import { emptyScrollSurfaceScope } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "./(issues)/layout.js"; +import type { CaffoldGithubIssuesLayout } from "./(issues)/layout.js"; +import "./(pulls)/layout.js"; +import type { CaffoldGithubPullsLayout } from "./(pulls)/layout.js"; +import "./components/task-start-dialog.js"; import type { CaffoldGithubTaskStartDialog, TaskStartSource, -} from "./components/task-start-dialog.ts"; +} from "./components/task-start-dialog.js"; type GithubMode = "issues" | "pulls"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.test.js index 190e0852..0fa2b299 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { ChangesReading } from "./changes-reading.ts"; +import { ChangesReading } from "./changes-reading.js"; const facts = { empty: { hasData: false, fromBeforeReturn: false, reading: false, failed: false }, diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.test.js index bf783e0b..470c0396 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.test.js @@ -4,10 +4,10 @@ import test, { after } from "node:test"; import { installCustomElementUnitRegistry, } from "../../../../../../tests/support/custom-element-unit.js"; -import { ChangesReading } from "../changes-reading.ts"; +import { ChangesReading } from "../changes-reading.js"; const registry = installCustomElementUnitRegistry(); -await import("./branch-changes.ts"); +await import("./branch-changes.js"); const branchChanges = registry.element("caffold-review-branch-changes").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.ts index e7b56ac8..0c2b0790 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.ts @@ -1,18 +1,18 @@ import { html, LitElement } from "lit"; -import type { ActionHintPull } from "#app/action-hints.ts"; +import type { ActionHintPull } from "#app/action-hints.js"; import { type GitCompare, type GitRefs, getGitCompare, getGitCompareDiff, getGitRefs, -} from "#app/api.ts"; -import type { ScrollSurfacePull } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/git-compare-tree.ts"; -import type { CaffoldGitCompareTree } from "#components/git-compare-tree.ts"; -import { ChangesReading } from "../changes-reading.ts"; -import type { ReviewChange } from "./selected-file/content.ts"; +} from "#app/api.js"; +import type { ScrollSurfacePull } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/git-compare-tree.js"; +import type { CaffoldGitCompareTree } from "#components/git-compare-tree.js"; +import { ChangesReading } from "../changes-reading.js"; +import type { ReviewChange } from "./selected-file/content.js"; export class CaffoldReviewBranchChanges extends LitElement { private rootPath = ""; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.test.js index bbf58c23..40991ca7 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./selected-file.ts"); +await import("./selected-file.js"); const selectedFile = registry.element("caffold-review-selected-file").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.ts index 1f492363..8c6755ba 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.ts @@ -1,22 +1,22 @@ import { html, LitElement } from "lit"; -import type { ActionHintPull } from "#app/action-hints.ts"; -import { type GitDiff, readFile } from "#app/api.ts"; -import { fileStatusPresentation } from "#app/file-status.ts"; -import type { ScrollSurfacePull } from "#app/scroll-scope.ts"; -import { fileNameFromPath, imageTypeLabel } from "#components/dom.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/file-viewer.ts"; -import type { ScrollPosition } from "#components/file-viewer/components/code-viewer.ts"; -import type { CaffoldReviewFileViewer } from "#components/file-viewer.ts"; +import type { ActionHintPull } from "#app/action-hints.js"; +import { type GitDiff, readFile } from "#app/api.js"; +import { fileStatusPresentation } from "#app/file-status.js"; +import type { ScrollSurfacePull } from "#app/scroll-scope.js"; +import { fileNameFromPath, imageTypeLabel } from "#components/dom.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/file-viewer.js"; +import type { ScrollPosition } from "#components/file-viewer/components/code-viewer.js"; +import type { CaffoldReviewFileViewer } from "#components/file-viewer.js"; import { diffViewerPresentation, sourceViewerPresentation, -} from "#components/file-viewer-presentation.ts"; +} from "#components/file-viewer-presentation.js"; import { contentFor, type ReviewChange, type ReviewView, -} from "./selected-file/content.ts"; +} from "./selected-file/content.js"; interface ViewPosition { scroll: ScrollPosition | null; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.test.js index 0cae12c0..5db81d76 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { contentFor } from "./content.ts"; +import { contentFor } from "./content.js"; const modified = { file: { path: "repo/src/a.js", status: "M" }, diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.ts index 578c14ee..a923e5f6 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.ts @@ -3,12 +3,12 @@ import type { GitChangedFile, GitCompareFile, GitDiff, -} from "#app/api.ts"; -import { type DocumentPreviewKind, fileNameFromPath } from "#components/dom.ts"; +} from "#app/api.js"; +import { type DocumentPreviewKind, fileNameFromPath } from "#components/dom.js"; import { fileRepresentationCapabilities, type ReviewViewer, -} from "../../representations.ts"; +} from "../../representations.js"; // What the shown Working Tree or Branch changes say about one file. export interface ReviewChange { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.test.js index e2114469..e1babe09 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.test.js @@ -4,10 +4,10 @@ import test, { after } from "node:test"; import { installCustomElementUnitRegistry, } from "../../../../../../tests/support/custom-element-unit.js"; -import { ChangesReading } from "../changes-reading.ts"; +import { ChangesReading } from "../changes-reading.js"; const registry = installCustomElementUnitRegistry(); -await import("./working-tree-changes.ts"); +await import("./working-tree-changes.js"); const workingTreeChanges = registry .element("caffold-review-working-tree-changes") .prototype; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.ts index d74e3b67..3093dbbc 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.ts @@ -3,14 +3,14 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import { type GitStatus, getGitDiff, getGitStatus } from "#app/api.ts"; -import type { ScrollSurfacePull } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { ChangesReading } from "../changes-reading.ts"; -import type { ReviewChange } from "./selected-file/content.ts"; -import "./working-tree-changes/components/changes-tree.ts"; -import type { CaffoldGitDiffChangesTree } from "./working-tree-changes/components/changes-tree.ts"; +} from "#app/action-hints.js"; +import { type GitStatus, getGitDiff, getGitStatus } from "#app/api.js"; +import type { ScrollSurfacePull } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { ChangesReading } from "../changes-reading.js"; +import type { ReviewChange } from "./selected-file/content.js"; +import "./working-tree-changes/components/changes-tree.js"; +import type { CaffoldGitDiffChangesTree } from "./working-tree-changes/components/changes-tree.js"; export class CaffoldReviewWorkingTreeChanges extends LitElement { static properties = { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.test.js index eddbf3f6..1667f414 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./changes-tree.ts"); +await import("./changes-tree.js"); const changesTree = registry.element("caffold-git-diff-changes-tree").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts index e6b607ca..5c21ca1a 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts @@ -2,23 +2,23 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; import { type ActionHintPull, emptyActionHintScope, -} from "#app/action-hints.ts"; -import type { GitChangedFile, GitStatus } from "#app/api.ts"; -import { fileStatusPresentation } from "#app/file-status.ts"; +} from "#app/action-hints.js"; +import type { GitChangedFile, GitStatus } from "#app/api.js"; +import { fileStatusPresentation } from "#app/file-status.js"; import { emptyScrollSurfaceScope, type ScrollSurfacePull, -} from "#app/scroll-scope.ts"; -import { formatLineStats } from "#components/dom.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/scroll-scope.js"; +import { formatLineStats } from "#components/dom.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import { buildFileTreeNodes, type CaffoldFileTree, type FileTreeNode, type FileTreeSelectDetail, readyFileTreeChildren, -} from "#components/file-tree.ts"; -import "#components/loading-text.ts"; +} from "#components/file-tree.js"; +import "#components/loading-text.js"; const SECTIONS = [ ["unstaged", "Unstaged"], diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.test.js index 27d1b967..3fec176d 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const review = registry.element("caffold-task-review").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.ts index 8b3b3edf..fc26e246 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout.ts @@ -3,40 +3,40 @@ import { emptyActionHintScope, hasActionHintLayoutBox, mergeActionHintScopes, -} from "#app/action-hint-scope.ts"; -import { ACTION_HINT_ACTION } from "#app/action-hints.ts"; +} from "#app/action-hint-scope.js"; +import { ACTION_HINT_ACTION } from "#app/action-hints.js"; import { emptyScrollSurfaceScope, hasScrollLayoutBox, mergeScrollSurfaceScopes, -} from "#app/scroll-scope.ts"; -import { subscribeToWatch, watchChangeAffectsPath } from "#app/watch.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/file-navigator.ts"; -import type { CaffoldFileNavigator } from "#components/file-navigator.ts"; -import "#components/pane-resizer.ts"; -import type { CaffoldPaneResizer } from "#components/pane-resizer.ts"; -import "#components/segmented-control.ts"; -import type { CaffoldSegmentedControl } from "#components/segmented-control.ts"; -import { cleanLogicalPath } from "#tasks/task-format.ts"; -import { taskThreadId } from "#tasks/task-list-model.ts"; -import "./components/branch-changes.ts"; -import type { CaffoldReviewBranchChanges } from "./components/branch-changes.ts"; -import "./components/selected-file.ts"; +} from "#app/scroll-scope.js"; +import { subscribeToWatch, watchChangeAffectsPath } from "#app/watch.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/file-navigator.js"; +import type { CaffoldFileNavigator } from "#components/file-navigator.js"; +import "#components/pane-resizer.js"; +import type { CaffoldPaneResizer } from "#components/pane-resizer.js"; +import "#components/segmented-control.js"; +import type { CaffoldSegmentedControl } from "#components/segmented-control.js"; +import { cleanLogicalPath } from "#tasks/task-format.js"; +import { taskThreadId } from "#tasks/task-list-model.js"; +import "./components/branch-changes.js"; +import type { CaffoldReviewBranchChanges } from "./components/branch-changes.js"; +import "./components/selected-file.js"; import type { ReviewChanges, ReviewView, -} from "./components/selected-file/content.ts"; -import type { CaffoldReviewSelectedFile } from "./components/selected-file.ts"; -import "./components/working-tree-changes.ts"; -import type { CaffoldReviewWorkingTreeChanges } from "./components/working-tree-changes.ts"; +} from "./components/selected-file/content.js"; +import type { CaffoldReviewSelectedFile } from "./components/selected-file.js"; +import "./components/working-tree-changes.js"; +import type { CaffoldReviewWorkingTreeChanges } from "./components/working-tree-changes.js"; import { joinLogicalPath, type ReviewTask, relativeTaskPath, taskRootPath, taskWorktreeRootPath, -} from "./layout/paths.ts"; +} from "./layout/paths.js"; import { normalizeForTask, normalizeReviewRoute, @@ -47,11 +47,11 @@ import { routeForSelectedFile, taskRouteForReview, viewerStateKey, -} from "./layout/route.ts"; +} from "./layout/route.js"; import { fileRepresentationCapabilities, isFileRepresentation, -} from "./representations.ts"; +} from "./representations.js"; // Matches the width where layout.css stops showing one pane at a time. const REVIEW_SPLIT_MEDIA_QUERY = "(min-width: 561px)"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.ts index 2db4f80e..479bc129 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.ts @@ -1,4 +1,4 @@ -import { cleanLogicalPath } from "#tasks/task-format.ts"; +import { cleanLogicalPath } from "#tasks/task-format.js"; // What Review reads from the Task it reviews. export interface ReviewTask { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/route.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/route.ts index a5088ece..5b2cc554 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/route.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/layout/route.ts @@ -1,14 +1,14 @@ -import { taskThreadId } from "#tasks/task-list-model.ts"; +import { taskThreadId } from "#tasks/task-list-model.js"; import { type ReviewViewer, representationForFile, -} from "../representations.ts"; +} from "../representations.js"; import { type ReviewTask, reviewFilePath, taskRootPath, taskWorktreeRootPath, -} from "./paths.ts"; +} from "./paths.js"; export type ReviewScope = "working" | "branch"; export type ReviewNavigator = "changes" | "files"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/representations.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/representations.ts index 56fa5813..8b39c167 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(review)/representations.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(review)/representations.ts @@ -1,7 +1,7 @@ import { documentPreviewKind, isPreviewableImagePath, -} from "#components/dom.ts"; +} from "#components/dom.js"; export type ReviewViewer = "diff" | "source" | "preview"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.test.js index b3e99e25..6a7132af 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.test.js @@ -7,11 +7,11 @@ import { import { templateHtml } from "../../../../../../tests/support/lit-template.js"; import { createCodexStatusSnapshot, -} from "#app/pages/(task-workspace)/codex-status.ts"; +} from "#app/pages/(task-workspace)/codex-status.js"; const registry = installCustomElementUnitRegistry(); -await import("./conversation-shortcuts.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./conversation-shortcuts.js"); +const { ActionHints } = await import("#app/action-hints.js"); const shortcuts = registry.element("caffold-section-conversation-shortcuts").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts index fdb1fadf..3c16853a 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts @@ -3,21 +3,21 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { getCodexReadiness } from "#app/api.ts"; +} from "#app/action-hints.js"; +import { getCodexReadiness } from "#app/api.js"; import { type CodexStatusSnapshot, codexBlocksTaskOperations, codexState, createCodexStatusSnapshot, -} from "#app/pages/(task-workspace)/codex-status.ts"; +} from "#app/pages/(task-workspace)/codex-status.js"; import { type TaskStoreStatusSnapshot, taskStoreBlocksTaskOperations, -} from "#app/pages/(task-workspace)/task-store-status.ts"; -import "./conversation-shortcuts/components/fork-dialog.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import type { CaffoldConversationForkDialog } from "./conversation-shortcuts/components/fork-dialog.ts"; +} from "#app/pages/(task-workspace)/task-store-status.js"; +import "./conversation-shortcuts/components/fork-dialog.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import type { CaffoldConversationForkDialog } from "./conversation-shortcuts/components/fork-dialog.js"; // The Section the shortcuts start a Task in: its identity, and the path a // fork is shown with. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts index dc373e48..c9659ac7 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts @@ -1,19 +1,19 @@ import { html, LitElement, nothing } from "lit"; import { live } from "lit/directives/live.js"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; import { createTaskFork, previewTaskForkSource, type TaskForkSourcePreview, type TaskResponseError, type TaskThreadStatus, -} from "#app/api.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/loading-text.ts"; -import { formatDate } from "#tasks/task-format.ts"; +} from "#app/api.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/loading-text.js"; +import { formatDate } from "#tasks/task-format.js"; const CODEX_THREAD_URI_PREFIX = "codex://threads/"; const DESCRIPTION = diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts index 0a1802da..347acb2f 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts @@ -4,8 +4,8 @@ import { type ActionHintPull, ActionHints, emptyActionHintScope, -} from "#app/action-hints.ts"; -import { type GitHubStatus, getGitHubStatus } from "#app/api.ts"; +} from "#app/action-hints.js"; +import { type GitHubStatus, getGitHubStatus } from "#app/api.js"; // The Section's directory, and whether it is a Git repository. `key` names // the Section the directory belongs to. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts index a01f239e..697c51ea 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts @@ -1,5 +1,5 @@ import { html, LitElement, nothing } from "lit"; -import { sectionLabel } from "#tasks/task-format.ts"; +import { sectionLabel } from "#tasks/task-format.js"; /** * The heading of a Section's Detail: the last part of its directory, with the diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.test.js index a9fa09dc..f9effd9a 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const sectionDetail = registry.element("caffold-section-detail").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.ts index 3c53fe85..cfa80690 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(section)/layout.ts @@ -1,22 +1,22 @@ import { html, LitElement, nothing } from "lit"; import { keyed } from "lit/directives/keyed.js"; -import { mergeActionHintScopes } from "#app/action-hints.ts"; -import type { ActiveTaskSection } from "#app/api.ts"; -import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.ts"; -import type { TaskStoreStatusSnapshot } from "#app/pages/(task-workspace)/task-store-status.ts"; +import { mergeActionHintScopes } from "#app/action-hints.js"; +import type { ActiveTaskSection } from "#app/api.js"; +import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.js"; +import type { TaskStoreStatusSnapshot } from "#app/pages/(task-workspace)/task-store-status.js"; import { emptyScrollSurfaceScope, hasScrollLayoutBox, mergeScrollSurfaceScopes, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "../../components/task-create.ts"; -import type { CaffoldTaskCreate } from "../../components/task-create.ts"; -import { cleanLogicalPath } from "../../task-format.ts"; -import "./components/conversation-shortcuts.ts"; -import type { CaffoldSectionConversationShortcuts } from "./components/conversation-shortcuts.ts"; -import "./components/github-shortcuts.ts"; -import type { CaffoldSectionGithubShortcuts } from "./components/github-shortcuts.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "../../components/task-create.js"; +import type { CaffoldTaskCreate } from "../../components/task-create.js"; +import { cleanLogicalPath } from "../../task-format.js"; +import "./components/conversation-shortcuts.js"; +import type { CaffoldSectionConversationShortcuts } from "./components/conversation-shortcuts.js"; +import "./components/github-shortcuts.js"; +import type { CaffoldSectionGithubShortcuts } from "./components/github-shortcuts.js"; // The Section Detail opens: its identity, its directory as its name, whether // that is a Git repository, and what a new Task there starts from. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.test.js index 74f0bafc..952324cd 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.test.js @@ -4,10 +4,10 @@ import { installCustomElementUnitRegistry } from "../../../../../../tests/suppor import { templateHtml } from "../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./broken-delete-dialog.ts"); +await import("./broken-delete-dialog.js"); const Dialog = registry.element("caffold-broken-task-delete-dialog"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); +const { ActionHints } = await import("#app/action-hints.js"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); const original = { window: globalThis.window, fetch: globalThis.fetch }; after(() => registry.restore()); afterEach(() => { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.ts index a47eed71..139b243a 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.ts @@ -1,9 +1,9 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; -import { deleteTask, getTask, type TaskResponseError } from "#app/api.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; +import { deleteTask, getTask, type TaskResponseError } from "#app/api.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; // The Task a broken worktree keeps from opening, as its Task screen holds it. export interface BrokenTaskContext { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.test.js index 7a6b9905..992d6014 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.test.js @@ -7,11 +7,11 @@ import { import { templateHtml } from "../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./command-dialog.ts"); +await import("./command-dialog.js"); const CommandDialog = registry.element("caffold-task-command-dialog"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); -const { ScrollSurfaces } = await import("#app/scroll-scope.ts"); +const { ActionHints } = await import("#app/action-hints.js"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); +const { ScrollSurfaces } = await import("#app/scroll-scope.js"); after(() => registry.restore()); test("draws a failed command's details and output, and only the details it has", () => { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts index 0a4c5bdd..b76eb57b 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts @@ -1,16 +1,16 @@ import { html, LitElement, nothing } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; -import type { TaskEvent } from "#app/api.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; +import type { TaskEvent } from "#app/api.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; import { formatDate, formatDuration, taskEventObservedMs, -} from "#tasks/task-format.ts"; +} from "#tasks/task-format.js"; // One finished command as the dialog shows it. interface CommandView { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.test.js index 3ce2b7a1..6cb56132 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./conversation.ts"); +await import("./conversation.js"); const conversation = registry.element("caffold-task-conversation").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.ts index 99959d60..57584edb 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.ts @@ -7,61 +7,61 @@ import { ActionHints, emptyActionHintScope, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { Task, TaskEvent } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { Task, TaskEvent } from "#app/api.js"; import { emptyScrollSurfaceScope, mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; import { isTaskTransportStale, type TaskTransportState, -} from "#tasks/runtime-state.ts"; +} from "#tasks/runtime-state.js"; import { dedupeCanonicalEvents, eventIdentityKey, pendingApprovals, -} from "#tasks/task-events.ts"; -import "./conversation/components/active-turn.ts"; -import "./conversation/components/approval.ts"; +} from "#tasks/task-events.js"; +import "./conversation/components/active-turn.js"; +import "./conversation/components/approval.js"; import type { ApprovalIntentDetail, CaffoldTaskApproval, -} from "./conversation/components/approval.ts"; -import "./conversation/components/assistant-message.ts"; -import type { SuggestedPromptIntentDetail } from "./conversation/components/assistant-message.ts"; -import "./conversation/components/changed-files.ts"; +} from "./conversation/components/approval.js"; +import "./conversation/components/assistant-message.js"; +import type { SuggestedPromptIntentDetail } from "./conversation/components/assistant-message.js"; +import "./conversation/components/changed-files.js"; import { CaffoldTaskCommand, type CommandDisclosureIntentDetail, type CommandIntentDetail, -} from "./conversation/components/command.ts"; +} from "./conversation/components/command.js"; import { CaffoldTaskCommandGroup, type CommandGroupDisclosureIntentDetail, -} from "./conversation/components/command-group.ts"; -import "./conversation/components/markdown.ts"; -import type { MarkdownRenderedDetail } from "./conversation/components/markdown.ts"; -import "./conversation/components/message-attachments.ts"; -import "./conversation/components/older-history.ts"; +} from "./conversation/components/command-group.js"; +import "./conversation/components/markdown.js"; +import type { MarkdownRenderedDetail } from "./conversation/components/markdown.js"; +import "./conversation/components/message-attachments.js"; +import "./conversation/components/older-history.js"; import type { CaffoldTaskOlderHistory, OlderHistoryIntentDetail, -} from "./conversation/components/older-history.ts"; -import "./conversation/components/user-message.ts"; -import type { UploadProgress } from "./conversation/components/user-message.ts"; +} from "./conversation/components/older-history.js"; +import "./conversation/components/user-message.js"; +import type { UploadProgress } from "./conversation/components/user-message.js"; import { CaffoldTaskWorkDetails, type WorkDetailsDisclosureIntentDetail, -} from "./conversation/components/work-details.ts"; +} from "./conversation/components/work-details.js"; import { type ConversationEntry, conversationEntries, type ThinkingEntry, -} from "./conversation/entries.ts"; +} from "./conversation/entries.js"; // What the Task screen hands the conversation: the Task, its events, how far // its history has been read, and how the last change arrived. `updateKind` diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.test.js index 991e064e..eeea08c5 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { activeTurnDuration, activeTurnPresentation } from "./active-turn.ts"; +import { activeTurnDuration, activeTurnPresentation } from "./active-turn.js"; test("work Caffold has no surface for says what it is while it runs", () => { const task = activeTask(); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.ts index 1df8911b..e1d8b666 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.ts @@ -1,6 +1,6 @@ -import type { Task, TaskEvent } from "#app/api.ts"; -import { taskActiveFlagLabel } from "#tasks/runtime-state.ts"; -import { formatDuration } from "#tasks/task-format.ts"; +import type { Task, TaskEvent } from "#app/api.js"; +import { taskActiveFlagLabel } from "#tasks/runtime-state.js"; +import { formatDuration } from "#tasks/task-format.js"; export interface ActiveTurnPresentation { startedMs: number | null; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.test.js index cef68849..fa12149e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.test.js @@ -6,7 +6,7 @@ import { commandResult, groupFinishedCommands, isFinishedCommand, -} from "./command-runs.ts"; +} from "./command-runs.js"; test("folds two or more finished commands in a row and leaves one alone", () => { const first = command("a", "completed"); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.ts index 2df6ba0b..e8dd7099 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.ts @@ -1,5 +1,5 @@ -import type { TaskEvent } from "#app/api.ts"; -import { eventIdentityKey } from "#tasks/task-events.ts"; +import type { TaskEvent } from "#app/api.js"; +import { eventIdentityKey } from "#tasks/task-events.js"; export type CommandRunSegment = | { group: TaskEvent[]; event?: undefined } diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.ts index 8d99aade..f6bc0b2d 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.ts @@ -1,6 +1,6 @@ import { html, LitElement, type PropertyValues } from "lit"; -import { busySpin } from "#app/component-styles.ts"; -import { activeTurnDuration } from "../active-turn.ts"; +import { busySpin } from "#app/component-styles.js"; +import { activeTurnDuration } from "../active-turn.js"; // The turn in progress: how long it has run and what it is doing. Its clock // ticks only while the conversation is on screen. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.test.js index 6263bd5f..ac172bd7 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.test.js @@ -4,9 +4,9 @@ import { installCustomElementUnitRegistry } from "../../../../../../../../tests/ import { templateHtml } from "../../../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./approval.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { ScrollSurfaces } = await import("#app/scroll-scope.ts"); +await import("./approval.js"); +const { ActionHints } = await import("#app/action-hints.js"); +const { ScrollSurfaces } = await import("#app/scroll-scope.js"); const Approval = registry.element("caffold-task-approval"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.ts index f2ebd927..6bd2d7b2 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.ts @@ -4,14 +4,14 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import type { ApprovalTool, TaskEventPayload } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { ApprovalTool, TaskEventPayload } from "#app/api.js"; import { emptyScrollSurfaceScope, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { formatDecision } from "#tasks/task-format.ts"; +} from "#app/scroll-scope.js"; +import { formatDecision } from "#tasks/task-format.js"; export interface ApprovalIntentDetail { threadId: string; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.test.js index 8c7a87ac..b0529576 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.test.js @@ -4,10 +4,10 @@ import test, { after } from "node:test"; import { installCustomElementUnitRegistry, } from "../../../../../../../../tests/support/custom-element-unit.js"; -import { formatDate } from "../../../../../task-format.ts"; +import { formatDate } from "../../../../../task-format.js"; const registry = installCustomElementUnitRegistry(); -await import("./assistant-message.ts"); +await import("./assistant-message.js"); const message = registry.element("caffold-task-assistant-message").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.ts index 25e261be..b6f09efd 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.ts @@ -2,23 +2,23 @@ import { html, LitElement, nothing } from "lit"; import { type ActionHintPull, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { TaskEvent } from "#app/api.ts"; -import type { ScrollSurfacePull } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/action-hints.js"; +import type { TaskEvent } from "#app/api.js"; +import type { ScrollSurfacePull } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import { type AssistantMessagePhase, assistantMessagePhase, -} from "#tasks/task-events.ts"; -import { formatDate, taskEventObservedMs } from "#tasks/task-format.ts"; -import "./assistant-message/components/copy-button.ts"; -import type { CaffoldTaskAssistantMessageCopyButton } from "./assistant-message/components/copy-button.ts"; -import "./assistant-message/components/suggested-prompts.ts"; -import type { CaffoldTaskAssistantMessageSuggestedPrompts } from "./assistant-message/components/suggested-prompts.ts"; -import "./markdown.ts"; -import type { CaffoldTaskMarkdown } from "./markdown.ts"; +} from "#tasks/task-events.js"; +import { formatDate, taskEventObservedMs } from "#tasks/task-format.js"; +import "./assistant-message/components/copy-button.js"; +import type { CaffoldTaskAssistantMessageCopyButton } from "./assistant-message/components/copy-button.js"; +import "./assistant-message/components/suggested-prompts.js"; +import type { CaffoldTaskAssistantMessageSuggestedPrompts } from "./assistant-message/components/suggested-prompts.js"; +import "./markdown.js"; +import type { CaffoldTaskMarkdown } from "./markdown.js"; -export type { SuggestedPromptIntentDetail } from "./assistant-message/components/suggested-prompts.ts"; +export type { SuggestedPromptIntentDetail } from "./assistant-message/components/suggested-prompts.js"; /** * What the agent said, drawn the same way wherever the conversation shows it. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.test.js index 98ad8f76..f79eb735 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./copy-button.ts"); +await import("./copy-button.js"); const copyButton = registry .element("caffold-task-assistant-message-copy-button") .prototype; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.ts index b7f4445a..37f7fa01 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.ts @@ -3,8 +3,8 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/action-hints.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; const COPY_FEEDBACK_DURATION_MS = 1_800; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.test.js index 657d9104..84d9c2fe 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.test.js @@ -7,8 +7,8 @@ import { import { templateHtml } from "../../../../../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./suggested-prompts.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./suggested-prompts.js"); +const { ActionHints } = await import("#app/action-hints.js"); const suggestedPrompts = registry .element("caffold-task-assistant-message-suggested-prompts") .prototype; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.ts index f1d6a572..81532e00 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.ts @@ -4,9 +4,9 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import type { TaskEventPayload } from "#app/api.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/action-hints.js"; +import type { TaskEventPayload } from "#app/api.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; export interface SuggestedPromptIntentDetail { threadId: string; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.ts index 7510012c..8b92e058 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.ts @@ -1,6 +1,6 @@ import { html, LitElement, nothing } from "lit"; import { repeat } from "lit/directives/repeat.js"; -import type { TaskFilePath } from "#tasks/task-format.ts"; +import type { TaskFilePath } from "#tasks/task-format.js"; // The files a change touched, one row each, kept by identity as the list // grows. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.test.js index 05e8bf12..db0fe2e8 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.test.js @@ -7,10 +7,10 @@ import { import { templateHtml } from "../../../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./command-group.ts"); +await import("./command-group.js"); const CommandGroup = registry.element("caffold-task-command-group"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { DrawnRefs } = await import("#components/drawn-refs.ts"); +const { ActionHints } = await import("#app/action-hints.js"); +const { DrawnRefs } = await import("#components/drawn-refs.js"); const commandGroup = CommandGroup.prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.ts index 64e1d073..d2d003a8 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.ts @@ -5,16 +5,16 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { TaskEvent } from "#app/api.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import { eventIdentityKey } from "#tasks/task-events.ts"; -import { formatDate, taskEventObservedMs } from "#tasks/task-format.ts"; -import { commandGroupIdentity, isCommandEvent } from "../command-runs.ts"; -import "./command.ts"; -import type { CaffoldTaskCommand } from "./command.ts"; -import { commandGroupPresentation } from "./command-group/model.ts"; +} from "#app/action-hints.js"; +import type { TaskEvent } from "#app/api.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import { eventIdentityKey } from "#tasks/task-events.js"; +import { formatDate, taskEventObservedMs } from "#tasks/task-format.js"; +import { commandGroupIdentity, isCommandEvent } from "../command-runs.js"; +import "./command.js"; +import type { CaffoldTaskCommand } from "./command.js"; +import { commandGroupPresentation } from "./command-group/model.js"; const CHEVRON = "task-command-group-chevron-icon"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.test.js index dbc0f7b4..193175c7 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { commandGroupPresentation } from "./model.ts"; +import { commandGroupPresentation } from "./model.js"; test("says how many commands ran and how many did not succeed", () => { assert.deepEqual( diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.ts index 06709a81..f9db6df8 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.ts @@ -1,5 +1,5 @@ -import type { TaskEvent } from "#app/api.ts"; -import { commandResult, isCommandEvent } from "../../command-runs.ts"; +import type { TaskEvent } from "#app/api.js"; +import { commandResult, isCommandEvent } from "../../command-runs.js"; // The one line a folded group shows: how many commands ran, and how many of // them did not succeed, since those are what a reader opens the group for. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.ts index 4e01b4ba..add4462f 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.ts @@ -3,15 +3,15 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import type { TaskEvent } from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/action-hints.js"; +import type { TaskEvent } from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; import { type CommandPresentation, commandPresentation, -} from "./command/model.ts"; +} from "./command/model.js"; const CHEVRON = "task-command-disclosure-chevron-icon"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.test.js index 6f108486..377a642c 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.test.js @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { commandPresentation } from "./model.ts"; +import { commandPresentation } from "./model.js"; const event = (status, payload = {}) => ({ id: "event_command", diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.ts index 4cfc7d1f..8becc106 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.ts @@ -1,12 +1,12 @@ -import type { TaskEvent } from "#app/api.ts"; -import { eventIdentityKey } from "#tasks/task-events.ts"; +import type { TaskEvent } from "#app/api.js"; +import { eventIdentityKey } from "#tasks/task-events.js"; import { formatDate, formatDuration, formatStatus, taskEventObservedMs, -} from "#tasks/task-format.ts"; -import { commandResult, isFinishedCommand } from "../../command-runs.ts"; +} from "#tasks/task-format.js"; +import { commandResult, isFinishedCommand } from "../../command-runs.js"; export type CommandPresentation = ReturnType; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.test.js index 218df8e2..5a45d39b 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./markdown.ts"); +await import("./markdown.js"); const markdown = registry.element("caffold-task-markdown").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.ts index 110bcb0f..56a7600e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.ts @@ -8,19 +8,19 @@ import { linkActionHintTarget, matchesLinkActionHintBinding, mergeActionHintScopes, -} from "#app/action-hint-scope.ts"; -import { ACTION_HINT_ACTION, type ActionHintPull } from "#app/action-hints.ts"; -import type { TaskFileLink } from "#app/api.ts"; -import { parseLinkRoute, routeUrl } from "#app/navigation-routes.ts"; +} from "#app/action-hint-scope.js"; +import { ACTION_HINT_ACTION, type ActionHintPull } from "#app/action-hints.js"; +import type { TaskFileLink } from "#app/api.js"; +import { parseLinkRoute, routeUrl } from "#app/navigation-routes.js"; import { emptyScrollSurfaceScope, hasScrollLayoutBox, mergeScrollSurfaceScopes, type ScrollSurfacePull, type ScrollSurfaceScope, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { CaffoldTaskMarkdownCodeBlock } from "./markdown/components/code-block.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { CaffoldTaskMarkdownCodeBlock } from "./markdown/components/code-block.js"; const MARKED_IMPORT = "https://esm.sh/marked@15.0.12"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.test.js index 823b3ca8..1adda10a 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.test.js @@ -7,9 +7,9 @@ import { import { templateHtml } from "../../../../../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./code-block.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { ScrollSurfaces } = await import("#app/scroll-scope.ts"); +await import("./code-block.js"); +const { ActionHints } = await import("#app/action-hints.js"); +const { ScrollSurfaces } = await import("#app/scroll-scope.js"); const codeBlock = registry.element("caffold-task-markdown-code-block").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.ts index 2cb158e9..c039e1ac 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.ts @@ -3,10 +3,10 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/action-hints.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; const COPY_FEEDBACK_DURATION_MS = 1_800; const MARKDOWN_FENCE_LABELS = new Set(["markdown", "md"]); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.test.js index 7e16d7d6..f8694040 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.test.js @@ -7,9 +7,9 @@ import { const registry = installCustomElementUnitRegistry(); const { TASK_IMAGE_PREVIEW_EVENT } = await import( - "../../../../../components/image-preview-dialog.ts" + "../../../../../components/image-preview-dialog.js" ); -await import("./message-attachments.ts"); +await import("./message-attachments.js"); const Attachments = registry.element("caffold-task-message-attachments"); const attachments = Attachments.prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.ts index 506281c9..dbb47e4e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.ts @@ -4,12 +4,12 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { fetchInChunks } from "#app/chunked-fetch.ts"; -import { type DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import { requestTaskImagePreview } from "#tasks/components/image-preview-dialog.ts"; +} from "#app/action-hints.js"; +import { fetchInChunks } from "#app/chunked-fetch.js"; +import { type DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import { requestTaskImagePreview } from "#tasks/components/image-preview-dialog.js"; // A picture is fetched when it comes this near view, in the viewport and in // the conversation that scrolls it: the distance Chrome itself keeps for a diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.test.js index baa8cf08..c6c97610 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.test.js @@ -4,8 +4,8 @@ import { installCustomElementUnitRegistry } from "../../../../../../../../tests/ import { templateHtml } from "../../../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./older-history.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./older-history.js"); +const { ActionHints } = await import("#app/action-hints.js"); const olderHistory = registry.element("caffold-task-older-history").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.ts index 8b852569..8a1d1bf9 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.ts @@ -3,8 +3,8 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import "#components/loading-text.ts"; +} from "#app/action-hints.js"; +import "#components/loading-text.js"; // `retry` says the request follows a failed one. export interface OlderHistoryIntentDetail { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.test.js index 2d25bf5f..48eac209 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.test.js @@ -7,7 +7,7 @@ import { import { templateHtml } from "../../../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./user-message.ts"); +await import("./user-message.js"); const message = registry.element("caffold-task-user-message").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts index 63ef1cef..4aba2d03 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts @@ -1,16 +1,16 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; -import type { ActionHintPull } from "#app/action-hints.ts"; -import type { TaskUploadLine } from "#app/api.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +import type { ActionHintPull } from "#app/action-hints.js"; +import type { TaskUploadLine } from "#app/api.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import { PROMPT_SUBMISSION_STATE, type PromptSubmissionState, -} from "#tasks/runtime-state.ts"; -import "./message-attachments.ts"; +} from "#tasks/runtime-state.js"; +import "./message-attachments.js"; import type { CaffoldTaskMessageAttachments, MessageAttachment, -} from "./message-attachments.ts"; +} from "./message-attachments.js"; const { UPLOADING, SENDING, ACCEPTED, OUTCOME_UNKNOWN } = PROMPT_SUBMISSION_STATE; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.test.js index 1decfb51..661c8331 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./work-details.ts"); +await import("./work-details.js"); const workDetails = registry.element("caffold-task-work-details").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts index 89709a1e..a1835cf7 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts @@ -6,41 +6,41 @@ import { ActionHints, emptyActionHintScope, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { TaskEvent } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { TaskEvent } from "#app/api.js"; import { emptyScrollSurfaceScope, mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; import { eventIdentityKey, fileChangePathPresentations, sortEventsChronologically, taskEventAnchorMs, taskEventPositionIndex, -} from "#tasks/task-events.ts"; +} from "#tasks/task-events.js"; import { formatDate, formatStatus, type TaskFilePath, taskEventObservedMs, toolCallPresentation, -} from "#tasks/task-format.ts"; +} from "#tasks/task-format.js"; import { commandGroupIdentity, groupFinishedCommands, -} from "../command-runs.ts"; -import "./assistant-message.ts"; -import type { CaffoldTaskAssistantMessage } from "./assistant-message.ts"; -import "./changed-files.ts"; -import "./command.ts"; -import type { CaffoldTaskCommand } from "./command.ts"; -import "./command-group.ts"; -import type { CaffoldTaskCommandGroup } from "./command-group.ts"; +} from "../command-runs.js"; +import "./assistant-message.js"; +import type { CaffoldTaskAssistantMessage } from "./assistant-message.js"; +import "./changed-files.js"; +import "./command.js"; +import type { CaffoldTaskCommand } from "./command.js"; +import "./command-group.js"; +import type { CaffoldTaskCommandGroup } from "./command-group.js"; const CHEVRON = "task-work-details-chevron-icon"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.test.js index 7cf41b4e..bd73b743 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { conversationEntries, conversationEventEntry } from "./entries.ts"; +import { conversationEntries, conversationEventEntry } from "./entries.js"; test("hands the turn in progress what it is doing and since when", () => { const task = activeTask(); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.ts index 107c507b..ed4d615c 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.ts @@ -5,12 +5,12 @@ import type { TaskFileLink, TaskMessageContent, TaskUploadLine, -} from "#app/api.ts"; +} from "#app/api.js"; import { isTaskActivelyWorking, type PromptSubmissionState, promptSubmissionState, -} from "#tasks/runtime-state.ts"; +} from "#tasks/runtime-state.js"; import { type AssistantMessagePhase, assistantMessagePhase, @@ -26,7 +26,7 @@ import { sortEventsChronologically, taskEventAnchorMs, taskEventPositionIndex, -} from "#tasks/task-events.ts"; +} from "#tasks/task-events.js"; import { effectiveTaskFileRoot, formatDate, @@ -35,14 +35,14 @@ import { type TaskFilePath, taskEventObservedMs, toolCallPresentation, -} from "#tasks/task-format.ts"; -import { activeTurnPresentation } from "./active-turn.ts"; +} from "#tasks/task-format.js"; +import { activeTurnPresentation } from "./active-turn.js"; import { type CommandRunSegment, commandGroupIdentity, groupFinishedCommands, -} from "./command-runs.ts"; -import type { MessageAttachment } from "./components/message-attachments.ts"; +} from "./command-runs.js"; +import type { MessageAttachment } from "./components/message-attachments.js"; // The entries a conversation shows, in order, each with what it draws. An // entry's `identity`, which comes from the backend's event identity, keeps its diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.test.js index adba2d9c..de424dd2 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.test.js @@ -7,10 +7,10 @@ import { import { templateHtml } from "../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./current-plan.ts"); +await import("./current-plan.js"); const CurrentPlan = registry.element("caffold-task-current-plan"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); +const { ActionHints } = await import("#app/action-hints.js"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); after(() => registry.restore()); test("passes through the status popover and document dialog keyboard contexts", () => { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts index 0b57bf26..034f3c4f 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts @@ -3,20 +3,20 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { getCurrentPlan } from "#app/api.ts"; +} from "#app/action-hints.js"; +import { getCurrentPlan } from "#app/api.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, mergeKeyboardNavigationContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import type { WorkspaceLiveUpdates } from "#app/pages/(task-workspace)/live-updates.ts"; -import { subscribeToWatch, watchChangeAffectsPath } from "#app/watch.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "./current-plan/components/document-dialog.ts"; -import type { CaffoldCurrentPlanDocumentDialog } from "./current-plan/components/document-dialog.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import type { WorkspaceLiveUpdates } from "#app/pages/(task-workspace)/live-updates.js"; +import { subscribeToWatch, watchChangeAffectsPath } from "#app/watch.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "./current-plan/components/document-dialog.js"; +import type { CaffoldCurrentPlanDocumentDialog } from "./current-plan/components/document-dialog.js"; import { CURRENT_PLAN_NODE, type CurrentPlanNode, @@ -26,7 +26,7 @@ import { currentPlanPresentation, currentPlanTransitionAllowed, normalizeCurrentPlanProjection, -} from "./current-plan/model.ts"; +} from "./current-plan/model.js"; // The Task the plan belongs to: its thread, the folder its plan files are // read from, and the root its document paths are shown from. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.test.js index 8dfc30b5..d9d12a6e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./document-dialog.ts"); +await import("./document-dialog.js"); const DocumentDialog = registry.element("caffold-current-plan-document-dialog"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts index 9c411aff..6315b366 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts @@ -3,17 +3,17 @@ import { ACTION_HINT_ACTION, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import { readFile } from "#app/api.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { emptyScrollSurfaceScope } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import "#components/markdown-preview.ts"; -import type { CaffoldMarkdownPreview } from "#components/markdown-preview.ts"; -import type { CurrentPlanDocument } from "../model.ts"; +} from "#app/action-hints.js"; +import { readFile } from "#app/api.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { emptyScrollSurfaceScope } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import "#components/markdown-preview.js"; +import type { CaffoldMarkdownPreview } from "#components/markdown-preview.js"; +import type { CurrentPlanDocument } from "../model.js"; // What the Current Plan asks the dialog to show. `rootPath` is the Task root // that an image address starting with "/" is read from. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.test.js index 51c7ae82..32a6c89c 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.test.js @@ -8,7 +8,7 @@ import { currentPlanPresentation, currentPlanTransitionAllowed, normalizeCurrentPlanProjection, -} from "./model.ts"; +} from "./model.js"; test("current-plan document labels are relative only inside the Task project root", () => { assert.equal( diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.ts index 03fc4138..dae47d06 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.ts @@ -1,4 +1,4 @@ -import { presentTaskFilePath } from "#tasks/task-format.ts"; +import { presentTaskFilePath } from "#tasks/task-format.js"; // The graph tracks only whether a projection read is in flight. The accepted // projection, the latest read failure, and Watch availability stay orthogonal: diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.test.js index c07d0139..aa9de381 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./markdown-preview-dialog.ts"); +await import("./markdown-preview-dialog.js"); const PreviewDialog = registry.element("caffold-task-markdown-preview-dialog"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts index 65fb8cc5..1ef901b2 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts @@ -3,14 +3,14 @@ import { ACTION_HINT_ACTION, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { emptyScrollSurfaceScope } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/markdown-preview.ts"; -import type { CaffoldMarkdownPreview } from "#components/markdown-preview.ts"; +} from "#app/action-hints.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { emptyScrollSurfaceScope } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/markdown-preview.js"; +import type { CaffoldMarkdownPreview } from "#components/markdown-preview.js"; // What a Markdown preview opens with: the Markdown, and the control focus // returns to when the preview closes. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.test.js index 9893ea2e..0e382404 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.test.js @@ -7,10 +7,10 @@ import { import { templateHtml } from "../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./permission-instructions-dialog.ts"); +await import("./permission-instructions-dialog.js"); const Dialog = registry.element("caffold-task-permission-instructions-dialog"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); +const { ActionHints } = await import("#app/action-hints.js"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); after(() => registry.restore()); function draw(state) { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts index b28547d2..88f7fb29 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts @@ -1,15 +1,15 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; import { forgetTaskPermissionInstructions, getTaskPermissionInstructions, -} from "#app/api.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { emptyScrollSurfaceScope } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import { formatDate } from "#tasks/task-format.ts"; +} from "#app/api.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { emptyScrollSurfaceScope } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import { formatDate } from "#tasks/task-format.js"; const EMPTY_MESSAGE = "Nothing yet. A prompt that says what this Task may or may not do is kept here while it runs under Ask Jev first."; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts index a69b52c6..83d5eea6 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts @@ -2,15 +2,15 @@ import { html, LitElement, nothing } from "lit"; import { type ActionHintPull, emptyActionHintScope, -} from "#app/action-hints.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "./summary/components/info.ts"; +} from "#app/action-hints.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "./summary/components/info.js"; import { type CaffoldTaskDetailInfo, type TaskInfoIntent, type TaskInfoSnapshot, taskInfoSnapshot, -} from "./summary/components/info.ts"; +} from "./summary/components/info.js"; export type { TaskInfoSnapshot }; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.test.js index 52277028..54deb583 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.test.js @@ -7,12 +7,12 @@ import { import { templateHtml } from "../../../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -const { taskInfoSnapshot } = await import("./info.ts"); +const { taskInfoSnapshot } = await import("./info.js"); const info = registry.element("caffold-task-detail-info").prototype; const actions = registry.element( "caffold-task-detail-info-actions", ).prototype; -const { ActionHints } = await import("#app/action-hints.ts"); +const { ActionHints } = await import("#app/action-hints.js"); after(() => registry.restore()); // One of the components with the state and stand-ins a test gives it, drawn diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts index e067a37c..2e893c4e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts @@ -4,26 +4,26 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { Task, TaskWorktree } from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; +} from "#app/action-hints.js"; +import type { Task, TaskWorktree } from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#tasks/components/task-status-chip.ts"; -import { formatTaskStatus, taskStatusView } from "#tasks/runtime-state.ts"; -import { shortId } from "#tasks/task-format.ts"; -import { taskThreadId } from "#tasks/task-list-model.ts"; -import "./info/components/actions.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#tasks/components/task-status-chip.js"; +import { formatTaskStatus, taskStatusView } from "#tasks/runtime-state.js"; +import { shortId } from "#tasks/task-format.js"; +import { taskThreadId } from "#tasks/task-list-model.js"; +import "./info/components/actions.js"; import type { CaffoldTaskDetailInfoActions, TaskInfoActionIntent, TaskInfoActionsSnapshot, -} from "./info/components/actions.ts"; +} from "./info/components/actions.js"; let taskInfoInstanceId = 0; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts index 1501fbd7..a9ee4be1 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts @@ -3,13 +3,13 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { isTaskTransportStale, type TaskTransportState, taskThreadStatusType, -} from "#tasks/runtime-state.ts"; -import { type ListedTask, taskThreadId } from "#tasks/task-list-model.ts"; +} from "#tasks/runtime-state.js"; +import { type ListedTask, taskThreadId } from "#tasks/task-list-model.js"; /** The approval mode Caffold answers under, which is the one Jev judges for. */ const REVIEWED_PERMISSION_MODE = "caffold:ask-jev-first"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.test.js index 5becb32e..0364e254 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.test.js @@ -7,10 +7,10 @@ import { import { templateHtml } from "../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const TaskDetail = registry.element("caffold-task-detail"); const taskDetail = TaskDetail.prototype; -const { ActionHints } = await import("#app/action-hints.ts"); +const { ActionHints } = await import("#app/action-hints.js"); after(() => registry.restore()); // A Task screen with only the state its drawing reads, and no conversation. diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts index 717065d2..ae2278f9 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts @@ -4,7 +4,7 @@ import { ActionHints, emptyActionHintScope, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { archiveTask, discardTaskUploads, @@ -24,29 +24,29 @@ import { taskAlreadyActive, taskResponseError, uploadTaskFile, -} from "#app/api.ts"; -import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.ts"; -import { routeDomain } from "#app/navigation-routes.ts"; -import { CAFFOLD_ORIGIN_REACHABLE_EVENT } from "#app/origin-reachability.ts"; -import type { WorkspaceLiveUpdates } from "#app/pages/(task-workspace)/live-updates.ts"; +} from "#app/api.js"; +import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.js"; +import { routeDomain } from "#app/navigation-routes.js"; +import { CAFFOLD_ORIGIN_REACHABLE_EVENT } from "#app/origin-reachability.js"; +import type { WorkspaceLiveUpdates } from "#app/pages/(task-workspace)/live-updates.js"; import { emptyScrollSurfaceScope, hasScrollLayoutBox, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/loading-text.ts"; -import "../../components/composer.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/loading-text.js"; +import "../../components/composer.js"; import type { CaffoldTaskComposer, ComposerIntentDetail, ComposerSubmission, ComposerSubmitDetail, -} from "../../components/composer.ts"; -import "../../components/task-absence.ts"; +} from "../../components/composer.js"; +import "../../components/task-absence.js"; import type { CaffoldTaskAbsence, TaskAbsenceIntent, -} from "../../components/task-absence.ts"; +} from "../../components/task-absence.js"; import { classifyPromptFailure, isTaskActivelyWorking, @@ -57,70 +57,70 @@ import { type TaskTransportState, taskThreadStatusType, withPromptSubmissionState, -} from "../../runtime-state.ts"; +} from "../../runtime-state.js"; import { appendOptimisticEvent, eventIdentityKey, handoffOptimisticSubmission, optimisticUserMessageEvent, -} from "../../task-events.ts"; -import { cleanLogicalPath } from "../../task-format.ts"; +} from "../../task-events.js"; +import { cleanLogicalPath } from "../../task-format.js"; import { taskDetailThreadId, taskRepositoryPath, taskThreadId, -} from "../../task-list-model.ts"; -import "./components/broken-delete-dialog.ts"; +} from "../../task-list-model.js"; +import "./components/broken-delete-dialog.js"; import type { BrokenTaskDeleteIntent, CaffoldBrokenTaskDeleteDialog, -} from "./components/broken-delete-dialog.ts"; -import "./components/command-dialog.ts"; -import type { CaffoldTaskCommandDialog } from "./components/command-dialog.ts"; -import "./components/conversation.ts"; +} from "./components/broken-delete-dialog.js"; +import "./components/command-dialog.js"; +import type { CaffoldTaskCommandDialog } from "./components/command-dialog.js"; +import "./components/conversation.js"; import type { CaffoldTaskConversation, ConversationIntent, ConversationSnapshot, -} from "./components/conversation.ts"; -import "./components/current-plan.ts"; -import type { CaffoldTaskCurrentPlan } from "./components/current-plan.ts"; -import "./components/markdown-preview-dialog.ts"; +} from "./components/conversation.js"; +import "./components/current-plan.js"; +import type { CaffoldTaskCurrentPlan } from "./components/current-plan.js"; +import "./components/markdown-preview-dialog.js"; import type { CaffoldTaskMarkdownPreviewDialog, MarkdownPreviewRequest, -} from "./components/markdown-preview-dialog.ts"; -import "./components/permission-instructions-dialog.ts"; +} from "./components/markdown-preview-dialog.js"; +import "./components/permission-instructions-dialog.js"; import type { CaffoldTaskPermissionInstructionsDialog, TaskRequestFailedIntent, -} from "./components/permission-instructions-dialog.ts"; +} from "./components/permission-instructions-dialog.js"; import { ConversationProjection, projectionRevision, -} from "./layout/conversation.ts"; -import { ConversationHistory } from "./layout/history.ts"; +} from "./layout/conversation.js"; +import { ConversationHistory } from "./layout/history.js"; import { promptWithAttachedFiles, uploadFileNames, uploadFolderName, uploadPath, -} from "./layout/prompt-attachments.ts"; +} from "./layout/prompt-attachments.js"; import { nextPromptSubmissionNode, PROMPT_SUBMISSION_EVENT, PROMPT_SUBMISSION_NODE, type PromptSubmissionEvent, type PromptSubmissionNode, -} from "./layout/prompt-submission.ts"; -import { UploadDiscards } from "./layout/upload-discards.ts"; +} from "./layout/prompt-submission.js"; +import { UploadDiscards } from "./layout/upload-discards.js"; import { type TaskDetailOutcome, TaskDetailSession, type TaskEventMessage, type TaskStateMessage, type TaskSyncMessage, -} from "./session.ts"; +} from "./session.js"; const CLEAN_COMPOSER_CACHE_LIMIT = 6; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.test.js index 33884402..6444d23c 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.test.js @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { ConversationProjection } from "./conversation.ts"; +import { ConversationProjection } from "./conversation.js"; const item = (id, time, text = id) => ({ id, threadId: "t", type: "assistant_message", diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.ts index cf5cdb96..c99f3497 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.ts @@ -3,13 +3,13 @@ import type { TaskEvent, TaskEventPosition, TaskEventsRange, -} from "#app/api.ts"; +} from "#app/api.js"; import { applyDetailRange, applyProjectionDelta, eventIdentityKey, taskEventPosition, -} from "#tasks/task-events.ts"; +} from "#tasks/task-events.js"; interface RevisedRange extends TaskEventsRange { revision: number; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.test.js index 6f0dbb83..ede6df1e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.test.js @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { ConversationHistory } from "./history.ts"; +import { ConversationHistory } from "./history.js"; const position = (anchorMs) => ({ anchorMs, index: 0 }); function page(from, to, nextCursor = null, revision = 1) { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.ts index 656f9a87..0ad8fde4 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.ts @@ -1,6 +1,6 @@ -import type { TaskDetail, TaskEvent, TaskEventPosition } from "#app/api.ts"; -import { eventIdentityKey, taskEventPosition } from "#tasks/task-events.ts"; -import { comparePositions, projectionRevision } from "./conversation.ts"; +import type { TaskDetail, TaskEvent, TaskEventPosition } from "#app/api.js"; +import { eventIdentityKey, taskEventPosition } from "#tasks/task-events.js"; +import { comparePositions, projectionRevision } from "./conversation.js"; type HistoryState = "inactive" | "ready" | "loading" | "blocked"; type HistoryEvent = "activate" | "deactivate" | "request" | "resolve" | "block"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.test.js index 69d1be1f..a713ac63 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.test.js @@ -6,7 +6,7 @@ import { uploadFileNames, uploadFolderName, uploadPath, -} from "./prompt-attachments.ts"; +} from "./prompt-attachments.js"; test("a send's folder is named by when it was sent and four characters", () => { const values = [0, 0.5, 0.999, 0.1]; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.test.js index 63737db8..438d013b 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.test.js @@ -5,7 +5,7 @@ import { PROMPT_SUBMISSION_EVENT as EVENT, PROMPT_SUBMISSION_NODE as NODE, nextPromptSubmissionNode, -} from "./prompt-submission.ts"; +} from "./prompt-submission.js"; const ALLOWED = [ [NODE.IDLE, EVENT.SUBMIT, NODE.SENDING], diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.test.js index 2f2dd8b7..6d28f333 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.test.js @@ -6,7 +6,7 @@ import { UPLOAD_DISCARD_NODE as NODE, UploadDiscards, nextUploadDiscardNode, -} from "./upload-discards.ts"; +} from "./upload-discards.js"; const ALLOWED = [ [NODE.SENDING, EVENT.ANSWERED, NODE.SETTLED], diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.test.js index 979ded92..67ccd437 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.test.js @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; import test, { afterEach } from "node:test"; -import { TaskDetailSession } from "./session.ts"; -import { TASK_TRANSPORT_STATE } from "../../runtime-state.ts"; +import { TaskDetailSession } from "./session.js"; +import { TASK_TRANSPORT_STATE } from "../../runtime-state.js"; const originalBrowserGlobals = { document: globalThis.document, diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.ts index b364ca82..2e1f2e23 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/session.ts @@ -8,16 +8,16 @@ import { type TaskFileLink, taskAbsence, taskResponseError, -} from "#app/api.ts"; +} from "#app/api.js"; import type { LiveSubscription, LiveSubscriptionListener, -} from "#app/pages/(task-workspace)/live-updates.ts"; +} from "#app/pages/(task-workspace)/live-updates.js"; import { TASK_TRANSPORT_STATE, type TaskTransportState, -} from "../../runtime-state.ts"; -import { TaskStreamLifecycle } from "../../stream.ts"; +} from "../../runtime-state.js"; +import { TaskStreamLifecycle } from "../../stream.js"; // From a channel's opening to its first bootstrap, which the server keeps small // whatever the conversation's size. What follows has no limit here: the server diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.test.js index f182bd29..8c27b996 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./git-menu.ts"); +await import("./git-menu.js"); const gitMenu = registry.element("caffold-task-detail-git").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts b/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts index f0d41c9a..edf16445 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts @@ -3,14 +3,14 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { compactIconButton } from "#app/component-styles.ts"; +} from "#app/action-hints.js"; +import { compactIconButton } from "#app/component-styles.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; // What the menu asks the Detail layout to open. export interface GitMenuIntent { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.test.js index ccb48559..91be6b96 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.test.js @@ -6,7 +6,7 @@ import { } from "../../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./github-menu.ts"); +await import("./github-menu.js"); const githubMenu = registry.element("caffold-task-detail-github").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts b/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts index 1d085ef6..17ae6d81 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts @@ -3,14 +3,14 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { compactIconButton } from "#app/component-styles.ts"; +} from "#app/action-hints.js"; +import { compactIconButton } from "#app/component-styles.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; // What the menu asks the Detail layout to open. export interface GitHubMenuIntent { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts b/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts index 46871588..8cd9602e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts @@ -3,9 +3,9 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { compactIconButton } from "#app/component-styles.ts"; -import { inlineIcon } from "#components/icons.ts"; +} from "#app/action-hints.js"; +import { compactIconButton } from "#app/component-styles.js"; +import { inlineIcon } from "#components/icons.js"; export const TERMINAL_BUTTON_INTENT_EVENT = "caffold:task-detail-terminal-intent"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/layout.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/layout.test.js index 0b8c89e4..0a6424cd 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/layout.test.js @@ -7,7 +7,7 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const detailLayout = registry.element("caffold-detail-layout").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/layout.ts index cbd67e73..3149af49 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/layout.ts @@ -1,68 +1,68 @@ import { html, LitElement } from "lit"; -import type { ActionHintScope } from "#app/action-hint-scope.ts"; +import type { ActionHintScope } from "#app/action-hint-scope.js"; import { ACTION_HINT_ACTION, emptyActionHintScope, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { TaskDetail } from "#app/api.ts"; -import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.ts"; +} from "#app/action-hints.js"; +import type { TaskDetail } from "#app/api.js"; +import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.js"; import { routeDomain, routeMode, type SectionDetailRoute, sectionDetailRoute, -} from "#app/navigation-routes.ts"; -import type { WorkspaceLiveUpdates } from "#app/pages/(task-workspace)/live-updates.ts"; -import type { TaskStoreStatusSnapshot } from "#app/pages/(task-workspace)/task-store-status.ts"; -import { emptyScrollSurfaceScope } from "#app/scroll-scope.ts"; -import "#components/segmented-control.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import type { CaffoldSegmentedControl } from "#components/segmented-control.ts"; -import type { ComposerSubmission } from "../components/composer.ts"; +} from "#app/navigation-routes.js"; +import type { WorkspaceLiveUpdates } from "#app/pages/(task-workspace)/live-updates.js"; +import type { TaskStoreStatusSnapshot } from "#app/pages/(task-workspace)/task-store-status.js"; +import { emptyScrollSurfaceScope } from "#app/scroll-scope.js"; +import "#components/segmented-control.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import type { CaffoldSegmentedControl } from "#components/segmented-control.js"; +import type { ComposerSubmission } from "../components/composer.js"; import { type TaskTransportState, taskThreadStatusType, -} from "../runtime-state.ts"; -import { cleanLogicalPath } from "../task-format.ts"; -import { type ListedTask, taskThreadId } from "../task-list-model.ts"; -import "./(git)/layout.ts"; -import type { CaffoldTaskGitLayout } from "./(git)/layout.ts"; -import "./(github)/layout.ts"; -import type { CaffoldTaskGithubLayout } from "./(github)/layout.ts"; -import "./(review)/layout.ts"; -import type { CaffoldTaskReview } from "./(review)/layout.ts"; -import "./(section)/components/summary.ts"; -import type { CaffoldSectionDetailSummary } from "./(section)/components/summary.ts"; -import "./(section)/layout.ts"; +} from "../runtime-state.js"; +import { cleanLogicalPath } from "../task-format.js"; +import { type ListedTask, taskThreadId } from "../task-list-model.js"; +import "./(git)/layout.js"; +import type { CaffoldTaskGitLayout } from "./(git)/layout.js"; +import "./(github)/layout.js"; +import type { CaffoldTaskGithubLayout } from "./(github)/layout.js"; +import "./(review)/layout.js"; +import type { CaffoldTaskReview } from "./(review)/layout.js"; +import "./(section)/components/summary.js"; +import type { CaffoldSectionDetailSummary } from "./(section)/components/summary.js"; +import "./(section)/layout.js"; import type { CaffoldSectionDetail, DetailSection, -} from "./(section)/layout.ts"; -import "./(task)/components/summary.ts"; +} from "./(section)/layout.js"; +import "./(task)/components/summary.js"; import type { CaffoldTaskDetailSummary, TaskSummaryIntent, -} from "./(task)/components/summary.ts"; -import "./(task)/layout.ts"; +} from "./(task)/components/summary.js"; +import "./(task)/layout.js"; import type { CaffoldTaskDetail, TaskSubjectSnapshot, -} from "./(task)/layout.ts"; -import "./components/git-menu.ts"; +} from "./(task)/layout.js"; +import "./components/git-menu.js"; import type { CaffoldTaskDetailGit, GitMenuIntent, -} from "./components/git-menu.ts"; -import "./components/github-menu.ts"; +} from "./components/git-menu.js"; +import "./components/github-menu.js"; import type { CaffoldTaskDetailGithub, GitHubMenuIntent, -} from "./components/github-menu.ts"; -import "./components/terminal-button.ts"; -import type { CaffoldTaskDetailTerminal } from "./components/terminal-button.ts"; -import "./terminal/page.ts"; -import type { CaffoldTerminalPage } from "./terminal/page.ts"; +} from "./components/github-menu.js"; +import "./components/terminal-button.js"; +import type { CaffoldTaskDetailTerminal } from "./components/terminal-button.js"; +import "./terminal/page.js"; +import type { CaffoldTerminalPage } from "./terminal/page.js"; const CLEAN_REVIEW_CACHE_LIMIT = 6; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.test.js index bf38b25c..9d2ac596 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.test.js @@ -5,15 +5,15 @@ import { installCustomElementUnitRegistry, } from "../../../../../tests/support/custom-element-unit.js"; import { templateHtml } from "../../../../../tests/support/lit-template.js"; -import { TERMINAL_EFFECT, TERMINAL_EVENT, TERMINAL_NODE } from "./page/model.ts"; +import { TERMINAL_EFFECT, TERMINAL_EVENT, TERMINAL_NODE } from "./page/model.js"; const registry = installCustomElementUnitRegistry(); const { TERMINAL_PAGE_HEADER_EVENT, TERMINAL_PAGE_LEAVE_EVENT, -} = await import("./page.ts"); +} = await import("./page.js"); const terminalPage = registry.element("caffold-terminal-page").prototype; -const { ActionHints } = await import("#app/action-hints.ts"); +const { ActionHints } = await import("#app/action-hints.js"); after(() => registry.restore()); test("input reaches the shell only from the live screen", () => { diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.ts b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.ts index c6c07a1d..f196270b 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page.ts @@ -4,33 +4,33 @@ import { type ActionHintPull, ActionHints, emptyActionHintScope, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { killTerminal, openTerminal, type TaskResponseError, type TerminalSubject, -} from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon } from "#components/icons.ts"; +} from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon } from "#components/icons.js"; import { type CaffoldTerminalView, type TerminalSize, terminalInputBytes, -} from "#components/terminal-view.ts"; +} from "#components/terminal-view.js"; import { TASK_TRANSPORT_STATE, type TaskTransportState, -} from "../../runtime-state.ts"; -import "./components/special-keys.ts"; +} from "../../runtime-state.js"; +import "./components/special-keys.js"; import type { CaffoldTerminalSpecialKeys, TerminalSpecialKeyDetail, -} from "./components/special-keys.ts"; -import { TerminalConnection } from "./page/connection.ts"; -import { watchKeyboardInset } from "./page/keyboard-inset.ts"; -import { controlCharacter, specialKeySequence } from "./page/keys.ts"; +} from "./components/special-keys.js"; +import { TerminalConnection } from "./page/connection.js"; +import { watchKeyboardInset } from "./page/keyboard-inset.js"; +import { controlCharacter, specialKeySequence } from "./page/keys.js"; import { initialTerminalState, TERMINAL_EFFECT, @@ -43,7 +43,7 @@ import { type TerminalNode, type TerminalState, terminalTransition, -} from "./page/model.ts"; +} from "./page/model.js"; export const TERMINAL_PAGE_STATE_EVENT = "caffold:terminal-page-state"; export const TERMINAL_PAGE_FOCUS_RELEASE_EVENT = diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.test.js index 639bd1eb..15ec07b1 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { afterEach, test } from "node:test"; -import { TerminalConnection } from "./connection.ts"; +import { TerminalConnection } from "./connection.js"; const originalGlobals = { window: globalThis.window, diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.ts b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.ts index 46865df1..2f8441ae 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.ts @@ -1,7 +1,7 @@ // One WebSocket for one attempt to attach to a terminal. The backend closes // the socket after its last message; a close without one is a failure. -import { type TerminalSubject, terminalSocketUrl } from "#app/api.ts"; +import { type TerminalSubject, terminalSocketUrl } from "#app/api.js"; const LAST_MESSAGES = new Set(["elsewhere", "absent", "taken", "ended"]); const TAB_STORAGE_KEY = "caffold:terminal-tab"; diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.test.js index b79c7ccb..284870f9 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import { afterEach, test } from "node:test"; -import { watchKeyboardInset } from "./keyboard-inset.ts"; +import { watchKeyboardInset } from "./keyboard-inset.js"; const originalGlobals = { window: globalThis.window, diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keys.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keys.test.js index 711fede7..a4e9130d 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keys.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/keys.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { controlCharacter, specialKeySequence } from "./keys.ts"; +import { controlCharacter, specialKeySequence } from "./keys.js"; test("Esc and Tab send their own characters", () => { assert.equal(specialKeySequence("escape"), "\x1b"); diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/model.test.js b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/model.test.js index b72c8760..75e15b49 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/model.test.js +++ b/frontend/pages/(task-workspace)/tasks/(detail)/terminal/page/model.test.js @@ -8,7 +8,7 @@ import { TERMINAL_NODE, initialTerminalState, terminalTransition, -} from "./model.ts"; +} from "./model.js"; const { CONNECT, DISCONNECT, CLEAR, FOCUS, RELEASE_FOCUS, LEAVE } = TERMINAL_EFFECT; diff --git a/frontend/pages/(task-workspace)/tasks/components/active-task-list.test.js b/frontend/pages/(task-workspace)/tasks/components/active-task-list.test.js index 0a33ab30..8d495340 100644 --- a/frontend/pages/(task-workspace)/tasks/components/active-task-list.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/active-task-list.test.js @@ -7,7 +7,7 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./active-task-list.ts"); +await import("./active-task-list.js"); const List = registry.element("caffold-active-task-list"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/active-task-list.ts b/frontend/pages/(task-workspace)/tasks/components/active-task-list.ts index 6698df00..3391563d 100644 --- a/frontend/pages/(task-workspace)/tasks/components/active-task-list.ts +++ b/frontend/pages/(task-workspace)/tasks/components/active-task-list.ts @@ -3,7 +3,7 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type ActiveTask, type ActiveTaskComposerSettings, @@ -13,32 +13,32 @@ import { getTasks, reorderSection, reorderTask, -} from "#app/api.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import "#components/loading-text.ts"; -import type { WorkspaceLiveUpdates } from "../../live-updates.ts"; +} from "#app/api.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import "#components/loading-text.js"; +import type { WorkspaceLiveUpdates } from "../../live-updates.js"; import { type TaskOperationsPresentation, taskStoreOperationsPresentation, -} from "../../task-store-status.ts"; +} from "../../task-store-status.js"; import { isTaskTransportStale, type TaskTransportState, taskThreadStatusType, -} from "../runtime-state.ts"; -import { TaskStreamLifecycle, type TaskStreamRecovery } from "../stream.ts"; -import { sectionLabel } from "../task-format.ts"; -import { type ListedTask, taskThreadId } from "../task-list-model.ts"; +} from "../runtime-state.js"; +import { TaskStreamLifecycle, type TaskStreamRecovery } from "../stream.js"; +import { sectionLabel } from "../task-format.js"; +import { type ListedTask, taskThreadId } from "../task-list-model.js"; import { CaffoldActiveTaskSection, type ListedSection, type ReorderMode, type SectionIntent, type SectionSnapshot, -} from "./active-task-list/components/section.ts"; -import { moveListItemBefore } from "./active-task-list/move-list-item.ts"; +} from "./active-task-list/components/section.js"; +import { moveListItemBefore } from "./active-task-list/move-list-item.js"; -export type { ReorderMode } from "./active-task-list/components/section.ts"; +export type { ReorderMode } from "./active-task-list/components/section.js"; // The Section component in each group the list made. const groupSections = new WeakMap(); diff --git a/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.test.js b/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.test.js index 9e1f4efa..3dc77bf0 100644 --- a/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.test.js @@ -7,8 +7,8 @@ import { import { templateHtml } from "../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./section.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./section.js"); +const { ActionHints } = await import("#app/action-hints.js"); const Section = registry.element("caffold-active-task-section"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.ts b/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.ts index fae90dc5..57d517a4 100644 --- a/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.ts +++ b/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section.ts @@ -4,20 +4,20 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type TaskOperationsPresentation, taskStoreOperationsPresentation, -} from "#app/pages/(task-workspace)/task-store-status.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import { sectionLabel } from "#tasks/task-format.ts"; -import { type ListedTask, taskThreadId } from "#tasks/task-list-model.ts"; -import { moveListItemBefore } from "../move-list-item.ts"; +} from "#app/pages/(task-workspace)/task-store-status.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import { sectionLabel } from "#tasks/task-format.js"; +import { type ListedTask, taskThreadId } from "#tasks/task-list-model.js"; +import { moveListItemBefore } from "../move-list-item.js"; import { CaffoldActiveTaskRow, type TaskRowIntent, -} from "./section/components/row.ts"; +} from "./section/components/row.js"; export const ACTIVE_TASK_SECTION_INTENT_EVENT = "caffold:active-task-section-intent"; diff --git a/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.test.js b/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.test.js index 4b45610d..8f015568 100644 --- a/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.test.js @@ -7,8 +7,8 @@ import { import { templateHtml } from "../../../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./row.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./row.js"); +const { ActionHints } = await import("#app/action-hints.js"); const Row = registry.element("caffold-active-task-row"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts b/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts index 18786610..62c21237 100644 --- a/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts +++ b/frontend/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts @@ -3,18 +3,18 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import type { ActiveTaskRecovery } from "#app/api.ts"; +} from "#app/action-hints.js"; +import type { ActiveTaskRecovery } from "#app/api.js"; import { type TaskOperationsPresentation, taskStoreOperationsPresentation, -} from "#app/pages/(task-workspace)/task-store-status.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#tasks/components/task-status-chip.ts"; -import { taskStatusView, taskThreadStatusType } from "#tasks/runtime-state.ts"; -import { formatRelativeAgePresentation } from "#tasks/task-format.ts"; -import { type ListedTask, taskThreadId } from "#tasks/task-list-model.ts"; +} from "#app/pages/(task-workspace)/task-store-status.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#tasks/components/task-status-chip.js"; +import { taskStatusView, taskThreadStatusType } from "#tasks/runtime-state.js"; +import { formatRelativeAgePresentation } from "#tasks/task-format.js"; +import { type ListedTask, taskThreadId } from "#tasks/task-list-model.js"; export const ACTIVE_TASK_ROW_INTENT_EVENT = "caffold:active-task-row-intent"; diff --git a/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.test.js b/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.test.js index f0e684c5..1625ab93 100644 --- a/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.test.js @@ -6,7 +6,7 @@ import { } from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./archived-delete-dialog.ts"); +await import("./archived-delete-dialog.js"); const deleteDialog = registry.element("caffold-task-archived-delete-dialog").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts b/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts index 02a71517..93a496fa 100644 --- a/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts @@ -1,9 +1,9 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { taskThreadId } from "../task-list-model.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { taskThreadId } from "../task-list-model.js"; export const TASK_ARCHIVED_DELETE_CONFIRMED_EVENT = "caffold:task-archived-delete-confirmed"; diff --git a/frontend/pages/(task-workspace)/tasks/components/archived-task-list.test.js b/frontend/pages/(task-workspace)/tasks/components/archived-task-list.test.js index 940c65a7..fcebcfc5 100644 --- a/frontend/pages/(task-workspace)/tasks/components/archived-task-list.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/archived-task-list.test.js @@ -7,8 +7,8 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./archived-task-list.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./archived-task-list.js"); +const { ActionHints } = await import("#app/action-hints.js"); const Archived = registry.element("caffold-archived-task-list"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/archived-task-list.ts b/frontend/pages/(task-workspace)/tasks/components/archived-task-list.ts index 990682c9..0a1c4d52 100644 --- a/frontend/pages/(task-workspace)/tasks/components/archived-task-list.ts +++ b/frontend/pages/(task-workspace)/tasks/components/archived-task-list.ts @@ -4,7 +4,7 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type ActiveTask, type ActiveTaskTopPlacement, @@ -12,21 +12,21 @@ import { getArchivedTasks, restoreTask, type Task, -} from "#app/api.ts"; -import { busySpin } from "#app/component-styles.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; +} from "#app/api.js"; +import { busySpin } from "#app/component-styles.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; import { type TaskOperationsPresentation, taskStoreOperationsPresentation, -} from "../../task-store-status.ts"; +} from "../../task-store-status.js"; import { isTaskTransportStale, TASK_TRANSPORT_STATE, type TaskTransportState, taskStatusView, -} from "../runtime-state.ts"; -import { formatRelativeAgePresentation } from "../task-format.ts"; +} from "../runtime-state.js"; +import { formatRelativeAgePresentation } from "../task-format.js"; import { groupTasksByRepository, mergeTaskListPage, @@ -35,8 +35,8 @@ import { taskThreadId, taskWorktreeLabel, upsertTask, -} from "../task-list-model.ts"; -import "./task-status-chip.ts"; +} from "../task-list-model.js"; +import "./task-status-chip.js"; export const ARCHIVED_TASK_LIST_INITIAL_SETTLED_EVENT = "caffold:archived-task-list-initial-settled"; diff --git a/frontend/pages/(task-workspace)/tasks/components/composer.test.js b/frontend/pages/(task-workspace)/tasks/components/composer.test.js index 229a52f3..6134b6a1 100644 --- a/frontend/pages/(task-workspace)/tasks/components/composer.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/composer.test.js @@ -7,8 +7,8 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./composer.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./composer.js"); +const { ActionHints } = await import("#app/action-hints.js"); const Composer = registry.element("caffold-task-composer"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/composer.ts b/frontend/pages/(task-workspace)/tasks/components/composer.ts index 203c47da..57af6c3a 100644 --- a/frontend/pages/(task-workspace)/tasks/components/composer.ts +++ b/frontend/pages/(task-workspace)/tasks/components/composer.ts @@ -4,33 +4,33 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import { getVoiceStatus, transcribeVoice, type VoiceStatus } from "#app/api.ts"; -import { busySpin } from "#app/component-styles.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import { cleanLogicalPath } from "../task-format.ts"; -import "./composer/components/attachments.ts"; +} from "#app/action-hints.js"; +import { getVoiceStatus, transcribeVoice, type VoiceStatus } from "#app/api.js"; +import { busySpin } from "#app/component-styles.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import { cleanLogicalPath } from "../task-format.js"; +import "./composer/components/attachments.js"; import type { AttachmentRemoveDetail, CaffoldTaskComposerAttachments, ComposerAttachment, -} from "./composer/components/attachments.ts"; -import "./composer/components/context-usage.ts"; -import type { CaffoldTaskContextUsage } from "./composer/components/context-usage.ts"; -import "./composer/components/voice-level-meter.ts"; -import type { CaffoldVoiceLevelMeter } from "./composer/components/voice-level-meter.ts"; +} from "./composer/components/attachments.js"; +import "./composer/components/context-usage.js"; +import type { CaffoldTaskContextUsage } from "./composer/components/context-usage.js"; +import "./composer/components/voice-level-meter.js"; +import type { CaffoldVoiceLevelMeter } from "./composer/components/voice-level-meter.js"; import { formatRecordingDuration, VoiceRecorder, voiceCaptureSupport, -} from "./composer/voice-recorder.ts"; -import "./task-turn-options.ts"; +} from "./composer/voice-recorder.js"; +import "./task-turn-options.js"; import type { CaffoldTaskTurnOptions, TurnOptionsContext, TurnSubmissionOptions, -} from "./task-turn-options.ts"; +} from "./task-turn-options.js"; // Where the Composer sends and how it reads. A follow-up Composer belongs to // one Task; `provider` through `permissionMode` are what that Task or the diff --git a/frontend/pages/(task-workspace)/tasks/components/composer/components/attachments.ts b/frontend/pages/(task-workspace)/tasks/components/composer/components/attachments.ts index 85ab4543..3207f2f4 100644 --- a/frontend/pages/(task-workspace)/tasks/components/composer/components/attachments.ts +++ b/frontend/pages/(task-workspace)/tasks/components/composer/components/attachments.ts @@ -4,9 +4,9 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { inlineIcon } from "#components/icons.ts"; -import { requestTaskImagePreview } from "../../image-preview-dialog.ts"; +} from "#app/action-hints.js"; +import { inlineIcon } from "#components/icons.js"; +import { requestTaskImagePreview } from "../../image-preview-dialog.js"; // A file a draft will send. A picture every agent can be shown also carries // its contents as a data URL. diff --git a/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.test.js b/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.test.js index aee1e9b6..8514b489 100644 --- a/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.test.js @@ -7,8 +7,8 @@ import { import { templateHtml } from "../../../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./context-usage.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./context-usage.js"); +const { ActionHints } = await import("#app/action-hints.js"); const ContextUsage = registry.element("caffold-task-context-usage"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts b/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts index 74d9454a..b90a67c1 100644 --- a/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts +++ b/frontend/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts @@ -3,7 +3,7 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; // How full a Task's context is, as its agent last said. export interface ContextUsage { diff --git a/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.test.js b/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.test.js index d6170f46..98542c8e 100644 --- a/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.test.js @@ -8,7 +8,7 @@ import { resampleLinear, VoiceLevelTracker, VoiceRecorder, -} from "./voice-recorder.ts"; +} from "./voice-recorder.js"; test("formats recording durations without an unnecessary leading hour digit", () => { assert.equal(formatRecordingDuration(0), "0:00"); diff --git a/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.ts b/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.ts index 0fb4db35..f9ea7475 100644 --- a/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.ts +++ b/frontend/pages/(task-workspace)/tasks/components/composer/voice-recorder.ts @@ -175,7 +175,7 @@ export class VoiceRecorder { throw new Error("This browser does not support AudioWorklet capture."); } await context.audioWorklet.addModule( - new URL("./voice-worklet.ts", import.meta.url), + new URL("./voice-worklet.js", import.meta.url), ); await context.resume(); this.source = context.createMediaStreamSource(this.stream); diff --git a/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.test.js b/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.test.js index ce099b16..143dc3a8 100644 --- a/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.test.js @@ -7,9 +7,9 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./image-preview-dialog.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); +await import("./image-preview-dialog.js"); +const { ActionHints } = await import("#app/action-hints.js"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); const ImageDialog = registry.element("caffold-task-image-preview-dialog"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts b/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts index f941631f..fdb3fe54 100644 --- a/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/components/image-preview-dialog.ts @@ -1,11 +1,11 @@ import { html, LitElement, nothing } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; -import { fetchInChunks } from "#app/chunked-fetch.ts"; -import { KeyboardContexts } from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; +import { fetchInChunks } from "#app/chunked-fetch.js"; +import { KeyboardContexts } from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; export const TASK_IMAGE_PREVIEW_EVENT = "caffold:task-image-preview"; diff --git a/frontend/pages/(task-workspace)/tasks/components/navigator.test.js b/frontend/pages/(task-workspace)/tasks/components/navigator.test.js index c870d3bf..c49e7ced 100644 --- a/frontend/pages/(task-workspace)/tasks/components/navigator.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/navigator.test.js @@ -7,10 +7,10 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./navigator.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); -const { ScrollSurfaces } = await import("#app/scroll-scope.ts"); +await import("./navigator.js"); +const { ActionHints } = await import("#app/action-hints.js"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); +const { ScrollSurfaces } = await import("#app/scroll-scope.js"); const Navigator = registry.element("caffold-task-navigator"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/navigator.ts b/frontend/pages/(task-workspace)/tasks/components/navigator.ts index d721a14c..2e19b2c7 100644 --- a/frontend/pages/(task-workspace)/tasks/components/navigator.ts +++ b/frontend/pages/(task-workspace)/tasks/components/navigator.ts @@ -3,38 +3,38 @@ import { ACTION_HINT_ACTION, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import type { ActiveTask, ActiveTaskTopPlacement, Task } from "#app/api.ts"; -import { compactIconButton } from "#app/component-styles.ts"; +} from "#app/action-hints.js"; +import type { ActiveTask, ActiveTaskTopPlacement, Task } from "#app/api.js"; +import { compactIconButton } from "#app/component-styles.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { ScrollSurfaces, scrollBackToTop } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "../../components/workspace-brand.ts"; -import type { WorkspaceLiveUpdates } from "../../live-updates.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { ScrollSurfaces, scrollBackToTop } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "../../components/workspace-brand.js"; +import type { WorkspaceLiveUpdates } from "../../live-updates.js"; import { type TaskStoreStatusSnapshot, taskStoreOperationsPresentation, -} from "../../task-store-status.ts"; -import type { TaskTransportState } from "../runtime-state.ts"; -import { type ListedTask, taskThreadId } from "../task-list-model.ts"; -import "./active-task-list.ts"; +} from "../../task-store-status.js"; +import type { TaskTransportState } from "../runtime-state.js"; +import { type ListedTask, taskThreadId } from "../task-list-model.js"; +import "./active-task-list.js"; import type { ActiveTaskListState, ArchiveSyncDetail, CaffoldActiveTaskList, ReorderMode, SectionSummary, -} from "./active-task-list.ts"; -import "./archived-task-list.ts"; +} from "./active-task-list.js"; +import "./archived-task-list.js"; import type { ArchivedTaskRestoredDetail, CaffoldArchivedTaskList, -} from "./archived-task-list.ts"; +} from "./archived-task-list.js"; const ICON = "task-action-icon"; diff --git a/frontend/pages/(task-workspace)/tasks/components/task-absence.test.js b/frontend/pages/(task-workspace)/tasks/components/task-absence.test.js index f4b2df0a..54eff443 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-absence.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/task-absence.test.js @@ -7,8 +7,8 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./task-absence.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); +await import("./task-absence.js"); +const { ActionHints } = await import("#app/action-hints.js"); const Absence = registry.element("caffold-task-absence"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/task-absence.ts b/frontend/pages/(task-workspace)/tasks/components/task-absence.ts index 28273fab..19b99417 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-absence.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-absence.ts @@ -3,9 +3,9 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; -import { TASK_ABSENCE, type TaskAbsence } from "#app/api.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/action-hints.js"; +import { TASK_ABSENCE, type TaskAbsence } from "#app/api.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; export const TASK_ABSENCE_INTENT_EVENT = "caffold:task-absence-intent"; diff --git a/frontend/pages/(task-workspace)/tasks/components/task-create.ts b/frontend/pages/(task-workspace)/tasks/components/task-create.ts index 4e990702..54145dea 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-create.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-create.ts @@ -3,34 +3,34 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { emptyScrollSurfaceScope, type ScrollSurfacePull, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; import { type TaskStoreStatusSnapshot, taskStoreBlocksTaskOperations, -} from "../../task-store-status.ts"; -import { cleanLogicalPath } from "../task-format.ts"; -import "./composer.ts"; +} from "../../task-store-status.js"; +import { cleanLogicalPath } from "../task-format.js"; +import "./composer.js"; import type { CaffoldTaskComposer, ComposerSubmission, ComposerSubmitDetail, -} from "./composer.ts"; -import "./task-create/components/directory-field.ts"; +} from "./composer.js"; +import "./task-create/components/directory-field.js"; import type { CaffoldTaskDirectoryField, DirectoryChosenDetail, -} from "./task-create/components/directory-field.ts"; +} from "./task-create/components/directory-field.js"; import { directoryPathDisplay, type ServerPaths, -} from "./task-create/directory-path.ts"; -import type { TurnSubmissionOptions } from "./task-turn-options.ts"; +} from "./task-create/directory-path.js"; +import type { TurnSubmissionOptions } from "./task-turn-options.js"; // What a New Task surface last ran under, which a new Task starts from. export interface ComposerSettings { diff --git a/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field.ts b/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field.ts index 739cc323..d3e28597 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field.ts @@ -4,22 +4,22 @@ import { type ActionHintPull, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; -import { listDirectory } from "#app/api.ts"; +} from "#app/action-hints.js"; +import { listDirectory } from "#app/api.js"; import { emptyScrollSurfaceScope, type ScrollSurfacePull, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; import type { CaffoldFileTree, FileTreeModel, FileTreeNode, FileTreeSelectDetail, -} from "#components/file-tree.ts"; -import "#components/file-tree.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import { directoryPathDisplay, type ServerPaths } from "../directory-path.ts"; +} from "#components/file-tree.js"; +import "#components/file-tree.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import { directoryPathDisplay, type ServerPaths } from "../directory-path.js"; import { DIRECTORY_FIELD_NODE, type DirectoryFieldEffect, @@ -29,7 +29,7 @@ import { initialDirectoryFieldState, PARENT_ROW_KEY, reduceDirectoryField, -} from "./directory-field/control.ts"; +} from "./directory-field/control.js"; export const DIRECTORY_CHOSEN_EVENT = "caffold:task-directory-chosen"; diff --git a/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.test.js b/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.test.js index ffffa129..41fff684 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.test.js @@ -6,7 +6,7 @@ import { directoryFieldRows, initialDirectoryFieldState, reduceDirectoryField, -} from "./control.ts"; +} from "./control.js"; const SERVER = { root: "/", homePath: "Users/me" }; const HOME = "Users/me"; diff --git a/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.ts b/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.ts index c26f76ce..0bd32a9c 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.ts @@ -1,4 +1,4 @@ -import type { DirectoryEntry, DirectoryListing } from "#app/api.ts"; +import type { DirectoryEntry, DirectoryListing } from "#app/api.js"; import { completeTypedDirectoryPath, directoryPathEditingText, @@ -6,7 +6,7 @@ import { readTypedDirectoryPath, type ServerPaths, type TypedDirectoryPath, -} from "../../directory-path.ts"; +} from "../../directory-path.js"; // The directory field's control model. // diff --git a/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.test.js b/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.test.js index 89ecf2e8..a86e3aec 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.test.js @@ -7,7 +7,7 @@ import { directoryPathEditingText, matchesTypedName, readTypedDirectoryPath, -} from "./directory-path.ts"; +} from "./directory-path.js"; const MAC = { root: "/", homePath: "Users/taehoon" }; const ROOTED = { root: "/srv/work", homePath: null }; diff --git a/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.ts b/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.ts index 04b9f21b..3d9136a9 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-create/directory-path.ts @@ -1,4 +1,4 @@ -import { cleanLogicalPath } from "../../task-format.ts"; +import { cleanLogicalPath } from "../../task-format.js"; // How a working directory reads, and how a typed one is understood. // diff --git a/frontend/pages/(task-workspace)/tasks/components/task-status-chip.test.js b/frontend/pages/(task-workspace)/tasks/components/task-status-chip.test.js index 4f40453c..51a5dad7 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-status-chip.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/task-status-chip.test.js @@ -7,7 +7,7 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./task-status-chip.ts"); +await import("./task-status-chip.js"); const Chip = registry.element("caffold-task-status-chip"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/task-status-chip.ts b/frontend/pages/(task-workspace)/tasks/components/task-status-chip.ts index ceaaf8cb..b16e3364 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-status-chip.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-status-chip.ts @@ -1,8 +1,8 @@ import { html, LitElement, nothing } from "lit"; -import type { TaskThreadStatus } from "#app/api.ts"; -import { busySpin } from "#app/component-styles.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import { taskStatusView } from "../runtime-state.ts"; +import type { TaskThreadStatus } from "#app/api.js"; +import { busySpin } from "#app/component-styles.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import { taskStatusView } from "../runtime-state.js"; // A spinner turns once in this long; a staggered one starts somewhere along // the turn. diff --git a/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.test.js b/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.test.js index 1ffe5a1f..92eb189c 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.test.js @@ -8,11 +8,11 @@ import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); const { TASK_STORE_RETRY_REQUEST_EVENT } = await import( - "../../task-store-status.ts" + "../../task-store-status.js" ); -await import("./task-store-recovery.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { ScrollSurfaces } = await import("#app/scroll-scope.ts"); +await import("./task-store-recovery.js"); +const { ActionHints } = await import("#app/action-hints.js"); +const { ScrollSurfaces } = await import("#app/scroll-scope.js"); const Recovery = registry.element("caffold-task-store-recovery"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts b/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts index b5556e9d..a1d445d3 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-store-recovery.ts @@ -3,14 +3,14 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { INITIAL_TASK_STORE_STATUS_SNAPSHOT, TASK_STORE_RETRY_REQUEST_EVENT, type TaskStoreStatusSnapshot, -} from "#app/pages/(task-workspace)/task-store-status.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; +} from "#app/pages/(task-workspace)/task-store-status.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; const PREPARING_MESSAGE = "Caffold is preparing the local Task navigator before Tasks start."; diff --git a/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.test.js b/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.test.js index 4c41f011..4ce31ff2 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.test.js @@ -7,11 +7,11 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -const { TASK_SWITCHER_SELECT_EVENT } = await import("./task-switcher-dialog.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); -const { ScrollSurfaces } = await import("#app/scroll-scope.ts"); -const { DrawnRefs } = await import("#components/drawn-refs.ts"); +const { TASK_SWITCHER_SELECT_EVENT } = await import("./task-switcher-dialog.js"); +const { ActionHints } = await import("#app/action-hints.js"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); +const { ScrollSurfaces } = await import("#app/scroll-scope.js"); +const { DrawnRefs } = await import("#components/drawn-refs.js"); const Switcher = registry.element("caffold-task-switcher-dialog"); after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts b/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts index 7195fcc4..699989a1 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts @@ -1,19 +1,19 @@ import { html, LitElement } from "lit"; import { repeat } from "lit/directives/repeat.js"; -import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import { taskStatusKey, taskStatusView } from "../runtime-state.ts"; -import { formatRelativeAgePresentation } from "../task-format.ts"; -import { type ListedTask, taskThreadId } from "../task-list-model.ts"; -import type { TaskWithSection } from "./active-task-list.ts"; -import "./task-status-chip.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, DrawnRefs, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import { taskStatusKey, taskStatusView } from "../runtime-state.js"; +import { formatRelativeAgePresentation } from "../task-format.js"; +import { type ListedTask, taskThreadId } from "../task-list-model.js"; +import type { TaskWithSection } from "./active-task-list.js"; +import "./task-status-chip.js"; export const TASK_SWITCHER_SELECT_EVENT = "caffold:task-switcher-select"; diff --git a/frontend/pages/(task-workspace)/tasks/components/task-turn-options.test.js b/frontend/pages/(task-workspace)/tasks/components/task-turn-options.test.js index 13c841ed..d54891ff 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-turn-options.test.js +++ b/frontend/pages/(task-workspace)/tasks/components/task-turn-options.test.js @@ -11,7 +11,7 @@ const previousElement = globalThis.Element; const previousDocument = globalThis.document; globalThis.Element = globalThis.HTMLElement; globalThis.document = { ...previousDocument, activeElement: null }; -await import("./task-turn-options.ts"); +await import("./task-turn-options.js"); const TurnOptions = registry.element("caffold-task-turn-options"); const turnOptions = TurnOptions.prototype; after(() => { diff --git a/frontend/pages/(task-workspace)/tasks/components/task-turn-options.ts b/frontend/pages/(task-workspace)/tasks/components/task-turn-options.ts index aa2d473c..11a7652b 100644 --- a/frontend/pages/(task-workspace)/tasks/components/task-turn-options.ts +++ b/frontend/pages/(task-workspace)/tasks/components/task-turn-options.ts @@ -4,24 +4,24 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type AgentModels, type AgentPermissionModes, getAgentModels, getAgentPermissions, -} from "#app/api.ts"; -import { busySpin } from "#app/component-styles.ts"; +} from "#app/api.js"; +import { busySpin } from "#app/component-styles.js"; import { KEYBOARD_SESSION_DISMISS_EVENT, KeyboardContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import { emptyScrollSurfaceScope } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import { cleanLogicalPath } from "../task-format.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import { emptyScrollSurfaceScope } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import { cleanLogicalPath } from "../task-format.js"; // What the Task or Section last ran under, as far as the turn options read it. export interface TurnOptionsSelection { diff --git a/frontend/pages/(task-workspace)/tasks/layout.test.js b/frontend/pages/(task-workspace)/tasks/layout.test.js index 0722c3d4..2bda5302 100644 --- a/frontend/pages/(task-workspace)/tasks/layout.test.js +++ b/frontend/pages/(task-workspace)/tasks/layout.test.js @@ -6,7 +6,7 @@ import { } from "../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const tasksPage = registry.element("caffold-tasks-page").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/layout.ts b/frontend/pages/(task-workspace)/tasks/layout.ts index 4ed0a11c..91c443ea 100644 --- a/frontend/pages/(task-workspace)/tasks/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/layout.ts @@ -3,7 +3,7 @@ import { type ActionHintScope, hasActionHintLayoutBox, mergeActionHintScopes, -} from "#app/action-hint-scope.ts"; +} from "#app/action-hint-scope.js"; import { type ActiveTask, type ActiveTaskRecovery, @@ -11,46 +11,46 @@ import { type CreatedTask, createTask, type Task, -} from "#app/api.ts"; -import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.ts"; -import { routeDomain, routeTarget } from "#app/navigation-routes.ts"; +} from "#app/api.js"; +import { mergeKeyboardNavigationContexts } from "#app/keyboard-navigation.js"; +import { routeDomain, routeTarget } from "#app/navigation-routes.js"; import { hasScrollLayoutBox, mergeScrollSurfaceScopes, type ScrollSurfaceScope, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import type { WorkspaceLiveUpdates } from "../live-updates.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import type { WorkspaceLiveUpdates } from "../live-updates.js"; import { INITIAL_TASK_STORE_STATUS_SNAPSHOT, type TaskStoreStatusSnapshot, taskStoreBlocksTaskOperations, -} from "../task-store-status.ts"; -import "./(detail)/layout.ts"; -import type { CaffoldDetailLayout } from "./(detail)/layout.ts"; -import { TERMINAL_PAGE_FOCUS_RELEASE_EVENT } from "./(detail)/terminal/page.ts"; -import type { ComposerSubmission } from "./components/composer.ts"; +} from "../task-store-status.js"; +import "./(detail)/layout.js"; +import type { CaffoldDetailLayout } from "./(detail)/layout.js"; +import { TERMINAL_PAGE_FOCUS_RELEASE_EVENT } from "./(detail)/terminal/page.js"; +import type { ComposerSubmission } from "./components/composer.js"; import { type CaffoldTaskImagePreviewDialog, TASK_IMAGE_PREVIEW_EVENT, type TaskImagePreviewRequest, -} from "./components/image-preview-dialog.ts"; +} from "./components/image-preview-dialog.js"; import type { CaffoldTaskNavigator, TaskNavigatorListState, -} from "./components/navigator.ts"; -import type { TaskStartIntent } from "./components/task-create.ts"; -import "./components/task-store-recovery.ts"; -import type { CaffoldTaskStoreRecovery } from "./components/task-store-recovery.ts"; -import "./new/page.ts"; -import type { CaffoldTaskNew } from "./new/page.ts"; -import "./recovery/page.ts"; +} from "./components/navigator.js"; +import type { TaskStartIntent } from "./components/task-create.js"; +import "./components/task-store-recovery.js"; +import type { CaffoldTaskStoreRecovery } from "./components/task-store-recovery.js"; +import "./new/page.js"; +import type { CaffoldTaskNew } from "./new/page.js"; +import "./recovery/page.js"; import type { CaffoldTaskRecovery, TaskRecoveryIntent, -} from "./recovery/page.ts"; -import { TASK_TRANSPORT_STATE } from "./runtime-state.ts"; -import { taskDetailThreadId } from "./task-list-model.ts"; +} from "./recovery/page.js"; +import { TASK_TRANSPORT_STATE } from "./runtime-state.js"; +import { taskDetailThreadId } from "./task-list-model.js"; type TasksView = "home" | "new" | "detail" | "recovery"; diff --git a/frontend/pages/(task-workspace)/tasks/new/page.test.js b/frontend/pages/(task-workspace)/tasks/new/page.test.js index 5333168e..fb4d077d 100644 --- a/frontend/pages/(task-workspace)/tasks/new/page.test.js +++ b/frontend/pages/(task-workspace)/tasks/new/page.test.js @@ -6,7 +6,7 @@ import { } from "../../../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); +await import("./page.js"); const taskNew = registry.element("caffold-task-new").prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/new/page.ts b/frontend/pages/(task-workspace)/tasks/new/page.ts index 5efa0067..48faa701 100644 --- a/frontend/pages/(task-workspace)/tasks/new/page.ts +++ b/frontend/pages/(task-workspace)/tasks/new/page.ts @@ -1,19 +1,19 @@ import { html, LitElement } from "lit"; -import { mergeActionHintScopes } from "#app/action-hints.ts"; +import { mergeActionHintScopes } from "#app/action-hints.js"; import { mergeScrollSurfaceScopes, type ScrollSurfacePull, ScrollSurfaces, -} from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import type { TaskStoreStatusSnapshot } from "../../task-store-status.ts"; -import "../components/task-create.ts"; -import type { ServerPaths } from "../components/task-create/directory-path.ts"; +} from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import type { TaskStoreStatusSnapshot } from "../../task-store-status.js"; +import "../components/task-create.js"; +import type { ServerPaths } from "../components/task-create/directory-path.js"; import type { CaffoldTaskCreate, TaskCreateIntentDetail, -} from "../components/task-create.ts"; -import { cleanLogicalPath } from "../task-format.ts"; +} from "../components/task-create.js"; +import { cleanLogicalPath } from "../task-format.js"; const GUIDE_INTRO = "Start with a setup-only request when you want this task separated from your main checkout. By default, Caffold prepares a clean worktree and leaves staged, unstaged, and untracked changes in your current checkout. The same setup request also works in an existing task."; diff --git a/frontend/pages/(task-workspace)/tasks/recovery/page.test.js b/frontend/pages/(task-workspace)/tasks/recovery/page.test.js index e4da0d2d..ca768418 100644 --- a/frontend/pages/(task-workspace)/tasks/recovery/page.test.js +++ b/frontend/pages/(task-workspace)/tasks/recovery/page.test.js @@ -7,9 +7,9 @@ import { import { templateHtml } from "../../../../tests/support/lit-template.js"; const registry = installCustomElementUnitRegistry(); -await import("./page.ts"); -const { ActionHints } = await import("#app/action-hints.ts"); -const { ScrollSurfaces } = await import("#app/scroll-scope.ts"); +await import("./page.js"); +const { ActionHints } = await import("#app/action-hints.js"); +const { ScrollSurfaces } = await import("#app/scroll-scope.js"); const Recovery = registry.element("caffold-task-recovery"); const recovery = Recovery.prototype; after(() => registry.restore()); diff --git a/frontend/pages/(task-workspace)/tasks/recovery/page.ts b/frontend/pages/(task-workspace)/tasks/recovery/page.ts index f3b15476..d9dc0ad1 100644 --- a/frontend/pages/(task-workspace)/tasks/recovery/page.ts +++ b/frontend/pages/(task-workspace)/tasks/recovery/page.ts @@ -3,7 +3,7 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { type ActiveTask, type ActiveTaskRecovery, @@ -17,17 +17,17 @@ import { type TaskAbsence, taskAbsence, taskAlreadyActive, -} from "#app/api.ts"; -import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "#components/loading-text.ts"; -import "../components/task-absence.ts"; +} from "#app/api.js"; +import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "#components/loading-text.js"; +import "../components/task-absence.js"; import type { CaffoldTaskAbsence, TaskAbsenceIntent, -} from "../components/task-absence.ts"; -import { taskThreadId } from "../task-list-model.ts"; +} from "../components/task-absence.js"; +import { taskThreadId } from "../task-list-model.js"; const REMOVAL_WARNING = "The Codex Thread could not be found. Caffold will remove its membership and managed Task resources. This cannot be undone."; diff --git a/frontend/pages/(task-workspace)/tasks/runtime-state.test.js b/frontend/pages/(task-workspace)/tasks/runtime-state.test.js index b7b97cd8..c2bea860 100644 --- a/frontend/pages/(task-workspace)/tasks/runtime-state.test.js +++ b/frontend/pages/(task-workspace)/tasks/runtime-state.test.js @@ -11,7 +11,7 @@ import { taskActiveFlags, taskStatusView, taskThreadStatusType, -} from "./runtime-state.ts"; +} from "./runtime-state.js"; function task(type, activeFlags = []) { return { diff --git a/frontend/pages/(task-workspace)/tasks/runtime-state.ts b/frontend/pages/(task-workspace)/tasks/runtime-state.ts index 9663ce6e..01ddc3e8 100644 --- a/frontend/pages/(task-workspace)/tasks/runtime-state.ts +++ b/frontend/pages/(task-workspace)/tasks/runtime-state.ts @@ -1,4 +1,4 @@ -import type { TaskEvent, TaskThreadStatus } from "#app/api.ts"; +import type { TaskEvent, TaskThreadStatus } from "#app/api.js"; export const TASK_TRANSPORT_STATE = Object.freeze({ IDLE: "idle", diff --git a/frontend/pages/(task-workspace)/tasks/stream.test.js b/frontend/pages/(task-workspace)/tasks/stream.test.js index 02b58134..a733d1fe 100644 --- a/frontend/pages/(task-workspace)/tasks/stream.test.js +++ b/frontend/pages/(task-workspace)/tasks/stream.test.js @@ -1,8 +1,8 @@ import assert from "node:assert/strict"; import test, { afterEach } from "node:test"; -import { TASK_TRANSPORT_STATE } from "./runtime-state.ts"; -import { TaskStreamLifecycle } from "./stream.ts"; +import { TASK_TRANSPORT_STATE } from "./runtime-state.js"; +import { TaskStreamLifecycle } from "./stream.js"; const originalBrowserGlobals = { document: globalThis.document, diff --git a/frontend/pages/(task-workspace)/tasks/stream.ts b/frontend/pages/(task-workspace)/tasks/stream.ts index f6742745..06982c9d 100644 --- a/frontend/pages/(task-workspace)/tasks/stream.ts +++ b/frontend/pages/(task-workspace)/tasks/stream.ts @@ -1,12 +1,12 @@ -import { type TaskAbsence, taskAbsence } from "#app/api.ts"; +import { type TaskAbsence, taskAbsence } from "#app/api.js"; import type { LiveSubscription, LiveSubscriptionListener, -} from "../live-updates.ts"; +} from "../live-updates.js"; import { TASK_TRANSPORT_STATE, type TaskTransportState, -} from "./runtime-state.ts"; +} from "./runtime-state.js"; import { presentTaskStream, TASK_STREAM_EVENT, @@ -14,7 +14,7 @@ import { type TaskStreamEvent, type TaskStreamNode, transitionTaskStream, -} from "./stream/machine.ts"; +} from "./stream/machine.js"; const DEFAULT_RETRY_DELAYS_MS = Object.freeze([250, 1_000, 3_000]); // Once its own retry budget is spent, a stream tries again at these delays; diff --git a/frontend/pages/(task-workspace)/tasks/stream/machine.test.js b/frontend/pages/(task-workspace)/tasks/stream/machine.test.js index b101ef49..4cdae92b 100644 --- a/frontend/pages/(task-workspace)/tasks/stream/machine.test.js +++ b/frontend/pages/(task-workspace)/tasks/stream/machine.test.js @@ -1,14 +1,14 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { TASK_TRANSPORT_STATE } from "../runtime-state.ts"; +import { TASK_TRANSPORT_STATE } from "../runtime-state.js"; import { TASK_STREAM_EDGES, TASK_STREAM_EVENT, TASK_STREAM_NODE, presentTaskStream, transitionTaskStream, -} from "./machine.ts"; +} from "./machine.js"; const NODE = TASK_STREAM_NODE; const EVENT = TASK_STREAM_EVENT; diff --git a/frontend/pages/(task-workspace)/tasks/stream/machine.ts b/frontend/pages/(task-workspace)/tasks/stream/machine.ts index 1f6e2153..442a7fad 100644 --- a/frontend/pages/(task-workspace)/tasks/stream/machine.ts +++ b/frontend/pages/(task-workspace)/tasks/stream/machine.ts @@ -1,8 +1,8 @@ -import type { TaskAbsence } from "#app/api.ts"; +import type { TaskAbsence } from "#app/api.js"; import { TASK_TRANSPORT_STATE, type TaskTransportState, -} from "../runtime-state.ts"; +} from "../runtime-state.js"; // The control graph of one Task stream: the logical subscription a Task List or // Task Detail owner keeps on the workspace gateway, from choosing a context diff --git a/frontend/pages/(task-workspace)/tasks/task-events.test.js b/frontend/pages/(task-workspace)/tasks/task-events.test.js index 6bbedd13..6b365a18 100644 --- a/frontend/pages/(task-workspace)/tasks/task-events.test.js +++ b/frontend/pages/(task-workspace)/tasks/task-events.test.js @@ -12,7 +12,7 @@ import { optimisticUserMessageEvent, sortEventsChronologically, taskEventPosition, -} from "./task-events.ts"; +} from "./task-events.js"; const WHOLE_PROJECTION = { from: null, to: null }; diff --git a/frontend/pages/(task-workspace)/tasks/task-events.ts b/frontend/pages/(task-workspace)/tasks/task-events.ts index 84459edb..cd0ab7a7 100644 --- a/frontend/pages/(task-workspace)/tasks/task-events.ts +++ b/frontend/pages/(task-workspace)/tasks/task-events.ts @@ -2,13 +2,13 @@ import type { TaskEvent, TaskEventPosition, TaskEventsRange, -} from "#app/api.ts"; -import { PROMPT_SUBMISSION_STATE } from "./runtime-state.ts"; +} from "#app/api.js"; +import { PROMPT_SUBMISSION_STATE } from "./runtime-state.js"; import { presentTaskFilePath, type TaskFilePath, taskEventObservedMs, -} from "./task-format.ts"; +} from "./task-format.js"; // A turn's events in conversation order, or an event outside any turn. export type ConversationGroup = diff --git a/frontend/pages/(task-workspace)/tasks/task-format.test.js b/frontend/pages/(task-workspace)/tasks/task-format.test.js index 4c3e1328..ca6239ca 100644 --- a/frontend/pages/(task-workspace)/tasks/task-format.test.js +++ b/frontend/pages/(task-workspace)/tasks/task-format.test.js @@ -14,7 +14,7 @@ import { shortId, taskEventObservedMs, toolCallPresentation, -} from "./task-format.ts"; +} from "./task-format.js"; test("Task event time distinguishes direct evidence from a history placement anchor", () => { assert.equal(taskEventObservedMs({ observedMs: 120 }), 120); diff --git a/frontend/pages/(task-workspace)/tasks/task-format.ts b/frontend/pages/(task-workspace)/tasks/task-format.ts index 913fa8a7..76fbc038 100644 --- a/frontend/pages/(task-workspace)/tasks/task-format.ts +++ b/frontend/pages/(task-workspace)/tasks/task-format.ts @@ -1,4 +1,4 @@ -import type { Task, TaskEventPayload } from "#app/api.ts"; +import type { Task, TaskEventPayload } from "#app/api.js"; export function normalizeTaskPath(path: unknown) { return `${path ?? ""}` diff --git a/frontend/pages/(task-workspace)/tasks/task-list-model.test.js b/frontend/pages/(task-workspace)/tasks/task-list-model.test.js index 1e0542dc..9ff44852 100644 --- a/frontend/pages/(task-workspace)/tasks/task-list-model.test.js +++ b/frontend/pages/(task-workspace)/tasks/task-list-model.test.js @@ -7,7 +7,7 @@ import { sortTasksByRecency, taskDetailThreadId, upsertTask, -} from "./task-list-model.ts"; +} from "./task-list-model.js"; function task(threadId, recencyMs, overrides = {}) { return { diff --git a/frontend/pages/(task-workspace)/tasks/task-list-model.ts b/frontend/pages/(task-workspace)/tasks/task-list-model.ts index 1ef127e7..3c867d4a 100644 --- a/frontend/pages/(task-workspace)/tasks/task-list-model.ts +++ b/frontend/pages/(task-workspace)/tasks/task-list-model.ts @@ -1,9 +1,9 @@ -import type { ActiveTask, ActiveTaskRecovery, Task } from "#app/api.ts"; +import type { ActiveTask, ActiveTaskRecovery, Task } from "#app/api.js"; import { cleanLogicalPath, cleanRelativeTaskPath, shortId, -} from "./task-format.ts"; +} from "./task-format.js"; // A Task as the Task list holds it: the Active list's own listing, one the // person must recover, or a whole Task an owner handed in after creating, diff --git a/frontend/pages/components/build-mismatch-alert.ts b/frontend/pages/components/build-mismatch-alert.ts index e05f734a..ee08d390 100644 --- a/frontend/pages/components/build-mismatch-alert.ts +++ b/frontend/pages/components/build-mismatch-alert.ts @@ -3,7 +3,7 @@ import { ACTION_HINT_ACTION, type ActionHintPull, ActionHints, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; export const CAFFOLD_BUILD_MISMATCH_RELOAD_EVENT = "caffold:build-mismatch-reload"; diff --git a/frontend/pages/components/update-dialog.ts b/frontend/pages/components/update-dialog.ts index 9e2d51cf..bbc46aeb 100644 --- a/frontend/pages/components/update-dialog.ts +++ b/frontend/pages/components/update-dialog.ts @@ -1,8 +1,8 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "../../action-hints.ts"; -import { DrawnRef, drawn } from "../../components/drawn-refs.ts"; -import { KeyboardContexts } from "../../keyboard-navigation.ts"; -import "../../keyboard-navigation/components/presentation.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "../../action-hints.js"; +import { DrawnRef, drawn } from "../../components/drawn-refs.js"; +import { KeyboardContexts } from "../../keyboard-navigation.js"; +import "../../keyboard-navigation/components/presentation.js"; export const CAFFOLD_UPDATE_LATER_EVENT = "caffold:update-later"; export const CAFFOLD_UPDATE_RELOAD_EVENT = "caffold:update-reload"; diff --git a/frontend/pages/components/update-result-dialog.test.js b/frontend/pages/components/update-result-dialog.test.js index bcef8258..33163f21 100644 --- a/frontend/pages/components/update-result-dialog.test.js +++ b/frontend/pages/components/update-result-dialog.test.js @@ -6,7 +6,7 @@ import { } from "../../tests/support/custom-element-unit.js"; const registry = installCustomElementUnitRegistry(); -const { updateResultNotice } = await import("./update-result-dialog.ts"); +const { updateResultNotice } = await import("./update-result-dialog.js"); after(() => registry.restore()); test("tells a rollback with the reason the attempt recorded", () => { diff --git a/frontend/pages/components/update-result-dialog.ts b/frontend/pages/components/update-result-dialog.ts index fd60f7ea..526ba296 100644 --- a/frontend/pages/components/update-result-dialog.ts +++ b/frontend/pages/components/update-result-dialog.ts @@ -1,9 +1,9 @@ import { html, LitElement } from "lit"; -import { ACTION_HINT_ACTION, ActionHints } from "../../action-hints.ts"; -import type { CaffoldUpdateAttempt } from "../../api.ts"; -import { DrawnRef, drawn } from "../../components/drawn-refs.ts"; -import { KeyboardContexts } from "../../keyboard-navigation.ts"; -import "../../keyboard-navigation/components/presentation.ts"; +import { ACTION_HINT_ACTION, ActionHints } from "../../action-hints.js"; +import type { CaffoldUpdateAttempt } from "../../api.js"; +import { DrawnRef, drawn } from "../../components/drawn-refs.js"; +import { KeyboardContexts } from "../../keyboard-navigation.js"; +import "../../keyboard-navigation/components/presentation.js"; type UpdateResultNotice = { title: string; description: string }; diff --git a/frontend/pages/foreground-recovery.test.js b/frontend/pages/foreground-recovery.test.js index ef02fd76..96cba785 100644 --- a/frontend/pages/foreground-recovery.test.js +++ b/frontend/pages/foreground-recovery.test.js @@ -4,7 +4,7 @@ import test from "node:test"; import { FOREGROUND_RECOVERY_PRESENTATION, ForegroundRecoveryLifecycle, -} from "./foreground-recovery.ts"; +} from "./foreground-recovery.js"; function eventTarget() { const listeners = new Map(); diff --git a/frontend/pages/foreground-recovery.ts b/frontend/pages/foreground-recovery.ts index ffb2056b..6dd952d9 100644 --- a/frontend/pages/foreground-recovery.ts +++ b/frontend/pages/foreground-recovery.ts @@ -1,21 +1,21 @@ -import type { LiveConnectionReport } from "./(task-workspace)/live-updates.ts"; +import type { LiveConnectionReport } from "./(task-workspace)/live-updates.js"; import { ForegroundRecoveryDiagnostics, type ForegroundRecoveryDiagnosticsOptions, -} from "./foreground-recovery/diagnostics.ts"; +} from "./foreground-recovery/diagnostics.js"; import { ForegroundRecoveryRuntime, type ForegroundRecoveryRuntimeOptions, type ForegroundRecoveryRuntimeSnapshot, type RecoveryAttempt, -} from "./foreground-recovery/lifecycle.ts"; +} from "./foreground-recovery/lifecycle.js"; import { FOREGROUND_RECOVERY_INTENT, FOREGROUND_RECOVERY_NODE, FOREGROUND_RECOVERY_TRIGGER, type ForegroundRecoveryNode, selectForegroundRecoveryPresentation, -} from "./foreground-recovery/machine.ts"; +} from "./foreground-recovery/machine.js"; // Public presentation contract consumed by the App Shell notice. export const FOREGROUND_RECOVERY_PRESENTATION = Object.freeze({ diff --git a/frontend/pages/foreground-recovery/browser-signals.test.js b/frontend/pages/foreground-recovery/browser-signals.test.js index 4b191710..58abdb6f 100644 --- a/frontend/pages/foreground-recovery/browser-signals.test.js +++ b/frontend/pages/foreground-recovery/browser-signals.test.js @@ -4,7 +4,7 @@ import test from "node:test"; import { FOREGROUND_RECOVERY_BROWSER_SIGNAL, ForegroundRecoveryBrowserSignals, -} from "./browser-signals.ts"; +} from "./browser-signals.js"; function eventTarget() { const listeners = new Map(); diff --git a/frontend/pages/foreground-recovery/browser-signals.ts b/frontend/pages/foreground-recovery/browser-signals.ts index 1de943b0..c23d1c93 100644 --- a/frontend/pages/foreground-recovery/browser-signals.ts +++ b/frontend/pages/foreground-recovery/browser-signals.ts @@ -27,7 +27,7 @@ export const FOREGROUND_RECOVERY_BROWSER_SIGNAL = Object.freeze({ NOTIFICATION_ACTIVATED: "notification-activated", }); -import type { ForegroundRecoveryObservation } from "./machine.ts"; +import type { ForegroundRecoveryObservation } from "./machine.js"; export type ForegroundRecoveryBrowserSignal = (typeof FOREGROUND_RECOVERY_BROWSER_SIGNAL)[keyof typeof FOREGROUND_RECOVERY_BROWSER_SIGNAL]; diff --git a/frontend/pages/foreground-recovery/diagnostics.test.js b/frontend/pages/foreground-recovery/diagnostics.test.js index f5a5c66a..d331e935 100644 --- a/frontend/pages/foreground-recovery/diagnostics.test.js +++ b/frontend/pages/foreground-recovery/diagnostics.test.js @@ -8,7 +8,7 @@ import { DIAGNOSTICS_STORAGE_KEY, ForegroundRecoveryDiagnostics, transitionDiagnostics, -} from "./diagnostics.ts"; +} from "./diagnostics.js"; const ORIGIN = "https://caffold.test"; diff --git a/frontend/pages/foreground-recovery/diagnostics.ts b/frontend/pages/foreground-recovery/diagnostics.ts index 7f93b124..994c6b2f 100644 --- a/frontend/pages/foreground-recovery/diagnostics.ts +++ b/frontend/pages/foreground-recovery/diagnostics.ts @@ -1,10 +1,10 @@ -import type { LiveConnectionReport } from "../(task-workspace)/live-updates.ts"; -import type { ForegroundRecoveryRuntimeSnapshot } from "./lifecycle.ts"; +import type { LiveConnectionReport } from "../(task-workspace)/live-updates.js"; +import type { ForegroundRecoveryRuntimeSnapshot } from "./lifecycle.js"; import { FOREGROUND_RECOVERY_NODE, type ForegroundRecoveryTarget, type ForegroundRecoveryTargets, -} from "./machine.ts"; +} from "./machine.js"; // Diagnostics of each foreground recovery that follows a return to the page, // timed in this browser as each thing happened and sent to the server log once diff --git a/frontend/pages/foreground-recovery/lifecycle.test.js b/frontend/pages/foreground-recovery/lifecycle.test.js index 7bbb1022..123f0f02 100644 --- a/frontend/pages/foreground-recovery/lifecycle.test.js +++ b/frontend/pages/foreground-recovery/lifecycle.test.js @@ -3,17 +3,17 @@ import test from "node:test"; import { FOREGROUND_RECOVERY_PRESENTATION, -} from "../foreground-recovery.ts"; +} from "../foreground-recovery.js"; import { ForegroundRecoveryRuntime, NOTIFICATION_ACTIVATION_MESSAGE, -} from "./lifecycle.ts"; +} from "./lifecycle.js"; import { FOREGROUND_RECOVERY_INTENT, FOREGROUND_RECOVERY_NODE, FOREGROUND_RECOVERY_TRIGGER, selectForegroundRecoveryPresentation, -} from "./machine.ts"; +} from "./machine.js"; function deferred() { let resolve; diff --git a/frontend/pages/foreground-recovery/lifecycle.ts b/frontend/pages/foreground-recovery/lifecycle.ts index 2cbbc275..dbbb7a85 100644 --- a/frontend/pages/foreground-recovery/lifecycle.ts +++ b/frontend/pages/foreground-recovery/lifecycle.ts @@ -4,7 +4,7 @@ import { FOREGROUND_RECOVERY_BROWSER_SIGNAL, type ForegroundRecoveryBrowserSignal, ForegroundRecoveryBrowserSignals, -} from "./browser-signals.ts"; +} from "./browser-signals.js"; import { createForegroundRecoveryState, @@ -23,7 +23,7 @@ import { normalizeRecoveryRequest, type RecoveryRequest, transitionForegroundRecovery, -} from "./machine.ts"; +} from "./machine.js"; export const NOTIFICATION_ACTIVATION_MESSAGE = "caffold:notification-activation"; diff --git a/frontend/pages/foreground-recovery/machine.test.js b/frontend/pages/foreground-recovery/machine.test.js index 633c93d8..734c2cd7 100644 --- a/frontend/pages/foreground-recovery/machine.test.js +++ b/frontend/pages/foreground-recovery/machine.test.js @@ -3,7 +3,7 @@ import test from "node:test"; import { FOREGROUND_RECOVERY_PRESENTATION, -} from "../foreground-recovery.ts"; +} from "../foreground-recovery.js"; import { FOREGROUND_RECOVERY_EVENT, FOREGROUND_RECOVERY_INTENT, @@ -13,7 +13,7 @@ import { createForegroundRecoveryState, selectForegroundRecoveryPresentation, transitionForegroundRecovery, -} from "./machine.ts"; +} from "./machine.js"; const VISIBLE = Object.freeze({ focused: true, diff --git a/frontend/pages/layout.test.js b/frontend/pages/layout.test.js index 006d22d7..37d508e0 100644 --- a/frontend/pages/layout.test.js +++ b/frontend/pages/layout.test.js @@ -5,7 +5,7 @@ import test, { after } from "node:test"; import { installCustomElementUnitRegistry, } from "../tests/support/custom-element-unit.js"; -import { NavigationHistory } from "./navigation-history.ts"; +import { NavigationHistory } from "./navigation-history.js"; const previousDocument = globalThis.document; const previousWindow = globalThis.window; @@ -35,9 +35,9 @@ const buildInfoHook = registerHooks({ }, }); const registry = installCustomElementUnitRegistry(); -await import("./layout.ts"); +await import("./layout.js"); const appShell = registry.element("caffold-app-shell").prototype; -const { KeyboardContexts } = await import("#app/keyboard-navigation.ts"); +const { KeyboardContexts } = await import("#app/keyboard-navigation.js"); after(() => { registry.restore(); diff --git a/frontend/pages/layout.ts b/frontend/pages/layout.ts index f916e508..0a64409e 100644 --- a/frontend/pages/layout.ts +++ b/frontend/pages/layout.ts @@ -3,64 +3,64 @@ import { ACTION_HINT_ACTION, ActionHints, mergeActionHintScopes, -} from "#app/action-hints.ts"; +} from "#app/action-hints.js"; import { checkCaffoldUpdate, getCaffoldUpdate, getHealth, type Health, sendForegroundRecoveryDiagnostics, -} from "#app/api.ts"; +} from "#app/api.js"; import { BUILD_INFO } from "#app/build-info.js"; -import { busySpin } from "#app/component-styles.ts"; +import { busySpin } from "#app/component-styles.js"; import { KeyboardContexts, KeyboardNavigationController, mergeKeyboardNavigationContexts, -} from "#app/keyboard-navigation.ts"; -import "#app/keyboard-navigation/components/presentation.ts"; -import "#app/keyboard-navigation/components/shortcut-dialog.ts"; -import type { CaffoldKeyboardShortcutDialog } from "#app/keyboard-navigation/components/shortcut-dialog.ts"; +} from "#app/keyboard-navigation.js"; +import "#app/keyboard-navigation/components/presentation.js"; +import "#app/keyboard-navigation/components/shortcut-dialog.js"; +import type { CaffoldKeyboardShortcutDialog } from "#app/keyboard-navigation/components/shortcut-dialog.js"; import { type ParsedRoute, parseRoute, routeEquals, routeUrl, -} from "#app/navigation-routes.ts"; -import { CAFFOLD_ORIGIN_REACHABLE_EVENT } from "#app/origin-reachability.ts"; -import { getSettings } from "#app/settings.ts"; -import { DrawnRef, drawn } from "#components/drawn-refs.ts"; -import { inlineIcon, warmIcons } from "#components/icons.ts"; -import "./(task-workspace)/layout.ts"; -import type { CaffoldTaskWorkspace } from "./(task-workspace)/layout.ts"; -import { LIVE_CONNECTION_REPORT_EVENT } from "./(task-workspace)/live-updates.ts"; +} from "#app/navigation-routes.js"; +import { CAFFOLD_ORIGIN_REACHABLE_EVENT } from "#app/origin-reachability.js"; +import { getSettings } from "#app/settings.js"; +import { DrawnRef, drawn } from "#components/drawn-refs.js"; +import { inlineIcon, warmIcons } from "#components/icons.js"; +import "./(task-workspace)/layout.js"; +import type { CaffoldTaskWorkspace } from "./(task-workspace)/layout.js"; +import { LIVE_CONNECTION_REPORT_EVENT } from "./(task-workspace)/live-updates.js"; import { CAFFOLD_UPDATE_CHECK_REQUEST_EVENT, type CaffoldUpdateSnapshot, -} from "./(task-workspace)/settings/about/page.ts"; +} from "./(task-workspace)/settings/about/page.js"; import { CAFFOLD_BUILD_MISMATCH_RELOAD_EVENT, type CaffoldBuildMismatchAlert, -} from "./components/build-mismatch-alert.ts"; +} from "./components/build-mismatch-alert.js"; import { CAFFOLD_UPDATE_LATER_EVENT, CAFFOLD_UPDATE_RELOAD_EVENT, type CaffoldUpdateDialog, -} from "./components/update-dialog.ts"; -import "./components/update-result-dialog.ts"; -import type { CaffoldUpdateResultDialog } from "./components/update-result-dialog.ts"; +} from "./components/update-dialog.js"; +import "./components/update-result-dialog.js"; +import type { CaffoldUpdateResultDialog } from "./components/update-result-dialog.js"; import { FOREGROUND_RECOVERY_PRESENTATION, ForegroundRecoveryLifecycle, type ForegroundRecoveryRequest, type ForegroundRecoverySnapshot, -} from "./foreground-recovery.ts"; +} from "./foreground-recovery.js"; import { type HistoryEntry, NAVIGATION_HISTORY_ACTION, NavigationHistory, -} from "./navigation-history.ts"; -import { PwaUpdateLifecycle } from "./pwa-update-lifecycle.ts"; +} from "./navigation-history.js"; +import { PwaUpdateLifecycle } from "./pwa-update-lifecycle.js"; // The Navigation API as far as the shell and the workspace use it; TypeScript's // DOM types do not have it yet. diff --git a/frontend/pages/navigation-history.test.js b/frontend/pages/navigation-history.test.js index 0fa265a9..d83fb5a8 100644 --- a/frontend/pages/navigation-history.test.js +++ b/frontend/pages/navigation-history.test.js @@ -5,9 +5,9 @@ globalThis.window = { location: { origin: "http://caffold.test" }, }; -const { parseRoute, routeUrl } = await import("../navigation-routes.ts"); +const { parseRoute, routeUrl } = await import("../navigation-routes.js"); const { NAVIGATION_HISTORY_ACTION, NavigationHistory } = await import( - "./navigation-history.ts" + "./navigation-history.js" ); // Walks a person's route requests through the history and reports what the diff --git a/frontend/pages/navigation-history.ts b/frontend/pages/navigation-history.ts index 7bc58ea0..8ccaa6b8 100644 --- a/frontend/pages/navigation-history.ts +++ b/frontend/pages/navigation-history.ts @@ -6,7 +6,7 @@ import { routeEquals, routeRelation, routeTab, -} from "../navigation-routes.ts"; +} from "../navigation-routes.js"; export const NAVIGATION_HISTORY_ACTION = { PUSH: "push", diff --git a/frontend/pages/pwa-update-lifecycle.ts b/frontend/pages/pwa-update-lifecycle.ts index 1cdd885e..3588deab 100644 --- a/frontend/pages/pwa-update-lifecycle.ts +++ b/frontend/pages/pwa-update-lifecycle.ts @@ -1,7 +1,7 @@ import { type PwaUpdateOptions, PwaUpdateRuntime, -} from "./pwa-update-lifecycle/runtime.ts"; +} from "./pwa-update-lifecycle/runtime.js"; // Public feature owner. Registration details and the handoff graph remain private. export class PwaUpdateLifecycle { diff --git a/frontend/pages/pwa-update-lifecycle/machine.test.js b/frontend/pages/pwa-update-lifecycle/machine.test.js index c7acf4b7..9d90cd08 100644 --- a/frontend/pages/pwa-update-lifecycle/machine.test.js +++ b/frontend/pages/pwa-update-lifecycle/machine.test.js @@ -9,7 +9,7 @@ import { PWA_UPDATE_TARGET_PHASE, createPwaUpdateHandoffState, transitionPwaUpdateHandoff, -} from "./machine.ts"; +} from "./machine.js"; function transition(state, type, values = {}) { return transitionPwaUpdateHandoff(state, { type, ...values }); diff --git a/frontend/pages/pwa-update-lifecycle/runtime.ts b/frontend/pages/pwa-update-lifecycle/runtime.ts index ce8169dc..31f581f0 100644 --- a/frontend/pages/pwa-update-lifecycle/runtime.ts +++ b/frontend/pages/pwa-update-lifecycle/runtime.ts @@ -10,7 +10,7 @@ import { type PwaUpdateHandoffState, type PwaUpdateTargetPhase, transitionPwaUpdateHandoff, -} from "./machine.ts"; +} from "./machine.js"; const ACTIVATE_PREPARED_BUILD_MESSAGE = "caffold:activate-prepared-build"; const CLAIM_PREPARED_BUILD_MESSAGE = "caffold:claim-prepared-build"; diff --git a/frontend/scroll-scope.test.js b/frontend/scroll-scope.test.js index a77d3d8f..58d2b576 100644 --- a/frontend/scroll-scope.test.js +++ b/frontend/scroll-scope.test.js @@ -11,7 +11,7 @@ import { mergeScrollSurfaceScopes, normalizeScrollAxes, scrollBackToTop, -} from "./scroll-scope.ts"; +} from "./scroll-scope.js"; test("Scroll surface scopes compose direct owners in declaration order", () => { const first = { id: "first" }; diff --git a/frontend/scroll-scope.ts b/frontend/scroll-scope.ts index b22377d1..fb9cc498 100644 --- a/frontend/scroll-scope.ts +++ b/frontend/scroll-scope.ts @@ -1,7 +1,7 @@ import { type DeclaredElement, ElementDeclarations, -} from "./element-declarations.ts"; +} from "./element-declarations.js"; export type ScrollAxis = "vertical" | "horizontal"; diff --git a/frontend/service-worker.test.js b/frontend/service-worker.test.js index fd90bfe0..9305583c 100644 --- a/frontend/service-worker.test.js +++ b/frontend/service-worker.test.js @@ -147,39 +147,39 @@ test("installs the complete shell without replacing the current worker", async ( assert.ok( harness.puts.every(({ cache }) => cache === "caffold-shell-test-build"), ); - assert.ok(installedAssets.includes("/assets/file-status.ts")); - assert.ok(installedAssets.includes("/assets/pages/foreground-recovery.ts")); + assert.ok(installedAssets.includes("/assets/file-status.js")); + assert.ok(installedAssets.includes("/assets/pages/foreground-recovery.js")); assert.ok( installedAssets.includes( - "/assets/pages/foreground-recovery/browser-signals.ts", + "/assets/pages/foreground-recovery/browser-signals.js", ), ); assert.ok( installedAssets.includes( - "/assets/pages/foreground-recovery/lifecycle.ts", + "/assets/pages/foreground-recovery/lifecycle.js", ), ); assert.ok( installedAssets.includes( - "/assets/pages/foreground-recovery/machine.ts", + "/assets/pages/foreground-recovery/machine.js", ), ); - assert.ok(installedAssets.includes("/assets/pages/pwa-update-lifecycle.ts")); + assert.ok(installedAssets.includes("/assets/pages/pwa-update-lifecycle.js")); assert.ok( - installedAssets.includes("/assets/pages/pwa-update-lifecycle/machine.ts"), + installedAssets.includes("/assets/pages/pwa-update-lifecycle/machine.js"), ); assert.ok( - installedAssets.includes("/assets/pages/pwa-update-lifecycle/runtime.ts"), + installedAssets.includes("/assets/pages/pwa-update-lifecycle/runtime.js"), ); - assert.ok(installedAssets.includes("/assets/pages/components/update-dialog.ts")); + assert.ok(installedAssets.includes("/assets/pages/components/update-dialog.js")); assert.ok( installedAssets.includes( - "/assets/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts", + "/assets/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.js", ), ); assert.ok( installedAssets.includes( - "/assets/pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts", + "/assets/pages/(task-workspace)/codex-status/components/runtime-update-dialog.js", ), ); }); @@ -199,16 +199,16 @@ test("copies a file the server calls unchanged from an earlier build's cache", a const harness = createHarness({ cacheEntries: { "caffold-shell-older-build": new Map([ - ["/assets/app.ts", taggedResponse("older app", '"app-older"')], + ["/assets/app.js", taggedResponse("older app", '"app-older"')], ]), "caffold-shell-current-build": new Map([ - ["/assets/app.ts", taggedResponse("current app", '"app-current"')], + ["/assets/app.js", taggedResponse("current app", '"app-current"')], ]), "caffold-shell-test-build": new Map(), }, fetch: async (path, init) => { requests.push({ path, init }); - return path === "/assets/app.ts" + return path === "/assets/app.js" ? new Response(null, { status: 304 }) : new Response(`network ${path}`); }, @@ -216,12 +216,12 @@ test("copies a file the server calls unchanged from an earlier build's cache", a await harness.dispatchExtendable("install"); - assert.equal(await installedResponse(harness, "/assets/app.ts").text(), "current app"); + assert.equal(await installedResponse(harness, "/assets/app.js").text(), "current app"); // The worker builds the request options in its own context. assert.deepEqual( - JSON.parse(JSON.stringify(requests.filter(({ path }) => path === "/assets/app.ts"))), + JSON.parse(JSON.stringify(requests.filter(({ path }) => path === "/assets/app.js"))), [{ - path: "/assets/app.ts", + path: "/assets/app.js", init: { cache: "no-store", headers: { "If-None-Match": '"app-current"' } }, }], ); @@ -235,18 +235,18 @@ test("downloads a file the server says changed since the earlier build", async ( const harness = createHarness({ cacheEntries: { "caffold-shell-current-build": new Map([ - ["/assets/app.ts", taggedResponse("current app", '"app-current"')], + ["/assets/app.js", taggedResponse("current app", '"app-current"')], ]), "caffold-shell-test-build": new Map(), }, fetch: async (path) => - new Response(path === "/assets/app.ts" ? "replacement app" : `network ${path}`), + new Response(path === "/assets/app.js" ? "replacement app" : `network ${path}`), }); await harness.dispatchExtendable("install"); assert.equal( - await installedResponse(harness, "/assets/app.ts").text(), + await installedResponse(harness, "/assets/app.js").text(), "replacement app", ); }); @@ -257,7 +257,7 @@ test("downloads a file without asking when no earlier copy carries a tag", async cacheEntries: { "caffold-shell-current-build": new Map([["/", new Response("current shell")]]), "other-cache": new Map([ - ["/assets/app.ts", taggedResponse("other app", '"app-other"')], + ["/assets/app.js", taggedResponse("other app", '"app-other"')], ]), "caffold-shell-test-build": new Map([ ["/assets/styles.css", taggedResponse("partial styles", '"styles-partial"')], @@ -271,7 +271,7 @@ test("downloads a file without asking when no earlier copy carries a tag", async await harness.dispatchExtendable("install"); - for (const path of ["/", "/assets/app.ts", "/assets/styles.css"]) { + for (const path of ["/", "/assets/app.js", "/assets/styles.css"]) { assert.deepEqual(requests.filter((request) => request.path === path), [ { path, init: undefined }, ]); @@ -283,7 +283,7 @@ test("discards the build's cache when any shell file cannot be fetched", async ( const harness = createHarness({ cacheEntries: { "caffold-shell-current-build": new Map([ - ["/assets/app.ts", taggedResponse("current app", '"app-current"')], + ["/assets/app.js", taggedResponse("current app", '"app-current"')], ]), "caffold-shell-test-build": new Map(), }, @@ -291,7 +291,7 @@ test("discards the build's cache when any shell file cannot be fetched", async ( if (path === "/assets/styles.css") { return new Response("unavailable", { status: 503 }); } - return path === "/assets/app.ts" + return path === "/assets/app.js" ? new Response(null, { status: 304 }) : new Response(`network ${path}`); }, @@ -303,7 +303,7 @@ test("discards the build's cache when any shell file cannot be fetched", async ( for (const [name, url, cachePath] of [ ["navigation", "https://caffold.test/tasks/83", "/"], - ["asset", "https://caffold.test/assets/app.ts", "/assets/app.ts"], + ["asset", "https://caffold.test/assets/app.js", "/assets/app.js"], ]) { test(`serves cached ${name} without waiting for a stalled network`, async () => { let fetchCalls = 0; @@ -340,7 +340,7 @@ test("fails a missing shell request explicitly after the network bound", async ( const response = await harness.dispatchFetch({ method: "GET", - url: "https://caffold.test/assets/app.ts", + url: "https://caffold.test/assets/app.js", }); assert.equal(response.status, 504); assert.equal(await response.text(), "Caffold app shell is unavailable."); diff --git a/frontend/service-worker.js b/frontend/service-worker.ts similarity index 71% rename from frontend/service-worker.js rename to frontend/service-worker.ts index e69d4647..461dd3ed 100644 --- a/frontend/service-worker.js +++ b/frontend/service-worker.ts @@ -18,7 +18,7 @@ const APP_SHELL_ASSETS = [ "/", "/assets/manifest.webmanifest", "/assets/styles.css", - "/assets/app.ts", + "/assets/app.js", "/assets/build-info.js", "/assets/lit/index.js", "/assets/lit/async-directive.js", @@ -38,22 +38,22 @@ const APP_SHELL_ASSETS = [ "/assets/lit-element/lit-element.js", "/assets/@lit/reactive-element/reactive-element.js", "/assets/@lit/reactive-element/css-tag.js", - "/assets/api.ts", - "/assets/action-hint-scope.ts", - "/assets/element-declarations.ts", - "/assets/component-styles.ts", + "/assets/api.js", + "/assets/action-hint-scope.js", + "/assets/element-declarations.js", + "/assets/component-styles.js", "/assets/component-styles/compact-icon-button.css", "/assets/component-styles/busy-spin.css", - "/assets/origin-reachability.ts", - "/assets/file-status.ts", - "/assets/chunked-fetch.ts", - "/assets/chunked-fetch/cell-scheduler.ts", - "/assets/chunked-fetch/in-flight-limit.ts", - "/assets/chunked-fetch/transfer.ts", - "/assets/fonts.ts", - "/assets/navigation-routes.ts", - "/assets/settings.ts", - "/assets/theme.ts", + "/assets/origin-reachability.js", + "/assets/file-status.js", + "/assets/chunked-fetch.js", + "/assets/chunked-fetch/cell-scheduler.js", + "/assets/chunked-fetch/in-flight-limit.js", + "/assets/chunked-fetch/transfer.js", + "/assets/fonts.js", + "/assets/navigation-routes.js", + "/assets/settings.js", + "/assets/theme.js", "/assets/fonts/GeistSans-Variable.woff2", "/assets/fonts/Inter-Variable.woff2", "/assets/fonts/Pretendard-Variable.woff2", @@ -97,378 +97,383 @@ const APP_SHELL_ASSETS = [ "/assets/brand/claude-template.png", "/assets/brand/grok-template.png", "/assets/pages/layout.css", - "/assets/pages/layout.ts", - "/assets/pages/navigation-history.ts", - "/assets/pages/foreground-recovery.ts", - "/assets/pages/foreground-recovery/browser-signals.ts", - "/assets/pages/foreground-recovery/lifecycle.ts", - "/assets/pages/foreground-recovery/machine.ts", - "/assets/pages/foreground-recovery/diagnostics.ts", - "/assets/pages/pwa-update-lifecycle.ts", - "/assets/pages/pwa-update-lifecycle/machine.ts", - "/assets/pages/pwa-update-lifecycle/runtime.ts", + "/assets/pages/layout.js", + "/assets/pages/navigation-history.js", + "/assets/pages/foreground-recovery.js", + "/assets/pages/foreground-recovery/browser-signals.js", + "/assets/pages/foreground-recovery/lifecycle.js", + "/assets/pages/foreground-recovery/machine.js", + "/assets/pages/foreground-recovery/diagnostics.js", + "/assets/pages/pwa-update-lifecycle.js", + "/assets/pages/pwa-update-lifecycle/machine.js", + "/assets/pages/pwa-update-lifecycle/runtime.js", "/assets/pages/components/build-mismatch-alert.css", - "/assets/pages/components/build-mismatch-alert.ts", + "/assets/pages/components/build-mismatch-alert.js", "/assets/pages/components/update-dialog.css", - "/assets/pages/components/update-dialog.ts", + "/assets/pages/components/update-dialog.js", "/assets/pages/components/update-result-dialog.css", - "/assets/pages/components/update-result-dialog.ts", + "/assets/pages/components/update-result-dialog.js", "/assets/components/file-tree.css", - "/assets/components/file-tree.ts", + "/assets/components/file-tree.js", "/assets/components/file-navigator.css", - "/assets/components/file-navigator.ts", + "/assets/components/file-navigator.js", "/assets/components/file-navigator/components/list.css", - "/assets/components/file-navigator/components/list.ts", + "/assets/components/file-navigator/components/list.js", "/assets/components/pane-resizer.css", - "/assets/components/pane-resizer.ts", - "/assets/components/review-responsive.ts", - "/assets/watch.ts", + "/assets/components/pane-resizer.js", + "/assets/components/review-responsive.js", + "/assets/watch.js", "/assets/pages/(task-workspace)/settings/appearance/page.css", - "/assets/pages/(task-workspace)/settings/appearance/page.ts", + "/assets/pages/(task-workspace)/settings/appearance/page.js", "/assets/pages/(task-workspace)/settings/keyboard/page.css", - "/assets/pages/(task-workspace)/settings/keyboard/page.ts", + "/assets/pages/(task-workspace)/settings/keyboard/page.js", "/assets/pages/(task-workspace)/settings/files/page.css", - "/assets/pages/(task-workspace)/settings/files/page.ts", + "/assets/pages/(task-workspace)/settings/files/page.js", "/assets/pages/(task-workspace)/settings/notifications/page.css", - "/assets/pages/(task-workspace)/settings/notifications/page.ts", - "/assets/pages/(task-workspace)/settings/notifications/lifecycle.ts", + "/assets/pages/(task-workspace)/settings/notifications/page.js", + "/assets/pages/(task-workspace)/settings/notifications/lifecycle.js", "/assets/pages/(task-workspace)/settings/remote-access/page.css", - "/assets/pages/(task-workspace)/settings/remote-access/page.ts", - "/assets/pages/(task-workspace)/settings/remote-access/tailscale.ts", + "/assets/pages/(task-workspace)/settings/remote-access/page.js", + "/assets/pages/(task-workspace)/settings/remote-access/tailscale.js", "/assets/pages/(task-workspace)/settings/voice/page.css", - "/assets/pages/(task-workspace)/settings/voice/page.ts", - "/assets/pages/(task-workspace)/settings/voice/lifecycle.ts", + "/assets/pages/(task-workspace)/settings/voice/page.js", + "/assets/pages/(task-workspace)/settings/voice/lifecycle.js", "/assets/pages/(task-workspace)/settings/jev/page.css", - "/assets/pages/(task-workspace)/settings/jev/page.ts", - "/assets/pages/(task-workspace)/settings/jev/lifecycle.ts", + "/assets/pages/(task-workspace)/settings/jev/page.js", + "/assets/pages/(task-workspace)/settings/jev/lifecycle.js", "/assets/pages/(task-workspace)/layout.css", - "/assets/pages/(task-workspace)/layout.ts", - "/assets/keyboard-navigation.ts", - "/assets/scroll-scope.ts", - "/assets/keyboard-navigation/control.ts", - "/assets/keyboard-navigation/context.ts", - "/assets/keyboard-navigation/model.ts", - "/assets/keyboard-navigation/shortcuts.ts", + "/assets/pages/(task-workspace)/layout.js", + "/assets/keyboard-navigation.js", + "/assets/scroll-scope.js", + "/assets/keyboard-navigation/control.js", + "/assets/keyboard-navigation/context.js", + "/assets/keyboard-navigation/model.js", + "/assets/keyboard-navigation/shortcuts.js", "/assets/keyboard-navigation/components/hud.css", - "/assets/keyboard-navigation/components/hud.ts", + "/assets/keyboard-navigation/components/hud.js", "/assets/keyboard-navigation/components/selector.css", - "/assets/keyboard-navigation/components/selector.ts", + "/assets/keyboard-navigation/components/selector.js", "/assets/keyboard-navigation/components/presentation.css", - "/assets/keyboard-navigation/components/presentation.ts", + "/assets/keyboard-navigation/components/presentation.js", "/assets/keyboard-navigation/components/shortcut-list.css", - "/assets/keyboard-navigation/components/shortcut-list.ts", + "/assets/keyboard-navigation/components/shortcut-list.js", "/assets/keyboard-navigation/components/shortcut-dialog.css", - "/assets/keyboard-navigation/components/shortcut-dialog.ts", - "/assets/action-hints.ts", - "/assets/action-hints/control.ts", - "/assets/action-hints/declarations.ts", - "/assets/action-hints/model.ts", + "/assets/keyboard-navigation/components/shortcut-dialog.js", + "/assets/action-hints.js", + "/assets/action-hints/control.js", + "/assets/action-hints/declarations.js", + "/assets/action-hints/model.js", "/assets/action-hints/components/dialog.css", - "/assets/action-hints/components/dialog.ts", - "/assets/pages/(task-workspace)/live-updates.ts", - "/assets/pages/(task-workspace)/live-updates/lifecycle.ts", + "/assets/action-hints/components/dialog.js", + "/assets/pages/(task-workspace)/live-updates.js", + "/assets/pages/(task-workspace)/live-updates/lifecycle.js", "/assets/pages/(task-workspace)/components/navigation.css", - "/assets/pages/(task-workspace)/components/navigation.ts", + "/assets/pages/(task-workspace)/components/navigation.js", "/assets/pages/(task-workspace)/components/update-task-dialog.css", - "/assets/pages/(task-workspace)/components/update-task-dialog.ts", + "/assets/pages/(task-workspace)/components/update-task-dialog.js", "/assets/pages/(task-workspace)/components/workspace-brand.css", - "/assets/pages/(task-workspace)/components/workspace-brand.ts", - "/assets/pages/(task-workspace)/codex-status.ts", - "/assets/pages/(task-workspace)/codex-status/model.ts", - "/assets/pages/(task-workspace)/codex-status/lifecycle.ts", - "/assets/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.ts", - "/assets/pages/(task-workspace)/codex-status/runtime-update-lifecycle.ts", + "/assets/pages/(task-workspace)/components/workspace-brand.js", + "/assets/pages/(task-workspace)/codex-status.js", + "/assets/pages/(task-workspace)/codex-status/model.js", + "/assets/pages/(task-workspace)/codex-status/lifecycle.js", + "/assets/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.js", + "/assets/pages/(task-workspace)/codex-status/runtime-update-lifecycle.js", "/assets/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.css", - "/assets/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts", + "/assets/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.js", "/assets/pages/(task-workspace)/codex-status/components/runtime-update-dialog.css", - "/assets/pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts", + "/assets/pages/(task-workspace)/codex-status/components/runtime-update-dialog.js", "/assets/pages/(task-workspace)/codex-status/components/reset-credit-dialog.css", - "/assets/pages/(task-workspace)/codex-status/components/reset-credit-dialog.ts", - "/assets/pages/(task-workspace)/task-store-status.ts", - "/assets/pages/(task-workspace)/task-store-status/model.ts", - "/assets/pages/(task-workspace)/task-store-status/lifecycle.ts", + "/assets/pages/(task-workspace)/codex-status/components/reset-credit-dialog.js", + "/assets/pages/(task-workspace)/task-store-status.js", + "/assets/pages/(task-workspace)/task-store-status/model.js", + "/assets/pages/(task-workspace)/task-store-status/lifecycle.js", "/assets/pages/(task-workspace)/notes/components/document.css", - "/assets/pages/(task-workspace)/notes/components/document.ts", + "/assets/pages/(task-workspace)/notes/components/document.js", "/assets/pages/(task-workspace)/notes/components/info.css", - "/assets/pages/(task-workspace)/notes/components/info.ts", + "/assets/pages/(task-workspace)/notes/components/info.js", "/assets/pages/(task-workspace)/notes/components/info/components/copy-markdown.css", - "/assets/pages/(task-workspace)/notes/components/info/components/copy-markdown.ts", + "/assets/pages/(task-workspace)/notes/components/info/components/copy-markdown.js", "/assets/pages/(task-workspace)/notes/components/info/components/copy-path.css", - "/assets/pages/(task-workspace)/notes/components/info/components/copy-path.ts", + "/assets/pages/(task-workspace)/notes/components/info/components/copy-path.js", "/assets/pages/(task-workspace)/notes/components/navigator.css", - "/assets/pages/(task-workspace)/notes/components/navigator.ts", + "/assets/pages/(task-workspace)/notes/components/navigator.js", "/assets/pages/(task-workspace)/notes/layout.css", - "/assets/pages/(task-workspace)/notes/layout.ts", - "/assets/pages/(task-workspace)/notes/layout/selection.ts", - "/assets/pages/(task-workspace)/notes/tree.ts", + "/assets/pages/(task-workspace)/notes/layout.js", + "/assets/pages/(task-workspace)/notes/layout/selection.js", + "/assets/pages/(task-workspace)/notes/tree.js", "/assets/pages/(task-workspace)/settings/layout.css", - "/assets/pages/(task-workspace)/settings/layout.ts", + "/assets/pages/(task-workspace)/settings/layout.js", "/assets/pages/(task-workspace)/settings/components/navigator.css", - "/assets/pages/(task-workspace)/settings/components/navigator.ts", + "/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.ts", + "/assets/pages/(task-workspace)/settings/components/navigator/components/item.js", "/assets/pages/(task-workspace)/settings/components/detail-list.css", - "/assets/pages/(task-workspace)/settings/components/detail-list.ts", + "/assets/pages/(task-workspace)/settings/components/detail-list.js", "/assets/pages/(task-workspace)/settings/components/refresh-button.css", - "/assets/pages/(task-workspace)/settings/components/refresh-button.ts", + "/assets/pages/(task-workspace)/settings/components/refresh-button.js", "/assets/pages/(task-workspace)/settings/codex/page.css", - "/assets/pages/(task-workspace)/settings/codex/page.ts", + "/assets/pages/(task-workspace)/settings/codex/page.js", "/assets/pages/(task-workspace)/settings/claude/page.css", - "/assets/pages/(task-workspace)/settings/claude/page.ts", - "/assets/pages/(task-workspace)/settings/claude/display.ts", + "/assets/pages/(task-workspace)/settings/claude/page.js", + "/assets/pages/(task-workspace)/settings/claude/display.js", "/assets/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.css", - "/assets/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.ts", + "/assets/pages/(task-workspace)/settings/claude/components/runtime-restart-dialog.js", "/assets/pages/(task-workspace)/settings/grok/page.css", - "/assets/pages/(task-workspace)/settings/grok/page.ts", - "/assets/pages/(task-workspace)/settings/grok/display.ts", + "/assets/pages/(task-workspace)/settings/grok/page.js", + "/assets/pages/(task-workspace)/settings/grok/display.js", "/assets/pages/(task-workspace)/settings/about/page.css", - "/assets/pages/(task-workspace)/settings/about/page.ts", + "/assets/pages/(task-workspace)/settings/about/page.js", "/assets/pages/(task-workspace)/tasks/controls.css", "/assets/pages/(task-workspace)/tasks/layout.css", - "/assets/pages/(task-workspace)/tasks/layout.ts", + "/assets/pages/(task-workspace)/tasks/layout.js", "/assets/pages/(task-workspace)/tasks/(detail)/layout.css", - "/assets/pages/(task-workspace)/tasks/(detail)/layout.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/layout.js", "/assets/pages/(task-workspace)/tasks/(detail)/(section)/layout.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(section)/layout.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(section)/layout.js", "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts.js", "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.js", "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/github-shortcuts.js", "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.ts", - "/assets/pages/(task-workspace)/tasks/runtime-state.ts", - "/assets/pages/(task-workspace)/tasks/stream.ts", - "/assets/pages/(task-workspace)/tasks/stream/machine.ts", - "/assets/pages/(task-workspace)/tasks/task-events.ts", - "/assets/pages/(task-workspace)/tasks/task-format.ts", - "/assets/pages/(task-workspace)/tasks/task-list-model.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(section)/components/summary.js", + "/assets/pages/(task-workspace)/tasks/runtime-state.js", + "/assets/pages/(task-workspace)/tasks/stream.js", + "/assets/pages/(task-workspace)/tasks/stream/machine.js", + "/assets/pages/(task-workspace)/tasks/task-events.js", + "/assets/pages/(task-workspace)/tasks/task-format.js", + "/assets/pages/(task-workspace)/tasks/task-list-model.js", "/assets/pages/(task-workspace)/tasks/components/composer.css", - "/assets/pages/(task-workspace)/tasks/components/composer.ts", + "/assets/pages/(task-workspace)/tasks/components/composer.js", "/assets/pages/(task-workspace)/tasks/components/composer/components/attachments.css", - "/assets/pages/(task-workspace)/tasks/components/composer/components/attachments.ts", + "/assets/pages/(task-workspace)/tasks/components/composer/components/attachments.js", "/assets/pages/(task-workspace)/tasks/components/composer/components/context-usage.css", - "/assets/pages/(task-workspace)/tasks/components/composer/components/context-usage.ts", + "/assets/pages/(task-workspace)/tasks/components/composer/components/context-usage.js", "/assets/pages/(task-workspace)/tasks/components/task-turn-options.css", - "/assets/pages/(task-workspace)/tasks/components/task-turn-options.ts", + "/assets/pages/(task-workspace)/tasks/components/task-turn-options.js", "/assets/pages/(task-workspace)/tasks/components/task-create/components/directory-field.css", - "/assets/pages/(task-workspace)/tasks/components/task-create/components/directory-field.ts", - "/assets/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.ts", - "/assets/pages/(task-workspace)/tasks/components/task-create/directory-path.ts", + "/assets/pages/(task-workspace)/tasks/components/task-create/components/directory-field.js", + "/assets/pages/(task-workspace)/tasks/components/task-create/components/directory-field/control.js", + "/assets/pages/(task-workspace)/tasks/components/task-create/directory-path.js", "/assets/pages/(task-workspace)/tasks/components/archived-delete-dialog.css", - "/assets/pages/(task-workspace)/tasks/components/archived-delete-dialog.ts", + "/assets/pages/(task-workspace)/tasks/components/archived-delete-dialog.js", "/assets/pages/(task-workspace)/tasks/components/task-switcher-dialog.css", - "/assets/pages/(task-workspace)/tasks/components/task-switcher-dialog.ts", + "/assets/pages/(task-workspace)/tasks/components/task-switcher-dialog.js", "/assets/pages/(task-workspace)/tasks/components/image-preview-dialog.css", - "/assets/pages/(task-workspace)/tasks/components/image-preview-dialog.ts", + "/assets/pages/(task-workspace)/tasks/components/image-preview-dialog.js", "/assets/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.css", - "/assets/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.ts", - "/assets/pages/(task-workspace)/tasks/components/composer/voice-recorder.ts", - "/assets/pages/(task-workspace)/tasks/components/composer/voice-worklet.ts", + "/assets/pages/(task-workspace)/tasks/components/composer/components/voice-level-meter.js", + "/assets/pages/(task-workspace)/tasks/components/composer/voice-recorder.js", + "/assets/pages/(task-workspace)/tasks/components/composer/voice-worklet.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/session.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/session.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/conversation.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/history.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-attachments.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/prompt-submission.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/layout/upload-discards.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/model.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.js", "/assets/pages/(task-workspace)/tasks/(detail)/components/git-menu.css", - "/assets/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/components/git-menu.js", "/assets/pages/(task-workspace)/tasks/(detail)/components/github-menu.css", - "/assets/pages/(task-workspace)/tasks/(detail)/components/github-menu.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/components/github-menu.js", "/assets/pages/(task-workspace)/tasks/(detail)/components/terminal-button.css", - "/assets/pages/(task-workspace)/tasks/(detail)/components/terminal-button.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/components/terminal-button.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.css", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/older-history.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/active-turn.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/active-turn.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/copy-button.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/assistant-message/components/suggested-prompts.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/message-attachments.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/changed-files.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.css", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/approval.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command/model.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/command-group/model.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/command-runs.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/broken-delete-dialog.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/command-dialog.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.css", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/markdown/components/code-block.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/entries.js", "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.js", "/assets/pages/(task-workspace)/tasks/(detail)/(review)/layout.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/layout.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/layout/route.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/representations.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/layout.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/layout/paths.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/layout/route.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/changes-reading.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/representations.js", "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes.js", "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.js", "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/branch-changes.js", "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/selected-file/content.js", "/assets/pages/(task-workspace)/tasks/components/active-task-list.css", - "/assets/pages/(task-workspace)/tasks/components/active-task-list.ts", - "/assets/pages/(task-workspace)/tasks/components/active-task-list/move-list-item.ts", + "/assets/pages/(task-workspace)/tasks/components/active-task-list.js", + "/assets/pages/(task-workspace)/tasks/components/active-task-list/move-list-item.js", "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section.css", - "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section.ts", + "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section.js", "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.css", - "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.ts", + "/assets/pages/(task-workspace)/tasks/components/active-task-list/components/section/components/row.js", "/assets/pages/(task-workspace)/tasks/components/archived-task-list.css", - "/assets/pages/(task-workspace)/tasks/components/archived-task-list.ts", + "/assets/pages/(task-workspace)/tasks/components/archived-task-list.js", "/assets/pages/(task-workspace)/tasks/recovery/page.css", - "/assets/pages/(task-workspace)/tasks/recovery/page.ts", + "/assets/pages/(task-workspace)/tasks/recovery/page.js", "/assets/pages/(task-workspace)/tasks/components/task-store-recovery.css", - "/assets/pages/(task-workspace)/tasks/components/task-store-recovery.ts", + "/assets/pages/(task-workspace)/tasks/components/task-store-recovery.js", "/assets/pages/(task-workspace)/tasks/components/task-absence.css", - "/assets/pages/(task-workspace)/tasks/components/task-absence.ts", + "/assets/pages/(task-workspace)/tasks/components/task-absence.js", "/assets/pages/(task-workspace)/tasks/components/navigator.css", - "/assets/pages/(task-workspace)/tasks/components/navigator.ts", + "/assets/pages/(task-workspace)/tasks/components/navigator.js", "/assets/pages/(task-workspace)/tasks/components/task-create.css", - "/assets/pages/(task-workspace)/tasks/components/task-create.ts", + "/assets/pages/(task-workspace)/tasks/components/task-create.js", "/assets/pages/(task-workspace)/tasks/new/page.css", - "/assets/pages/(task-workspace)/tasks/new/page.ts", + "/assets/pages/(task-workspace)/tasks/new/page.js", "/assets/pages/(task-workspace)/tasks/components/task-status-chip.css", - "/assets/pages/(task-workspace)/tasks/components/task-status-chip.ts", + "/assets/pages/(task-workspace)/tasks/components/task-status-chip.js", "/assets/pages/(task-workspace)/tasks/(detail)/(git)/layout.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(git)/layout.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(git)/layout.js", "/assets/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(git)/components/controls.js", "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/layout.js", "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/list/page.js", "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/page.js", "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(git)/(log)/commit/components/changes-tree.js", "/assets/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(git)/compare/page.js", "/assets/components/git-compare-browser.css", - "/assets/components/git-compare-browser.ts", + "/assets/components/git-compare-browser.js", "/assets/components/git-compare-tree.css", - "/assets/components/git-compare-tree.ts", + "/assets/components/git-compare-tree.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/layout.css", "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page/keys.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page/model.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page.js", + "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page/connection.js", + "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page/keyboard-inset.js", + "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page/keys.js", + "/assets/pages/(task-workspace)/tasks/(detail)/terminal/page/model.js", "/assets/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.css", - "/assets/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/layout.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/terminal/components/special-keys.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/layout.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/markdown.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-issue.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/components/task-start-dialog/components/github-pull.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/layout.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/list/page.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(issues)/detail/page.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/layout.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/list/page.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/detail/page.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/page.js", "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.css", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.ts", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/(pulls)/files/components/tree.js", "/assets/components/file-viewer/components/code-viewer.css", - "/assets/components/file-viewer/components/code-viewer.ts", + "/assets/components/file-viewer/components/code-viewer.js", "/assets/components/file-viewer/components/diff-viewer.css", - "/assets/components/file-viewer/components/diff-viewer.ts", - "/assets/components/file-viewer/syntax-spans.ts", - "/assets/components/dom.ts", + "/assets/components/file-viewer/components/diff-viewer.js", + "/assets/components/file-viewer/syntax-spans.js", + "/assets/components/dom.js", "/assets/components/file-viewer.css", - "/assets/components/file-viewer.ts", + "/assets/components/file-viewer.js", "/assets/components/markdown-preview.css", - "/assets/components/markdown-preview.ts", + "/assets/components/markdown-preview.js", "/assets/components/pdf-viewer.css", - "/assets/components/pdf-viewer.ts", + "/assets/components/pdf-viewer.js", "/assets/components/docx-viewer.css", - "/assets/components/docx-viewer.ts", + "/assets/components/docx-viewer.js", "/assets/components/pptx-viewer.css", - "/assets/components/pptx-viewer.ts", + "/assets/components/pptx-viewer.js", "/assets/components/xlsx-viewer.css", - "/assets/components/xlsx-viewer.ts", - "/assets/components/xlsx-viewer/window.ts", - "/assets/components/xlsx-viewer/workbook.ts", - "/assets/components/file-viewer-presentation.ts", - "/assets/components/icons.ts", - "/assets/components/drawn-refs.ts", + "/assets/components/xlsx-viewer.js", + "/assets/components/xlsx-viewer/window.js", + "/assets/components/xlsx-viewer/workbook.js", + "/assets/components/file-viewer-presentation.js", + "/assets/components/icons.js", + "/assets/components/drawn-refs.js", "/assets/components/loading-text.css", - "/assets/components/loading-text.ts", + "/assets/components/loading-text.js", "/assets/components/pagination.css", - "/assets/components/pagination.ts", + "/assets/components/pagination.js", "/assets/components/pagination/components/button.css", - "/assets/components/pagination/components/button.ts", + "/assets/components/pagination/components/button.js", "/assets/components/segmented-control.css", - "/assets/components/segmented-control.ts", + "/assets/components/segmented-control.js", "/assets/components/terminal-view.css", - "/assets/components/terminal-view.ts", + "/assets/components/terminal-view.js", ]; const APP_SHELL_ASSET_PATHS = new Set(APP_SHELL_ASSETS); -self.addEventListener("install", (event) => { +// This is a classic script, where TypeScript types `self` as a plain worker. +const worker = self as unknown as ServiceWorkerGlobalScope; + +type NoticePayload = Record; + +worker.addEventListener("install", (event) => { event.waitUntil(installShell()); }); -self.addEventListener("activate", (event) => { +worker.addEventListener("activate", (event) => { event.waitUntil(activatePreparedShell()); }); -self.addEventListener("message", (event) => { +worker.addEventListener("message", (event) => { if (event.data?.type === PRUNE_SHELL_CACHES_MESSAGE) { event.waitUntil(pruneShellCachesWhenUnused(event.data.cacheNames)); return; @@ -478,7 +483,7 @@ self.addEventListener("message", (event) => { return; } if (event.data?.type === ACTIVATE_PREPARED_BUILD_MESSAGE) { - event.waitUntil(self.skipWaiting()); + event.waitUntil(worker.skipWaiting()); return; } if (event.data?.type === CLAIM_PREPARED_BUILD_MESSAGE) { @@ -486,23 +491,26 @@ self.addEventListener("message", (event) => { } }); -self.addEventListener("push", (event) => { +worker.addEventListener("push", (event) => { event.waitUntil(showTaskNotification(event.data)); }); -self.addEventListener("notificationclick", (event) => { +worker.addEventListener("notificationclick", (event) => { event.notification?.close(); event.waitUntil(openNotificationRoute(event.notification?.data?.route)); }); -self.addEventListener("fetch", (event) => { +worker.addEventListener("fetch", (event) => { const request = event.request; if (request.method !== "GET") { return; } const url = new URL(request.url); - if (url.origin !== self.location.origin || url.pathname.startsWith("/api/")) { + if ( + url.origin !== worker.location.origin || + url.pathname.startsWith("/api/") + ) { return; } @@ -530,7 +538,7 @@ self.addEventListener("fetch", (event) => { // no longer keeps its own copies. Each answer is stored as it arrives, since // unread bodies hold their connections and would leave later requests waiting. // A file that cannot be fetched discards the cache, leaving nothing half made. -async function installShell() { +async function installShell(): Promise { const cache = await caches.open(CACHE_NAME); const previousCaches = await Promise.all( (await caches.keys()) @@ -550,31 +558,42 @@ async function installShell() { } } -async function shellAssetResponse(path, previousCaches) { - let previous = null; +async function shellAssetResponse( + path: string, + previousCaches: Cache[], +): Promise { + let previous: Response | undefined; + let previousTag = ""; for (const previousCache of previousCaches) { const copy = await previousCache.match(path); - if (copy?.headers.get("ETag")) { + const tag = copy?.headers.get("ETag"); + if (copy && tag) { previous = copy; + previousTag = tag; break; } } const response = previous ? await fetch(path, { cache: "no-store", - headers: { "If-None-Match": previous.headers.get("ETag") }, + headers: { "If-None-Match": previousTag }, }) : await fetch(path); if (previous && response.status === 304) { return previous; } if (!response.ok) { - throw new Error(`The app shell could not fetch ${path}: ${response.status}`); + throw new Error( + `The app shell could not fetch ${path}: ${response.status}`, + ); } return response; } -async function activeShellFirst(request, cachePath) { +async function activeShellFirst( + request: Request, + cachePath: string, +): Promise { const cache = await caches.open(CACHE_NAME); const cached = await cache.match(cachePath, { ignoreSearch: true }); if (cached) { @@ -591,9 +610,12 @@ async function activeShellFirst(request, cachePath) { } } -async function fetchWithTimeout(request) { +async function fetchWithTimeout(request: Request): Promise { const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), SHELL_NETWORK_TIMEOUT_MS); + const timeout = setTimeout( + () => controller.abort(), + SHELL_NETWORK_TIMEOUT_MS, + ); try { return await fetch(request, { signal: controller.signal }); } finally { @@ -601,10 +623,10 @@ async function fetchWithTimeout(request) { } } -async function pruneShellCachesWhenUnused(cacheNames) { +async function pruneShellCachesWhenUnused(cacheNames: unknown): Promise { const [controlledClients, allClients] = await Promise.all([ - self.clients.matchAll({ type: "window" }), - self.clients.matchAll({ type: "window", includeUncontrolled: true }), + worker.clients.matchAll({ type: "window" }), + worker.clients.matchAll({ type: "window", includeUncontrolled: true }), ]); const controlledIds = new Set(controlledClients.map((client) => client.id)); if (allClients.some((client) => !controlledIds.has(client.id))) { @@ -623,8 +645,8 @@ async function pruneShellCachesWhenUnused(cacheNames) { ); } -async function activatePreparedShell() { - const clients = await self.clients.matchAll({ +async function activatePreparedShell(): Promise { + const clients = await worker.clients.matchAll({ type: "window", includeUncontrolled: true, }); @@ -637,13 +659,17 @@ function updateReadyMessage() { return { type: UPDATE_READY_MESSAGE, buildId: BUILD_ID }; } -async function claimPreparedBuild(client) { - await self.clients.claim(); +async function claimPreparedBuild( + client: ExtendableMessageEvent["source"], +): Promise { + await worker.clients.claim(); client?.postMessage({ type: UPDATE_CONTROLLED_MESSAGE, buildId: BUILD_ID }); } -async function showTaskNotification(data) { - let payload; +async function showTaskNotification( + data: PushMessageData | null, +): Promise { + let payload: NoticePayload | undefined; try { payload = data?.json(); } catch { @@ -652,9 +678,10 @@ async function showTaskNotification(data) { if (!payload || typeof payload !== "object") { return; } - const notice = payload.kind === ACTION_REQUIRED_KIND - ? readActionRequiredPayload(payload) - : readTerminalPayload(payload); + const notice = + payload.kind === ACTION_REQUIRED_KIND + ? readActionRequiredPayload(payload) + : readTerminalPayload(payload); if (!notice) { return; } @@ -662,7 +689,7 @@ async function showTaskNotification(data) { if (!route) { return; } - await self.registration.showNotification(notice.title, { + await worker.registration.showNotification(notice.title, { body: notice.body, tag: notice.tag, icon: "/assets/icons/icon-192.png", @@ -671,7 +698,7 @@ async function showTaskNotification(data) { }); } -function readTerminalPayload(payload) { +function readTerminalPayload(payload: NoticePayload) { const threadId = typeof payload.threadId === "string" ? payload.threadId : ""; const turnId = typeof payload.turnId === "string" ? payload.turnId : ""; const taskName = safeTaskName(payload.taskName); @@ -679,6 +706,7 @@ function readTerminalPayload(payload) { if ( !safeTaskId(threadId) || !safeTaskId(turnId) || + typeof payload.status !== "string" || !["completed", "failed", "interrupted"].includes(payload.status) || !safeNotificationTag(tag) || taskName === null @@ -696,7 +724,7 @@ function readTerminalPayload(payload) { // A Task stopped for a person. Which question it stopped on is not sent, so // the copy says only that one is waiting. -function readActionRequiredPayload(payload) { +function readActionRequiredPayload(payload: NoticePayload) { const threadId = typeof payload.threadId === "string" ? payload.threadId : ""; const taskName = safeTaskName(payload.taskName); const tag = typeof payload.tag === "string" ? payload.tag : ""; @@ -711,23 +739,25 @@ function readActionRequiredPayload(payload) { }; } -function safeNotificationTag(tag) { +function safeNotificationTag(tag: string): boolean { return /^[A-Za-z0-9_-]{1,64}$/.test(tag); } // The bounded name, "" when the notice carries none, and null when it carries // one that no notification should show. -function safeTaskName(taskName) { +function safeTaskName(taskName: unknown): string | null { if (typeof taskName !== "string" || !taskName.trim()) { return ""; } const name = [...taskName.trim()].slice(0, 120).join(""); - return [...name].some((character) => isControlCharacter(character)) ? null : name; + return [...name].some((character) => isControlCharacter(character)) + ? null + : name; } // An open window is focused and told which route to apply, because loading the // route into that window would discard the workspace state it still holds. -async function openNotificationRoute(route) { +async function openNotificationRoute(route: unknown): Promise { const safeRoute = safeNotificationRoute(route); if (!safeRoute) { return; @@ -745,42 +775,44 @@ async function openNotificationRoute(route) { } } try { - await self.clients.openWindow(safeRoute); + await worker.clients.openWindow(safeRoute); } catch { // Notification navigation is best-effort. } } // Caffold windows, the one already showing the Task first. -async function notificationTargets(route) { - const windows = (await self.clients.matchAll({ - type: "window", - includeUncontrolled: true, - })).filter((client) => sameOriginClient(client.url)); +async function notificationTargets(route: string): Promise { + const windows = ( + await worker.clients.matchAll({ + type: "window", + includeUncontrolled: true, + }) + ).filter((client) => sameOriginClient(client.url)); return [ ...windows.filter((client) => clientShowsTask(client.url, route)), ...windows.filter((client) => !clientShowsTask(client.url, route)), ]; } -function safeNotificationRoute(route) { +function safeNotificationRoute(route: unknown): string { if (typeof route !== "string") { return ""; } - let url; + let url: URL; try { - url = new URL(route, self.location.origin); + url = new URL(route, worker.location.origin); } catch { return ""; } - if (url.origin !== self.location.origin || url.search || url.hash) { + if (url.origin !== worker.location.origin || url.search || url.hash) { return ""; } const match = url.pathname.match(/^\/tasks\/([^/]+)$/); if (!match) { return ""; } - let threadId; + let threadId: string; try { threadId = decodeURIComponent(match[1]); } catch { @@ -789,48 +821,49 @@ function safeNotificationRoute(route) { return taskRoute(threadId); } -function taskRoute(threadId) { +function taskRoute(threadId: string): string { return safeTaskId(threadId) ? `/tasks/${encodeURIComponent(threadId)}` : ""; } -function safeTaskId(value) { - if (typeof value !== "string") { - return false; - } +function safeTaskId(value: string): boolean { const characters = [...value]; - return characters.length > 0 && + return ( + characters.length > 0 && characters.length <= 256 && value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\") && - !characters.some((character) => isControlCharacter(character)); + !characters.some((character) => isControlCharacter(character)) + ); } -function isControlCharacter(character) { - const code = character.codePointAt(0); +function isControlCharacter(character: string): boolean { + const code = character.codePointAt(0) ?? 0; return code <= 0x1f || (code >= 0x7f && code <= 0x9f); } -function terminalStatusCopy(status) { +function terminalStatusCopy(status: string): string { if (status === "failed") return "Failed"; if (status === "interrupted") return "Interrupted"; return "Completed"; } -function clientShowsTask(clientUrl, route) { +function clientShowsTask(clientUrl: string, route: string): boolean { try { const url = new URL(clientUrl); - return url.origin === self.location.origin && - (url.pathname === route || url.pathname.startsWith(`${route}/`)); + return ( + url.origin === worker.location.origin && + (url.pathname === route || url.pathname.startsWith(`${route}/`)) + ); } catch { return false; } } -function sameOriginClient(clientUrl) { +function sameOriginClient(clientUrl: string): boolean { try { - return new URL(clientUrl).origin === self.location.origin; + return new URL(clientUrl).origin === worker.location.origin; } catch { return false; } diff --git a/frontend/settings.test.js b/frontend/settings.test.js index 302c61b2..60402179 100644 --- a/frontend/settings.test.js +++ b/frontend/settings.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -const settingsModuleUrl = new URL("./settings.ts", import.meta.url); +const settingsModuleUrl = new URL("./settings.js", import.meta.url); async function importFreshSettings(label) { const url = new URL(settingsModuleUrl); diff --git a/frontend/settings.ts b/frontend/settings.ts index cb95a6ac..12665288 100644 --- a/frontend/settings.ts +++ b/frontend/settings.ts @@ -7,16 +7,16 @@ import { normalizeCodeTypefacePreset, normalizeUiTypefacePreset, UI_TYPEFACE_PRESETS, -} from "./fonts.ts"; +} from "./fonts.js"; import { applyThemeMode, DEFAULT_THEME_MODE, normalizeThemeMode, THEME_MODES, type ThemeMode, -} from "./theme.ts"; +} from "./theme.js"; -export type { TypefacePreset } from "./fonts.ts"; +export type { TypefacePreset } from "./fonts.js"; const STORAGE_KEY = "caffold:settings"; diff --git a/frontend/strip-types.mjs b/frontend/strip-types.mjs deleted file mode 100644 index 6a95fe33..00000000 --- a/frontend/strip-types.mjs +++ /dev/null @@ -1,30 +0,0 @@ -// Writes each production TypeScript module under the frontend to the output -// directory, at the same path, as the JavaScript the browser loads: the module -// with its type syntax replaced by spaces, so every line and column stays -// where the source has it. Node applies the same transform when tests import -// the module. -import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; -import { stripTypeScriptTypes } from "node:module"; -import path from "node:path"; - -const [sourceRoot, outputRoot] = process.argv.slice(2); - -for (const module of productionTypeScriptModules(sourceRoot)) { - const output = path.join(outputRoot, module); - mkdirSync(path.dirname(output), { recursive: true }); - writeFileSync(output, stripTypeScriptTypes(readFileSync(path.join(sourceRoot, module), "utf8"))); -} - -function productionTypeScriptModules(root, prefix = "") { - return readdirSync(path.join(root, prefix), { withFileTypes: true }).flatMap((entry) => { - const relative = path.join(prefix, entry.name); - if (entry.isDirectory()) { - return entry.name === "node_modules" || entry.name === "tests" - ? [] - : productionTypeScriptModules(root, relative); - } - return entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts") && !entry.name.endsWith(".d.ts") - ? [relative] - : []; - }); -} diff --git a/frontend/tests/contracts/appearance-theme.test.mjs b/frontend/tests/contracts/appearance-theme.test.mjs index b7d24972..f26ea6bc 100644 --- a/frontend/tests/contracts/appearance-theme.test.mjs +++ b/frontend/tests/contracts/appearance-theme.test.mjs @@ -89,7 +89,7 @@ test("theme-sensitive component assets follow app theme roles, not OS media", () /prefers-color-scheme/, `${path} must consume the resolved app theme instead of OS preference`, ); - if (/\.(?:js|ts)$/.test(path) && path !== "service-worker.js") { + if (/\.(?:js|ts)$/.test(path) && path !== "service-worker.ts") { assert.doesNotMatch( source, /(?:git-logomark|github-invertocat)-(?:light|dark)\.svg/, diff --git a/frontend/tests/contracts/docx-viewer-library.test.mjs b/frontend/tests/contracts/docx-viewer-library.test.mjs index 82803c68..3b75a026 100644 --- a/frontend/tests/contracts/docx-viewer-library.test.mjs +++ b/frontend/tests/contracts/docx-viewer-library.test.mjs @@ -38,7 +38,7 @@ test("the browser suite answers docx-preview's jsDelivr module from the installe }); test("docx-preview stays a runtime CDN import rather than a shipped asset", () => { - const serviceWorker = read("service-worker.js"); + const serviceWorker = read("service-worker.ts"); const staticAssets = readFileSync( new URL("../caffold/src/static_assets.rs", frontendUrl), "utf8", @@ -47,6 +47,6 @@ test("docx-preview stays a runtime CDN import rather than a shipped asset", () = for (const manifest of [serviceWorker, staticAssets]) { assert.doesNotMatch(manifest, /docx-preview|jszip/); } - assert.match(serviceWorker, /"\/assets\/components\/docx-viewer\.ts"/); - assert.match(staticAssets, /"components\/docx-viewer\.ts"/); + assert.match(serviceWorker, /"\/assets\/components\/docx-viewer\.js"/); + assert.match(staticAssets, /"components\/docx-viewer\.js"/); }); diff --git a/frontend/tests/contracts/font-presets.test.mjs b/frontend/tests/contracts/font-presets.test.mjs index 11b3b92a..3c484dd2 100644 --- a/frontend/tests/contracts/font-presets.test.mjs +++ b/frontend/tests/contracts/font-presets.test.mjs @@ -1,12 +1,6 @@ import assert from "node:assert/strict"; import { readFileSync, readdirSync } from "node:fs"; import test from "node:test"; -import { - CODE_TYPEFACE_PRESETS, - DEFAULT_CODE_TYPEFACE_PRESET, - DEFAULT_UI_TYPEFACE_PRESET, - UI_TYPEFACE_PRESETS, -} from "../../fonts.ts"; const frontendUrl = new URL("../../", import.meta.url); const fontsUrl = new URL("assets/fonts/", frontendUrl); @@ -15,22 +9,9 @@ function bundledFiles(suffix) { return readdirSync(fontsUrl).filter((name) => name.endsWith(suffix)); } -function facesByFamily() { - const stylesheet = readFileSync(new URL("styles.css", frontendUrl), "utf8"); - const faces = new Map(); - for (const [, body] of stylesheet.matchAll(/@font-face\s*\{([^}]*)\}/g)) { - const family = body.match(/font-family:\s*("[^"]+")/)?.[1]; - const file = body.match(/url\("\.\/fonts\/([^"]+)"\)/)?.[1]; - if (family && file) { - faces.set(family, [...(faces.get(family) ?? []), file]); - } - } - return faces; -} - test("bundled font files remain explicit shell assets", () => { const serviceWorker = readFileSync( - new URL("service-worker.js", frontendUrl), + new URL("service-worker.ts", frontendUrl), "utf8", ); const faces = bundledFiles(".woff2"); @@ -61,20 +42,3 @@ test("bundled font files remain explicit shell assets", () => { // a separate font cache with its own strategy. assert.doesNotMatch(serviceWorker, /caffold-fonts|OPTIONAL_FONT|cacheFirst/); }); - -test("the index preloads exactly the faces the default presets name", () => { - const index = readFileSync(new URL("index.html", frontendUrl), "utf8"); - const faces = facesByFamily(); - const expected = [ - UI_TYPEFACE_PRESETS[DEFAULT_UI_TYPEFACE_PRESET], - CODE_TYPEFACE_PRESETS[DEFAULT_CODE_TYPEFACE_PRESET], - ].flatMap((preset) => faces.get(preset.stack.split(", ")[0]) ?? []); - - assert.ok(expected.length > 0, "default presets must name bundled faces"); - assert.deepEqual( - [...index.matchAll(/href="\/assets\/fonts\/([^"]+)"/g)].map( - ([, name]) => name, - ), - expected, - ); -}); diff --git a/frontend/tests/contracts/frontend-test-placement.test.mjs b/frontend/tests/contracts/frontend-test-placement.test.mjs index 4936a9f4..8ff8ce2c 100644 --- a/frontend/tests/contracts/frontend-test-placement.test.mjs +++ b/frontend/tests/contracts/frontend-test-placement.test.mjs @@ -30,18 +30,18 @@ test("the frontend unit-test command reaches every colocated test", () => { const packageManifest = JSON.parse( readFileSync(join(frontendRoot, "package.json"), "utf8"), ); - const patterns = packageManifest.scripts["test:unit"] - .match(/"[^"]+"/g) - .map((pattern) => pattern.slice(1, -1)); - const roots = patterns.map((pattern) => pattern.split("/")[0]); + const unitBuild = JSON.parse( + readFileSync(join(frontendRoot, "tsconfig.test.json"), "utf8"), + ); - for (const testPath of unitTestPaths) { - const root = testPath.includes("/") ? testPath.split("/")[0] : "*.test.js"; - assert.ok( - roots.includes(root), - `${testPath} is not reached by test:unit patterns ${patterns.join(" ")}`, - ); - } + // Every colocated test is compiled into the unit-test build, and the + // command runs every test that build holds. + assert.ok(unitBuild.include.includes("**/*.test.js")); + assert.ok( + packageManifest.scripts["test:unit"].includes( + `"${unitBuild.compilerOptions.outDir}/**/*.test.js"`, + ), + ); }); test("focused frontend Node tests stay beside exact same-stem owners", () => { @@ -69,7 +69,7 @@ test("colocated tests stay outside production imports and runtime assets", () => } const serviceWorker = readFileSync( - join(frontendRoot, "service-worker.js"), + join(frontendRoot, "service-worker.ts"), "utf8", ); const staticAssets = readFileSync( diff --git a/frontend/tests/contracts/lit-library.test.mjs b/frontend/tests/contracts/lit-library.test.mjs index fb1986ae..429e3e86 100644 --- a/frontend/tests/contracts/lit-library.test.mjs +++ b/frontend/tests/contracts/lit-library.test.mjs @@ -16,7 +16,7 @@ test("Caffold builds in the Lit release the frontend package pins", () => { assert.equal( manifest.devDependencies?.lit, undefined, - "a build installs runtime dependencies only, so Lit cannot be a development package", + "a build installs `dependencies` only, so Lit cannot be a development package", ); assert.equal(JSON.parse(read(nodeModulesUrl, "lit/package.json")).version, pinned); }); @@ -31,7 +31,7 @@ test("frontend modules reach Lit through the lit package alone", () => { test("every Lit module a page reaches is its package's browser build, served and precached once", () => { const staticAssets = readFileSync(new URL("../caffold/src/static_assets.rs", frontendUrl), "utf8"); - const serviceWorker = read(frontendUrl, "service-worker.js"); + const serviceWorker = read(frontendUrl, "service-worker.ts"); const reached = new Set(); const specifiers = productionModules().flatMap(([, source]) => litSpecifiers(source)); assert.ok(specifiers.includes("lit"), "the frontend renders with Lit"); diff --git a/frontend/tests/contracts/native-imports.test.mjs b/frontend/tests/contracts/native-imports.test.mjs index a637a715..1823c067 100644 --- a/frontend/tests/contracts/native-imports.test.mjs +++ b/frontend/tests/contracts/native-imports.test.mjs @@ -16,7 +16,7 @@ const nodeImports = JSON.parse( readFileSync(new URL("package.json", frontendUrl), "utf8"), ).imports; const workerEntrypoints = new Set([ - "service-worker.js", + "service-worker.ts", "pages/(task-workspace)/tasks/components/composer/voice-worklet.ts", ]); @@ -48,7 +48,7 @@ test("production alias imports resolve to the same served and precached files in "utf8", ); const serviceWorker = readFileSync( - new URL("service-worker.js", frontendUrl), + new URL("service-worker.ts", frontendUrl), "utf8", ); const usedPrefixes = new Set(); @@ -69,12 +69,12 @@ test("production alias imports resolve to the same served and precached files in new URL(assetPath, frontendUrl).href, `${path}: ${specifier} must resolve identically in both runtimes`, ); - assert.ok(/\.[jt]s$/.test(assetPath) && !/\.(?:test|d)\.[jt]s$/.test(assetPath)); + assert.ok(assetPath.endsWith(".js") && !assetPath.endsWith(".test.js")); assert.ok(!assetPath.split("/").includes("tests")); // Rust generates build-info.js; it is served and cached with source modules. if (assetPath !== "build-info.js") { assert.match( - readFileSync(new URL(assetPath, frontendUrl), "utf8"), + readFileSync(new URL(assetPath.replace(/\.js$/, ".ts"), frontendUrl), "utf8"), /\S/, `${path}: ${specifier} must refer to a production module`, ); diff --git a/frontend/tests/contracts/pdf-viewer-library.test.mjs b/frontend/tests/contracts/pdf-viewer-library.test.mjs index 324d6639..bd86c5f5 100644 --- a/frontend/tests/contracts/pdf-viewer-library.test.mjs +++ b/frontend/tests/contracts/pdf-viewer-library.test.mjs @@ -39,7 +39,7 @@ test("the browser suite serves pdf.js from the installed package", () => { }); test("pdf.js stays a runtime CDN import rather than a shipped asset", () => { - const serviceWorker = read("service-worker.js"); + const serviceWorker = read("service-worker.ts"); const staticAssets = readFileSync( new URL("../caffold/src/static_assets.rs", frontendUrl), "utf8", @@ -47,6 +47,6 @@ test("pdf.js stays a runtime CDN import rather than a shipped asset", () => { assert.doesNotMatch(serviceWorker, /pdfjs/); assert.doesNotMatch(staticAssets, /pdfjs/); - assert.match(serviceWorker, /"\/assets\/components\/pdf-viewer\.ts"/); - assert.match(staticAssets, /"components\/pdf-viewer\.ts"/); + assert.match(serviceWorker, /"\/assets\/components\/pdf-viewer\.js"/); + assert.match(staticAssets, /"components\/pdf-viewer\.js"/); }); diff --git a/frontend/tests/contracts/pptx-viewer-library.test.mjs b/frontend/tests/contracts/pptx-viewer-library.test.mjs index 0fc77540..85e8758b 100644 --- a/frontend/tests/contracts/pptx-viewer-library.test.mjs +++ b/frontend/tests/contracts/pptx-viewer-library.test.mjs @@ -33,7 +33,7 @@ test("the browser suite serves pptx-renderer from the installed package", () => }); test("pptx-renderer stays a runtime CDN import rather than a shipped asset", () => { - const serviceWorker = read("service-worker.js"); + const serviceWorker = read("service-worker.ts"); const staticAssets = readFileSync( new URL("../caffold/src/static_assets.rs", frontendUrl), "utf8", @@ -42,6 +42,6 @@ test("pptx-renderer stays a runtime CDN import rather than a shipped asset", () for (const manifest of [serviceWorker, staticAssets]) { assert.doesNotMatch(manifest, /pptx-renderer/); } - assert.match(serviceWorker, /"\/assets\/components\/pptx-viewer\.ts"/); - assert.match(staticAssets, /"components\/pptx-viewer\.ts"/); + assert.match(serviceWorker, /"\/assets\/components\/pptx-viewer\.js"/); + assert.match(staticAssets, /"components\/pptx-viewer\.js"/); }); diff --git a/frontend/tests/contracts/service-worker.test.mjs b/frontend/tests/contracts/service-worker.test.mjs index 47ad8921..eacb0832 100644 --- a/frontend/tests/contracts/service-worker.test.mjs +++ b/frontend/tests/contracts/service-worker.test.mjs @@ -2,79 +2,29 @@ import assert from "node:assert/strict"; import { readdirSync, readFileSync } from "node:fs"; import path from "node:path"; import test from "node:test"; -import vm from "node:vm"; import { repositoryRoot } from "../repository-paths.mjs"; const frontendRoot = path.join(repositoryRoot, "frontend"); const source = readFileSync( - path.join(frontendRoot, "service-worker.js"), + path.join(frontendRoot, "service-worker.ts"), "utf8", -).replace( - '"caffold-shell-__CAFFOLD_BUILD_ID__"', - '"caffold-shell-test-build"', ); -test("precaches every bundled runtime asset", async () => { - let installListener; - const installedAssets = new Set(); - const context = { - AbortController, - Promise, - Response, - Set, - URL, - caches: { - async keys() { - return []; - }, - async open() { - return { - async match() { - return undefined; - }, - async put(asset) { - installedAssets.add(asset); - }, - }; - }, - }, - clearTimeout, - fetch: async () => new Response(""), - self: { - addEventListener(type, listener) { - if (type === "install") { - installListener = listener; - } - }, - clients: {}, - location: { origin: "https://caffold.test" }, - registration: {}, - }, - setTimeout, - }; - vm.runInNewContext(source, context); - - let completion = Promise.resolve(); - installListener({ - waitUntil(value) { - completion = Promise.resolve(value); - }, - }); - await completion; - +test("precaches every bundled runtime asset", () => { + const shellAssets = appShellAssets(source); const runtimeAssets = runtimeFrontendFiles(frontendRoot).map(assetUrl); runtimeAssets.push("/assets/build-info.js"); - assert.equal(installedAssets.size, appShellAssetCount(source)); - const missing = runtimeAssets.filter((asset) => !installedAssets.has(asset)); + assert.equal(new Set(shellAssets).size, shellAssets.length); + const missing = runtimeAssets.filter((asset) => !shellAssets.includes(asset)); assert.deepEqual(missing, []); }); -function appShellAssetCount(serviceWorkerSource) { +function appShellAssets(serviceWorkerSource) { const sourceList = serviceWorkerSource.match( /const APP_SHELL_ASSETS = \[(.*?)\];/s, )?.[1]; assert.ok(sourceList); - return [...sourceList.matchAll(/^\s*"[^"]+",$/gm)].length; + return [...sourceList.matchAll(/^\s*"([^"]+)",$/gm)].map(([, asset]) => asset); } function runtimeFrontendFiles(root) { @@ -90,7 +40,7 @@ function runtimeFrontendFiles(root) { } const relative = path.relative(frontendRoot, fullPath).split(path.sep).join("/"); if ( - relative === "service-worker.js" || + relative === "service-worker.ts" || relative === "assets/fonts/D2Coding-OFL.txt" || /\.(?:test|d)\.[jt]s$/.test(relative) || !/[.](?:css|html|js|png|svg|ts|webmanifest|woff2)$/.test(relative) @@ -109,5 +59,6 @@ function assetUrl(relative) { if (relative === "manifest.webmanifest") { return "/assets/manifest.webmanifest"; } - return `/assets/${relative.replace(/^assets\//, "")}`; + // A TypeScript module is served as the JavaScript tsc writes for it. + return `/assets/${relative.replace(/^assets\//, "").replace(/\.ts$/, ".js")}`; } diff --git a/frontend/tests/contracts/task-workspace-structure.test.mjs b/frontend/tests/contracts/task-workspace-structure.test.mjs index 355c3423..24c4e23d 100644 --- a/frontend/tests/contracts/task-workspace-structure.test.mjs +++ b/frontend/tests/contracts/task-workspace-structure.test.mjs @@ -161,7 +161,7 @@ test("task workspace declares one shared master pane and one detail pane", () => shortcutDialog, /customElements\.define\(\s*"caffold-keyboard-shortcut-dialog"/, ); - assert.match(settingsWorkspace, /import "\.\/keyboard\/page\.ts"/); + assert.match(settingsWorkspace, /import "\.\/keyboard\/page\.js"/); assert.equal( [...settingsWorkspace.matchAll(/ { "pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts", ); - assert.match(info, /import "\.\/info\/components\/actions\.ts"/); + assert.match(info, /import "\.\/info\/components\/actions\.js"/); assert.match(info, / match[1]); @@ -674,19 +674,19 @@ test("PWA update lifecycle keeps one public non-visual owner and private graph", const positions = declarations.map((declaration) => machine.indexOf(declaration)); assert.deepEqual(exportedNames, ["PwaUpdateLifecycle"]); - assert.match(appShell, /from "\.\/pwa-update-lifecycle\.ts"/); - assert.doesNotMatch(appShell, /pwa-update-lifecycle\/(?:machine|runtime)\.ts/); - assert.doesNotMatch(appShell, /components\/pwa-update-lifecycle\.ts/); + assert.match(appShell, /from "\.\/pwa-update-lifecycle\.js"/); + assert.doesNotMatch(appShell, /pwa-update-lifecycle\/(?:machine|runtime)\.js/); + assert.doesNotMatch(appShell, /components\/pwa-update-lifecycle\.js/); assert.ok(positions.every((position) => position >= 0)); assert.deepEqual(positions, [...positions].sort((left, right) => left - right)); - assert.match(runtime, /from "\.\/machine\.ts"/); + assert.match(runtime, /from "\.\/machine\.js"/); assert.match(runtime, /"controllerchange"/); assert.match(runtime, /transitionPwaUpdateHandoff\(this\.handoffState, event\)/); assert.doesNotMatch(lifecycle, /addEventListener|postMessage|controllerchange/); for (const path of [ - "/assets/pages/pwa-update-lifecycle.ts", - "/assets/pages/pwa-update-lifecycle/machine.ts", - "/assets/pages/pwa-update-lifecycle/runtime.ts", + "/assets/pages/pwa-update-lifecycle.js", + "/assets/pages/pwa-update-lifecycle/machine.js", + "/assets/pages/pwa-update-lifecycle/runtime.js", ]) { assert.match(serviceWorker, new RegExp(path.replaceAll("/", "\\/"))); } @@ -710,7 +710,7 @@ test("App Shell solely coordinates foreground recovery through public owners", ( "pages/(task-workspace)/tasks/(detail)/(task)/layout.ts", ); const stream = readFrontend("pages/(task-workspace)/tasks/stream.ts"); - const serviceWorker = readFrontend("service-worker.js"); + const serviceWorker = readFrontend("service-worker.ts"); assert.match(appShell, /new ForegroundRecoveryLifecycle\(/); assert.match(appShell, /onRecover: \(request\) => this\.recoverForeground\(request\)/); @@ -740,11 +740,11 @@ test("App Shell solely coordinates foreground recovery through public owners", ( ); assert.match( foregroundLifecycle, - /from "\.\/browser-signals\.ts"/, + /from "\.\/browser-signals\.js"/, ); assert.doesNotMatch( appShell, - /foreground-recovery\/(?:browser-signals|lifecycle|machine)\.ts/, + /foreground-recovery\/(?:browser-signals|lifecycle|machine)\.js/, ); assert.match(workspace, /async recoverForeground\(/); assert.match(tasksPage, /async recoverForeground\(/); @@ -828,7 +828,7 @@ test("active and archived Task lists own distinct state and lifecycle boundaries /ACTIVE_TASK_ROW_INTENT_EVENT|caffold-active-task-row|li\[data-thread-id/, ); assert.match(section, /new CaffoldActiveTaskRow\(\)/); - assert.match(section, /\.\/section\/components\/row\.ts/); + assert.match(section, /\.\/section\/components\/row\.js/); assert.match(section, /@caffold:active-task-row-intent=/); assert.match(section, /detail: \{ subject: "task", \.\.\.event\.detail \}/); assert.doesNotMatch(activeRow, /ACTIVE_TASK_SECTION_INTENT_EVENT/); @@ -1056,10 +1056,10 @@ test("Codex status, Task-store status, and the Task-store card keep explicit lif "pages/(task-workspace)/settings/codex/page.ts", ); - assert.match(workspace, /from "\.\/codex-status\.ts"/); + assert.match(workspace, /from "\.\/codex-status\.js"/); assert.match( workspace, - /from "\.\/codex-status\/components\/runtime-restart-dialog\.ts"/, + /from "\.\/codex-status\/components\/runtime-restart-dialog\.js"/, ); assert.match( workspace, @@ -1067,16 +1067,16 @@ test("Codex status, Task-store status, and the Task-store card keep explicit lif ); assert.match( workspace, - /from "\.\/codex-status\/components\/runtime-update-dialog\.ts"/, + /from "\.\/codex-status\/components\/runtime-update-dialog\.js"/, ); assert.match( workspace, /]*><\/caffold-codex-runtime-update-dialog>/, ); - assert.match(owner, /from "\.\/codex-status\/model\.ts"/); + assert.match(owner, /from "\.\/codex-status\/model\.js"/); assert.match( owner, - /from "\.\/codex-status\/lifecycle\.ts"/, + /from "\.\/codex-status\/lifecycle\.js"/, ); assert.match(owner, /function createCodexStatusLifecycle/); assert.doesNotMatch(owner, /class CodexStatusLifecycle/); @@ -1086,9 +1086,9 @@ test("Codex status, Task-store status, and the Task-store card keep explicit lif assert.doesNotMatch(codexSource, /taskStore|TaskStore/); } assert.match(lifecycle, /class CodexStatusLifecycle/); - assert.match(workspace, /from "\.\/task-store-status\.ts"/); - assert.match(storeOwner, /from "\.\/task-store-status\/model\.ts"/); - assert.match(storeOwner, /from "\.\/task-store-status\/lifecycle\.ts"/); + assert.match(workspace, /from "\.\/task-store-status\.js"/); + assert.match(storeOwner, /from "\.\/task-store-status\/model\.js"/); + assert.match(storeOwner, /from "\.\/task-store-status\/lifecycle\.js"/); assert.match(storeOwner, /function createTaskStoreStatusLifecycle/); assert.doesNotMatch(storeOwner, /class TaskStoreStatusLifecycle/); assert.doesNotMatch(storeOwner, /export \*/); @@ -1108,7 +1108,7 @@ test("Codex status, Task-store status, and the Task-store card keep explicit lif assert.match(updateDialog, / { }); test("xterm.js stays a runtime CDN import rather than a shipped asset", () => { - const serviceWorker = read("service-worker.js"); + const serviceWorker = read("service-worker.ts"); const staticAssets = readFileSync( new URL("../caffold/src/static_assets.rs", frontendUrl), "utf8", @@ -38,6 +38,6 @@ test("xterm.js stays a runtime CDN import rather than a shipped asset", () => { assert.doesNotMatch(serviceWorker, /@xterm|xterm\.(?:mjs|css)/); assert.doesNotMatch(staticAssets, /@xterm|xterm\.(?:mjs|css)/); - assert.match(serviceWorker, /"\/assets\/components\/terminal-view\.ts"/); - assert.match(staticAssets, /"components\/terminal-view\.ts"/); + assert.match(serviceWorker, /"\/assets\/components\/terminal-view\.js"/); + assert.match(staticAssets, /"components\/terminal-view\.js"/); }); diff --git a/frontend/tests/contracts/typescript-modules.test.mjs b/frontend/tests/contracts/typescript-modules.test.mjs index eeac3967..be3d2d30 100644 --- a/frontend/tests/contracts/typescript-modules.test.mjs +++ b/frontend/tests/contracts/typescript-modules.test.mjs @@ -5,28 +5,18 @@ import test from "node:test"; const frontendUrl = new URL("../../", import.meta.url); const sources = frontendSources(); -test("a module is JavaScript or TypeScript, never both", () => { - for (const stem of stems(".ts")) { - assert.ok(!sources.has(`${stem}.js`), `${stem} is both ${stem}.js and ${stem}.ts`); - } +test("production modules are TypeScript", () => { + assert.deepEqual([...sources].filter((path) => path.endsWith(".js")), []); }); // Rust generates build-info.js; it is served and cached with source modules. -const GENERATED_MODULES = new Set(["build-info"]); - -test("a type declaration file stands beside the JavaScript module it describes", () => { - for (const stem of stems(".d.ts")) { - assert.ok( - sources.has(`${stem}.js`) || GENERATED_MODULES.has(stem), - `${stem}.d.ts describes a module that is no longer JavaScript; remove it once ${stem} moves to TypeScript`, - ); - } +test("the one type declaration file describes the module the server build writes", () => { + assert.deepEqual(stems(".d.ts"), ["build-info"]); }); -test("the frontend type check is strict and only removes erasable syntax", () => { +test("the frontend type check is strict", () => { const { compilerOptions } = JSON.parse(readFileSync(new URL("tsconfig.json", frontendUrl), "utf8")); assert.equal(compilerOptions.strict, true); - assert.equal(compilerOptions.erasableSyntaxOnly, true); assert.equal(compilerOptions.verbatimModuleSyntax, true); }); diff --git a/frontend/tests/contracts/xlsx-viewer-library.test.mjs b/frontend/tests/contracts/xlsx-viewer-library.test.mjs index 746bdd75..d67c3bd6 100644 --- a/frontend/tests/contracts/xlsx-viewer-library.test.mjs +++ b/frontend/tests/contracts/xlsx-viewer-library.test.mjs @@ -38,7 +38,7 @@ test("the browser suite answers SheetJS's CDN from the installed package", () => }); test("SheetJS stays a runtime CDN import rather than a shipped asset", () => { - const serviceWorker = read("service-worker.js"); + const serviceWorker = read("service-worker.ts"); const staticAssets = readFileSync( new URL("../caffold/src/static_assets.rs", frontendUrl), "utf8", @@ -47,6 +47,6 @@ test("SheetJS stays a runtime CDN import rather than a shipped asset", () => { for (const manifest of [serviceWorker, staticAssets]) { assert.doesNotMatch(manifest, /sheetjs|xlsx\.(?:mjs|full|core|mini)/i); } - assert.match(serviceWorker, /"\/assets\/components\/xlsx-viewer\.ts"/); - assert.match(staticAssets, /"components\/xlsx-viewer\.ts"/); + assert.match(serviceWorker, /"\/assets\/components\/xlsx-viewer\.js"/); + assert.match(staticAssets, /"components\/xlsx-viewer\.js"/); }); diff --git a/frontend/tests/e2e/app-shell-update.spec.js b/frontend/tests/e2e/app-shell-update.spec.js index cea7cca7..142dccd9 100644 --- a/frontend/tests/e2e/app-shell-update.spec.js +++ b/frontend/tests/e2e/app-shell-update.spec.js @@ -12,7 +12,7 @@ import { import { captureReviewScreenshot } from "./support/task-fixtures.js"; const serviceWorkerSource = readFileSync( - new URL("../../service-worker.js", import.meta.url), + new URL("../../service-worker.ts", import.meta.url), "utf8", ); const appShellAssets = JSON.parse( @@ -2103,6 +2103,10 @@ function activationGateDiagnostic(snapshot) { } async function startBuildLifecycleServer(upstreamOrigin) { + // The worker runtime the server under test serves, as tsc compiled it. + const servedWorker = await ( + await fetch(new URL("/service-worker.js", upstreamOrigin)) + ).text(); let currentBuild = { id: "browser-build-a", number: 1, @@ -2113,15 +2117,19 @@ async function startBuildLifecycleServer(upstreamOrigin) { try { const url = new URL(request.url, "http://caffold.test"); if (url.pathname === "/service-worker.js") { - let body = serviceWorkerSource.replace( - '"caffold-shell-__CAFFOLD_BUILD_ID__"', - JSON.stringify(`caffold-shell-${currentBuild.id}`), + const servedCacheName = /^const CACHE_NAME = "caffold-shell-[^"\n]*";$/m; + if (!servedCacheName.test(servedWorker)) { + throw new Error("PWA lifecycle fixture cache name hook is stale"); + } + let body = servedWorker.replace( + servedCacheName, + `const CACHE_NAME = ${JSON.stringify(`caffold-shell-${currentBuild.id}`)};`, ); const productionActivationHandler = [ - " if (event.data?.type === ACTIVATE_PREPARED_BUILD_MESSAGE) {", - " event.waitUntil(self.skipWaiting());", - " return;", - " }", + " if (event.data?.type === ACTIVATE_PREPARED_BUILD_MESSAGE) {", + " event.waitUntil(worker.skipWaiting());", + " return;", + " }", ].join("\n"); if (!body.includes(productionActivationHandler)) { throw new Error("PWA lifecycle fixture activation hook is stale"); @@ -2129,11 +2137,11 @@ async function startBuildLifecycleServer(upstreamOrigin) { body = body.replace( productionActivationHandler, [ - " if (event.data?.type === ACTIVATE_PREPARED_BUILD_MESSAGE) {", - ` event.source?.postMessage({ type: ${JSON.stringify(TEST_ACTIVATION_REQUEST_OBSERVED_MESSAGE)}, buildId: BUILD_ID });`, - " // The lifecycle fixture releases this edge through the exact worker runtime.", - " return;", - " }", + " if (event.data?.type === ACTIVATE_PREPARED_BUILD_MESSAGE) {", + ` event.source?.postMessage({ type: ${JSON.stringify(TEST_ACTIVATION_REQUEST_OBSERVED_MESSAGE)}, buildId: BUILD_ID });`, + " // The lifecycle fixture releases this edge through the exact worker runtime.", + " return;", + " }", ].join("\n"), ); body += `\nself.addEventListener("message", (event) => { @@ -2154,9 +2162,14 @@ async function startBuildLifecycleServer(upstreamOrigin) { } });\n`; if (currentBuild.omitControlledAck) { + const controlledAck = + " client?.postMessage({ type: UPDATE_CONTROLLED_MESSAGE, buildId: BUILD_ID });"; + if (!body.includes(controlledAck)) { + throw new Error("PWA lifecycle fixture acknowledgement hook is stale"); + } body = body.replace( - " client?.postMessage({ type: UPDATE_CONTROLLED_MESSAGE, buildId: BUILD_ID });", - " // Lifecycle fixture intentionally omits the custom acknowledgement.", + controlledAck, + " // Lifecycle fixture intentionally omits the custom acknowledgement.", ); } response.writeHead(200, { diff --git a/frontend/tests/e2e/app-shell.spec.js b/frontend/tests/e2e/app-shell.spec.js index 111ffbf8..06216462 100644 --- a/frontend/tests/e2e/app-shell.spec.js +++ b/frontend/tests/e2e/app-shell.spec.js @@ -40,33 +40,33 @@ test("serves one Task workspace shell and only active static assets", { tag: "@a `const CACHE_NAME = ${JSON.stringify(`caffold-shell-${health.buildId}`)};`, ); for (const asset of [ - "/assets/pages/layout.ts", - "/assets/pages/pwa-update-lifecycle.ts", - "/assets/pages/pwa-update-lifecycle/machine.ts", - "/assets/pages/pwa-update-lifecycle/runtime.ts", + "/assets/pages/layout.js", + "/assets/pages/pwa-update-lifecycle.js", + "/assets/pages/pwa-update-lifecycle/machine.js", + "/assets/pages/pwa-update-lifecycle/runtime.js", "/assets/pages/components/build-mismatch-alert.css", - "/assets/pages/components/build-mismatch-alert.ts", + "/assets/pages/components/build-mismatch-alert.js", "/assets/pages/components/update-dialog.css", - "/assets/pages/components/update-dialog.ts", - "/assets/action-hints.ts", - "/assets/action-hints/components/dialog.ts", - "/assets/keyboard-navigation.ts", - "/assets/keyboard-navigation/context.ts", - "/assets/keyboard-navigation/components/presentation.ts", - "/assets/keyboard-navigation/components/selector.ts", - "/assets/pages/(task-workspace)/layout.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(git)/layout.ts", - "/assets/pages/(task-workspace)/tasks/(detail)/(github)/layout.ts", - "/assets/pages/(task-workspace)/codex-status.ts", - "/assets/pages/(task-workspace)/codex-status/model.ts", - "/assets/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.ts", - "/assets/pages/(task-workspace)/codex-status/runtime-update-lifecycle.ts", - "/assets/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.ts", - "/assets/pages/(task-workspace)/codex-status/components/runtime-update-dialog.ts", + "/assets/pages/components/update-dialog.js", + "/assets/action-hints.js", + "/assets/action-hints/components/dialog.js", + "/assets/keyboard-navigation.js", + "/assets/keyboard-navigation/context.js", + "/assets/keyboard-navigation/components/presentation.js", + "/assets/keyboard-navigation/components/selector.js", + "/assets/pages/(task-workspace)/layout.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(review)/components/working-tree-changes/components/changes-tree.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(git)/layout.js", + "/assets/pages/(task-workspace)/tasks/(detail)/(github)/layout.js", + "/assets/pages/(task-workspace)/codex-status.js", + "/assets/pages/(task-workspace)/codex-status/model.js", + "/assets/pages/(task-workspace)/codex-status/runtime-restart-lifecycle.js", + "/assets/pages/(task-workspace)/codex-status/runtime-update-lifecycle.js", + "/assets/pages/(task-workspace)/codex-status/components/runtime-restart-dialog.js", + "/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.ts", - "/assets/components/file-navigator/components/list.ts", + "/assets/pages/(task-workspace)/settings/components/detail-list.js", + "/assets/components/file-navigator/components/list.js", ]) { expect(serviceWorker).toContain(asset); expect((await request.get(asset)).ok(), asset).toBe(true); diff --git a/frontend/tests/e2e/component-styles.spec.js b/frontend/tests/e2e/component-styles.spec.js index ef7d72c7..0206c6eb 100644 --- a/frontend/tests/e2e/component-styles.spec.js +++ b/frontend/tests/e2e/component-styles.spec.js @@ -44,7 +44,7 @@ test("new consumers declare their own scope, retain local overrides and reconnec await expect(page.locator("caffold-task-workspace-navigation")).toBeVisible(); const originalSheets = await page.evaluate(() => document.adoptedStyleSheets.length); await page.evaluate(async () => { - const { compactIconButton } = await import("/assets/component-styles.ts"); + const { compactIconButton } = await import("/assets/component-styles.js"); await compactIconButton.register("caffold-style-contract", "> header > .contract-actions > .contract-button"); customElements.define("caffold-style-contract", class extends HTMLElement { connectedCallback() { @@ -97,7 +97,7 @@ test("a busy spin turns only its owner's busy element and stops for reduced moti await page.goto("/"); await expect(page.locator("caffold-task-workspace-navigation")).toBeVisible(); await page.evaluate(async () => { - const { busySpin } = await import("/assets/component-styles.ts"); + const { busySpin } = await import("/assets/component-styles.js"); await busySpin.register("caffold-spin-contract", "> .contract-button.is-busy > .contract-icon"); customElements.define("caffold-spin-contract", class extends HTMLElement { connectedCallback() { @@ -183,7 +183,7 @@ test("a drawn ref keeps its element out of the document and lets go once it is n await expect(page.locator("caffold-task-workspace")).toBeVisible(); const result = await page.evaluate(async () => { const { html, LitElement, nothing } = await import("lit"); - const { DrawnRef, DrawnRefs, drawn } = await import("/assets/components/drawn-refs.ts"); + const { DrawnRef, DrawnRefs, drawn } = await import("/assets/components/drawn-refs.js"); class DrawnRefProbe extends LitElement { static properties = { shown: { attribute: false }, keys: { attribute: false } }; panel = new DrawnRef(this); diff --git a/frontend/tests/e2e/file-tree.spec.js b/frontend/tests/e2e/file-tree.spec.js index a53285b8..3fd23417 100644 --- a/frontend/tests/e2e/file-tree.spec.js +++ b/frontend/tests/e2e/file-tree.spec.js @@ -11,8 +11,8 @@ test("reorders existing rows while preserving structural and reconciled state", await page.goto("/settings/files"); const result = await page.evaluate(async () => { - await import("/assets/components/file-tree.ts"); - const { setFileSortMode } = await import("/assets/settings.ts"); + await import("/assets/components/file-tree.js"); + const { setFileSortMode } = await import("/assets/settings.js"); const ready = (nodes) => ({ status: "ready", nodes }); const tree = document.createElement("caffold-file-tree"); tree.setAttribute("aria-label", "Ordering contract tree"); @@ -178,10 +178,10 @@ for (const themeMode of ["light", "dark"]) { }) => { await page.goto("/settings/files"); await page.evaluate(async (mode) => { - await import("/assets/components/file-tree.ts"); - const { warmIcons } = await import("/assets/components/icons.ts"); + await import("/assets/components/file-tree.js"); + const { warmIcons } = await import("/assets/components/icons.js"); await warmIcons(); - const { setThemeMode } = await import("/assets/settings.ts"); + const { setThemeMode } = await import("/assets/settings.js"); setThemeMode(mode); const ready = (nodes) => ({ status: "ready", nodes }); const directory = (name, children) => ({ @@ -333,7 +333,7 @@ test("places a status row's message on its branch guide below its folder's name" }) => { await page.goto("/settings/files"); const offsets = await page.evaluate(async () => { - await import("/assets/components/file-tree.ts"); + await import("/assets/components/file-tree.js"); const directory = (name, children) => ({ key: `directory:${name}`, kind: "directory", @@ -387,7 +387,7 @@ test("offers its rows as Action Hints: directories toggle, entries open, the ope await page.goto("/settings/files"); const result = await page.evaluate(async () => { - await import("/assets/components/file-tree.ts"); + await import("/assets/components/file-tree.js"); const tree = document.createElement("caffold-file-tree"); document.body.append(tree); const nodes = [ @@ -464,7 +464,7 @@ test("names its scrollport as a Scroll surface after the owner", { tag: "@viewpo await page.goto("/settings/files"); const surfaces = await page.evaluate(async () => { - await import("/assets/components/file-tree.ts"); + await import("/assets/components/file-tree.js"); const tree = document.createElement("caffold-file-tree"); document.body.append(tree); tree.setModel({ entityKey: "review", nodes: [{ key: "a.js", kind: "file", name: "a.js" }] }); @@ -497,7 +497,7 @@ test("the file list offers Refresh files while it shows the button, under the la await page.goto("/settings/files"); const labels = await page.evaluate(async () => { - await import("/assets/components/file-navigator/components/list.ts"); + await import("/assets/components/file-navigator/components/list.js"); const list = document.createElement("caffold-file-list"); document.body.append(list); list.setDirectory({ diff --git a/frontend/tests/e2e/loading-text.spec.js b/frontend/tests/e2e/loading-text.spec.js index 532a1271..1f900174 100644 --- a/frontend/tests/e2e/loading-text.spec.js +++ b/frontend/tests/e2e/loading-text.spec.js @@ -9,7 +9,7 @@ async function placePhrases(page) { await page.goto("/"); await expect(page.locator("caffold-task-workspace-navigation")).toBeVisible(); await page.evaluate(async () => { - await import("/assets/components/loading-text.ts"); + await import("/assets/components/loading-text.js"); const delayed = document.createElement("caffold-loading-text"); delayed.id = "delayed-phrase"; delayed.textContent = "Loading files..."; diff --git a/frontend/tests/e2e/native-imports.spec.js b/frontend/tests/e2e/native-imports.spec.js index 0b7d7bff..19f690bf 100644 --- a/frontend/tests/e2e/native-imports.spec.js +++ b/frontend/tests/e2e/native-imports.spec.js @@ -17,19 +17,19 @@ test("native import aliases share module identity with asset URLs and register t const result = await page.evaluate(async () => { const modules = [ - ["#app/api.ts", "/assets/api.ts"], - ["#app/component-styles.ts", "/assets/component-styles.ts"], - ["#components/dom.ts", "/assets/components/dom.ts"], - ["#tasks/task-format.ts", "/assets/pages/(task-workspace)/tasks/task-format.ts"], + ["#app/api.js", "/assets/api.js"], + ["#app/component-styles.js", "/assets/component-styles.js"], + ["#components/dom.js", "/assets/components/dom.js"], + ["#tasks/task-format.js", "/assets/pages/(task-workspace)/tasks/task-format.js"], ]; const identities = await Promise.all(modules.map(async ([alias, url]) => { const [aliased, direct] = await Promise.all([import(alias), import(url)]); return { alias, sameModule: aliased === direct }; })); const consumers = [ - ["#tasks/components/task-create/components/directory-field.ts", "caffold-task-directory-field"], - ["#tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.ts", "caffold-conversation-fork-dialog"], - ["#tasks/(detail)/(github)/components/task-start-dialog.ts", "caffold-github-task-start-dialog"], + ["#tasks/components/task-create/components/directory-field.js", "caffold-task-directory-field"], + ["#tasks/(detail)/(section)/components/conversation-shortcuts/components/fork-dialog.js", "caffold-conversation-fork-dialog"], + ["#tasks/(detail)/(github)/components/task-start-dialog.js", "caffold-github-task-start-dialog"], ]; const registrations = await Promise.all(consumers.map(async ([alias, tag]) => { await import(alias); @@ -39,10 +39,10 @@ test("native import aliases share module identity with asset URLs and register t }); expect(result.identities).toEqual([ - { alias: "#app/api.ts", sameModule: true }, - { alias: "#app/component-styles.ts", sameModule: true }, - { alias: "#components/dom.ts", sameModule: true }, - { alias: "#tasks/task-format.ts", sameModule: true }, + { alias: "#app/api.js", sameModule: true }, + { alias: "#app/component-styles.js", sameModule: true }, + { alias: "#components/dom.js", sameModule: true }, + { alias: "#tasks/task-format.js", sameModule: true }, ]); expect(result.registrations.every(({ registered }) => registered)).toBe(true); expect(errors).toEqual([]); @@ -98,21 +98,36 @@ test("Lit loads by its package name, one module per package, served and precache expect(errors).toEqual([]); }); -test("TypeScript modules are served at their source path as JavaScript without their types", { tag: "@viewport-independent" }, async ({ page }) => { +test("TypeScript modules are served as the JavaScript tsc writes, with source maps outside the shell cache", { tag: "@viewport-independent" }, async ({ page }) => { await installBrowserDefaults(page); await page.goto("/"); await expect(page.locator("caffold-task-workspace")).toBeVisible(); const result = await page.evaluate(async () => { - const response = await fetch("/assets/components/pagination.ts"); + const [response, mapResponse, sourcePath] = await Promise.all([ + fetch("/assets/components/pagination.js"), + fetch("/assets/components/pagination.js.map"), + fetch("/assets/components/pagination.ts"), + ]); const source = await response.text(); + const map = await mapResponse.json(); await customElements.whenDefined("caffold-pagination"); + await navigator.serviceWorker.ready; return { status: response.status, contentType: response.headers.get("content-type"), defined: source.includes("class CaffoldPagination extends LitElement"), typed: /declare page: number|: PageButton\)/.test(source), + mapUrl: source.endsWith("//# sourceMappingURL=pagination.js.map"), registered: Boolean(customElements.get("caffold-pagination")), + map: { + status: mapResponse.status, + contentType: mapResponse.headers.get("content-type"), + source: `${map.sourceRoot}${map.sources[0]}`, + typed: /declare page: number/.test(map.sourcesContent[0]), + precached: Boolean(await caches.match("/assets/components/pagination.js.map")), + }, + sourcePathStatus: sourcePath.status, }; }); @@ -121,6 +136,15 @@ test("TypeScript modules are served at their source path as JavaScript without t contentType: "text/javascript; charset=utf-8", defined: true, typed: false, + mapUrl: true, registered: true, + map: { + status: 200, + contentType: "application/json; charset=utf-8", + source: "/assets/components/pagination.ts", + typed: true, + precached: false, + }, + sourcePathStatus: 404, }); }); diff --git a/frontend/tests/e2e/notes.spec.js b/frontend/tests/e2e/notes.spec.js index aa1b1f36..662d393f 100644 --- a/frontend/tests/e2e/notes.spec.js +++ b/frontend/tests/e2e/notes.spec.js @@ -170,7 +170,7 @@ test("Note details keeps compact paint, touch targets and native keyboard disclo await expect(button).toBeVisible(); for (const scale of [90, 120]) { await page.evaluate(async (value) => { - const { setAppearanceRangeSetting } = await import("/assets/settings.ts"); + const { setAppearanceRangeSetting } = await import("/assets/settings.js"); setAppearanceRangeSetting("interfaceScalePercent", value); }, scale); const metrics = await button.evaluate((element) => { @@ -1175,7 +1175,7 @@ test("constrained pairs keep long headings and horizontal code inside their pane const secondary = documentPane(page, "secondary"); for (const [scale, theme] of [[90, "light"], [120, "dark"]]) { await page.evaluate(async ({ scale, theme }) => { - const settings = await import("/assets/settings.ts"); + const settings = await import("/assets/settings.js"); settings.setAppearanceRangeSetting("interfaceScalePercent", scale); settings.setThemeMode(theme); }, { scale, theme }); @@ -1208,7 +1208,7 @@ test("paired Notes size three equal floating tabs from the selected font and kee for (const [scale, theme] of [[100, "light"], [90, "light"], [120, "dark"]]) { await page.setViewportSize({ width: 1920, height: 704 }); await page.evaluate(async ({ scale, theme, preset }) => { - const settings = await import("/assets/settings.ts"); + const settings = await import("/assets/settings.js"); settings.setUiTypefacePreset(preset); settings.setAppearanceRangeSetting("interfaceScalePercent", scale); settings.setThemeMode(theme); @@ -1451,7 +1451,7 @@ test("Notes action paint gaps match the compact toolbar while floating shadows s for (const preset of ["geist-sans", "inter", "pretendard", "system"]) { for (const [scale, theme] of [[90, "light"], [100, "light"], [120, "dark"]]) { await page.evaluate(async ({ scale, theme, preset }) => { - const settings = await import("/assets/settings.ts"); + const settings = await import("/assets/settings.js"); settings.setUiTypefacePreset(preset); settings.setAppearanceRangeSetting("interfaceScalePercent", scale); settings.setThemeMode(theme); diff --git a/frontend/tests/e2e/settings.spec.js b/frontend/tests/e2e/settings.spec.js index e95c08d7..63f11b97 100644 --- a/frontend/tests/e2e/settings.spec.js +++ b/frontend/tests/e2e/settings.spec.js @@ -3855,7 +3855,7 @@ async function expectThemeState(page, { mode, resolvedTheme }) { async function representativeThemeStyles(page) { return page.evaluate(async () => { - await import("/assets/components/file-viewer/components/diff-viewer.ts"); + await import("/assets/components/file-viewer/components/diff-viewer.js"); const review = document.createElement("caffold-task-review"); review.style.cssText = diff --git a/frontend/tests/e2e/task-workspace.spec.js b/frontend/tests/e2e/task-workspace.spec.js index c89be638..3cdc955f 100644 --- a/frontend/tests/e2e/task-workspace.spec.js +++ b/frontend/tests/e2e/task-workspace.spec.js @@ -407,7 +407,7 @@ test("shares navigation pane resizing across Tasks and Settings", { tag: "@all-v for (const interfaceScalePercent of [90, 120]) { await page.evaluate(async (value) => { - const { setAppearanceRangeSetting } = await import("/assets/settings.ts"); + const { setAppearanceRangeSetting } = await import("/assets/settings.js"); setAppearanceRangeSetting("interfaceScalePercent", value); }, interfaceScalePercent); const geometry = await taskWorkspace.evaluate((element) => { diff --git a/frontend/tests/e2e/tasks/action-hints.spec.js b/frontend/tests/e2e/tasks/action-hints.spec.js index 43f237a7..adf8345f 100644 --- a/frontend/tests/e2e/tasks/action-hints.spec.js +++ b/frontend/tests/e2e/tasks/action-hints.spec.js @@ -1668,7 +1668,7 @@ test("keeps badges aligned and legible at appearance and zoom extremes", { tag: ]) { await page.evaluate(async ({ interfaceScalePercent, themeMode }) => { const { setAppearanceRangeSetting, setThemeMode } = await import( - "/assets/settings.ts" + "/assets/settings.js" ); setAppearanceRangeSetting("interfaceScalePercent", interfaceScalePercent); setThemeMode(themeMode); diff --git a/frontend/tests/e2e/tasks/code-block-controls.spec.js b/frontend/tests/e2e/tasks/code-block-controls.spec.js index a53a8931..49896bea 100644 --- a/frontend/tests/e2e/tasks/code-block-controls.spec.js +++ b/frontend/tests/e2e/tasks/code-block-controls.spec.js @@ -114,7 +114,7 @@ test("keeps the code-block toolbar dense and usable across Task viewports", { ta expect(markdownToolbar.endOffsets.slice(1)).toEqual(plainToolbar.endOffsets); await page.evaluate(async () => { - const { setAppearanceRangeSetting } = await import("/assets/settings.ts"); + const { setAppearanceRangeSetting } = await import("/assets/settings.js"); setAppearanceRangeSetting("interfaceScalePercent", 120); setAppearanceRangeSetting("codeTextPx", 20); }); diff --git a/frontend/tests/e2e/tasks/composer.spec.js b/frontend/tests/e2e/tasks/composer.spec.js index a9cf6b16..9f3712fe 100644 --- a/frontend/tests/e2e/tasks/composer.spec.js +++ b/frontend/tests/e2e/tasks/composer.spec.js @@ -989,7 +989,7 @@ test("places attachment remove controls over thumbnail corners inside the attach for (const interfaceScalePercent of [90, 100, 120]) { await page.evaluate(async (value) => { - const { setAppearanceRangeSetting } = await import("/assets/settings.ts"); + const { setAppearanceRangeSetting } = await import("/assets/settings.js"); setAppearanceRangeSetting("interfaceScalePercent", value); }, interfaceScalePercent); await prompt.focus(); @@ -1040,7 +1040,7 @@ test("keeps the attachment remove X legible on its circle in Light and Dark", { for (const themeMode of ["light", "dark"]) { await page.evaluate(async (value) => { - const { setThemeMode } = await import("/assets/settings.ts"); + const { setThemeMode } = await import("/assets/settings.js"); setThemeMode(value); }, themeMode); await expect(page.locator("html")).toHaveAttribute("data-theme", themeMode); diff --git a/frontend/tests/e2e/tasks/git-review.spec.js b/frontend/tests/e2e/tasks/git-review.spec.js index e31f3f10..91326fda 100644 --- a/frontend/tests/e2e/tasks/git-review.spec.js +++ b/frontend/tests/e2e/tasks/git-review.spec.js @@ -878,7 +878,7 @@ test("applies the global ordering to Compare and Commit without refetching", { t expect(counts.compare).toBe(1); await page.evaluate(async () => { - const { setFileSortMode } = await import("/assets/settings.ts"); + const { setFileSortMode } = await import("/assets/settings.js"); setFileSortMode("name"); }); await expect @@ -895,7 +895,7 @@ test("applies the global ordering to Compare and Commit without refetching", { t expect(counts.commit).toBe(1); await page.evaluate(async () => { - const { setFileSortMode } = await import("/assets/settings.ts"); + const { setFileSortMode } = await import("/assets/settings.js"); setFileSortMode("folders-first"); }); await expect diff --git a/frontend/tests/e2e/tasks/github-review.spec.js b/frontend/tests/e2e/tasks/github-review.spec.js index 4d909df2..7d0b156b 100644 --- a/frontend/tests/e2e/tasks/github-review.spec.js +++ b/frontend/tests/e2e/tasks/github-review.spec.js @@ -1565,7 +1565,7 @@ test("applies the global ordering to GitHub PR Files without refetching", { tag: expect(fixture.counts.pullFiles).toBe(1); await page.evaluate(async () => { - const { setFileSortMode } = await import("/assets/settings.ts"); + const { setFileSortMode } = await import("/assets/settings.js"); setFileSortMode("name"); }); expect(await rootPullTreeNames(tree)).toEqual(["README.md", "src"]); diff --git a/frontend/tests/e2e/tasks/interface-scaling.spec.js b/frontend/tests/e2e/tasks/interface-scaling.spec.js index af261a1f..66d1562d 100644 --- a/frontend/tests/e2e/tasks/interface-scaling.spec.js +++ b/frontend/tests/e2e/tasks/interface-scaling.spec.js @@ -68,7 +68,7 @@ test("scales visible Task controls without shrinking their touch targets", { tag const compact = await taskInterfaceMetrics(page); await page.evaluate(async () => { - const { setAppearanceRangeSetting } = await import("/assets/settings.ts"); + const { setAppearanceRangeSetting } = await import("/assets/settings.js"); setAppearanceRangeSetting("interfaceScalePercent", 120); }); const detailsPopover = page.locator(".task-detail-popover"); diff --git a/frontend/tests/e2e/tasks/lifecycle.spec.js b/frontend/tests/e2e/tasks/lifecycle.spec.js index bf6bcea6..1b542f0e 100644 --- a/frontend/tests/e2e/tasks/lifecycle.spec.js +++ b/frontend/tests/e2e/tasks/lifecycle.spec.js @@ -1016,7 +1016,7 @@ test("fresh origin reachability recovers a foreground offline pause without an o configurable: true, get: () => true, }); - const { getHealth } = await import("/assets/api.ts"); + const { getHealth } = await import("/assets/api.js"); await getHealth(); }); await expect(notice).toBeHidden(); diff --git a/frontend/tests/e2e/tasks/review-files.spec.js b/frontend/tests/e2e/tasks/review-files.spec.js index 3d16770c..92741942 100644 --- a/frontend/tests/e2e/tasks/review-files.spec.js +++ b/frontend/tests/e2e/tasks/review-files.spec.js @@ -125,7 +125,7 @@ test("browses source through the shared Files navigator and one root watch", { t ); const listRequestsBeforeOrderChange = listRequests; await page.evaluate(async () => { - const { setFileSortMode } = await import("/assets/settings.ts"); + const { setFileSortMode } = await import("/assets/settings.js"); setFileSortMode("name"); }); expect((await rootFile.boundingBox()).y).toBeLessThan( @@ -1416,7 +1416,7 @@ test("the file viewer offers a notice's representation button, and details only await page.goto("/settings/files"); const result = await page.evaluate(async () => { - await import("/assets/components/file-viewer.ts"); + await import("/assets/components/file-viewer.js"); const viewer = document.createElement("caffold-review-file-viewer"); document.body.append(viewer); const drawn = () => new Promise(requestAnimationFrame); @@ -1487,7 +1487,7 @@ test("each document preview scrolls as one vertical surface named after its file await page.goto("/settings/files"); const surfaces = await page.evaluate(async () => { - await import("/assets/components/file-viewer.ts"); + await import("/assets/components/file-viewer.js"); const described = []; for (const kind of ["pdf", "docx", "pptx"]) { const element = document.createElement(`caffold-${kind}-viewer`); @@ -1529,7 +1529,7 @@ test("the file viewer scrolls a source file and a notice under the names it show await page.goto("/settings/files"); const surfaces = await page.evaluate(async () => { - await import("/assets/components/file-viewer.ts"); + await import("/assets/components/file-viewer.js"); const viewer = document.createElement("caffold-review-file-viewer"); viewer.style.cssText = "display: block; height: 300px;"; document.body.append(viewer); diff --git a/frontend/tests/e2e/tasks/review-git.spec.js b/frontend/tests/e2e/tasks/review-git.spec.js index a43f9626..c4f91684 100644 --- a/frontend/tests/e2e/tasks/review-git.spec.js +++ b/frontend/tests/e2e/tasks/review-git.spec.js @@ -197,7 +197,7 @@ test("normalizes and compacts shared file-tree statuses", { tag: "@all-viewports ); const statusRequestsBeforeOrderChange = reviewScenario.gitStatusRequests; await page.evaluate(async () => { - const { setFileSortMode } = await import("/assets/settings.ts"); + const { setFileSortMode } = await import("/assets/settings.js"); setFileSortMode("name"); }); expect((await file.boundingBox()).y).toBeLessThan( diff --git a/frontend/tests/e2e/tasks/review-responsive.spec.js b/frontend/tests/e2e/tasks/review-responsive.spec.js index 4950070c..a912d54d 100644 --- a/frontend/tests/e2e/tasks/review-responsive.spec.js +++ b/frontend/tests/e2e/tasks/review-responsive.spec.js @@ -635,7 +635,7 @@ test("keeps Review reflowed at the appearance extremes", { tag: "@all-viewports" { interfaceScalePercent: 120, conversationTextPx: 20, codeTextPx: 20 }, ]) { await page.evaluate(async (appearance) => { - const { setAppearanceRangeSetting } = await import("/assets/settings.ts"); + const { setAppearanceRangeSetting } = await import("/assets/settings.js"); for (const [name, value] of Object.entries(appearance)) { setAppearanceRangeSetting(name, value); } @@ -712,7 +712,7 @@ test("keeps compact Task segments pixel-aligned on Retina displays", { tag: "@al for (const interfaceScalePercent of [90, 100, 105, 120]) { await page.evaluate(async (value) => { - const { setAppearanceRangeSetting } = await import("/assets/settings.ts"); + const { setAppearanceRangeSetting } = await import("/assets/settings.js"); setAppearanceRangeSetting("interfaceScalePercent", value); }, interfaceScalePercent); const { devicePixelRatio, segments } = await page.evaluate(() => ({ diff --git a/frontend/tests/e2e/tasks/scroll-mode.spec.js b/frontend/tests/e2e/tasks/scroll-mode.spec.js index c46544dd..1e617347 100644 --- a/frontend/tests/e2e/tasks/scroll-mode.spec.js +++ b/frontend/tests/e2e/tasks/scroll-mode.spec.js @@ -1189,7 +1189,7 @@ test("keeps selector and HUD visible at appearance and zoom extremes", { tag: "@ ].entries()) { await page.evaluate(async ({ interfaceScalePercent, themeMode }) => { const { setAppearanceRangeSetting, setThemeMode } = await import( - "/assets/settings.ts" + "/assets/settings.js" ); setAppearanceRangeSetting("interfaceScalePercent", interfaceScalePercent); setThemeMode(themeMode); diff --git a/frontend/tests/support/custom-element-unit.js b/frontend/tests/support/custom-element-unit.js index 08bd9bbb..9cffe1ca 100644 --- a/frontend/tests/support/custom-element-unit.js +++ b/frontend/tests/support/custom-element-unit.js @@ -1,5 +1,7 @@ import { readFile } from "node:fs/promises"; +import { sourceUrl } from "./source-url.js"; + export function installCustomElementUnitRegistry() { const previousHTMLElement = globalThis.HTMLElement; const previousCustomElements = globalThis.customElements; @@ -14,7 +16,7 @@ export function installCustomElementUnitRegistry() { const url = input instanceof URL ? input : typeof input === "string" && input.startsWith("file:") ? new URL(input) : null; return url?.protocol === "file:" && url.pathname.endsWith(".css") - ? readFile(url, "utf8").then((text) => new Response(text)) + ? readFile(sourceUrl(url), "utf8").then((text) => new Response(text)) : previousFetch(input, ...options); }; // lit-html walks templates with a document tree walker it creates on import. diff --git a/frontend/tests/support/source-url.js b/frontend/tests/support/source-url.js new file mode 100644 index 00000000..b1cb687a --- /dev/null +++ b/frontend/tests/support/source-url.js @@ -0,0 +1,11 @@ +// Unit tests run from the tsc output in tests/unit-build/, which holds only +// JavaScript; the CSS, fonts, and TypeScript sources they read stay in +// frontend/. +const buildRoot = new URL("../../", import.meta.url).href; +const frontendRoot = new URL("../../../../", import.meta.url); + +export function sourceUrl(url) { + return url.href.startsWith(buildRoot) + ? new URL(url.href.slice(buildRoot.length), frontendRoot) + : url; +} diff --git a/frontend/theme.test.js b/frontend/theme.test.js index f0b1b08a..37a7f0cf 100644 --- a/frontend/theme.test.js +++ b/frontend/theme.test.js @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -const themeModuleUrl = new URL("./theme.ts", import.meta.url); +const themeModuleUrl = new URL("./theme.js", import.meta.url); test("theme modes resolve System, Light, and Dark explicitly", async () => { const { DEFAULT_THEME_MODE, normalizeThemeMode, resolveTheme } = diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c0eed614..83d1ba0b 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -6,14 +6,14 @@ "moduleResolution": "nodenext", "strict": true, "noEmit": true, - "allowJs": true, - "checkJs": false, - "allowImportingTsExtensions": true, - "erasableSyntaxOnly": true, + "rootDir": ".", + "sourceMap": true, + "inlineSources": true, + "sourceRoot": "/assets/", "verbatimModuleSyntax": true, "skipLibCheck": true, "types": [] }, "include": ["**/*.ts"], - "exclude": ["node_modules", "tests"] + "exclude": ["node_modules", "tests", "service-worker.ts"] } diff --git a/frontend/tsconfig.test.json b/frontend/tsconfig.test.json new file mode 100644 index 00000000..1c5f099c --- /dev/null +++ b/frontend/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": false, + "outDir": "tests/unit-build", + "allowJs": true, + "resolveJsonModule": true + }, + "include": ["**/*.ts", "**/*.test.js", "package.json"] +} diff --git a/frontend/tsconfig.worker.json b/frontend/tsconfig.worker.json new file mode 100644 index 00000000..8767d9b5 --- /dev/null +++ b/frontend/tsconfig.worker.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["es2023", "webworker"], + "moduleDetection": "legacy", + "sourceMap": false, + "inlineSources": false, + "sourceRoot": null + }, + "include": ["service-worker.ts"], + "exclude": [] +} diff --git a/frontend/watch.test.js b/frontend/watch.test.js index 48ac37c9..c013e6bb 100644 --- a/frontend/watch.test.js +++ b/frontend/watch.test.js @@ -50,7 +50,7 @@ const { subscribeToWatch, watchChangeAffectsPath, } = await import( - "./watch.ts" + "./watch.js" ); test("shares one gateway Watch binding until the final scope subscriber leaves", async () => { diff --git a/frontend/watch.ts b/frontend/watch.ts index fb61e9a5..3a39d058 100644 --- a/frontend/watch.ts +++ b/frontend/watch.ts @@ -1,7 +1,7 @@ import type { LiveSubscription, WorkspaceLiveUpdates, -} from "./pages/(task-workspace)/live-updates.ts"; +} from "./pages/(task-workspace)/live-updates.js"; export type RefreshState = "refreshing" | "idle" | "error"; From 1aff19117056e32eaffa4d854ed4aec42fb88c68 Mon Sep 17 00:00:00 2001 From: Taehoon Moon Date: Tue, 27 Oct 2026 15:16:34 +0900 Subject: [PATCH 17/19] Declare Lit properties and registration with standard decorators A reactive field was spread over `static properties`, a `declare` line, and a constructor assignment. Components now declare it as a decorated `accessor` field and register with `@customElement`, lifecycle members carry `override`, and each tag has an `HTMLElementTagNameMap` entry. tsc imports the decorator helpers from `tslib`, which the server serves beside Lit's decorator modules. --- .github/workflows/macos-release.yml | 4 +- .github/workflows/rust-checks.yml | 4 +- caffold/build.rs | 6 +- caffold/src/static_assets.rs | 72 +++++++++++++++-- docs/architecture/frontend.md | 28 ++++--- frontend/README.md | 13 +-- frontend/action-hints/components/dialog.ts | 14 +++- frontend/components/docx-viewer.ts | 43 +++++----- frontend/components/drawn-refs.ts | 2 +- frontend/components/file-navigator.ts | 18 +++-- .../file-navigator/components/list.test.js | 3 +- .../file-navigator/components/list.ts | 51 ++++++------ frontend/components/file-tree.ts | 65 +++++++-------- frontend/components/file-viewer.test.js | 7 +- frontend/components/file-viewer.ts | 69 ++++++++-------- .../file-viewer/components/code-viewer.ts | 31 ++++--- .../file-viewer/components/diff-viewer.ts | 33 ++++---- frontend/components/git-compare-browser.ts | 18 +++-- frontend/components/git-compare-tree.test.js | 5 +- frontend/components/git-compare-tree.ts | 39 ++++----- frontend/components/loading-text.ts | 9 ++- frontend/components/markdown-preview.test.js | 3 +- frontend/components/markdown-preview.ts | 42 +++++----- frontend/components/pagination.ts | 59 +++++++------- .../pagination/components/button.ts | 53 ++++++------ frontend/components/pane-resizer.ts | 18 +++-- frontend/components/pdf-viewer.ts | 43 +++++----- frontend/components/pptx-viewer.ts | 43 +++++----- frontend/components/segmented-control.ts | 43 +++++----- frontend/components/terminal-view.ts | 20 +++-- frontend/components/xlsx-viewer.test.js | 3 +- frontend/components/xlsx-viewer.ts | 43 +++++----- frontend/element-declarations.ts | 6 +- frontend/index.html | 4 +- .../keyboard-navigation/components/hud.ts | 14 +++- .../components/presentation.ts | 15 ++-- .../components/selector.ts | 19 +++-- .../components/shortcut-dialog.ts | 19 +++-- .../components/shortcut-list.ts | 15 ++-- frontend/package-lock.json | 22 ++++- frontend/package.json | 1 + .../components/reset-credit-dialog.ts | 26 +++--- .../components/runtime-restart-dialog.ts | 15 ++-- .../components/runtime-update-dialog.ts | 15 ++-- .../components/navigation.test.js | 21 ++--- .../(task-workspace)/components/navigation.ts | 40 ++++------ .../components/update-task-dialog.ts | 14 +++- .../components/workspace-brand.ts | 12 ++- frontend/pages/(task-workspace)/layout.ts | 30 ++++--- .../notes/components/document.ts | 46 +++++------ .../(task-workspace)/notes/components/info.ts | 38 ++++----- .../info/components/copy-markdown.ts | 35 ++++---- .../components/info/components/copy-path.ts | 32 ++++---- .../notes/components/navigator.ts | 54 +++++++------ .../pages/(task-workspace)/notes/layout.ts | 57 ++++++------- .../(task-workspace)/settings/about/page.ts | 45 +++++------ .../settings/appearance/page.ts | 32 ++++---- .../components/runtime-restart-dialog.ts | 15 ++-- .../(task-workspace)/settings/claude/page.ts | 42 +++++----- .../(task-workspace)/settings/codex/page.ts | 80 ++++++++----------- .../settings/components/detail-list.ts | 27 +++---- .../settings/components/navigator.ts | 35 ++++---- .../navigator/components/item.test.js | 4 +- .../components/navigator/components/item.ts | 47 +++++------ .../settings/components/refresh-button.ts | 47 +++++------ .../(task-workspace)/settings/files/page.ts | 27 +++---- .../(task-workspace)/settings/grok/page.ts | 29 ++++--- .../(task-workspace)/settings/jev/page.ts | 37 +++++---- .../settings/keyboard/page.ts | 30 +++---- .../(task-workspace)/settings/layout.test.js | 3 +- .../pages/(task-workspace)/settings/layout.ts | 61 +++++++------- .../settings/notifications/page.ts | 36 ++++----- .../settings/remote-access/page.ts | 43 +++++----- .../(task-workspace)/settings/voice/page.ts | 35 ++++---- .../(log)/commit/components/changes-tree.ts | 27 +++---- .../tasks/(detail)/(git)/(log)/commit/page.ts | 14 +++- .../tasks/(detail)/(git)/(log)/layout.ts | 14 +++- .../tasks/(detail)/(git)/(log)/list/page.ts | 36 ++++----- .../tasks/(detail)/(git)/compare/page.ts | 14 +++- .../(detail)/(git)/components/controls.ts | 50 ++++++------ .../tasks/(detail)/(git)/layout.ts | 51 ++++++------ .../(detail)/(github)/(issues)/detail/page.ts | 30 +++---- .../(detail)/(github)/(issues)/layout.ts | 19 +++-- .../(detail)/(github)/(issues)/list/page.ts | 33 ++++---- .../(detail)/(github)/(pulls)/detail/page.ts | 30 +++---- .../(github)/(pulls)/files/components/tree.ts | 30 +++---- .../(detail)/(github)/(pulls)/files/page.ts | 17 ++-- .../tasks/(detail)/(github)/(pulls)/layout.ts | 16 ++-- .../(detail)/(github)/(pulls)/list/page.ts | 37 ++++----- .../(detail)/(github)/components/markdown.ts | 38 +++++---- .../(github)/components/task-start-dialog.ts | 46 +++++------ .../components/github-issue.ts | 55 ++++++------- .../components/github-pull.ts | 45 +++++------ .../tasks/(detail)/(github)/layout.ts | 51 ++++++------ .../(review)/components/branch-changes.ts | 19 +++-- .../(review)/components/selected-file.ts | 19 +++-- .../components/working-tree-changes.ts | 30 +++---- .../components/changes-tree.ts | 30 +++---- .../tasks/(detail)/(review)/layout.ts | 39 +++++---- .../components/conversation-shortcuts.test.js | 7 +- .../components/conversation-shortcuts.ts | 53 ++++++------ .../components/fork-dialog.ts | 56 ++++++------- .../(section)/components/github-shortcuts.ts | 34 ++++---- .../(detail)/(section)/components/summary.ts | 27 +++---- .../tasks/(detail)/(section)/layout.ts | 14 +++- .../(task)/components/broken-delete-dialog.ts | 38 ++++----- .../(task)/components/command-dialog.ts | 27 +++---- .../(task)/components/conversation.ts | 32 ++++---- .../conversation/components/active-turn.ts | 54 ++++++------- .../conversation/components/approval.ts | 45 +++++------ .../components/assistant-message.ts | 43 +++++----- .../components/copy-button.ts | 37 ++++----- .../components/suggested-prompts.ts | 44 +++++----- .../conversation/components/changed-files.ts | 26 +++--- .../components/command-group.test.js | 9 ++- .../conversation/components/command-group.ts | 32 ++++---- .../conversation/components/command.ts | 31 ++++--- .../conversation/components/markdown.ts | 62 +++++++------- .../markdown/components/code-block.ts | 42 +++++----- .../components/message-attachments.test.js | 2 +- .../components/message-attachments.ts | 42 +++++----- .../conversation/components/older-history.ts | 47 +++++------ .../conversation/components/user-message.ts | 52 ++++++------ .../conversation/components/work-details.ts | 51 ++++++------ .../(task)/components/current-plan.ts | 49 ++++++------ .../components/document-dialog.ts | 19 +++-- .../components/markdown-preview-dialog.ts | 30 +++---- .../permission-instructions-dialog.ts | 37 +++++---- .../(detail)/(task)/components/summary.ts | 25 +++--- .../components/summary/components/info.ts | 37 +++++---- .../components/info/components/actions.ts | 28 +++---- .../tasks/(detail)/(task)/layout.ts | 26 +++--- .../tasks/(detail)/components/git-menu.ts | 31 +++---- .../tasks/(detail)/components/github-menu.ts | 31 +++---- .../(detail)/components/terminal-button.ts | 45 +++++------ .../(task-workspace)/tasks/(detail)/layout.ts | 34 ++++---- .../terminal/components/special-keys.ts | 27 +++---- .../tasks/(detail)/terminal/page.ts | 30 ++++--- .../tasks/components/active-task-list.ts | 16 ++-- .../active-task-list/components/section.ts | 18 +++-- .../components/section/components/row.ts | 16 ++-- .../components/archived-delete-dialog.test.js | 3 +- .../components/archived-delete-dialog.ts | 31 +++---- .../tasks/components/archived-task-list.ts | 28 ++++--- .../tasks/components/composer.ts | 43 +++++----- .../composer/components/attachments.ts | 32 ++++---- .../composer/components/context-usage.ts | 23 +++--- .../composer/components/voice-level-meter.ts | 14 +++- .../tasks/components/image-preview-dialog.ts | 42 +++++----- .../tasks/components/navigator.ts | 28 ++++--- .../tasks/components/task-absence.ts | 27 +++---- .../tasks/components/task-create.ts | 16 ++-- .../task-create/components/directory-field.ts | 23 +++--- .../tasks/components/task-status-chip.ts | 40 +++++----- .../tasks/components/task-store-recovery.ts | 24 +++--- .../tasks/components/task-switcher-dialog.ts | 19 +++-- .../tasks/components/task-turn-options.ts | 41 +++++----- .../pages/(task-workspace)/tasks/layout.ts | 18 +++-- .../pages/(task-workspace)/tasks/new/page.ts | 16 ++-- .../(task-workspace)/tasks/recovery/page.ts | 18 +++-- .../pages/components/build-mismatch-alert.ts | 30 +++---- frontend/pages/components/update-dialog.ts | 12 ++- .../pages/components/update-result-dialog.ts | 26 +++--- frontend/pages/layout.ts | 34 ++++---- frontend/service-worker.ts | 12 +++ .../contracts/appearance-ownership.test.mjs | 2 +- .../task-workspace-structure.test.mjs | 32 ++++---- frontend/tests/e2e/native-imports.spec.js | 41 +++++++++- frontend/tests/support/custom-element-unit.js | 14 ++++ frontend/tsconfig.json | 2 + 170 files changed, 2543 insertions(+), 2473 deletions(-) diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml index ddcff385..4c6e4ed4 100644 --- a/.github/workflows/macos-release.yml +++ b/.github/workflows/macos-release.yml @@ -84,8 +84,8 @@ jobs: node-version: 24 package-manager-cache: false - # The server embeds Lit from frontend/node_modules and compiles its - # TypeScript modules with the TypeScript installed there. + # The server embeds Lit and tslib from frontend/node_modules and + # compiles its TypeScript modules with the TypeScript installed there. - name: Install frontend build dependencies run: npm ci --omit=dev working-directory: frontend diff --git a/.github/workflows/rust-checks.yml b/.github/workflows/rust-checks.yml index 139cf32b..a90c3cc2 100644 --- a/.github/workflows/rust-checks.yml +++ b/.github/workflows/rust-checks.yml @@ -37,8 +37,8 @@ jobs: cache: npm cache-dependency-path: frontend/package-lock.json - # The server embeds Lit from frontend/node_modules and compiles its - # TypeScript modules with the TypeScript installed there. + # The server embeds Lit and tslib from frontend/node_modules and + # compiles its TypeScript modules with the TypeScript installed there. - name: Install frontend build dependencies run: npm ci --omit=dev working-directory: frontend diff --git a/caffold/build.rs b/caffold/build.rs index 59f9fefe..bc0e7d5e 100644 --- a/caffold/build.rs +++ b/caffold/build.rs @@ -13,7 +13,7 @@ fn main() { let out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("Cargo OUT_DIR must be set")); - for package in ["lit", "typescript"] { + for package in ["lit", "tslib", "typescript"] { if let Err(problem) = check_installed_package(package) { println!("cargo::error={problem}; run `npm ci` in frontend/ before building"); return; @@ -53,8 +53,8 @@ fn main() { println!("cargo:rustc-env=CAFFOLD_BUILD_NUMBER={build_number}"); } -// The static asset table embeds Lit from frontend/node_modules, and the -// TypeScript there compiles the frontend modules; each must be the version +// The static asset table embeds Lit and tslib from frontend/node_modules, and +// the TypeScript there compiles the frontend modules; each must be the version // frontend/package.json names. fn check_installed_package(name: &str) -> Result<(), String> { let wanted = read_json("../frontend/package.json") diff --git a/caffold/src/static_assets.rs b/caffold/src/static_assets.rs index 63ce5c87..70f789d1 100644 --- a/caffold/src/static_assets.rs +++ b/caffold/src/static_assets.rs @@ -27,6 +27,9 @@ pub(crate) fn get(path: &str) -> Option { "lit/async-directive.js" => Some(js(include_str!( "../../frontend/node_modules/lit/async-directive.js" ))), + "lit/decorators.js" => Some(js(include_str!( + "../../frontend/node_modules/lit/decorators.js" + ))), "lit/directives/keyed.js" => Some(js(include_str!( "../../frontend/node_modules/lit/directives/keyed.js" ))), @@ -75,6 +78,39 @@ pub(crate) fn get(path: &str) -> Option { "@lit/reactive-element/css-tag.js" => Some(js(include_str!( "../../frontend/node_modules/@lit/reactive-element/css-tag.js" ))), + "@lit/reactive-element/decorators/base.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/base.js" + ))), + "@lit/reactive-element/decorators/custom-element.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/custom-element.js" + ))), + "@lit/reactive-element/decorators/event-options.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/event-options.js" + ))), + "@lit/reactive-element/decorators/property.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/property.js" + ))), + "@lit/reactive-element/decorators/query-all.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/query-all.js" + ))), + "@lit/reactive-element/decorators/query-assigned-elements.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/query-assigned-elements.js" + ))), + "@lit/reactive-element/decorators/query-assigned-nodes.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/query-assigned-nodes.js" + ))), + "@lit/reactive-element/decorators/query-async.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/query-async.js" + ))), + "@lit/reactive-element/decorators/query.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/query.js" + ))), + "@lit/reactive-element/decorators/state.js" => Some(js(include_str!( + "../../frontend/node_modules/@lit/reactive-element/decorators/state.js" + ))), + "tslib/tslib.es6.mjs" => Some(js(include_str!( + "../../frontend/node_modules/tslib/tslib.es6.mjs" + ))), "api.js" => Some(js(include_str!(concat!( env!("OUT_DIR"), "/frontend/api.js" @@ -2073,6 +2109,7 @@ mod tests { "lit/directives/live.js", "lit/directives/repeat.js", "lit/directives/unsafe-html.js", + "lit/decorators.js", "lit-html/lit-html.js", "lit-html/async-directive.js", "lit-html/directive.js", @@ -2085,6 +2122,16 @@ mod tests { "lit-element/lit-element.js", "@lit/reactive-element/reactive-element.js", "@lit/reactive-element/css-tag.js", + "@lit/reactive-element/decorators/base.js", + "@lit/reactive-element/decorators/custom-element.js", + "@lit/reactive-element/decorators/event-options.js", + "@lit/reactive-element/decorators/property.js", + "@lit/reactive-element/decorators/query-all.js", + "@lit/reactive-element/decorators/query-assigned-elements.js", + "@lit/reactive-element/decorators/query-assigned-nodes.js", + "@lit/reactive-element/decorators/query-async.js", + "@lit/reactive-element/decorators/query.js", + "@lit/reactive-element/decorators/state.js", ] { let asset = get(path).unwrap_or_else(|| panic!("missing Lit asset {path}")); assert_eq!(asset.content_type, "text/javascript; charset=utf-8"); @@ -2100,21 +2147,25 @@ mod tests { #[test] fn serves_typescript_modules_as_the_javascript_and_source_maps_tsc_writes() { - for (path, class) in [ + for (path, registration) in [ ( "components/pagination.js", - "class CaffoldPagination extends LitElement", + r#"customElement("caffold-pagination")"#, ), ( "components/pagination/components/button.js", - "class CaffoldPaginationButton extends LitElement", + r#"customElement("caffold-pagination-button")"#, ), ] { let module = get(path).unwrap_or_else(|| panic!("missing TypeScript module {path}")); assert_eq!(module.content_type, "text/javascript; charset=utf-8"); let source = std::str::from_utf8(module.body).expect("UTF-8 module"); - assert!(source.contains(class)); - assert!(!source.contains("declare page: number")); + assert!(source.contains(registration)); + // tsc writes the decorators as calls of the helpers tslib holds. + assert!(!source.contains("@property(")); + assert!( + source.starts_with(r#"import { __esDecorate, __runInitializers } from "tslib";"#) + ); let map_name = format!("{}.map", path.rsplit('/').next().unwrap()); assert!(source.ends_with(&format!("//# sourceMappingURL={map_name}"))); @@ -2128,7 +2179,7 @@ mod tests { map["sourcesContent"][0] .as_str() .unwrap() - .contains("declare page") + .contains("@property(") ); } assert!(get("components/pagination.ts").is_none()); @@ -2136,6 +2187,15 @@ mod tests { assert!(get("service-worker.js.map").is_none()); } + #[test] + fn serves_the_tslib_helpers_compiled_modules_import() { + let tslib = get("tslib/tslib.es6.mjs").expect("tslib asset"); + assert_eq!(tslib.content_type, "text/javascript; charset=utf-8"); + let source = std::str::from_utf8(tslib.body).expect("UTF-8 module"); + assert!(source.contains("export function __esDecorate(")); + assert!(source.contains("export function __runInitializers(")); + } + #[test] fn serves_pwa_icon_assets() { let manifest = get("manifest.webmanifest").expect("manifest asset"); diff --git a/docs/architecture/frontend.md b/docs/architecture/frontend.md index ba7bfc2d..c122f2e2 100644 --- a/docs/architecture/frontend.md +++ b/docs/architecture/frontend.md @@ -80,11 +80,13 @@ resolve aliases to the same files, and that those files are served and cached. Worker and AudioWorklet modules do not inherit the document's import map; their imports and asset URLs remain relative or explicit URLs. -Lit is imported through its `lit` package, as `lit` and `lit/…`. The import -map sends those, and the `lit-html`, `lit-element`, and `@lit/reactive-element` -modules they import, to `/assets/`, which the server answers with the release -`frontend/package.json` pins as a runtime dependency, embedded from -`frontend/node_modules` when the server is built. A build therefore needs +Lit is imported through its `lit` package, as `lit` and `lit/…`, its standard +decorators included (`lit/decorators.js`). The import map sends those, and the +`lit-html`, `lit-element`, and `@lit/reactive-element` modules they import, to +`/assets/`, which the server answers with the release `frontend/package.json` +pins as a runtime dependency, embedded from `frontend/node_modules` when the +server is built. tsc writes the decorators as calls of helpers it imports from +`tslib`, which reaches `/assets/` the same way. A build therefore needs `npm ci` in `frontend/` first and Node.js on `PATH` for the TypeScript modules. Each package's modules reach its core through paths that resolve to one URL, so a page loads one module per package. Node unit tests resolve the same @@ -94,12 +96,13 @@ for the browser globals Node lacks. ## Rendering A component that renders through Lit extends `LitElement` and renders into its -own Light DOM. The state it shows lives in reactive properties, and an owner -hands a child what it shows through property bindings in its template. When -one changes, Lit calls `render()` once the code that changed it has finished, -so the DOM changes then rather than at the assignment. Work that reads the -rendered DOM, such as scroll restoration, focus, or measurement, runs in -`updated()`, or, in a flow that is already asynchronous, after +own Light DOM; `@customElement()` registers it. The state it shows lives in +reactive properties, `accessor` fields that `@property()` or `@state()` +declares, and an owner hands a child what it shows through property bindings in +its template. When one changes, Lit calls `render()` once the code that changed +it has finished, so the DOM changes then rather than at the assignment. Work +that reads the rendered DOM, such as scroll restoration, focus, or measurement, +runs in `updated()`, or, in a flow that is already asynchronous, after `updateComplete` once it has checked that its request is still current. An owner that needs a child's rendered DOM awaits the child's `updateComplete`. A component that calls methods on a child its own template draws renders its @@ -2114,7 +2117,8 @@ size or hide a child host, but descendant styling belongs to the child. `frontend/component-styles.ts` exports two shared styles, `compactIconButton` and `busySpin`. Components await `register(owner, target)` on each style they -use before defining their element, declaring the explicit child path from the +use before defining their element, so the await stands above the class +`@customElement()` registers, declaring the explicit child path from the owner to their own local class; a step in that path may add the owner's state classes. Each style fetches its stylesheet (`component-styles/compact-icon-button.css`, `component-styles/busy-spin.css`) diff --git a/frontend/README.md b/frontend/README.md index 4b3bfa02..f0b96166 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -42,11 +42,12 @@ working directory. Browser sources are embedded by `caffold/src/static_assets.rs` at build time, so a new runtime asset must be added there and precached by `service-worker.ts`. Nothing in `tests/` is part of that inventory. From -`node_modules/`, only the Lit modules a page reaches are. The server embeds the -Lit release `package.json` pins under `dependencies` and compiles each `.ts` -module with the TypeScript release pinned beside it, so a Cargo build needs -`npm ci` here first and Node.js on `PATH`. `npm ci --omit=dev` installs those -two alone when the browser suites and lint are not needed. To change a release, -change its pin and run `npm install`. The +`node_modules/`, only the Lit modules a page reaches and `tslib` are. The +server embeds the Lit and `tslib` releases `package.json` pins under +`dependencies` and compiles each `.ts` module with the TypeScript release +pinned beside them, so a Cargo build needs `npm ci` here first and Node.js on +`PATH`. `npm ci --omit=dev` installs those three alone when the browser suites +and lint are not needed. To change a release, change its pin and run +`npm install`. The [frontend architecture](../docs/architecture/frontend.md#native-module-imports) describes how pages and unit tests load the compiled modules. diff --git a/frontend/action-hints/components/dialog.ts b/frontend/action-hints/components/dialog.ts index 371da737..17d4ab3a 100644 --- a/frontend/action-hints/components/dialog.ts +++ b/frontend/action-hints/components/dialog.ts @@ -1,4 +1,5 @@ import { html, LitElement } from "lit"; +import { customElement } from "lit/decorators.js"; import { repeat } from "lit/directives/repeat.js"; import type { ActionHintSessionTarget } from "../../action-hints.js"; import { DrawnRef, DrawnRefs, drawn } from "../../components/drawn-refs.js"; @@ -18,6 +19,7 @@ let actionHintDialogInstanceId = 0; * person types or the targets move. A badge keeps its element while its code * stays, so focus and measurements carry over. */ +@customElement("caffold-action-hint-dialog") export class CaffoldActionHintDialog extends LitElement { private readonly titleId: string; private readonly descriptionId: string; @@ -61,11 +63,11 @@ export class CaffoldActionHintDialog extends LitElement { this.descriptionId = `action-hint-description-${actionHintDialogInstanceId}`; } - createRenderRoot() { + override createRenderRoot() { return this; } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); const dialog = this.dialog.value; if (dialog?.open) { @@ -73,7 +75,7 @@ export class CaffoldActionHintDialog extends LitElement { } } - render() { + override render() { const { buffer, status } = this.progress; const matching = new Set(this.progress.matches); return html` | undefined; +@customElement("caffold-docx-viewer") class CaffoldDocxViewer extends LitElement { - static properties = { - file: { attribute: false }, - renderState: { state: true }, - mount: { state: true }, - }; - - declare file: DocumentPreviewFile | null; - declare private renderState: RenderState; - declare private mount: HTMLElement | null; + @property({ attribute: false }) + accessor file: DocumentPreviewFile | null = null; + @state() + private accessor renderState: RenderState = "empty"; + @state() + private accessor mount: HTMLElement | null = null; + private generation = 0; private pages: PageRecord[] | null = null; private widthObserver: ResizeObserver | null = null; - constructor() { - super(); - this.file = null; - this.renderState = "empty"; - this.mount = null; - } - private readonly surfaces = new ScrollSurfaces(this); - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); this.setAttribute("role", "region"); this.setAttribute("aria-label", "Word document preview"); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); this.release(); } - render() { + override render() { const message = this.renderState === "loading" ? html`Loading document...` @@ -132,7 +125,7 @@ class CaffoldDocxViewer extends LitElement { } // A file handed over again before it is drawn replaces the one before it. - willUpdate(changed: PropertyValues) { + override willUpdate(changed: PropertyValues) { if (changed.has("file") && this.file) { this.release(); this.renderState = "loading"; @@ -140,7 +133,7 @@ class CaffoldDocxViewer extends LitElement { } } - updated(changed: PropertyValues) { + override updated(changed: PropertyValues) { if (changed.has("renderState")) { this.dataset.renderState = this.renderState; } @@ -287,4 +280,8 @@ function loadLibrary() { return libraryPromise; } -customElements.define("caffold-docx-viewer", CaffoldDocxViewer); +declare global { + interface HTMLElementTagNameMap { + "caffold-docx-viewer": CaffoldDocxViewer; + } +} diff --git a/frontend/components/drawn-refs.ts b/frontend/components/drawn-refs.ts index 22855fe7..d3bf8d79 100644 --- a/frontend/components/drawn-refs.ts +++ b/frontend/components/drawn-refs.ts @@ -68,7 +68,7 @@ class DrawnDirective extends Directive { return nothing; } - update(part: ElementPart, [ref]: [DrawnRef]) { + override update(part: ElementPart, [ref]: [DrawnRef]) { drawnElements.set(ref, part.element); return nothing; } diff --git a/frontend/components/file-navigator.ts b/frontend/components/file-navigator.ts index 79aa55c6..e1ce7c67 100644 --- a/frontend/components/file-navigator.ts +++ b/frontend/components/file-navigator.ts @@ -1,4 +1,5 @@ import { html, LitElement } from "lit"; +import { customElement } from "lit/decorators.js"; import type { ActionHintPull } from "../action-hints.js"; import { type DirectoryListing, listDirectory } from "../api.js"; import type { ScrollSurfacePull } from "../scroll-scope.js"; @@ -31,6 +32,7 @@ export type ResolvedPath = | { kind: "stale"; directory: null } | { kind: "error"; directory: DirectoryListing | false | null }; +@customElement("caffold-file-navigator") export class CaffoldFileNavigator extends LitElement { private readonly list = new DrawnRef(this); private currentPath = ""; @@ -51,18 +53,18 @@ export class CaffoldFileNavigator extends LitElement { (state) => this.setRefreshState(state), ); - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); if (this.watchActive && this.watchScopePath !== undefined) { this.subscribeWatchScope(this.watchScopePath); } } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); this.directoryRequestId += 1; this.refreshGeneration += 1; @@ -70,7 +72,7 @@ export class CaffoldFileNavigator extends LitElement { this.watchUnsubscribe = null; } - render() { + override render() { return html`; } +await compactIconButton.register( + "caffold-file-list", + "> .file-list-panel > header > .file-list-title-row > .file-list-actions > .file-refresh-button", +); + +@customElement("caffold-file-list") export class CaffoldFileList extends LitElement { - static properties = { - state: { state: true }, - refreshState: { state: true }, - refreshVisible: { state: true }, - }; + @state() + private accessor state: ListState = { status: "idle" }; + @state() + private accessor refreshState: RefreshButtonState = "idle"; + @state() + private accessor refreshVisible = true; - declare private state: ListState; - declare private refreshState: RefreshButtonState; - declare private refreshVisible: boolean; private selectedPath = ""; private keyByPath = new Map(); private treeState: TreeState | null = null; @@ -74,30 +79,23 @@ export class CaffoldFileList extends LitElement { private readonly surfaces = new ScrollSurfaces(this); private readonly tree = new DrawnRef(this); - constructor() { - super(); - this.state = { status: "idle" }; - this.refreshState = "idle"; - this.refreshVisible = true; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); this.clearTreeLoadingTimers(); } - render() { + override render() { const state = this.state; const tree = html` .file-list-panel > header > .file-list-title-row > .file-list-actions > .file-refresh-button", -); - -customElements.define("caffold-file-list", CaffoldFileList); - function listedEntry(node: FileTreeNode) { return node.source as DirectoryEntry | undefined; } @@ -727,3 +718,9 @@ function sameDirectoryPresentation( ); }); } + +declare global { + interface HTMLElementTagNameMap { + "caffold-file-list": CaffoldFileList; + } +} diff --git a/frontend/components/file-tree.ts b/frontend/components/file-tree.ts index faabb6fd..c3032415 100644 --- a/frontend/components/file-tree.ts +++ b/frontend/components/file-tree.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { repeat } from "lit/directives/repeat.js"; import { type ActionHintPull, ActionHints } from "../action-hints.js"; import { @@ -85,27 +86,28 @@ interface TreeRow { lastSibling: boolean; } +@customElement("caffold-file-tree") export class CaffoldFileTree extends LitElement { - static properties = { - nodes: { state: true }, - expandedKeys: { state: true }, - selectedKey: { state: true }, - hiddenEntriesLast: { state: true }, - listboxLabel: { state: true }, - statusColumn: { state: true }, - globalFileSortMode: { state: true }, - }; - - declare private nodes: FileTreeNode[]; - declare private expandedKeys: Set; - declare private selectedKey: string; - declare private hiddenEntriesLast: boolean; + @state() + private accessor nodes: FileTreeNode[] = []; + @state() + private accessor expandedKeys: Set = new Set(); + @state() + private accessor selectedKey = ""; + @state() + private accessor hiddenEntriesLast = false; // With a label the rows are the options of a listbox whose owner keeps // focus in a text field, so they leave the Tab order. An owner keeps one // way for the tree's whole life. - declare private listboxLabel: string; - declare private statusColumn: boolean; - declare private globalFileSortMode: FileSortMode; + @state() + private accessor listboxLabel = ""; + @state() + private accessor statusColumn = false; + @state() + private accessor globalFileSortMode: FileSortMode = normalizeFileSortMode( + getSettings().fileSortMode, + ); + private entityKey: string | null = null; private nodeByKey = new Map(); private knownDirectoryKeys = new Set(); @@ -123,22 +125,11 @@ export class CaffoldFileTree extends LitElement { ); }; - constructor() { - super(); - this.nodes = []; - this.expandedKeys = new Set(); - this.selectedKey = ""; - this.hiddenEntriesLast = false; - this.listboxLabel = ""; - this.statusColumn = false; - this.globalFileSortMode = normalizeFileSortMode(getSettings().fileSortMode); - } - - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); window.addEventListener("caffold:settings-change", this.settingsChanged); @@ -146,7 +137,7 @@ export class CaffoldFileTree extends LitElement { void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); window.removeEventListener("caffold:settings-change", this.settingsChanged); @@ -155,7 +146,7 @@ export class CaffoldFileTree extends LitElement { // Rows declare their Action Hints: an expandable directory toggles, any // other entry opens, except the one already open (an owner's actions for // `disclosure` and `file`). - render() { + override render() { const listbox = Boolean(this.listboxLabel); return html`
| null | undefined, options: { namespace?: string; expandedByDefault?: boolean } = {}, @@ -867,3 +856,9 @@ function optionId(tree: Element, key: string) { function nextAnimationFrame() { return new Promise((resolve) => requestAnimationFrame(resolve)); } + +declare global { + interface HTMLElementTagNameMap { + "caffold-file-tree": CaffoldFileTree; + } +} diff --git a/frontend/components/file-viewer.test.js b/frontend/components/file-viewer.test.js index 91651ddf..1be03d02 100644 --- a/frontend/components/file-viewer.test.js +++ b/frontend/components/file-viewer.test.js @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import test, { after } from "node:test"; import { + elementStandIn, installCustomElementUnitRegistry, } from "../tests/support/custom-element-unit.js"; @@ -13,8 +14,7 @@ after(() => registry.restore()); // A viewer whose own declarations make `ownTarget` and `ownSurface`, and // whose drawn children are the given refs. function viewerWith(state, children = {}) { - const owner = Object.create(fileViewer); - return Object.assign(owner, { + const owner = elementStandIn(fileViewer, { state, hints: { scope: () => ({ blocked: false, targets: [{ id: "own" }], mutationRoots: [owner], scrollRoots: [] }), @@ -27,6 +27,7 @@ function viewerWith(state, children = {}) { preview: { value: children.preview }, documentViewer: { value: children.documentViewer }, }); + return owner; } test("merges the Action Hints of the Markdown preview it draws", () => { @@ -319,7 +320,7 @@ function fileServer(files) { function previewHost(status = "document") { const handed = []; const errors = []; - const host = Object.assign(Object.create(fileViewer), { + const host = elementStandIn(fileViewer, { state: status === "image" ? { status, image: { path: "picture.png", name: "picture.png" } } : { status, document: { kind: "pdf", path: "document.pdf" } }, diff --git a/frontend/components/file-viewer.ts b/frontend/components/file-viewer.ts index fbb9cef8..0a7abd52 100644 --- a/frontend/components/file-viewer.ts +++ b/frontend/components/file-viewer.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type TemplateResult } from "lit"; +import { customElement, state } from "lit/decorators.js"; import type { ActionHintScope } from "../action-hint-scope.js"; import { ACTION_HINT_ACTION, @@ -218,25 +219,33 @@ interface RetainedContent { let viewerInstanceId = 0; -export class CaffoldReviewFileViewer extends LitElement { - static properties = { - state: { state: true }, - closeLabel: { state: true }, - closeMode: { state: true }, - imageDrawn: { state: true }, - imageSource: { state: true }, - documentFile: { state: true }, - }; +await compactIconButton.register( + "caffold-review-file-viewer", + "> .viewer-panel > .viewer-header > .viewer-title-row > .viewer-actions > .viewer-refresh-button", +); +await compactIconButton.register( + "caffold-review-file-viewer", + "> .viewer-panel > .viewer-header > .viewer-title-row > .viewer-close-button", +); - declare private state: ViewerState; - declare private closeLabel: string; - declare private closeMode: "back" | "close"; +@customElement("caffold-review-file-viewer") +export class CaffoldReviewFileViewer extends LitElement { + @state() + private accessor state: ViewerState = { status: "empty" }; + @state() + private accessor closeLabel = "Back to files"; + @state() + private accessor closeMode: "back" | "close" = "close"; // Until the picture is drawn, its place says it is loading rather than // showing an empty image the browser would draw as broken. - declare private imageDrawn: boolean; + @state() + private accessor imageDrawn = false; // The fetched copy of the previewed file the image or document draws from. - declare private imageSource: string; - declare private documentFile: DocumentPreviewFile | null; + @state() + private accessor imageSource = ""; + @state() + private accessor documentFile: DocumentPreviewFile | null = null; + private readonly detailsPopoverId: string; private previewFile: PreviewFile | null = null; private retainedContent: RetainedContent | null = null; @@ -254,12 +263,6 @@ export class CaffoldReviewFileViewer extends LitElement { constructor() { super(); - this.state = { status: "empty" }; - this.closeLabel = "Back to files"; - this.closeMode = "close"; - this.imageDrawn = false; - this.imageSource = ""; - this.documentFile = null; viewerInstanceId += 1; this.detailsPopoverId = `caffold-viewer-details-${viewerInstanceId}`; this.addEventListener(KEYBOARD_SESSION_DISMISS_EVENT, (event) => { @@ -269,23 +272,23 @@ export class CaffoldReviewFileViewer extends LitElement { }); } - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); this.deactivate(); window.removeEventListener("caffold:icons-ready", this.iconsReady); } - render() { + override render() { const state = this.state; if (state.status === "empty") { return html` @@ -1079,14 +1082,8 @@ export class CaffoldReviewFileViewer extends LitElement { } } -await compactIconButton.register( - "caffold-review-file-viewer", - "> .viewer-panel > .viewer-header > .viewer-title-row > .viewer-actions > .viewer-refresh-button", -); - -await compactIconButton.register( - "caffold-review-file-viewer", - "> .viewer-panel > .viewer-header > .viewer-title-row > .viewer-close-button", -); - -customElements.define("caffold-review-file-viewer", CaffoldReviewFileViewer); +declare global { + interface HTMLElementTagNameMap { + "caffold-review-file-viewer": CaffoldReviewFileViewer; + } +} diff --git a/frontend/components/file-viewer/components/code-viewer.ts b/frontend/components/file-viewer/components/code-viewer.ts index 9cc27a2b..62b01a3a 100644 --- a/frontend/components/file-viewer/components/code-viewer.ts +++ b/frontend/components/file-viewer/components/code-viewer.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; +import { customElement, property } from "lit/decorators.js"; import type { SyntaxSpan } from "#app/api.js"; import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; import { DrawnRef, drawn } from "../../drawn-refs.js"; @@ -28,30 +29,22 @@ export interface ViewerPosition { line?: number | string | null; } +@customElement("caffold-code-viewer") export class CaffoldCodeViewer extends LitElement { - static properties = { - file: { attribute: false }, - position: { attribute: false }, - }; - - declare file: CodeFile | null; - declare position: ViewerPosition | null; + @property({ attribute: false }) + accessor file: CodeFile | null = null; + @property({ attribute: false }) + accessor position: ViewerPosition | null = null; private readonly surfaces = new ScrollSurfaces(this); private readonly scrollport = new DrawnRef(this); private readonly table = new DrawnRef(this); - constructor() { - super(); - this.file = null; - this.position = null; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - render() { + override render() { if (!this.file) { return nothing; } @@ -97,7 +90,7 @@ export class CaffoldCodeViewer extends LitElement { // The lines stay in place from one file to the next, so a file that keeps // no position starts from the top again. - updated(changed: PropertyValues) { + override updated(changed: PropertyValues) { if (changed.has("file") || changed.has("position")) { this.restorePosition(this.position); } @@ -203,4 +196,8 @@ function monospaceColumns(text: string) { return columns; } -customElements.define("caffold-code-viewer", CaffoldCodeViewer); +declare global { + interface HTMLElementTagNameMap { + "caffold-code-viewer": CaffoldCodeViewer; + } +} diff --git a/frontend/components/file-viewer/components/diff-viewer.ts b/frontend/components/file-viewer/components/diff-viewer.ts index 6982fe38..95fc684e 100644 --- a/frontend/components/file-viewer/components/diff-viewer.ts +++ b/frontend/components/file-viewer/components/diff-viewer.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; +import { customElement, property } from "lit/decorators.js"; import type { SyntaxSpan } from "#app/api.js"; import { type ScrollSurfacePull, ScrollSurfaces } from "#app/scroll-scope.js"; import { DrawnRef, drawn } from "../../drawn-refs.js"; @@ -33,30 +34,22 @@ const EMPTY_DIFF_MESSAGE = "No diff for this file."; // the diff. const SOURCE_ROWS = new Set(["added", "removed", "context"]); +@customElement("caffold-diff-viewer") export class CaffoldDiffViewer extends LitElement { - static properties = { - diff: { attribute: false }, - position: { attribute: false }, - }; - - declare diff: FileDiff | null; - declare position: ViewerPosition | null; + @property({ attribute: false }) + accessor diff: FileDiff | null = null; + @property({ attribute: false }) + accessor position: ViewerPosition | null = null; private readonly surfaces = new ScrollSurfaces(this); private readonly scrollport = new DrawnRef(this); private readonly table = new DrawnRef(this); - constructor() { - super(); - this.diff = null; - this.position = null; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - render() { + override render() { if (!this.diff) { return nothing; } @@ -125,7 +118,7 @@ export class CaffoldDiffViewer extends LitElement { // The rows stay in place from one diff to the next, so a diff that keeps // no position starts from the top again. - updated(changed: PropertyValues) { + override updated(changed: PropertyValues) { if (changed.has("diff") || changed.has("position")) { this.restorePosition(this.position); } @@ -228,8 +221,6 @@ function normalizeLine(line: number | string | null | undefined) { return Number.isInteger(value) && value > 0 ? value : null; } -customElements.define("caffold-diff-viewer", CaffoldDiffViewer); - function diffCodeColumns(rows: DiffRow[]) { const columns = rows.reduce( (max, row) => Math.max(max, monospaceColumns(row.content)), @@ -359,3 +350,9 @@ function parseUnifiedDiff(diffText: string): DiffRow[] { function hunkLineCount(count: string | undefined) { return count === undefined ? 1 : Number(count); } + +declare global { + interface HTMLElementTagNameMap { + "caffold-diff-viewer": CaffoldDiffViewer; + } +} diff --git a/frontend/components/git-compare-browser.ts b/frontend/components/git-compare-browser.ts index 7743d9ba..42da369d 100644 --- a/frontend/components/git-compare-browser.ts +++ b/frontend/components/git-compare-browser.ts @@ -1,4 +1,5 @@ import { html, LitElement } from "lit"; +import { customElement } from "lit/decorators.js"; import { type ActionHintPull, mergeActionHintScopes } from "../action-hints.js"; import { type GitCompare, @@ -28,6 +29,7 @@ const LOADING_DELAY_MS = 180; type DetailView = "list" | "viewer"; +@customElement("caffold-git-compare-browser") export class CaffoldGitCompareBrowser extends LitElement { // What the Compare page reads back. repository: GitRepository | null = null; @@ -49,16 +51,16 @@ export class CaffoldGitCompareBrowser extends LitElement { private readonly resizer = new DrawnRef(this); private readonly fileViewer = new DrawnRef(this); - createRenderRoot() { + override createRenderRoot() { return this; } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); this.deactivate(); } - render() { + override render() { return html` registry.restore()); // A compare tree whose own declarations make `ownTargets` and whose drawn // File Tree is `tree`. function compareTreeWith({ status = "ready", tree = null, ownTargets = [] } = {}) { - const owner = Object.create(compareTree); - return Object.assign(owner, { + const owner = elementStandIn(compareTree, { state: { status }, hints: { scope: () => ({ blocked: false, targets: ownTargets, mutationRoots: [owner], scrollRoots: [] }), }, tree: { value: tree }, }); + return owner; } test("passes the pull to the File Tree it draws once the compare is ready", () => { diff --git a/frontend/components/git-compare-tree.ts b/frontend/components/git-compare-tree.ts index 38f0cacc..43e0652e 100644 --- a/frontend/components/git-compare-tree.ts +++ b/frontend/components/git-compare-tree.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { type ActionHintPull, ActionHints, @@ -42,16 +43,19 @@ interface BaseSelection { value: string; } +@customElement("caffold-git-compare-tree") export class CaffoldGitCompareTree extends LitElement { - static properties = { - state: { state: true }, - baseSelection: { state: true }, - emptyMessage: { state: true }, + @state() + private accessor state: CompareState = { status: "idle" }; + @state() + private accessor baseSelection: BaseSelection = { + enabled: false, + refs: [], + value: "", }; + @state() + private accessor emptyMessage = ""; - declare private state: CompareState; - declare private baseSelection: BaseSelection; - declare private emptyMessage: string; selectedPath = ""; private fileKeyByPath = new Map(); private readonly hints = new ActionHints(this); @@ -60,18 +64,11 @@ export class CaffoldGitCompareTree extends LitElement { private readonly baseRefSelect = new DrawnRef(this); private readonly tree = new DrawnRef(this); - constructor() { - super(); - this.state = { status: "idle" }; - this.baseSelection = { enabled: false, refs: [], value: "" }; - this.emptyMessage = ""; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - render() { + override render() { const state = this.state; const compare = state.status === "ready" ? state.comparePayload : null; const files = compare?.files ?? []; @@ -174,7 +171,7 @@ export class CaffoldGitCompareTree extends LitElement { // A `.value` binding would land before the options it names, and the tree // is handed the compared files once it is drawn. - updated(changed: PropertyValues) { + override updated(changed: PropertyValues) { const select = this.baseRefSelect.value; const { enabled, value } = this.baseSelection; if (enabled && select && select.value !== value) { @@ -330,8 +327,6 @@ export class CaffoldGitCompareTree extends LitElement { } } -customElements.define("caffold-git-compare-tree", CaffoldGitCompareTree); - function comparedFile(node: FileTreeNode) { return node.source as GitCompareFile | undefined; } @@ -406,3 +401,9 @@ function compareEntityKey(payload: GitCompare) { payload.headRef ?? "", ].join("\u0000"); } + +declare global { + interface HTMLElementTagNameMap { + "caffold-git-compare-tree": CaffoldGitCompareTree; + } +} diff --git a/frontend/components/loading-text.ts b/frontend/components/loading-text.ts index d6ad08f9..6c38ca83 100644 --- a/frontend/components/loading-text.ts +++ b/frontend/components/loading-text.ts @@ -1,10 +1,17 @@ +import { customElement } from "lit/decorators.js"; + // A short phrase standing in for content that is still on its way. Its // stylesheet keeps it out of sight for a brief wait, so loads that finish // quickly never flash it; `immediate` is for owners that already waited. +@customElement("caffold-loading-text") class CaffoldLoadingText extends HTMLElement { connectedCallback() { this.setAttribute("role", "status"); } } -customElements.define("caffold-loading-text", CaffoldLoadingText); +declare global { + interface HTMLElementTagNameMap { + "caffold-loading-text": CaffoldLoadingText; + } +} diff --git a/frontend/components/markdown-preview.test.js b/frontend/components/markdown-preview.test.js index 9b19b230..cbc52c41 100644 --- a/frontend/components/markdown-preview.test.js +++ b/frontend/components/markdown-preview.test.js @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import test, { after } from "node:test"; import { + elementStandIn, installCustomElementUnitRegistry, } from "../tests/support/custom-element-unit.js"; @@ -18,7 +19,7 @@ test("adds the code blocks and tables drawn from the Markdown to its own surface }; const viewer = { id: "viewer" }; const pulls = []; - const owner = Object.assign(Object.create(markdownPreview), { + const owner = elementStandIn(markdownPreview, { renderState: "markdown", body: { value: body }, scrollSurfaceRecords: [ diff --git a/frontend/components/markdown-preview.ts b/frontend/components/markdown-preview.ts index 7d05b6b0..295145fd 100644 --- a/frontend/components/markdown-preview.ts +++ b/frontend/components/markdown-preview.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { type ActionHintScope, captureLinkActionHintBinding, @@ -82,16 +83,15 @@ type MarkedParser = ParseMarkdown & { parse?: ParseMarkdown }; let parserPromise: Promise | null = null; +@customElement("caffold-markdown-preview") export class CaffoldMarkdownPreview extends LitElement { - static properties = { - renderState: { state: true }, - markdown: { state: true }, - renderedContent: { state: true }, - }; - - declare private renderState: RenderState | null; - declare private markdown: string | undefined; - declare private renderedContent: DocumentFragment | null; + @state() + private accessor renderState: RenderState | null = null; + @state() + private accessor markdown: string | undefined; + @state() + private accessor renderedContent: DocumentFragment | null = null; + private images: MarkdownImages | null = null; private pendingScroll: ScrollPosition | null = null; private renderToken: symbol | null = null; @@ -102,17 +102,11 @@ export class CaffoldMarkdownPreview extends LitElement { private readonly surfaces = new ScrollSurfaces(this); private readonly body = new DrawnRef(this); - constructor() { - super(); - this.renderState = null; - this.renderedContent = null; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); this.setAttribute("role", "region"); this.setAttribute("aria-label", "Markdown preview"); @@ -123,13 +117,13 @@ export class CaffoldMarkdownPreview extends LitElement { } } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); this.renderToken = null; this.releaseImages(); } - render() { + override render() { const content = this.renderState === "markdown" ? this.renderedContent @@ -153,7 +147,7 @@ export class CaffoldMarkdownPreview extends LitElement { >${content}`; } - updated(changed: PropertyValues) { + override updated(changed: PropertyValues) { if (changed.has("renderState") && this.renderState) { this.dataset.renderState = this.renderState; } @@ -469,8 +463,6 @@ function collectScrollSurfaceRecords(root: Element): ScrollSurfaceRecord[] { }); } -customElements.define("caffold-markdown-preview", CaffoldMarkdownPreview); - async function markdownContent( markdown: string | undefined, images: MarkdownImages | null, @@ -738,3 +730,9 @@ const FORBIDDEN_ELEMENTS = new Set([ "style", "textarea", ]); + +declare global { + interface HTMLElementTagNameMap { + "caffold-markdown-preview": CaffoldMarkdownPreview; + } +} diff --git a/frontend/components/pagination.ts b/frontend/components/pagination.ts index 69bcea66..2cb1a889 100644 --- a/frontend/components/pagination.ts +++ b/frontend/components/pagination.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; import { type ActionHintPull, ActionHints, @@ -14,55 +15,45 @@ import type { const PAGE_KINDS: readonly PageKind[] = ["first", "previous", "next", "last"]; +@customElement("caffold-pagination") export class CaffoldPagination extends LitElement { - static properties = { - page: { type: Number }, - totalPages: { type: Number, attribute: "total-pages" }, - hasPrevious: { type: Boolean, attribute: "has-previous" }, - hasNext: { type: Boolean, attribute: "has-next" }, - firstLabel: { attribute: "first-label" }, - previousLabel: { attribute: "previous-label" }, - nextLabel: { attribute: "next-label" }, - lastLabel: { attribute: "last-label" }, - }; - - declare page: number; - declare totalPages: number; - declare hasPrevious: boolean; - declare hasNext: boolean; - declare firstLabel: string | null | undefined; - declare previousLabel: string | null | undefined; - declare nextLabel: string | null | undefined; - declare lastLabel: string | null | undefined; + @property({ type: Number }) + accessor page = 1; + @property({ type: Number, attribute: "total-pages" }) + accessor totalPages = 0; + @property({ type: Boolean, attribute: "has-previous" }) + accessor hasPrevious = false; + @property({ type: Boolean, attribute: "has-next" }) + accessor hasNext = false; + @property({ attribute: "first-label" }) + accessor firstLabel: string | null | undefined; + @property({ attribute: "previous-label" }) + accessor previousLabel: string | null | undefined; + @property({ attribute: "next-label" }) + accessor nextLabel: string | null | undefined; + @property({ attribute: "last-label" }) + accessor lastLabel: string | null | undefined; private readonly iconsReady = () => this.requestUpdate(); private readonly hints = new ActionHints(this); private readonly pageButtons = new DrawnRefs(this); - constructor() { - super(); - this.page = 1; - this.totalPages = 0; - this.hasPrevious = false; - this.hasNext = false; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); } - render() { + override render() { const page = finiteOr(this.page, 1); const totalPages = finiteOr(this.totalPages, 0); if (totalPages <= 1) { @@ -130,4 +121,8 @@ function finiteOr(value: number, fallback: number) { return Number.isFinite(value) ? value : fallback; } -customElements.define("caffold-pagination", CaffoldPagination); +declare global { + interface HTMLElementTagNameMap { + "caffold-pagination": CaffoldPagination; + } +} diff --git a/frontend/components/pagination/components/button.ts b/frontend/components/pagination/components/button.ts index b58ea584..17397b38 100644 --- a/frontend/components/pagination/components/button.ts +++ b/frontend/components/pagination/components/button.ts @@ -1,40 +1,36 @@ import { html, LitElement } from "lit"; +import { customElement, property } from "lit/decorators.js"; import { type ActionHintPull, ActionHints } from "#app/action-hints.js"; import { compactIconButton } from "#app/component-styles.js"; import { inlineIcon } from "../../icons.js"; export type PageKind = "first" | "previous" | "next" | "last"; -export class CaffoldPaginationButton extends LitElement { - static properties = { - kind: {}, - page: { type: Number }, - label: {}, - icon: {}, - disabled: { type: Boolean }, - }; +await compactIconButton.register( + "caffold-pagination-button", + "> .pagination-button", +); - declare kind: PageKind; - declare page: number; - declare label: string; - declare icon: string; - declare disabled: boolean; +@customElement("caffold-pagination-button") +export class CaffoldPaginationButton extends LitElement { + @property() + accessor kind!: PageKind; + @property({ type: Number }) + accessor page = 0; + @property() + accessor label = ""; + @property() + accessor icon = ""; + @property({ type: Boolean }) + accessor disabled = false; private readonly hints = new ActionHints(this); - constructor() { - super(); - this.page = 0; - this.label = ""; - this.icon = ""; - this.disabled = false; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - render() { + override render() { return html`
`; } - updated() { + override updated() { const hidden = this.empty; if (this.hidden !== hidden) { this.hidden = hidden; @@ -134,4 +125,8 @@ export class CaffoldTaskOlderHistory extends LitElement { } } -customElements.define("caffold-task-older-history", CaffoldTaskOlderHistory); +declare global { + interface HTMLElementTagNameMap { + "caffold-task-older-history": CaffoldTaskOlderHistory; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts index 4aba2d03..d7806a8e 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/user-message.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; +import { customElement, property } from "lit/decorators.js"; import type { ActionHintPull } from "#app/action-hints.js"; import type { TaskUploadLine } from "#app/api.js"; import { DrawnRef, drawn } from "#components/drawn-refs.js"; @@ -31,42 +32,31 @@ export interface UploadProgress { * each line of its file list carries that file's progress, drawn over the line * so the text sits exactly where it will once sent. */ +@customElement("caffold-task-user-message") export class CaffoldTaskUserMessage extends LitElement { - static properties = { - text: { attribute: false }, - attachments: { attribute: false }, - deliveryState: { attribute: false }, - time: { attribute: false }, - uploadLines: { attribute: false }, - uploadProgress: { attribute: false }, - }; - - declare text: string; - declare attachments: MessageAttachment[]; + @property({ attribute: false }) + accessor text = ""; + @property({ attribute: false }) + accessor attachments: MessageAttachment[] = []; // `null` once the agent's history holds the message. - declare deliveryState: PromptSubmissionState | null; - declare time: string; - declare uploadLines: TaskUploadLine[]; - declare uploadProgress: UploadProgress | null; + @property({ attribute: false }) + accessor deliveryState: PromptSubmissionState | null = null; + @property({ attribute: false }) + accessor time = ""; + @property({ attribute: false }) + accessor uploadLines: TaskUploadLine[] = []; + @property({ attribute: false }) + accessor uploadProgress: UploadProgress | null = null; + private readonly attachmentList = new DrawnRef( this, ); - constructor() { - super(); - this.text = ""; - this.attachments = []; - this.deliveryState = null; - this.time = ""; - this.uploadLines = []; - this.uploadProgress = null; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - render() { + override render() { const lines = this.deliveryState === UPLOADING ? this.uploadLines : []; return html`
@@ -92,7 +82,7 @@ export class CaffoldTaskUserMessage extends LitElement { `; } - updated(changed: PropertyValues) { + override updated(changed: PropertyValues) { if (changed.has("deliveryState")) { if (this.deliveryState) { this.dataset.deliveryState = this.deliveryState; @@ -156,4 +146,8 @@ function messageText( )}`; } -customElements.define("caffold-task-user-message", CaffoldTaskUserMessage); +declare global { + interface HTMLElementTagNameMap { + "caffold-task-user-message": CaffoldTaskUserMessage; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts index a1835cf7..6049bd9d 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/conversation/components/work-details.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; +import { customElement, property } from "lit/decorators.js"; import { repeat } from "lit/directives/repeat.js"; import { ACTION_HINT_ACTION, @@ -85,20 +86,19 @@ type WorkItem = // A finished turn's work, folded behind one disclosure that says how long the // turn worked and how many updates it made. +@customElement("caffold-task-work-details") export class CaffoldTaskWorkDetails extends LitElement { - static properties = { - identity: { attribute: false }, - label: { attribute: false }, - updateText: { attribute: false }, - events: { attribute: false, hasChanged: changedEventList }, - filePathPresentationBase: { attribute: false }, - }; + @property({ attribute: false }) + accessor identity = ""; + @property({ attribute: false }) + accessor label = "Work details"; + @property({ attribute: false }) + accessor updateText = ""; + @property({ attribute: false, hasChanged: changedEventList }) + accessor events: TaskEvent[] = []; + @property({ attribute: false }) + accessor filePathPresentationBase = ""; - declare identity: string; - declare label: string; - declare updateText: string; - declare events: TaskEvent[]; - declare filePathPresentationBase: string; private items: WorkItem[] = []; private readonly iconsReady = () => this.requestUpdate(); private readonly hints = new ActionHints(this, { @@ -112,32 +112,23 @@ export class CaffoldTaskWorkDetails extends LitElement { // Each item's command, command group, or message, by kind and identity. private readonly parts = new DrawnRefs(this); - constructor() { - super(); - this.identity = ""; - this.label = "Work details"; - this.updateText = ""; - this.events = []; - this.filePathPresentationBase = ""; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); this.rememberDisclosureState(); } - willUpdate(changed: PropertyValues) { + override willUpdate(changed: PropertyValues) { if (changed.has("events") || changed.has("filePathPresentationBase")) { this.items = workItems(this.events, this.filePathPresentationBase); } @@ -145,7 +136,7 @@ export class CaffoldTaskWorkDetails extends LitElement { // The summary toggles the native disclosure; drawing again keeps what the // person chose. - render() { + override render() { const open = this.disclosureState().get("root") ?? false; const outputs = outputOrdinals(this.items); const parts = { @@ -203,7 +194,7 @@ export class CaffoldTaskWorkDetails extends LitElement { `; } - updated(changed: PropertyValues) { + override updated(changed: PropertyValues) { if (changed.has("identity")) { this.dataset.workDetailsIdentity = this.identity; } @@ -615,4 +606,8 @@ function changedEventList(next: TaskEvent[], previous?: TaskEvent[]) { ); } -customElements.define("caffold-task-work-details", CaffoldTaskWorkDetails); +declare global { + interface HTMLElementTagNameMap { + "caffold-task-work-details": CaffoldTaskWorkDetails; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts index 034f3c4f..52412e73 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { ACTION_HINT_ACTION, type ActionHintPull, @@ -43,20 +44,19 @@ let currentPlanInstanceId = 0; * progress, each opening its file, and a status that explains a plan that * cannot be read. It reads the plan again whenever its files change. */ +@customElement("caffold-task-current-plan") export class CaffoldTaskCurrentPlan extends LitElement { - static properties = { - node: { state: true }, - context: { state: true }, - projection: { state: true }, - readError: { state: true }, - watchError: { state: true }, - }; - - declare private node: CurrentPlanNode; - declare private context: CurrentPlanContext | null; - declare private projection: CurrentPlanProjection | null; - declare private readError: Error | null; - declare private watchError: Error | null; + @state() + private accessor node: CurrentPlanNode = CURRENT_PLAN_NODE.INACTIVE; + @state() + private accessor context: CurrentPlanContext | null = null; + @state() + private accessor projection: CurrentPlanProjection | null = null; + @state() + private accessor readError: Error | null = null; + @state() + private accessor watchError: Error | null = null; + private readonly statusPopoverId = `task-current-plan-status-${++currentPlanInstanceId}`; private contextGeneration = 0; @@ -79,21 +79,16 @@ export class CaffoldTaskCurrentPlan extends LitElement { constructor() { super(); - this.node = CURRENT_PLAN_NODE.INACTIVE; - this.context = null; - this.projection = null; - this.readError = null; - this.watchError = null; this.addEventListener(KEYBOARD_SESSION_DISMISS_EVENT, (event) => this.handleDismiss(event), ); } - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); @@ -102,7 +97,7 @@ export class CaffoldTaskCurrentPlan extends LitElement { } } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); this.deactivate(); @@ -170,7 +165,7 @@ export class CaffoldTaskCurrentPlan extends LitElement { ); } - render() { + override render() { const presentation = currentPlanPresentation({ projection: this.projection, readError: this.readError, @@ -336,7 +331,7 @@ export class CaffoldTaskCurrentPlan extends LitElement { `; } - willUpdate() { + override willUpdate() { const focused = document.activeElement; this.statusHadFocus = Boolean( focused && @@ -348,7 +343,7 @@ export class CaffoldTaskCurrentPlan extends LitElement { // A plan with nothing to explain closes its status. Only a ready plan has // no issues, so its title segment takes focus from the status controls the // drawing just hid. - updated() { + override updated() { if (this.dataset.lifecycle !== this.node) { this.dataset.lifecycle = this.node; } @@ -574,4 +569,8 @@ function progressText( : `${plan.completed} / ${plan.total}`; } -customElements.define("caffold-task-current-plan", CaffoldTaskCurrentPlan); +declare global { + interface HTMLElementTagNameMap { + "caffold-task-current-plan": CaffoldTaskCurrentPlan; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts index 6315b366..6308c5ae 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/current-plan/components/document-dialog.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing } from "lit"; +import { customElement } from "lit/decorators.js"; import { ACTION_HINT_ACTION, ActionHints, @@ -40,6 +41,7 @@ type ReadPhase = "idle" | "loading" | "refreshing" | "failed" | "shown"; * One of a Task's plan files, read and drawn as Markdown over the Task screen. * It reads the file again when the Current Plan reports that it changed. */ +@customElement("caffold-current-plan-document-dialog") export class CaffoldCurrentPlanDocumentDialog extends LitElement { private requestId = 0; private requestController: AbortController | null = null; @@ -54,17 +56,17 @@ export class CaffoldCurrentPlanDocumentDialog extends LitElement { private readonly dialog = new DrawnRef(this); private readonly preview = new DrawnRef(this); - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); this.deactivate(); @@ -138,7 +140,7 @@ export class CaffoldCurrentPlanDocumentDialog extends LitElement { }); } - render() { + override render() { const current = this.current; const phase = this.phase; const closeIcon = inlineIcon( @@ -310,7 +312,8 @@ export class CaffoldCurrentPlanDocumentDialog extends LitElement { } } -customElements.define( - "caffold-current-plan-document-dialog", - CaffoldCurrentPlanDocumentDialog, -); +declare global { + interface HTMLElementTagNameMap { + "caffold-current-plan-document-dialog": CaffoldCurrentPlanDocumentDialog; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts index 1ef901b2..d67ad712 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/markdown-preview-dialog.ts @@ -1,4 +1,5 @@ import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { ACTION_HINT_ACTION, ActionHints, @@ -23,12 +24,11 @@ export interface MarkdownPreviewRequest { * Markdown an agent wrote, drawn over the Task screen. The dialog belongs to * one Task and closes when another Task opens. */ +@customElement("caffold-task-markdown-preview-dialog") export class CaffoldTaskMarkdownPreviewDialog extends LitElement { - static properties = { - threadId: { state: true }, - }; + @state() + private accessor threadId = ""; - declare private threadId: string; private opener: HTMLElement | null = null; private readonly iconsReady = () => this.requestUpdate(); private readonly hints = new ActionHints(this); @@ -36,22 +36,17 @@ export class CaffoldTaskMarkdownPreviewDialog extends LitElement { private readonly dialog = new DrawnRef(this); private readonly preview = new DrawnRef(this); - constructor() { - super(); - this.threadId = ""; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); } @@ -102,7 +97,7 @@ export class CaffoldTaskMarkdownPreviewDialog extends LitElement { }); } - render() { + override render() { const closeLabel = "Close Markdown preview"; const closeIcon = inlineIcon( "X", @@ -162,7 +157,8 @@ export class CaffoldTaskMarkdownPreviewDialog extends LitElement { } } -customElements.define( - "caffold-task-markdown-preview-dialog", - CaffoldTaskMarkdownPreviewDialog, -); +declare global { + interface HTMLElementTagNameMap { + "caffold-task-markdown-preview-dialog": CaffoldTaskMarkdownPreviewDialog; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts index 88f7fb29..eeacfe9f 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/permission-instructions-dialog.ts @@ -1,4 +1,5 @@ import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { ACTION_HINT_ACTION, ActionHints } from "#app/action-hints.js"; import { forgetTaskPermissionInstructions, @@ -36,14 +37,17 @@ interface RecordState { * Task details popover it opens from: a popover is for the facts that fit * beside it, and this is a page of the person's own sentences. */ +@customElement("caffold-task-permission-instructions-dialog") export class CaffoldTaskPermissionInstructionsDialog extends LitElement { - static properties = { - threadId: { state: true }, - state: { state: true }, + @state() + private accessor threadId = ""; + @state() + private accessor state: RecordState = { + loading: false, + instructions: null, + error: "", }; - declare private threadId: string; - declare private state: RecordState; private opener: HTMLElement | null = null; private request = 0; private readonly iconsReady = () => this.requestUpdate(); @@ -51,23 +55,17 @@ export class CaffoldTaskPermissionInstructionsDialog extends LitElement { private readonly keyboard = new KeyboardContexts(this); private readonly dialog = new DrawnRef(this); - constructor() { - super(); - this.threadId = ""; - this.state = { loading: false, instructions: null, error: "" }; - } - - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); this.request += 1; @@ -110,7 +108,7 @@ export class CaffoldTaskPermissionInstructionsDialog extends LitElement { }); } - render() { + override render() { const { loading, error } = this.state; const instructions = loading ? "" @@ -322,7 +320,8 @@ function messageOf(error: unknown, fallback: string) { return (error as { message?: string } | null)?.message ?? fallback; } -customElements.define( - "caffold-task-permission-instructions-dialog", - CaffoldTaskPermissionInstructionsDialog, -); +declare global { + interface HTMLElementTagNameMap { + "caffold-task-permission-instructions-dialog": CaffoldTaskPermissionInstructionsDialog; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts index 83d5eea6..18fc0114 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { type ActionHintPull, emptyActionHintScope, @@ -20,24 +21,18 @@ export type TaskSummaryIntent = TaskInfoIntent; /** * The open Task's heading: its title, and Task Info beside it. */ +@customElement("caffold-task-detail-summary") export class CaffoldTaskDetailSummary extends LitElement { - static properties = { - snapshot: { state: true }, - }; + @state() + private accessor snapshot: TaskInfoSnapshot = taskInfoSnapshot(); - declare private snapshot: TaskInfoSnapshot; private readonly info = new DrawnRef(this); - constructor() { - super(); - this.snapshot = taskInfoSnapshot(); - } - - createRenderRoot() { + override createRenderRoot() { return this; } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); this.deactivate(); } @@ -62,7 +57,7 @@ export class CaffoldTaskDetailSummary extends LitElement { return this.info.value?.keyboardNavigationContexts(options) ?? []; } - render() { + override render() { const task = this.snapshot.task; if (!task) { return nothing; @@ -93,4 +88,8 @@ export class CaffoldTaskDetailSummary extends LitElement { } } -customElements.define("caffold-task-detail-summary", CaffoldTaskDetailSummary); +declare global { + interface HTMLElementTagNameMap { + "caffold-task-detail-summary": CaffoldTaskDetailSummary; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts index 2e893c4e..2a48d589 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type PropertyValues } from "lit"; +import { customElement, property } from "lit/decorators.js"; import { ACTION_HINT_ACTION, type ActionHintPull, @@ -60,18 +61,22 @@ export function taskInfoSnapshot( }; } +await compactIconButton.register( + "caffold-task-detail-info", + "> .task-detail-info-button", +); + /** * The Task's details button in the summary: its status, and a popover with * the Task's thread, directory, and worktree above its actions. * * The popover closes when another Task takes this one's place. */ +@customElement("caffold-task-detail-info") export class CaffoldTaskDetailInfo extends LitElement { - static properties = { - snapshot: { attribute: false }, - }; + @property({ attribute: false }) + accessor snapshot: TaskInfoSnapshot = taskInfoSnapshot(); - declare snapshot: TaskInfoSnapshot; private readonly popoverId = `task-detail-info-${++taskInfoInstanceId}`; private readonly iconsReady = () => this.requestUpdate(); private readonly hints = new ActionHints(this); @@ -82,29 +87,28 @@ export class CaffoldTaskDetailInfo extends LitElement { constructor() { super(); - this.snapshot = taskInfoSnapshot(); this.addEventListener(KEYBOARD_SESSION_DISMISS_EVENT, (event) => this.handleDismiss(event), ); } - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener("caffold:icons-ready", this.iconsReady); void warmIcons(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener("caffold:icons-ready", this.iconsReady); this.deactivate(); } - protected willUpdate(changed: PropertyValues) { + protected override willUpdate(changed: PropertyValues) { if ( changed.has("snapshot") && taskThreadId(changed.get("snapshot")?.task) !== @@ -114,7 +118,7 @@ export class CaffoldTaskDetailInfo extends LitElement { } } - render() { + override render() { const task = this.snapshot.task; if (!task) { return nothing; @@ -255,14 +259,13 @@ export class CaffoldTaskDetailInfo extends LitElement { } } -await compactIconButton.register( - "caffold-task-detail-info", - "> .task-detail-info-button", -); - -customElements.define("caffold-task-detail-info", CaffoldTaskDetailInfo); - function worktreeRef(worktree: TaskWorktree | null) { const branch = `${worktree?.branch ?? ""}`.trim(); return branch || shortId(worktree?.headSha ?? ""); } + +declare global { + interface HTMLElementTagNameMap { + "caffold-task-detail-info": CaffoldTaskDetailInfo; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts index a9ee4be1..3c2262e2 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/components/summary/components/info/components/actions.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing } from "lit"; +import { customElement, property } from "lit/decorators.js"; import { ACTION_HINT_ACTION, type ActionHintPull, @@ -52,20 +53,14 @@ export interface TaskInfoActionIntent { * prompts settled, fork it, and archive it, each with why it cannot be done * now. */ +@customElement("caffold-task-detail-info-actions") export class CaffoldTaskDetailInfoActions extends LitElement { - static properties = { - snapshot: { attribute: false }, - }; + @property({ attribute: false }) + accessor snapshot: TaskInfoActionsSnapshot | null = null; - declare snapshot: TaskInfoActionsSnapshot | null; private readonly hints = new ActionHints(this); - constructor() { - super(); - this.snapshot = null; - } - - createRenderRoot() { + override createRenderRoot() { return this; } @@ -73,7 +68,7 @@ export class CaffoldTaskDetailInfoActions extends LitElement { return this.hints.scope(pull); } - render() { + override render() { const snapshot = this.snapshot; if (!snapshot?.task) { return nothing; @@ -182,11 +177,6 @@ export class CaffoldTaskDetailInfoActions extends LitElement { } } -customElements.define( - "caffold-task-detail-info-actions", - CaffoldTaskDetailInfoActions, -); - function actionErrorMessage(error: Error | null) { return error ? `${error.message ?? error}` : ""; } @@ -209,3 +199,9 @@ function forkDisabledReason(snapshot: TaskInfoActionsSnapshot) { } return ""; } + +declare global { + interface HTMLElementTagNameMap { + "caffold-task-detail-info-actions": CaffoldTaskDetailInfoActions; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts index ae2278f9..c5e183d2 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/(task)/layout.ts @@ -1,4 +1,5 @@ import { html, LitElement, nothing, type TemplateResult } from "lit"; +import { customElement } from "lit/decorators.js"; import { ACTION_HINT_ACTION, ActionHints, @@ -208,6 +209,7 @@ type UpdateKind = ConversationSnapshot["updateKind"]; * and its dialogs keep their state; each Task's Composer is kept apart and * moved into the pane's slot. */ +@customElement("caffold-task-detail") export class CaffoldTaskDetail extends LitElement { private reviewView: TaskDetailView = "conversation"; private taskDetail: TaskDetail | null = null; @@ -307,11 +309,11 @@ export class CaffoldTaskDetail extends LitElement { new DrawnRef(this); private readonly absenceNotice = new DrawnRef(this); - createRenderRoot() { + override createRenderRoot() { return this; } - connectedCallback() { + override connectedCallback() { super.connectedCallback(); window.addEventListener( CAFFOLD_ORIGIN_REACHABLE_EVENT, @@ -320,7 +322,7 @@ export class CaffoldTaskDetail extends LitElement { this.draw(); } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); window.removeEventListener( CAFFOLD_ORIGIN_REACHABLE_EVENT, @@ -1848,9 +1850,7 @@ export class CaffoldTaskDetail extends LitElement { if (composer) { return composer; } - composer = document.createElement( - "caffold-task-composer", - ) as CaffoldTaskComposer; + composer = document.createElement("caffold-task-composer"); composer.dataset.threadId = threadId; this.followUpComposers.set(threadId, composer); this.followUpComposerLastUsed.set( @@ -2112,7 +2112,7 @@ export class CaffoldTaskDetail extends LitElement { ); } - protected willUpdate() { + protected override willUpdate() { this.notice = this.regionNotice(); const task = this.taskDetail?.task; if (this.notice === null && task) { @@ -2120,13 +2120,13 @@ export class CaffoldTaskDetail extends LitElement { } } - protected updated() { + protected override updated() { if (this.dataset.taskDetailView !== this.reviewView) { this.dataset.taskDetailView = this.reviewView; } } - render() { + override render() { const notice = this.notice; const pane = this.pane; const conversation = @@ -2411,8 +2411,6 @@ export class CaffoldTaskDetail extends LitElement { } } -customElements.define("caffold-task-detail", CaffoldTaskDetail); - // Where each attached file goes when the prompt is sent: one folder for the // send, and a name within it no other file of the send has. function plannedUpload(attachments: ComposerAttachment[]): PlannedUpload { @@ -2557,3 +2555,9 @@ function taskDetailSurface( } return route.kind === "tasks" && route.review ? "review" : "conversation"; } + +declare global { + interface HTMLElementTagNameMap { + "caffold-task-detail": CaffoldTaskDetail; + } +} diff --git a/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts b/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts index edf16445..7adc58d9 100644 --- a/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts +++ b/frontend/pages/(task-workspace)/tasks/(detail)/components/git-menu.ts @@ -1,4 +1,5 @@ import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; import { ACTION_HINT_ACTION, type ActionHintPull, @@ -20,16 +21,20 @@ export interface GitMenuIntent { let taskDetailGitInstanceId = 0; +await compactIconButton.register( + "caffold-task-detail-git", + "> .task-git-button", +); + /** * The Detail header's Git button, and the popover of Git work it opens: * Compare and Log. */ +@customElement("caffold-task-detail-git") export class CaffoldTaskDetailGit extends LitElement { - static properties = { - available: { state: true }, - }; + @state() + private accessor available = false; - declare private available: boolean; private readonly popoverId = `task-git-actions-${++taskDetailGitInstanceId}`; private readonly hints = new ActionHints(this); private readonly keyboard = new KeyboardContexts(this); @@ -37,17 +42,16 @@ export class CaffoldTaskDetailGit extends LitElement { constructor() { super(); - this.available = false; this.addEventListener(KEYBOARD_SESSION_DISMISS_EVENT, (event) => this.handleDismiss(event), ); } - createRenderRoot() { + override createRenderRoot() { return this; } - disconnectedCallback() { + override disconnectedCallback() { super.disconnectedCallback(); this.deactivate(); } @@ -86,7 +90,7 @@ export class CaffoldTaskDetailGit extends LitElement { }); } - render() { + override render() { return html`