diff --git a/CONTEXT.md b/CONTEXT.md index f0a7e588..972a4656 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,7 +1,7 @@ --- status: truth topic: context -last-verified: 506e85b363a53511d14a44db22804a53331f2c03 +last-verified: 3fd7a24bb6e734f5c50a69d3a69144e21d52fae9 --- # BazaarPlusPlus Installer Context @@ -12,10 +12,10 @@ Current behavior truth lives under `docs/truth/` (topic-sliced, code-cited, hash ## System Map -- The app is a Tauri 2 desktop app with a React/Vite frontend and Rust backend. The package entry declares the app version and scripts in `package.json:2-21`; the Tauri app config sets the product name, frontend dev URL, build hooks, window size, and updater endpoint in `src-tauri/tauri.conf.json:3-35`. -- The native runtime registers single-instance, window-state, updater, process, dialog, opener, tray, selected-installation, installer-context, and stream-runtime state in `src-tauri/src/lib.rs:20-44`. -- Startup warms installer context on a blocking task while setup asks the stream runtime to ensure the HTTP service in `src-tauri/src/lib.rs:45-59`. Install detection calls the same `OnceLock` initializer, so the first completed command response cannot observe a bootstrap seed in `src-tauri/src/services/startup.rs:23-34` and `src-tauri/src/services/detect/mod.rs:27-39`. -- When the stream service is running, closing the main window hides it instead of quitting so OBS can keep using the local HTTP overlay in `src-tauri/src/lib.rs:61-75`. +- The app is a Tauri 2 desktop app with a React/Vite frontend and Rust backend. The package entry declares the app version and scripts in `package.json:2-36`; the Tauri app config sets the product name, frontend dev URL, build hooks, fixed default window, and updater endpoint in `src-tauri/tauri.conf.json:3-40`, while Windows supplies a fixed frameless override in `src-tauri/tauri.windows.conf.json:3-20`. +- The native runtime registers single-instance, window-state, updater, process, dialog, opener, tray, selected-installation, installer-context, and stream-runtime state in `src-tauri/src/lib.rs:19-46`. +- Startup warms installer context on a blocking task while setup asks the stream runtime to ensure the HTTP service in `src-tauri/src/lib.rs:80-92`. Install detection calls the same `OnceLock` initializer, so the first completed command response cannot observe a bootstrap seed in `src-tauri/src/services/startup.rs:23-34` and `src-tauri/src/services/detect/mod.rs:27-39`. +- When the stream service is running, closing the main window hides it instead of quitting so OBS can keep using the local HTTP overlay in `src-tauri/src/lib.rs:94-109`. ## Glossary @@ -25,7 +25,7 @@ Current behavior truth lives under `docs/truth/` (topic-sliced, code-cited, hash - **Trampoline mode** — macOS launch mode (forced on macOS 27+): the real Unity executable is renamed to `.orig` and a build-time stub is swapped in (`src-tauri/src/services/bepinex/trampoline.rs:362-445`). - **Launch-mode marker** — the `.bpp-launch-mode` file next to the game directory persisting the chosen mode (`src-tauri/src/services/bepinex/trampoline.rs:23-71`). - **InstallState** — the frontend/backend contract for the install page: paths, game/mod state, compat state, action gates, and semantic warning codes plus parameters (`src-tauri/src/services/install/types.rs:5-20`, `src-tauri/src/services/install/types.rs:73-85`). -- **Selected game installation** — the one session-scoped The Bazaar installation shared by Install, History, and Stream. Valid explicit paths update it; resolution then uses explicit, selected, startup-detected, and fallback priority. It is held only in managed memory and is recreated empty on app restart (`src-tauri/src/services/selected_game_installation.rs:14-115`, `src-tauri/src/lib.rs:38-41`). +- **Selected game installation** — the one session-scoped The Bazaar installation shared by Install, History, and Stream. Valid explicit paths update it; resolution then uses explicit, selected, startup-detected, and fallback priority. It is held only in managed memory and is recreated empty on app restart (`src-tauri/src/services/selected_game_installation.rs:14-115`, `src-tauri/src/lib.rs:40-43`). - **Reset (local data)** — the only flow that deletes the mod's `BazaarPlusPlusV4/` data directory; explicit, confirmed, refused while the game runs, and performed under exclusive stream-runtime maintenance (`src-tauri/src/services/bepinex/mod.rs:20-62`, `src-tauri/src/stream/runtime.rs:100-108`). Uninstall never touches it. - **History** — the facade around the Selected game installation's mod-owned SQLite database, including reads, detail, reveal, video deletion, and storage cleanup (`src-tauri/src/services/history.rs:48-288`); the database is created and primarily written by the mod. - **Semantic problem** — a command failure contract made of a stable code, string parameters, and an optional troubleshooting diagnostic (`src-tauri/src/problem.rs:3-42`). History publishes unavailable/read/action codes, including cleanup preview/execute operation parameters; Install publishes detection/action/game-running/partial-failure codes; Stream publishes service/window/crop capability codes at the native boundary and adds polling/clipboard/opener codes in its frontend workflow (`src-tauri/src/services/history.rs:74-188`, `src-tauri/src/services/history.rs:258-274`, `src-tauri/src/services/install/mod.rs:200-235`, `src-tauri/src/commands/stream.rs:12-110`, `src/features/stream/streamProblems.ts:7-105`). Presenters localize these codes without using the diagnostic as user copy, while the native adapter preserves the structured payload (`src/api/problems.ts:3-49`, `src/api/nativeCommands.ts:5-15`). diff --git a/debug.sh b/debug.sh new file mode 100755 index 00000000..89c06dbf --- /dev/null +++ b/debug.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +cd "$SCRIPT_DIR" + +if [ ! -x node_modules/.bin/tauri ]; then + echo "==> Installing npm dependencies" + npm install +fi + +exec npm run tauri dev -- "$@" diff --git a/docs/INDEX.md b/docs/INDEX.md index ad838312..e7bf4f65 100644 --- a/docs/INDEX.md +++ b/docs/INDEX.md @@ -26,6 +26,10 @@ Updater-recovery citation refresh: `2026-07-19` on `2bf15726776492127c3eca2162dd About-bootstrap citation refresh: `2026-07-19` on `506e85b363a53511d14a44db22804a53331f2c03` — the context glossary plus architecture, frontend, and verification topics were checked against the explicit bootstrap resource states, packaged fallback provenance, semantic recovery, and accessible About feedback. +UI redesign integration citation refresh: `2026-07-19` on `3fd7a24bb6e734f5c50a69d3a69144e21d52fae9` — `CONTEXT.md` plus the architecture and frontend truth topics were checked against the fixed platform window chrome, refined `bpp-*` surfaces, Install maintenance update placement, and preserved master state-machine integrations. + +UI redesign standards follow-up: `2026-07-19` on `2af743045b72631519a11dadea4b474c903519bd` — the architecture, Install/Reset, History/Stream, Updater/Release, and verification truth topics were rechecked after the portability fix and final standards review. + ## Current Manifest | Path | Topic | Status | Last verified | @@ -35,14 +39,14 @@ About-bootstrap citation refresh: `2026-07-19` on `506e85b363a53511d14a44db22804 | `README.md` | project entrypoint | current-entrypoint | 2026-07-11 | | `.trae/rules/git-commit-message.md` | ignored local rule | ignored-operational | n/a | | `docs/INDEX.md` | documentation manifest | manifest | 2026-07-19 | -| `CONTEXT.md` | entry map + glossary | truth | `506e85b363a53511d14a44db22804a53331f2c03` | -| `docs/truth/architecture.md` | architecture | truth | `506e85b363a53511d14a44db22804a53331f2c03` | -| `docs/truth/frontend.md` | frontend | truth | `506e85b363a53511d14a44db22804a53331f2c03` | -| `docs/truth/install-reset.md` | install-reset | truth | `0f609de844c0cbc48e7fb53396a90d5f32776c2b` | +| `CONTEXT.md` | entry map + glossary | truth | `3fd7a24bb6e734f5c50a69d3a69144e21d52fae9` | +| `docs/truth/architecture.md` | architecture | truth | `2af743045b72631519a11dadea4b474c903519bd` | +| `docs/truth/frontend.md` | frontend | truth | `3fd7a24bb6e734f5c50a69d3a69144e21d52fae9` | +| `docs/truth/install-reset.md` | install-reset | truth | `2af743045b72631519a11dadea4b474c903519bd` | | `docs/truth/launch-modes.md` | launch-modes | truth | `f23d786ab3bf1998f556f5fe05b6e47467a7ea48` | -| `docs/truth/history-stream.md` | history-stream | truth | `0f609de844c0cbc48e7fb53396a90d5f32776c2b` | -| `docs/truth/updater-release.md` | updater-release | truth | `2bf15726776492127c3eca2162dd26306c3ab310` | -| `docs/truth/verification.md` | verification | truth | `506e85b363a53511d14a44db22804a53331f2c03` | +| `docs/truth/history-stream.md` | history-stream | truth | `2af743045b72631519a11dadea4b474c903519bd` | +| `docs/truth/updater-release.md` | updater-release | truth | `2af743045b72631519a11dadea4b474c903519bd` | +| `docs/truth/verification.md` | verification | truth | `2af743045b72631519a11dadea4b474c903519bd` | | `docs/plans/manual-validation.md` | manual-validation | active-plan | `7500016b1c4adfc7b5d0206c7def0ceabae514d5` | | `docs/agents/issue-tracker.md` | agent skills: issue tracker | operational | 2026-07-11 | | `docs/agents/triage-labels.md` | agent skills: triage labels | operational | 2026-07-11 | diff --git a/docs/truth/architecture.md b/docs/truth/architecture.md index 13372f8f..7630535a 100644 --- a/docs/truth/architecture.md +++ b/docs/truth/architecture.md @@ -1,29 +1,29 @@ --- status: truth topic: architecture -last-verified: 506e85b363a53511d14a44db22804a53331f2c03 +last-verified: 2af743045b72631519a11dadea4b474c903519bd --- # Architecture ## App Shape -- `package.json` defines the desktop app package as `bppinstaller` and exposes the development, build, test, type-check, and Tauri scripts in `package.json:2-21`; its `version` field is the single version source (see version sync below). -- The frontend is built by Vite from `src/`; the Tauri config invokes `npm run dev` for development and `npm run prebuild-check && npm run build` before bundle creation in `src-tauri/tauri.conf.json:6-11`. -- The main desktop shell is Rust/Tauri. It registers native plugins and shared state in `src-tauri/src/lib.rs:20-44`, then uses the canonical Specta builder's invoke handler and runs the generated Tauri context in `src-tauri/src/lib.rs:77-79`. -- The main window is configured as a 1000 x 720 window with 900 x 640 minimum dimensions in `src-tauri/tauri.conf.json:13-21`. +- `package.json` defines the desktop app package as `bppinstaller` and exposes the development, build, test, type-check, verification, and Tauri scripts in `package.json:2-36`; its `version` field is the single version source (see version sync below). +- The frontend is built by Vite from `src/`; the Tauri config invokes `npm run dev` for development and `npm run build` before bundle creation in `src-tauri/tauri.conf.json:6-11`. That build script generates bindings, runs the prebuild source/resource checks and TypeScript check, then builds the frontend in `package.json:19-24`. +- The main desktop shell is Rust/Tauri. It registers native plugins and shared state in `src-tauri/src/lib.rs:19-46`, then uses the canonical Specta builder's invoke handler and runs the generated Tauri context in `src-tauri/src/lib.rs:110-112`. +- The default main window is fixed at 1020 x 680 and cannot resize or maximize in `src-tauri/tauri.conf.json:14-26`. Windows overrides it with a fixed 972 x 612 frameless window that starts hidden in `src-tauri/tauri.windows.conf.json:3-20`; setup shows it and configures the native frame in `src-tauri/src/lib.rs:47-78`. ## Native Runtime -- Tauri startup builds the tray, warms `InstallerContextState`, and asks `StreamRuntime` to ensure the stream service in `src-tauri/src/lib.rs:45-59`. Install detection waits on the same `OnceLock` initializer in `src-tauri/src/services/startup.rs:23-34` and `src-tauri/src/services/detect/mod.rs:27-39`, so no frontend event/refetch race is required. -- Close behavior is service-aware: while the stream runtime reports `running`, the main window close request is prevented and the window is hidden in `src-tauri/src/lib.rs:61-75`. +- Tauri startup builds the tray, warms `InstallerContextState`, and asks `StreamRuntime` to ensure the stream service in `src-tauri/src/lib.rs:80-92`. Install detection waits on the same `OnceLock` initializer in `src-tauri/src/services/startup.rs:23-34` and `src-tauri/src/services/detect/mod.rs:27-39`, so no frontend event/refetch race is required. +- Close behavior is service-aware: while the stream runtime reports `running`, the main window close request is prevented and the window is hidden in `src-tauri/src/lib.rs:94-109`. - The default capability grants updater check/download/install, process restart, `steam://*` opening, and dialog permissions in `src-tauri/capabilities/default.json:6-20`. ## Feature Boundaries - Install state is produced by Rust detection and serialized through `InstallState`; the contract includes selected paths, game/mod state, macOS compatibility state, action gates, resettable-data and BepInEx-folder status in `src-tauri/src/services/install/types.rs:5-20`, while warnings use typed codes plus parameters in `src-tauri/src/services/install/types.rs:73-85`. - The complete install operation owns payload and launch-mode fact gathering, private planning, ordered production effects, first-error propagation, and a final state refresh in `src-tauri/src/services/install/operation.rs:17-130`; the Tauri command only constructs the request and invokes that operation in `src-tauri/src/commands/install.rs:44-59`. Reset, uninstall, and Steam-only launch remain in the install service facade. -- All Install command failures share `SemanticProblem`; detection and action boundaries classify stable codes and operation/recovery parameters in `src-tauri/src/commands/install.rs:15-99` and `src-tauri/src/services/install/mod.rs:200-235`. The frontend keeps Install page state and its sole primary-action derivation framework-independent in `src/features/install/installPageState.ts:5-132`. +- All Install command failures share `SemanticProblem`; detection and action boundaries classify stable codes and operation/recovery parameters in `src-tauri/src/commands/install.rs:15-99` and `src-tauri/src/services/install/mod.rs:200-235`. The frontend keeps initial, blocking, ready, and preserved-refresh state framework-independent in `src/features/install/installPageState.ts:5-73`; the Install route derives install/reinstall/launch mode from native mod state in `src/pages/Install.tsx:42-47`, and the primary button gates that mode with native action availability in `src/features/install/PrimaryInstallActionButton.tsx:19-44`. - The History facade resolves Selected game installation and privately owns storage derivation, reads, reveals, deletes, and cleanup dispatch in `src-tauri/src/services/history.rs:48-288`; commands expose ids plus domain cleanup scope/preset without raw paths or cutoffs in `src-tauri/src/commands/history.rs:7-79`. Reads use SQLite read-only connections by default, while mutation uses separate write connections in `src-tauri/src/history/queries.rs:29-54`. - History list/detail/reveal/delete/cleanup commands use `SemanticProblem`; the shared Rust DTO fixes code/parameter/diagnostic shape, the detail command models not-found as a successful `Option`, and the facade classifies unavailable selection, failed reads, and failed actions before the command boundary in `src-tauri/src/problem.rs:3-38`, `src-tauri/src/services/history.rs:74-188`, `src-tauri/src/services/history.rs:258-274`, and `src-tauri/src/commands/history.rs:7-79`. - History internals default to private modules; only cleanup algorithms and mapper/screenshots test seams are crate-visible, and the facade receives a narrowed repository surface in `src-tauri/src/history/mod.rs:1-13`. @@ -32,16 +32,16 @@ last-verified: 506e85b363a53511d14a44db22804a53331f2c03 - The frontend Stream workflow depends inward on command, scheduler, clipboard, and opener ports and exposes capability-oriented semantic snapshots in `src/features/stream/streamWorkflow.ts:25-122`. It owns response ordering and capability gates in `src/features/stream/streamWorkflow.ts:185-320` and `src/features/stream/streamWorkflow.ts:640-733`; the React hook provides outer adapters and only subscribes, starts, and disposes one locale-independent workflow in `src/features/stream/useStreamPage.ts:9-42`. - Frontend destructive confirmation state is centralized in a framework-neutral external-store controller rather than page-local booleans. It owns target retention, single-flight execution, success-only closure, and semantic failure retention in `src/features/shared/confirmedOperation.ts:3-94`; React only memoizes and subscribes to the controller at `src/features/shared/confirmedOperation.ts:82-94`. - Frontend modal scheduling is a separate framework-neutral external store. It owns priority/FIFO ordering, active-source retention, updates, and safe unregistration in `src/features/shared/modalCoordinator.ts:1-113`; the React provider only registers sources, renders the active one, carries dismissal policy into `Dialog`, and restores focus after the queue drains in `src/components/ui/ModalCoordinator.tsx:25-126` and `src/components/ui/Dialog.tsx:13-55`. -- The updater is a framework-neutral state machine over injected Tauri check/relaunch effects and a retained native update handle. Its discriminated snapshot prevents phase/progress/problem contradictions, classifies check/download/install/restart failures semantically, and publishes guarded transitions in `src/features/about/updater.ts:29-130` and `src/features/about/updater.ts:132-335`; one pure presentation contract derives both shell status and coordinated-modal policy/actions in `src/features/about/updaterPresentation.ts:22-129`. +- The updater is a framework-neutral state machine over injected Tauri check/relaunch effects and a retained native update handle. Its discriminated snapshot prevents phase/progress/problem contradictions, classifies check/download/install/restart failures semantically, and publishes guarded transitions in `src/features/about/updater.ts:29-130` and `src/features/about/updater.ts:132-335`; one pure presentation contract derives updater phase metadata and coordinated-modal policy/actions in `src/features/about/updaterPresentation.ts:22-129`. - About bootstrap loading is a framework-neutral state machine over an injected loader and optional packaged fallback. Its discriminated snapshot separates initial loading, authoritative native data, fallback data, and blocking failure; retry remains single-flight and preserves usable fallback until native recovery in `src/features/about/appBootstrap.ts:18-66` and `src/features/about/appBootstrap.ts:90-178`. The runtime adapter labels Browser Preview as fallback rather than native authority in `src/features/about/aboutApi.ts:5-10`. ## Build And Generated Artifacts -- `npm run check` generates TypeScript bindings and runs `tsc --noEmit`; `npm run test` combines generated bindings, Rust tests, and Vitest in `package.json:13-20`. -- The Tauri IPC schema is authored in Rust command signatures and collected once by the Specta builder in `src-tauri/src/commands/registry.rs:3-36`; the same builder supplies the production invoke handler in `src-tauri/src/lib.rs:18-18` and `src-tauri/src/lib.rs:77-77` and exports typed command functions plus DTOs in `src-tauri/src/commands/registry.rs:38-49`. +- `npm run check` generates TypeScript bindings and runs `tsc --noEmit`; `npm run test` generates bindings with their Rust export tests, checks binding freshness, and runs Vitest, while `npm run test:rust` is the full locked Rust test entry point in `package.json:26-35`. +- The Tauri IPC schema is authored in Rust command signatures and collected once by the Specta builder in `src-tauri/src/commands/registry.rs:3-36`; the same builder supplies the production invoke handler in `src-tauri/src/lib.rs:20-20` and `src-tauri/src/lib.rs:110-110` and exports typed command functions plus DTOs in `src-tauri/src/commands/registry.rs:38-49`. - Specta is the only direct TypeScript binding stack: the three packages are pinned in `src-tauri/Cargo.toml:39-41`, with no `ts-rs` dependency or fallback generator. - `npm run generate:bindings` exports to a temporary directory, validates and normalizes `commands.ts`, and atomically replaces `src/types/generated/`; replacement restores a captured backup and never removes the prior target when the backup rename itself fails in `scripts/generate-bindings.mjs:20-89` and `scripts/generate-bindings.mjs:91-123`. - Version sync treats `package.json` as the source, then writes package-lock.json (both root `version` fields), Tauri config, Cargo.toml, and Cargo.lock versions in `scripts/version-sync.mjs:195-206`; `npm run prebuild-check` fails on any misalignment via `collectVersionSnapshot` in `scripts/prebuild-check.mjs:329-331`. - `npm run prebuild-check` verifies generated bindings, version alignment, platform ZIP payloads, and the macOS trampoline stub when applicable in `scripts/prebuild-check.mjs:326-346`. -- Tauri updater artifacts are enabled in the Tauri bundle config in `src-tauri/tauri.conf.json:27-30`. +- Tauri updater artifacts are enabled in the Tauri bundle config in `src-tauri/tauri.conf.json:32-35`. - The Rust toolchain is pinned to 1.97.0 (minimal profile with `clippy` and `rustfmt`) via `rust-toolchain.toml:1-4`; rustup selects it automatically for all `cargo`/Tauri builds. diff --git a/docs/truth/frontend.md b/docs/truth/frontend.md index 92cf72fc..2b7bcdb6 100644 --- a/docs/truth/frontend.md +++ b/docs/truth/frontend.md @@ -1,26 +1,27 @@ --- status: truth topic: frontend -last-verified: 506e85b363a53511d14a44db22804a53331f2c03 +last-verified: 3fd7a24bb6e734f5c50a69d3a69144e21d52fae9 --- # Frontend ## Shell -- `GlobalShell` wraps the app in bootstrap, updater, and modal-coordinator providers before rendering the shell header, nav rail, and route outlet in `src/layouts/GlobalShell.tsx:18-27` and `src/layouts/GlobalShell.tsx:66-107`. -- Bilibili and Support are controlled disclosures: Escape closes the open menu and restores its trigger, while outside pointer-down closes it in `src/layouts/GlobalShell.tsx:39-64`; their triggers expose expanded/control/menu relationships and their entries are menu items in `src/layouts/ShellHeader.tsx:457-565` and `src/layouts/ShellHeader.tsx:590-679`. -- Payment and update dialogs register as global modal sources. Payment is informational; the updater's pure presentation contract supplies its open state, priority, dismissal policy, and modal action from the same snapshot used by the header in `src/layouts/GlobalShell.tsx:30-38`, `src/layouts/GlobalShell.tsx:110-128`, and `src/layouts/ShellHeader.tsx:183-239`. +- `GlobalShell` wraps the app in bootstrap, updater, and modal-coordinator providers before rendering the shell header, navigation rail, and route outlet in `src/layouts/GlobalShell.tsx:18-27` and `src/layouts/GlobalShell.tsx:67-99`. +- Bilibili and Support are controlled disclosures: Escape closes the open menu and restores its trigger, while outside pointer-down closes it in `src/layouts/GlobalShell.tsx:40-65`; their triggers expose expanded/control/menu relationships and their entries are menu items in `src/layouts/ShellHeader.tsx:397-501` and `src/layouts/ShellHeader.tsx:516-613`. +- Payment and update dialogs register as global modal sources in `src/layouts/GlobalShell.tsx:101-119`. Payment is informational; the updater presentation contract supplies the update modal's open state, priority, dismissal policy, and action, while manual update checking is exposed as an Install maintenance action in `src/pages/Install.tsx:137-143` and `src/features/install/InstallActionsPanel.tsx:54-61`. ## Native-Feel Rules -- Global app chrome disables page-style selection by default, keeps text controls selectable, and keeps focus-visible outlines in `src/styles/index.css:42-82`. -- `.selectable` and `.user-content` opt content back into text selection in `src/styles/index.css:84-89`. -- Reduced motion is honored through `prefers-reduced-motion` in `src/styles/index.css:91-99`. -- App modals use the native `` wrapper and top-layer dialog styling. `Dialog` consumes the active source's dismissal policy so Escape and backdrop cannot dismiss blocked work in `src/components/ui/Dialog.tsx:13-55`; dialog CSS is in `src/styles/index.css:101-128`. +- Shared `bpp-*` shell, panel, button, and page-surface rules define the refined desktop presentation in `src/styles/index.css:89-611`, with Install-specific surfaces in `src/styles/index.css:1169-1460`. +- Global app chrome disables page-style selection by default, keeps text controls selectable, and keeps focus-visible outlines in `src/styles/index.css:1937-1959`. +- `.selectable` and `.user-content` opt content back into text selection in `src/styles/index.css:1962-1967`. +- Reduced motion is honored through `prefers-reduced-motion` in `src/styles/index.css:1969-1979`. +- App modals use the native `` wrapper and top-layer dialog styling. `Dialog` consumes the active source's dismissal policy so Escape and backdrop cannot dismiss blocked work in `src/components/ui/Dialog.tsx:13-60`; dialog CSS is in `src/styles/index.css:1981-2008`. - The coordinator displays exactly one registered dialog, prioritizes critical work over confirmations, system messages, and informational dialogs, and preserves FIFO within equal priorities in `src/features/shared/modalCoordinator.ts:36-78`. Unmounting a queued route source is safe; after the queue drains, focus returns to the connected trigger or the current page heading/main fallback in `src/features/shared/modalCoordinator.ts:80-125`, `src/components/ui/ModalCoordinator.tsx:40-60`, and `src/components/ui/PageHeader.tsx:29-38`. -- Install, reset, cleanup, and video-delete confirmations share the `Dialog`-composing `ConfirmDialog`. It requires an explicit active dismissal policy for Escape, backdrop, close, and secondary actions; blocked work removes the secondary cancel affordance, disables close, and says that the operation cannot be cancelled in `src/components/ui/ConfirmDialog.tsx:14-62` and `src/components/ui/ConfirmDialog.tsx:98-242`. Their modal sources begin as confirmations and adopt critical blocked semantics for native work because none exposes a cancellation contract (`src/pages/Install.tsx:131-197`, `src/features/history/StorageCleanupCard.tsx:125-185`, `src/pages/RunDetail.tsx:247-292`). -- The current Tauri security config has `csp: null` in `src-tauri/tauri.conf.json:23-25`; treat any CSP hardening claim as future work until code changes. +- Install, reset, cleanup, and video-delete confirmations share the `Dialog`-composing `ConfirmDialog`. It requires an explicit active dismissal policy for Escape, backdrop, close, and secondary actions; blocked work removes the secondary cancel affordance, disables close, and says that the operation cannot be cancelled in `src/components/ui/ConfirmDialog.tsx:14-62` and `src/components/ui/ConfirmDialog.tsx:99-254`. Their modal sources begin as confirmations and adopt critical blocked semantics for native work because none exposes a cancellation contract (`src/pages/Install.tsx:147-213`, `src/features/history/StorageCleanupCard.tsx:139-199`, `src/pages/RunDetail.tsx:247-292`). +- The current Tauri security config has `csp: null` in `src-tauri/tauri.conf.json:28-30`; treat any CSP hardening claim as future work until code changes. ## Runtime Seam @@ -35,24 +36,24 @@ last-verified: 506e85b363a53511d14a44db22804a53331f2c03 ## Current Product Surfaces -- Install renders mutually exclusive initial-detection, blocking-failure, and completed-state branches; a refresh failure keeps the completed status and actions visible behind a localized retry banner in `src/pages/Install.tsx:63-105` and `src/features/install/installPageState.ts:34-73`. -- Install facts currently show only BazaarPlusPlus, not the broader fact list from the historical design spec, in `src/features/install/InstallActionsPanel.tsx:58-65`. -- Install renders exactly one primary action. Its choose/install/repair/launch mode, gate, and loading state are derived from one view model based on path validity, install/version state, compatibility consistency, and the active operation in `src/features/install/installPageState.ts:75-132` and `src/features/install/InstallActionsPanel.tsx:141-222`. +- Install renders mutually exclusive initial-detection, blocking-failure, and completed-state branches; a refresh failure keeps the completed status and actions visible behind a localized retry banner in `src/pages/Install.tsx:100-145` and `src/features/install/installPageState.ts:34-73`. +- Install's primary surface is a status hero with one install/reinstall/launch action, followed by selected-directory and application-version cards in `src/features/install/InstallStatusPanel.tsx:51-121`. The four maintenance actions are reset local data, reset BepInEx, check for updates, and uninstall in `src/features/install/InstallActionsPanel.tsx:28-71`. +- Install renders exactly one primary action. Its mode comes from installed/version-match state in `src/pages/Install.tsx:42-47`; busy state and native `can_install`, `can_reinstall`, or `can_launch` gates determine availability in `src/features/install/PrimaryInstallActionButton.tsx:19-44`. - Install warnings and failures are presented from stable semantic codes in bilingual frontend copy; native diagnostics are kept in the diagnostic disclosure rather than used as the message in `src/features/install/installProblems.ts:23-107` and `src/features/install/InstallProblemBanner.tsx:1-32`. -- The reset-local-data button is disabled unless backend action gates allow reset data, and its label switches to a no-data message when the game path is valid but no resettable data exists in `src/features/install/InstallActionsPanel.tsx:103-115`. Both reset confirmations retain the selected game path as their target; failure keeps the modal, semantic problem, acknowledgement, and partial-failure paths available for retry or safe close, while success alone closes and refreshes state in `src/pages/Install.tsx:54-85` and `src/pages/Install.tsx:151-197`. -- History renders loading, blocking failure, and the two successful list states as mutually exclusive branches; refresh failures remain inside the ready branch and keep prior data in `src/pages/History.tsx:42-97` and `src/features/shared/pageState.ts:43-54`. -- History summary cards are Runs, Videos, and Win Rate in `src/pages/History.tsx:50-64`. -- History rows link to details, show lazy-decoded preview images with an error fallback, and display hero, locale-formatted date, result, progress, rank, and rating in `src/pages/History.tsx:125-230` and `src/features/history/format.ts:4-27`. +- The reset-local-data button is disabled unless backend action gates allow reset data, and its label switches to a no-data message when the game path is valid but no resettable data exists in `src/features/install/InstallActionsPanel.tsx:34-45`. Both reset confirmations retain the selected game path as their target; failure keeps the modal, semantic problem, acknowledgement, and partial-failure paths available for retry or safe close, while success alone closes and refreshes state in `src/pages/Install.tsx:67-98` and `src/pages/Install.tsx:167-213`. +- History renders loading, blocking failure, and the two successful list states as mutually exclusive branches; refresh failures remain inside the ready branch and keep prior data in `src/pages/History.tsx:43-105` and `src/features/shared/pageState.ts:43-54`. +- History summary cards are Runs, Videos, and Win Rate in `src/pages/History.tsx:52-72`. +- History rows link to details, show lazy-decoded preview images with an error fallback, and display hero, locale-formatted date, result, progress, rank, and rating in `src/pages/History.tsx:130-230` and `src/features/history/format.ts:4-27`. - History list loading calls `listHistoryRuns` independently from status-only Stream preview discovery; stopped or failed Stream status produces a thumbnail-only problem and never rejects the list request in `src/features/history/useHistoryPage.ts:37-64` and `src/features/history/historyPreview.ts:14-45`. - Run detail renders explicit initial-loading, not-found, blocking-failure, and ready branches, preserving ready content behind a localized refresh-failure banner in `src/pages/RunDetail.tsx:66-130`. Screenshot, video, delete, and refresh controls share one action gate; video deletion keeps its battle/video target visible, blocks dismissal while running, retains localized semantic failure for retry/close, and closes only after the returned detail replaces page data in `src/features/history/useRunDetailPage.ts:84-153` and `src/pages/RunDetail.tsx:247-292`. -- Storage cleanup submits only generated `StorageCleanupScope` plus `StorageCleanupPreset`. Preview and execute are separately single-flight; the selected scope, preset, counts, and consequence remain in the confirmed-operation target through running/failure, and success refreshes History before publishing the outcome in `src/features/history/useStorageCleanup.ts:23-78` and `src/features/history/StorageCleanupCard.tsx:125-185`. Cleanup failures are localized from semantic problem codes/operation parameters while diagnostics remain separate in `src/features/history/storageCleanupProblems.ts:9-52`. -- Stream renders capability-localized service, polling, display-window, crop, and one-off action problems beside the controls that can recover them; diagnostics remain in the optional disclosure rather than becoming user copy in `src/pages/Stream.tsx:34-137`, `src/pages/Stream.tsx:182-319`, and `src/pages/Stream.tsx:324-358`. +- Storage cleanup submits only generated `StorageCleanupScope` plus `StorageCleanupPreset`. Preview and execute are separately single-flight; the selected scope, preset, counts, and consequence remain in the confirmed-operation target through running/failure, and success refreshes History before publishing the outcome in `src/features/history/useStorageCleanup.ts:23-78` and `src/features/history/StorageCleanupCard.tsx:139-199`. Cleanup failures are localized from semantic problem codes/operation parameters while diagnostics remain separate in `src/features/history/storageCleanupProblems.ts:9-52`. +- Stream renders capability-localized service, polling, display-window, crop, and one-off action problems beside the controls that can recover them; diagnostics remain in the optional disclosure rather than becoming user copy in `src/pages/Stream.tsx:34-176`, `src/pages/Stream.tsx:178-318`, and `src/pages/Stream.tsx:324-358`. - Stream status, database, window, and notice copy is derived from the current translator at render time. Stale running/stopped values have distinct presentation and are not presented as authoritative in `src/features/stream/streamPresentation.ts:20-80`. -- About renders source and unavailable-field labels beside selectable version values. Initial/fallback/blocking feedback uses live regions, native retry controls, bilingual semantic copy, and optional diagnostics; Browser Preview is explicitly labeled as packaged fallback rather than authoritative native data in `src/pages/About.tsx:38-107` and `src/pages/About.tsx:171-264`. +- About renders source and unavailable-field labels beside selectable version values. Initial/fallback/blocking feedback uses live regions, native retry controls, bilingual semantic copy, and optional diagnostics; Browser Preview is explicitly labeled as packaged fallback rather than authoritative native data in `src/pages/About.tsx:69-135`, `src/pages/About.tsx:189-249`, and `src/pages/About.tsx:251-274`. ## Update Modal - The updater snapshot explicitly separates `checking`, `available`, `downloading`, `installing`, `ready-to-restart`, `restarting`, and `failed`; empty phases cannot retain update data, only downloading can retain progress, and failures retain a structured problem in `src/features/about/updater.ts:61-120`. -- Header label/icon/busy/error tone and modal title/action/priority/dismissal are derived together for every phase. Available, ready, and recovery decisions are system/dismissible; downloading, installing, and restarting are critical/blocked and have no fake cancel action in `src/features/about/updaterPresentation.ts:39-118` and `src/layouts/ShellUpdateModal.tsx:19-149`. +- The updater presentation contract derives phase metadata plus modal title/action/priority/dismissal for every phase. Available, ready, and recovery decisions are system/dismissible; downloading, installing, and restarting are critical/blocked and have no fake cancel action in `src/features/about/updaterPresentation.ts:39-118` and `src/layouts/ShellUpdateModal.tsx:19-149`. The manual check entry point is the Install maintenance action in `src/features/install/InstallActionsPanel.tsx:54-61`. - Progress exposes an accessible label, minimum, known maximum/value, value text, and polite status announcement; unknown totals stay indeterminate rather than inventing a maximum in `src/layouts/ShellUpdateModal.tsx:155-207`. - Check, download, install, and restart failures use stable semantic codes with localized retry guidance and optional diagnostic disclosure. Restart failure preserves the installed version, explains reopening the app from Applications, and offers another restart attempt in `src/features/about/updaterProblems.ts:9-63`, `src/features/about/updater.ts:305-319`, and `src/layouts/ShellUpdateModal.tsx:112-147`. diff --git a/docs/truth/history-stream.md b/docs/truth/history-stream.md index dbb8da4f..7c4b1884 100644 --- a/docs/truth/history-stream.md +++ b/docs/truth/history-stream.md @@ -1,7 +1,7 @@ --- status: truth topic: history-stream -last-verified: 0f609de844c0cbc48e7fb53396a90d5f32776c2b +last-verified: 2af743045b72631519a11dadea4b474c903519bd --- # History And Stream @@ -20,14 +20,14 @@ last-verified: 0f609de844c0cbc48e7fb53396a90d5f32776c2b ## History UI -- History independently starts list loading and status-only preview discovery; it never ensures or starts a Stream session in `src/features/history/useHistoryPage.ts:37-68`. Stopped/failed Stream status becomes a non-blocking preview problem, and image load failures fall back inside the row in `src/features/history/historyPreview.ts:14-45` and `src/pages/History.tsx:194-230`. -- The History page state union makes initial loading, blocking failure, ready-empty, and ready-content exclusive, while refreshing and refresh failure retain successful data in `src/features/shared/pageState.ts:1-60`, `src/features/history/historyPageState.ts:10-42`, and `src/pages/History.tsx:42-97`. -- The History page renders Runs, Videos, and Win Rate summary cards in `src/pages/History.tsx:50-64`. -- History rows include optional preview images and link to `/history/:run_id` details in `src/pages/History.tsx:125-230`. -- Run detail makes initial loading, not-found, blocking failure, and ready content exclusive; refresh failure retains the last successful detail and stale completions are ignored in `src/features/history/runDetailPageState.ts:5-90` and `src/pages/RunDetail.tsx:66-130`. -- Detail refresh, screenshot reveal, video reveal, and video deletion share one visible single-flight gate. Action failures stay scoped to the screenshot or affected battle and clear when that target retries in `src/features/history/runDetailPageState.ts:93-167`, `src/features/history/useRunDetailPage.ts:46-165`, `src/pages/RunDetail.tsx:160-181`, and `src/pages/RunDetail.tsx:306-432`. -- Run detail formats dates, replay durations, and replay sizes through shared locale-aware helpers in `src/features/history/format.ts:4-61` and `src/features/history/format.ts:113-150`, with replay metadata rendered beside each video action in `src/pages/RunDetail.tsx:365-409`. -- The History page renders the storage cleanup card after the summary cards only in a successful ready state in `src/pages/History.tsx:50-67`; the card offers separate end-of-run screenshot and run-data rows, then confirms the exact scope, preset, counts, and consequence. Execute failure keeps that target and localized semantic problem in place for retry or safe close; success alone closes and refreshes History in `src/features/history/StorageCleanupCard.tsx:84-171` and `src/features/history/useStorageCleanup.ts:23-78`. +- History independently starts list loading and status-only preview discovery; it never ensures or starts a Stream session in `src/features/history/useHistoryPage.ts:37-68`. Stopped/failed Stream status becomes a non-blocking preview problem, and image load failures fall back inside the row in `src/features/history/historyPreview.ts:14-45` and `src/pages/History.tsx:196-230`. +- The History page state union makes initial loading, blocking failure, ready-empty, and ready-content exclusive, while refreshing and refresh failure retain successful data in `src/features/shared/pageState.ts:1-60`, `src/features/history/historyPageState.ts:10-42`, and `src/pages/History.tsx:43-105`. +- The History page renders Runs, Videos, and Win Rate summary cards in `src/pages/History.tsx:52-72`. +- History rows include optional preview images and link to `/history/:run_id` details in `src/pages/History.tsx:130-230`. +- Run detail makes initial loading, not-found, blocking failure, and ready content exclusive; refresh failure retains the last successful detail and stale completions are ignored in `src/features/history/runDetailPageState.ts:5-90` and `src/pages/RunDetail.tsx:86-130`. +- Detail refresh, screenshot reveal, video reveal, and video deletion share one visible single-flight gate. Action failures stay scoped to the screenshot or affected battle and clear when that target retries in `src/features/history/runDetailPageState.ts:93-167`, `src/features/history/useRunDetailPage.ts:46-165`, `src/pages/RunDetail.tsx:155-177`, and `src/pages/RunDetail.tsx:320-445`. +- Run detail formats dates, replay durations, and replay sizes through shared locale-aware helpers in `src/features/history/format.ts:4-61` and `src/features/history/format.ts:113-150`, with replay metadata rendered beside each video action in `src/pages/RunDetail.tsx:379-423`. +- The History page renders the storage cleanup card after the summary cards only in a successful ready state in `src/pages/History.tsx:51-75`; the card offers separate end-of-run screenshot and run-data rows, then confirms the exact scope, preset, counts, and consequence. Execute failure keeps that target and localized semantic problem in place for retry or safe close; success alone closes and refreshes History in `src/features/history/StorageCleanupCard.tsx:87-199` and `src/features/history/useStorageCleanup.ts:23-78`. ## Storage Cleanup @@ -46,7 +46,7 @@ last-verified: 0f609de844c0cbc48e7fb53396a90d5f32776c2b - `StreamRuntime` is the single lifecycle owner. Its async lifecycle mutex serializes ensure, restart, stop, window changes, and exclusive maintenance; task handles and captured installation paths remain private in `src-tauri/src/stream/runtime.rs:43-108` and `src-tauri/src/stream/runtime.rs:188-280`. - Ensure and restart resolve one Selected game installation snapshot while holding the lifecycle gate; window changes reuse the captured record path instead of re-resolving a possibly changed selection in `src-tauri/src/stream/runtime.rs:66-98`, `src-tauri/src/stream/runtime.rs:203-220`, and `src-tauri/src/stream/runtime.rs:300-357`. - The production server adapter constructs the overlay repository and settings store, reports database/window status, and serves the router with graceful shutdown in `src-tauri/src/stream/server.rs:19-102`; stop sends shutdown and awaits the task before publishing idle state in `src-tauri/src/stream/runtime.rs:188-201`. -- Startup, stream commands, tray stop/quit, and window-close behavior use the runtime rather than composing server mutations directly in `src-tauri/src/lib.rs:41-75`, `src-tauri/src/commands/stream.rs:12-92`, and `src-tauri/src/tray.rs:32-48`. +- Startup, stream commands, tray stop/quit, and window-close behavior use the runtime rather than composing server mutations directly in `src-tauri/src/lib.rs:80-109`, `src-tauri/src/commands/stream.rs:12-92`, and `src-tauri/src/tray.rs:32-48`. - Stream commands return `SemanticProblem`: service, display-window, and crop-setting failures keep stable capability and operation codes while native details remain optional diagnostics in `src-tauri/src/commands/stream.rs:12-110` and `src-tauri/src/problem.rs:3-42`. ## Stream UI Capabilities diff --git a/docs/truth/install-reset.md b/docs/truth/install-reset.md index 5fda02c0..f07308b7 100644 --- a/docs/truth/install-reset.md +++ b/docs/truth/install-reset.md @@ -1,7 +1,7 @@ --- status: truth topic: install-reset -last-verified: 0f609de844c0cbc48e7fb53396a90d5f32776c2b +last-verified: 2af743045b72631519a11dadea4b474c903519bd --- # Install And Reset @@ -11,8 +11,8 @@ last-verified: 0f609de844c0cbc48e7fb53396a90d5f32776c2b - `InstallState` is the frontend/backend contract for the install page. It includes selected paths, Steam launch-option support, game/mod state, compatibility state, action gates, resettable-data and BepInEx-folder status, and warnings in `src-tauri/src/services/install/types.rs:5-20`. Warnings cross the boundary only as `InstallWarningCode` plus string parameters, never backend-authored display copy, in `src-tauri/src/services/install/types.rs:73-85` and `src-tauri/src/services/install/mod.rs:172-198`. - Reset returns typed `ResetBppDataResult` and `ResetBepinexResult` values containing the refreshed state and removal outcome in `src-tauri/src/services/install/types.rs:22-32`. - Backend state derives game/mod/compat facts, resettable-data status, action gates, and warnings from one completed detection snapshot in `src-tauri/src/services/install/mod.rs:107-170`. -- `get_install_state` enters the detection facade in `src-tauri/src/services/install/mod.rs:34-49`, which waits on the process-wide `InstallerContextState` initializer in `src-tauri/src/services/detect/mod.rs:27-39`. That `OnceLock` is shared with startup warm-up in `src-tauri/src/services/startup.rs:23-34` and `src-tauri/src/lib.rs:45-52`, so the first successful frontend response is a real native detection rather than an all-false bootstrap seed. -- The Install page reducer has explicit initial-loading, blocking-failure, and ready states; refresh failures preserve the last valid `InstallState`, and request ids ignore stale completions in `src/features/install/installPageState.ts:5-73`. Path validity, install/version state, compatibility consistency, and backend gates derive exactly one primary choose/install/repair/launch action plus its disabled/running state in `src/features/install/installPageState.ts:75-132`. +- `get_install_state` enters the detection facade in `src-tauri/src/services/install/mod.rs:34-49`, which waits on the process-wide `InstallerContextState` initializer in `src-tauri/src/services/detect/mod.rs:27-39`. That `OnceLock` is shared with startup warm-up in `src-tauri/src/services/startup.rs:23-34` and `src-tauri/src/lib.rs:80-86`, so the first successful frontend response is a real native detection rather than an all-false bootstrap seed. +- The Install page reducer has explicit initial-loading, blocking-failure, and ready states; refresh failures preserve the last valid `InstallState`, and request ids ignore stale completions in `src/features/install/installPageState.ts:5-73`. The rendered primary action derives install/reinstall/launch mode from installed/version-match state in `src/pages/Install.tsx:42-47` and gates it with the matching backend action plus page busy state in `src/features/install/PrimaryInstallActionButton.tsx:19-44`. ## Install And Uninstall @@ -28,9 +28,9 @@ last-verified: 0f609de844c0cbc48e7fb53396a90d5f32776c2b ## Reset Local Data -- The frontend opens target-bearing reset confirmations and requires an acknowledgement checkbox before confirming. Both show the selected game path and concrete consequence; once the native operation starts, Escape, backdrop, close, and secondary dismissal are blocked because neither reset command is cancellable in `src/pages/Install.tsx:53-84`, `src/features/install/ResetDataConfirmModal.tsx:29-74`, and `src/features/install/ResetBepinexConfirmModal.tsx:26-69`. -- The reset button is disabled when reset is not allowed, and the UI distinguishes "no resettable data" from the destructive action label in `src/features/install/InstallActionsPanel.tsx:103-115`. -- `useInstallPage` returns a semantic success/problem outcome for every action; reset data treats an already-empty state as a no-op, otherwise calls `resetBppData`, replaces the completed state from the typed result, and chooses success versus no-op copy from `removed_data` in `src/features/install/useInstallPage.ts:103-147` and `src/features/install/useInstallPage.ts:172-190`. The confirmation closes only on that success outcome; failure retains its exact target, semantic problem, and partial-failure paths for retry or safe close in `src/pages/Install.tsx:67-84` and `src/pages/Install.tsx:143-176`. +- The frontend opens target-bearing reset confirmations and requires an acknowledgement checkbox before confirming. Both show the selected game path and concrete consequence; once the native operation starts, Escape, backdrop, close, and secondary dismissal are blocked because neither reset command is cancellable in `src/pages/Install.tsx:67-98`, `src/pages/Install.tsx:167-213`, `src/features/install/ResetDataConfirmModal.tsx:29-74`, and `src/features/install/ResetBepinexConfirmModal.tsx:26-69`. +- The reset button is disabled when reset is not allowed, and the UI distinguishes "no resettable data" from the destructive action label in `src/features/install/InstallActionsPanel.tsx:34-45`. +- `useInstallPage` returns a semantic success/problem outcome for every action; reset data treats an already-empty state as a no-op, otherwise calls `resetBppData`, replaces the completed state from the typed result, and chooses success versus no-op copy from `removed_data` in `src/features/install/useInstallPage.ts:103-147` and `src/features/install/useInstallPage.ts:172-190`. The confirmation closes only on that success outcome; failure retains its exact target, semantic problem, and partial-failure paths for retry or safe close in `src/pages/Install.tsx:81-98` and `src/pages/Install.tsx:167-213`. - The Rust reset path enters `StreamRuntime` exclusive maintenance, stops and awaits the stream task, and keeps lifecycle operations excluded throughout blocking deletion in `src-tauri/src/services/bepinex/mod.rs:29-42` and `src-tauri/src/stream/runtime.rs:100-108`. - Reset refuses to run while The Bazaar is detected as running, records whether the data directory existed before cleanup, and returns stable error-code prefixes for blocked or partial-failure cases in `src-tauri/src/services/bepinex/mod.rs:20-27` and `src-tauri/src/services/bepinex/mod.rs:44-70`. - Raw reset sentinels remain private to the Rust service boundary; the frontend consumes only semantic problem parameters, maps them to localized messages, and extracts partial-failure paths in `src/features/install/installProblems.ts:23-107` and `src/features/install/useInstallPage.ts:103-145`. diff --git a/docs/truth/updater-release.md b/docs/truth/updater-release.md index c0e9a478..cd2f3076 100644 --- a/docs/truth/updater-release.md +++ b/docs/truth/updater-release.md @@ -1,21 +1,21 @@ --- status: truth topic: updater-release -last-verified: 2bf15726776492127c3eca2162dd26306c3ab310 +last-verified: 2af743045b72631519a11dadea4b474c903519bd --- # Updater And Release ## In-App Updater -- The Tauri bundle config creates updater artifacts in `src-tauri/tauri.conf.json:27-30`. -- The updater endpoint is `https://bppinstaller.bazaarplusplus.com/latest.json`, and the public key is configured in `src-tauri/tauri.conf.json:31-36`. +- The Tauri bundle config creates updater artifacts in `src-tauri/tauri.conf.json:32-35`. +- The updater endpoint is `https://bppinstaller.bazaarplusplus.com/latest.json`, and the public key is configured in `src-tauri/tauri.conf.json:36-40`. - Runtime capabilities allow updater check, updater download/install, and process restart in `src-tauri/capabilities/default.json:6-11`. - The updater implementation keeps the `Update` handle alive across user interactions because `downloadAndInstall` must run on the same handle returned by `check()` in `src/features/about/updater.ts:9-17`. - `runCheck` returns `preview` outside Tauri runtime, `available` with version/notes/handle when a plugin update exists, or `current` when none exists in `src/features/about/updater.ts:45-59`. - The discriminated snapshot encodes checking, available, downloading, installing, ready-to-restart, restarting, and failed states without contradictory progress/problem fields in `src/features/about/updater.ts:61-120`. The machine guards duplicate work/dismissal, consumes handles once, refreshes a handle on retry, separates download from install failure at the Finished event, and preserves known version/notes across restart failure in `src/features/about/updater.ts:132-335`. - Known updater failures are stable semantic problems rather than native error copy. Check/download/install/restart codes carry operation and optional version parameters plus diagnostic detail; bilingual presenters supply recovery text in `src/features/about/updaterProblems.ts:9-63`, `src/i18n/messages.ts:175-204`, and `src/i18n/messages.ts:530-564`. -- One phase presentation supplies both the header status and modal contract. Update decisions remain system priority behind confirmations, while download/install/restart work upgrades the same modal source to critical blocked policy in `src/features/about/updaterPresentation.ts:22-118` and `src/layouts/GlobalShell.tsx:119-128`. +- One phase presentation supplies updater metadata and the modal contract. Update decisions remain system priority behind confirmations, while download/install/restart work upgrades the same modal source to critical blocked policy in `src/features/about/updaterPresentation.ts:22-118` and `src/layouts/GlobalShell.tsx:110-119`. Manual update checking is exposed on the Install page in `src/features/install/InstallActionsPanel.tsx:54-61`. ## Reproducible Release Inputs diff --git a/docs/truth/verification.md b/docs/truth/verification.md index 92ee8d04..b95ec08a 100644 --- a/docs/truth/verification.md +++ b/docs/truth/verification.md @@ -1,7 +1,7 @@ --- status: truth topic: verification -last-verified: 506e85b363a53511d14a44db22804a53331f2c03 +last-verified: 2af743045b72631519a11dadea4b474c903519bd --- # Verification @@ -45,7 +45,7 @@ Use the smallest command that verifies the changed behavior; use the authoritati - Run Detail tests cover its four page states, preserved refresh failure, stale completion, global action gate, target-scoped retry, and bilingual semantic problem presentation in `src/features/history/runDetailPageState.test.ts:56-187` and `src/features/history/runDetailProblems.test.ts:6-39`. Shared confirmation tests cover idle and active Escape/backdrop/close/secondary behavior for blocked, detachable, and genuinely cancelable policies in `src/components/ui/ConfirmDialog.test.tsx:104-205`; the confirmed-operation tests cover blocked dismissal, repeat submission, success-only closure, retained failure, retry, and cleanup/reset/delete targets in `src/features/shared/confirmedOperation.test.ts:15-96`. - Cleanup semantic presentation and native-adapter preservation are covered in `src/features/history/storageCleanupProblems.test.ts:8-30` and `src/api/commandClient.dispatch.test.ts:97-118`; neither test treats diagnostics as user-facing copy. - Install tests cover explicit initial detection, preserved refresh failure and retry, each primary-action branch, shared disabled/loading derivation, bilingual semantic warnings/problems, partial-failure recovery parameters, and native-adapter preservation in `src/features/install/installPageState.test.ts:43-228`, `src/features/install/installProblems.test.ts:10-89`, and `src/api/commandClient.dispatch.test.ts:57-76`. Rust tests pin Install semantic serialization and service-boundary classification in `src-tauri/src/problem.rs:46-84` and `src-tauri/src/services/install/mod.rs:282-366`. -- Modal-coordinator tests cover strict priority, equal-priority FIFO, queued-source unregistration, non-preempting update/support dialogs, active confirmation upgrades, critical preemption, and connected/fallback focus restoration in `src/features/shared/modalCoordinator.test.ts:17-110`. The shell/route integration case covers a routed confirmation upgrading to blocked critical work, route-source unmount, and ordered handoff to update then payment in `src/features/shared/modalCoordinator.integration.test.ts:4-40`; controlled disclosure semantics are rendered in `src/layouts/ShellHeader.test.tsx:88-100`. +- Modal-coordinator tests cover strict priority, equal-priority FIFO, queued-source unregistration, non-preempting update/support dialogs, active confirmation upgrades, critical preemption, and connected/fallback focus restoration in `src/features/shared/modalCoordinator.test.ts:17-110`. The shell/route integration case covers a routed confirmation upgrading to blocked critical work, route-source unmount, and ordered handoff to update then payment in `src/features/shared/modalCoordinator.integration.test.ts:4-40`; controlled disclosure semantics are rendered in `src/layouts/ShellHeader.test.tsx:107-119`. - Updater state tests cover manual/silent checks, mutually consistent phases, determinate/indeterminate progress, download-versus-install classification, fresh-handle retry, blocked dismissal, restart failure retention, and recovery in `src/features/about/updater.test.ts:58-353`. Presentation tests cover shared header/modal derivation and confirmation collision in `src/features/about/updaterPresentation.test.ts:15-111`; bilingual semantic presentation and optional diagnostics are covered in `src/features/about/updaterProblems.test.ts:10-53`, while modal markup tests cover progress ARIA and manual restart recovery in `src/layouts/ShellUpdateModal.test.tsx:33-89`. - About bootstrap tests cover initial loading, native success, packaged fallback, retry success, repeated failure, no-data blocking failure, and Preview provenance in `src/features/about/appBootstrap.test.ts:34-168`. The same semantic problem is presented in Chinese and English without exposing diagnostics as user copy in `src/features/about/aboutProblems.test.ts:8-35`; rendered markup tests cover live-region feedback, native retry/disclosure controls, selectable versions, and explicit unavailable values in `src/pages/About.test.tsx:34-73`. diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1070a188..e55c170f 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -49,8 +49,11 @@ winreg = "0.55" # (see Cargo.lock) to avoid a duplicate major version. windows = { version = "0.61", features = [ "Win32_Foundation", + "Win32_Graphics_Dwm", "Win32_Storage_FileSystem", "Win32_System_Diagnostics_ToolHelp", + "Win32_UI_Shell", + "Win32_UI_WindowsAndMessaging", ] } [target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies] diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 6ccf3f01..79df28c4 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -5,6 +5,10 @@ "windows": ["main"], "permissions": [ "core:default", + "core:window:allow-minimize", + "core:window:allow-close", + "core:window:allow-show", + "core:window:allow-start-dragging", "opener:default", "updater:allow-check", "updater:allow-download-and-install", diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 3f21111e..4619e056 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -5,6 +5,8 @@ mod problem; mod services; mod stream; mod tray; +#[cfg(target_os = "windows")] +mod windows_window; use tauri::{Manager, WindowEvent}; @@ -43,6 +45,38 @@ pub fn run() { .manage(TrayMenuState::default()) .plugin(tauri_plugin_opener::init()) .setup(|app| { + #[cfg(target_os = "windows")] + { + let window = app + .get_webview_window("main") + .ok_or_else(|| std::io::Error::other("main window is unavailable"))?; + window.set_decorations(false)?; + window.show()?; + + // Showing the window can refresh its Win32 frame. Compact Tao's + // wide resize insets after the window has its final native frame. + let border_window = window.clone(); + window.run_on_main_thread(move || { + if let Err(error) = + crate::windows_window::configure_native_frame(&border_window) + { + eprintln!("failed to configure the Windows native frame: {error}"); + } + })?; + + // Reapply after activation changes as recommended for DWM border + // color overrides. This also makes tray hide/show cycles robust. + let border_window = window.clone(); + window.on_window_event(move |event| { + if matches!(event, WindowEvent::Focused(_)) { + if let Err(error) = crate::windows_window::refresh_dwm_frame(&border_window) + { + eprintln!("failed to reapply the Windows DWM border override: {error}"); + } + } + }); + } + let handle = app.app_handle(); build_tray(handle)?; let startup_handle = handle.clone(); diff --git a/src-tauri/src/windows_window.rs b/src-tauri/src/windows_window.rs new file mode 100644 index 00000000..98a278c0 --- /dev/null +++ b/src-tauri/src/windows_window.rs @@ -0,0 +1,158 @@ +use std::ffi::c_void; + +use tauri::WebviewWindow; +use windows::Win32::{ + Foundation::{COLORREF, HWND, LPARAM, LRESULT, RECT, WPARAM}, + Graphics::Dwm::{ + DwmSetWindowAttribute, DWMWA_BORDER_COLOR, DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_ROUND, + DWM_WINDOW_CORNER_PREFERENCE, + }, + UI::{ + Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass}, + WindowsAndMessaging::{ + GetClientRect, GetWindowRect, SetWindowPos, NCCALCSIZE_PARAMS, SWP_FRAMECHANGED, + SWP_NOACTIVATE, SWP_NOZORDER, WM_NCCALCSIZE, WM_NCDESTROY, + }, + }, +}; + +const DWM_COLOR_NONE: COLORREF = COLORREF(0xFFFF_FFFE); +const COMPACT_FRAME_SUBCLASS_ID: usize = 0x4250_5057; +const COMPACT_FRAME_SIDE_INSET: i32 = 1; +const COMPACT_FRAME_TOP_INSET: i32 = 0; +const COMPACT_FRAME_BOTTOM_INSET: i32 = 1; + +unsafe extern "system" fn compact_frame_subclass( + hwnd: HWND, + message: u32, + wparam: WPARAM, + lparam: LPARAM, + _subclass_id: usize, + _reference_data: usize, +) -> LRESULT { + if message == WM_NCCALCSIZE && wparam.0 != 0 { + // Tao reserves the full resize-frame thickness on the left, right, + // and bottom of every undecorated shadow window. This fixed-size app + // does not need those resize handles. Keep only one physical pixel on + // those three edges and let the client area reach the top edge. Windows + // 11 still supplies the native shadow and rounded-corner treatment. + let params = unsafe { &mut *(lparam.0 as *mut NCCALCSIZE_PARAMS) }; + let rect = &mut params.rgrc[0]; + if rect.right - rect.left > COMPACT_FRAME_SIDE_INSET * 2 + && rect.bottom - rect.top > COMPACT_FRAME_TOP_INSET + COMPACT_FRAME_BOTTOM_INSET + { + rect.left += COMPACT_FRAME_SIDE_INSET; + rect.top += COMPACT_FRAME_TOP_INSET; + rect.right -= COMPACT_FRAME_SIDE_INSET; + rect.bottom -= COMPACT_FRAME_BOTTOM_INSET; + } + return LRESULT(0); + } + + if message == WM_NCDESTROY { + unsafe { + let _ = RemoveWindowSubclass( + hwnd, + Some(compact_frame_subclass), + COMPACT_FRAME_SUBCLASS_ID, + ); + } + } + + unsafe { DefSubclassProc(hwnd, message, wparam, lparam) } +} + +fn set_dwm_attribute( + hwnd: HWND, + attribute: windows::Win32::Graphics::Dwm::DWMWINDOWATTRIBUTE, + value: &T, +) -> Result<(), String> { + // SAFETY: `hwnd` belongs to the live WebviewWindow and `value` remains + // valid for the duration of this synchronous Win32 call. + unsafe { + DwmSetWindowAttribute( + hwnd, + attribute, + (value as *const T).cast::(), + std::mem::size_of::() as u32, + ) + } + .map_err(|error| format!("failed to set DWM window attribute {attribute:?}: {error}")) +} + +pub(crate) fn configure_native_frame(window: &WebviewWindow) -> Result<(), String> { + let hwnd = window + .hwnd() + .map_err(|error| format!("failed to get the native window handle: {error}"))?; + + let mut client_rect = RECT::default(); + let mut window_rect = RECT::default(); + + // Preserve the current content dimensions while replacing Tao's wide + // non-client insets with the compact frame. + unsafe { + GetClientRect(hwnd, &mut client_rect) + .map_err(|error| format!("failed to read the client rectangle: {error}"))?; + GetWindowRect(hwnd, &mut window_rect) + .map_err(|error| format!("failed to read the window rectangle: {error}"))?; + + if !SetWindowSubclass( + hwnd, + Some(compact_frame_subclass), + COMPACT_FRAME_SUBCLASS_ID, + 0, + ) + .as_bool() + { + return Err("failed to install the compact Windows frame handler".into()); + } + + let client_width = client_rect.right - client_rect.left; + let client_height = client_rect.bottom - client_rect.top; + let compact_width = client_width + COMPACT_FRAME_SIDE_INSET * 2; + let compact_height = client_height + COMPACT_FRAME_TOP_INSET + COMPACT_FRAME_BOTTOM_INSET; + let old_width = window_rect.right - window_rect.left; + let old_height = window_rect.bottom - window_rect.top; + let compact_left = window_rect.left + (old_width - compact_width) / 2; + let compact_top = window_rect.top + (old_height - compact_height) / 2; + + if let Err(error) = SetWindowPos( + hwnd, + None, + compact_left, + compact_top, + compact_width, + compact_height, + SWP_NOZORDER | SWP_NOACTIVATE | SWP_FRAMECHANGED, + ) { + let _ = RemoveWindowSubclass( + hwnd, + Some(compact_frame_subclass), + COMPACT_FRAME_SUBCLASS_ID, + ); + return Err(format!( + "failed to activate the compact Windows frame: {error}" + )); + } + } + + set_dwm_attribute(hwnd, DWMWA_BORDER_COLOR, &DWM_COLOR_NONE)?; + set_dwm_attribute::( + hwnd, + DWMWA_WINDOW_CORNER_PREFERENCE, + &DWMWCP_ROUND, + ) +} + +pub(crate) fn refresh_dwm_frame(window: &WebviewWindow) -> Result<(), String> { + let hwnd = window + .hwnd() + .map_err(|error| format!("failed to get the native window handle: {error}"))?; + + set_dwm_attribute(hwnd, DWMWA_BORDER_COLOR, &DWM_COLOR_NONE)?; + set_dwm_attribute::( + hwnd, + DWMWA_WINDOW_CORNER_PREFERENCE, + &DWMWCP_ROUND, + ) +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 742534d9..5d7d290a 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,10 +14,15 @@ "windows": [ { "title": "BazaarPlusPlus", - "width": 1000, - "height": 720, - "minWidth": 900, - "minHeight": 640 + "width": 1020, + "height": 680, + "minWidth": 1020, + "minHeight": 680, + "maxWidth": 1020, + "maxHeight": 680, + "resizable": false, + "maximizable": false, + "fullscreen": false } ], "security": { diff --git a/src-tauri/tauri.windows.conf.json b/src-tauri/tauri.windows.conf.json index f4711fa2..1aa2d95c 100644 --- a/src-tauri/tauri.windows.conf.json +++ b/src-tauri/tauri.windows.conf.json @@ -1,5 +1,24 @@ { "$schema": "https://schema.tauri.app/config/2", + "app": { + "windows": [ + { + "title": "BazaarPlusPlus", + "width": 972, + "height": 612, + "minWidth": 972, + "minHeight": 612, + "maxWidth": 972, + "maxHeight": 612, + "resizable": false, + "maximizable": false, + "fullscreen": false, + "decorations": false, + "shadow": true, + "visible": false + } + ] + }, "bundle": { "targets": ["nsis"], "icon": ["icons/icon.ico"], diff --git a/src/components/brand/BrandMark.tsx b/src/components/brand/BrandMark.tsx new file mode 100644 index 00000000..41cce0be --- /dev/null +++ b/src/components/brand/BrandMark.tsx @@ -0,0 +1,73 @@ +import clsx from 'clsx'; + +export function BrandMark({ + className, + compact = false +}: { + className?: string; + compact?: boolean; +}) { + return ( + + ); +} diff --git a/src/components/ui/ConfirmDialog.tsx b/src/components/ui/ConfirmDialog.tsx index c0d71d4f..a3afbbfe 100644 --- a/src/components/ui/ConfirmDialog.tsx +++ b/src/components/ui/ConfirmDialog.tsx @@ -33,6 +33,7 @@ export interface ConfirmDialogProps { * useId changes rendered bytes. Wired to

and Dialog labelledBy. */ titleId: string; title: string; + subtitle?: string; /** gold => DownloadCloud + gold chrome + body gap-6; * danger => AlertTriangle + red chrome + body gap-5. */ tone: ConfirmTone; @@ -65,23 +66,23 @@ export interface ConfirmDialogProps { const TONE = { gold: { Icon: DownloadCloud as LucideIcon, - card: 'bg-[#0b0906] border border-[rgba(200,148,55,0.18)] rounded-[4px] shadow-[0_24px_64px_rgba(0,0,0,0.5)] w-full max-w-md mx-4 relative', - bar: 'flex justify-between items-center px-5 py-4 border-b border-[rgba(200,148,55,0.15)] bg-[rgba(200,148,55,0.02)]', + card: 'bpp-modal-card bpp-install-confirm-card w-full max-w-[560px] mx-4 relative', + bar: 'bpp-modal-header bpp-install-confirm-header flex justify-between items-center px-5 py-4', icon: 'text-[rgba(200,148,55,0.8)]', title: 'cinzel text-[1.1rem] text-[#e8dcc8] m-0 tracking-wider', close: - 'text-[rgba(200,170,120,0.72)] hover:text-[#e8dcc8] transition-colors', - body: 'p-6 flex flex-col gap-6', + 'bpp-install-confirm-close text-[rgba(200,170,120,0.72)] hover:text-[#e8dcc8] transition-colors', + body: 'bpp-install-confirm-body p-6 flex flex-col gap-6', ackBox: - 'flex items-start gap-3 p-3 border border-[rgba(200,148,55,0.18)] rounded-[4px] bg-gradient-to-b from-[rgba(200,148,55,0.055)] to-[rgba(200,148,55,0.015)] group', + 'bpp-install-confirm-ack flex items-start gap-3 p-3 border border-[rgba(200,148,55,0.18)] rounded-[4px] bg-gradient-to-b from-[rgba(200,148,55,0.055)] to-[rgba(200,148,55,0.015)] group', ackText: 'text-[13px] leading-relaxed text-[rgba(232,220,194,0.78)]', confirm: - 'px-5 py-2 rounded-sm text-sm cinzel font-bold tracking-wider transition-all bg-gradient-to-b from-[#d4a040] to-[#9e5c1e] text-[#0b0906] shadow-[0_0_15px_rgba(212,160,64,0.4)] hover:brightness-110 active:brightness-95 disabled:opacity-45 disabled:hover:brightness-100' + 'bpp-install-confirm-submit px-5 py-2 rounded-sm text-sm cinzel font-bold tracking-wider transition-all bg-gradient-to-b from-[#d4a040] to-[#9e5c1e] text-[#0b0906] shadow-[0_0_15px_rgba(212,160,64,0.4)] hover:brightness-110 active:brightness-95 disabled:opacity-45 disabled:hover:brightness-100' }, danger: { Icon: AlertTriangle as LucideIcon, - card: 'bg-[#0b0906] border border-[rgba(190,80,80,0.24)] rounded-[4px] shadow-[0_24px_64px_rgba(0,0,0,0.5)] w-full max-w-md mx-4 relative', - bar: 'flex justify-between items-center px-5 py-4 border-b border-[rgba(190,80,80,0.18)] bg-[rgba(160,50,50,0.06)]', + card: 'bpp-modal-card bpp-modal-danger w-full max-w-md mx-4 relative', + bar: 'bpp-modal-header flex justify-between items-center px-5 py-4', icon: 'text-[rgba(232,120,120,0.9)]', title: 'cinzel text-[1.1rem] text-[#f0d8d8] m-0 tracking-wider', close: @@ -98,6 +99,7 @@ const TONE = { export function ConfirmDialog({ titleId, title, + subtitle, tone, children, acknowledge, @@ -130,14 +132,24 @@ export function ConfirmDialog({ }); return ( - +
-
- -

- {title} -

+
+ +
+

+ {title} +

+ {subtitle && ( +

{subtitle}

+ )} +
diff --git a/src/components/ui/Dialog.tsx b/src/components/ui/Dialog.tsx index 04ff631e..c2eb8045 100644 --- a/src/components/ui/Dialog.tsx +++ b/src/components/ui/Dialog.tsx @@ -13,11 +13,13 @@ import { useActiveModalDismissalPolicy } from './ModalCoordinator'; export function Dialog({ onClose, labelledBy, + focusContainerOnOpen = false, className = '', children }: { onClose: (reason: DialogCloseReason) => void; labelledBy?: string; + focusContainerOnOpen?: boolean; className?: string; children: ReactNode; }) { @@ -27,7 +29,10 @@ export function Dialog({ useEffect(() => { const el = ref.current; - if (el && !el.open) el.showModal(); + if (el && !el.open) { + el.showModal(); + if (focusContainerOnOpen) el.focus({ preventScroll: true }); + } return () => { if (el?.open) el.close(); }; @@ -37,6 +42,7 @@ export function Dialog({ { // Escape fires `cancel`; we own the close so the parent state stays in sync. diff --git a/src/components/ui/PageHeader.tsx b/src/components/ui/PageHeader.tsx index a445b927..095ecdb8 100644 --- a/src/components/ui/PageHeader.tsx +++ b/src/components/ui/PageHeader.tsx @@ -10,10 +10,8 @@ export function PageHeader({ action?: ReactNode; }) { return ( -
-

- {eyebrow} -

+
+

{eyebrow}

{action ? (
{title} @@ -28,11 +26,7 @@ export function PageHeader({ function Title({ children }: { children: ReactNode }) { return ( -

+

{children}

); diff --git a/src/components/ui/PageShell.tsx b/src/components/ui/PageShell.tsx index 473296bf..dee47556 100644 --- a/src/components/ui/PageShell.tsx +++ b/src/components/ui/PageShell.tsx @@ -5,15 +5,17 @@ export function PageShell({ eyebrow, title, action, + className, children }: { eyebrow: string; title: string; action?: ReactNode; + className?: string; children: ReactNode; }) { return ( -
+
{children}
diff --git a/src/features/history/StorageCleanupCard.tsx b/src/features/history/StorageCleanupCard.tsx index 33a0d31c..71068682 100644 --- a/src/features/history/StorageCleanupCard.tsx +++ b/src/features/history/StorageCleanupCard.tsx @@ -1,5 +1,7 @@ -import { ChevronRight, Trash2 } from 'lucide-react'; +import { ChevronDown, Trash2 } from 'lucide-react'; +import { useState } from 'react'; import { ConfirmDialog } from '../../components/ui/ConfirmDialog'; +import { ModalSource } from '../../components/ui/ModalCoordinator'; import { ProblemBanner } from '../../components/ui/ProblemBanner'; import { useI18n } from '../../i18n/LocaleProvider'; import type { StorageCleanupPreset } from '../../types/backend'; @@ -15,7 +17,6 @@ import { type CleanupScope, type PendingCleanup } from './useStorageCleanup'; -import { ModalSource } from '../../components/ui/ModalCoordinator'; const PRESETS: Array<{ preset: StorageCleanupPreset; @@ -49,6 +50,7 @@ export function StorageCleanupCard({ }) { const { locale, t } = useI18n(); const cleanup = useStorageCleanup(onCompleted); + const [expanded, setExpanded] = useState(false); const pendingBody = (pending: PendingCleanup): string => { if (pendingItemCount(pending) === 0) { @@ -84,43 +86,55 @@ export function StorageCleanupCard({ return ( <> -
- - - - +
+
+ -
- {cleanup.previewProblem && ( - - )} +
+
+ {cleanup.previewProblem && ( + + )} - - + + - {cleanup.outcome && ( -

- {outcomeText(cleanup.outcome)} -

- )} + {cleanup.outcome && ( +

+ {outcomeText(cleanup.outcome)} +

+ )} +
-
+ - {label} -
+
+
+ {label} + + {description} + +
+
{PRESETS.map(({ preset, labelKey }) => ( diff --git a/src/features/install/InstallActionsPanel.tsx b/src/features/install/InstallActionsPanel.tsx index 10ed06f6..d0b2993e 100644 --- a/src/features/install/InstallActionsPanel.tsx +++ b/src/features/install/InstallActionsPanel.tsx @@ -1,222 +1,131 @@ -import { - AlertCircle, - AlertTriangle, - DownloadCloud, - FolderOpen, - FolderX, - Loader2, - Play, - RefreshCw, - Trash2 -} from 'lucide-react'; -import type { useInstallPage } from './useInstallPage'; -import { InstallActionButton } from './InstallActionButton'; -import { InstallFactItem } from './InstallFactItem'; -import { ResetDataFailureDetails } from './ResetDataFailureDetails'; +import { Box, CloudDownload, Layers3, Loader2, Trash2 } from 'lucide-react'; +import type { ReactNode } from 'react'; import { useI18n } from '../../i18n/LocaleProvider'; -import type { InstallState } from '../../types/backend'; -import type { InstallPrimaryAction } from './installPageState'; -import { presentInstallWarning } from './installProblems'; +import { ResetDataFailureDetails } from './ResetDataFailureDetails'; import { InstallProblemBanner } from './InstallProblemBanner'; +import type { useInstallPage } from './useInstallPage'; type InstallPage = ReturnType; export function InstallActionsPanel({ page, - state, - primaryAction, - onOpenInstallModal, + updateChecking, + onCheckUpdate, onOpenResetDataModal, onOpenResetBepinexModal }: { page: InstallPage; - state: InstallState; - primaryAction: InstallPrimaryAction; - onOpenInstallModal: () => void; + updateChecking: boolean; + onCheckUpdate: () => void; onOpenResetDataModal: () => void; onOpenResetBepinexModal: () => void; }) { const { t } = useI18n(); - return ( -
-
-
-

- {t('installActionsHeading')} -

-
-
- -
- -
+ const state = page.installState; -
    - -
+ if (!state) return null; - {state.warnings.length > 0 && ( -
- {state.warnings.map((warning) => ( -

- - {presentInstallWarning(warning, t)} -

- ))} -
- )} - - {page.actionProblem && ( - - )} - {page.message && ( -

- {page.message} -

- )} - {page.resetDataFailurePaths.length > 0 && ( - - )} - -
- -
- } - label={ - state.game.path_valid && !state.has_resettable_data - ? t('actionNoResettableData') - : t('actionResetData') - } - danger - /> - } - label={t('actionResetBepinex')} - danger - /> - } - label={t('actionUninstall')} - danger - className="col-span-2" - /> -
-
-
+ return ( +
+

+ {t('maintenanceToolsHeading')} +

+
+ } + title={ + state.game.path_valid && !state.has_resettable_data + ? t('actionNoResettableData') + : t('actionResetData') + } + detail={t('maintenanceResetDataDescription')} + onClick={onOpenResetDataModal} + /> + } + title={t('actionResetBepinex')} + detail={t('maintenanceResetBepinexDescription')} + onClick={onOpenResetBepinexModal} + /> + } + title={t('headerCheckUpdate')} + detail={t('checkUpdateDescription')} + onClick={onCheckUpdate} + /> + } + title={t('actionUninstall')} + detail={t('maintenanceUninstallDescription')} + onClick={page.uninstall} + />
-
+ + {page.actionProblem && ( +
+ +
+ )} + {page.message && ( +

+ {page.message} +

+ )} + {page.resetDataFailurePaths.length > 0 && ( + + )} + ); } -function PrimaryActionButton({ - page, - primaryAction, - onOpenInstallModal +function MaintenanceAction({ + icon, + title, + detail, + onClick, + disabled, + busy, + danger = false }: { - page: InstallPage; - primaryAction: InstallPrimaryAction; - onOpenInstallModal: () => void; + icon: ReactNode; + title: string; + detail: string; + onClick: () => void; + disabled: boolean; + busy: boolean; + danger?: boolean; }) { - const { t } = useI18n(); - if (primaryAction.mode === 'launch') { - return ( -
- -
- ); - } - - if (primaryAction.mode === 'repair') { - return ( - - ); - } - - if (primaryAction.mode === 'choose-directory') { - return ( - - ); - } - return ( ); } diff --git a/src/features/install/InstallConfirmModal.tsx b/src/features/install/InstallConfirmModal.tsx index d9c86fac..28519b52 100644 --- a/src/features/install/InstallConfirmModal.tsx +++ b/src/features/install/InstallConfirmModal.tsx @@ -1,4 +1,4 @@ -import { AlertCircle, ExternalLink } from 'lucide-react'; +import { BookOpen, ExternalLink, TriangleAlert } from 'lucide-react'; import type { InstallCompatState } from '../../types/backend'; import { ConfirmDialog } from '../../components/ui/ConfirmDialog'; import { useI18n } from '../../i18n/LocaleProvider'; @@ -28,6 +28,7 @@ export function InstallConfirmModal({ -
-
-
-

- {t('tutorialKicker')} -

-

- {t('installModalBody')} -

-
- - {t('viewTutorial')} - - +
+ +
+

{t('tutorialKicker')}

+

+ {t('installModalBody')} +

-
+ + {t('viewTutorial')} + + + -
- + -

- {t('installSteamNotice')} -

-
+

{t('installSteamNotice')}

+ {showCompatToggle && ( -