fix: restore sidebar context for missing task routes (#2229)
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
---
|
||||
spec: docs/specs/missing-task-route-recovery/spec.md
|
||||
created: 2026-08-03
|
||||
status: completed
|
||||
---
|
||||
|
||||
# Implementation Plan: Missing task route recovery
|
||||
|
||||
## Overview
|
||||
|
||||
Make task-detail boot construction aware of whether route-specific data was
|
||||
actually produced. When the requested task cannot be loaded, reuse the existing
|
||||
home Kanban boot builder to hydrate only the selected authorized workspace and
|
||||
its sidebar snapshots; keep successful task routes on their current lean,
|
||||
task-owned hydration path. Then add a workspace-preserving overview action to
|
||||
the unavailable state and prove the full cold-load behavior on desktop and
|
||||
phone.
|
||||
|
||||
## Root cause
|
||||
|
||||
`bootInitialState` deliberately adds the full workspace/workflow/Kanban state
|
||||
for Home and unknown routes, but not for `RouteTaskDetail`. A valid task route
|
||||
receives that context inside `routeData.taskDetail.initialState`. When
|
||||
`taskDetailRouteData` cannot load the requested task, it returns `nil`, leaving
|
||||
the top-level state without `workspaces.activeId`, workflows, or snapshots.
|
||||
The client retry then renders the unavailable state, while
|
||||
`useWorkspaceSidebarTasks(null)` intentionally scopes the sidebar to an empty
|
||||
list to avoid leaking stale workspace data.
|
||||
|
||||
## Backend
|
||||
|
||||
### Conditional task-detail fallback boot state
|
||||
|
||||
- In `apps/backend/internal/backendapp/helpers.go`, update `bootPayload` to
|
||||
construct `initialState` and `routeData` before assembling the final payload.
|
||||
- When the classified route is `webapp.RouteTaskDetail` and route data is
|
||||
unavailable, call the existing
|
||||
`bootStateBuilder.addHomeKanbanRouteState(ctx, req, initialState)` path. This
|
||||
preserves URL/cookie/settings/default workspace precedence, Office-workflow
|
||||
exclusion, repository hydration, and all-workflow snapshots without adding a
|
||||
second data-loading implementation.
|
||||
- Only run that fallback for auth-disabled instances or requests carrying an
|
||||
authenticated identity. Auth-enabled anonymous bootstrap requests must keep
|
||||
the existing auth-only payload and must never invoke unscoped workspace or
|
||||
task loaders.
|
||||
- Leave successful task-detail boot unchanged: its top-level initial state
|
||||
remains lean and `routeData.taskDetail.initialState` remains authoritative for
|
||||
the task's actual workspace.
|
||||
- Preserve the existing not-found/access-denied ambiguity and request identity
|
||||
when listing fallback workspaces and tasks. No unscoped service call or new
|
||||
endpoint is introduced.
|
||||
- Route-data errors remain intentionally represented by the existing generic
|
||||
unavailable state; the client-side task fetch still retries and determines
|
||||
whether the task can be rendered after the shell loads.
|
||||
|
||||
## Frontend
|
||||
|
||||
### Unavailable task recovery action
|
||||
|
||||
- In `apps/web/components/task/task-page-content.tsx`, give
|
||||
`TaskLoadErrorState` the resolved `workspaces.activeId` and render an existing
|
||||
`Button`/routing `Link` to
|
||||
`linkToTaskOverview({ workspaceId: activeWorkspaceId ?? undefined })`.
|
||||
- Keep the error copy generic and the main error surface unchanged. Add a stable
|
||||
test ID to the overview action for rendered regression coverage.
|
||||
- Externalize the loading label, unavailable title/description, and new overview
|
||||
action through `react-i18next`; add matching English and pseudo-locale keys in
|
||||
`apps/web/src/locales/en/common.json` and
|
||||
`apps/web/src/locales/pseudo/common.json`.
|
||||
|
||||
### Mobile design contract
|
||||
|
||||
- **Desktop outcome:** the global task sidebar remains populated from the
|
||||
fallback workspace, and a valid row can replace the unavailable route.
|
||||
- **Mobile entry point:** the unavailable-state task-overview action is the
|
||||
direct recovery path because `AppSidebar` is intentionally hidden below the
|
||||
desktop breakpoint.
|
||||
- **Nearest shipped exemplar:** the task-overview back link in
|
||||
`apps/web/components/task/mobile/session-mobile-top-bar.tsx`; reuse its
|
||||
`linkToTaskOverview` destination and active-workspace context. The unavailable
|
||||
action uses a normal button/link rather than mounting the full task workbench
|
||||
or stacking a drawer onto an error state.
|
||||
- **Hierarchy and presentation:** the error remains the single focal surface;
|
||||
the recovery action follows its explanation and navigates directly to Home.
|
||||
No overlay, new scroll owner, or persisted responsive state is introduced.
|
||||
- **Geometry and input:** the error container remains viewport-contained, and
|
||||
the action has at least a 44px active height on phone while retaining semantic
|
||||
link and keyboard behavior.
|
||||
- **Shared logic:** both viewports use the same resolved workspace ID and link
|
||||
builder. Only responsive sizing differs.
|
||||
|
||||
## Tests
|
||||
|
||||
- **Missing task fallback boot:** add a focused backend test in
|
||||
`apps/backend/internal/backendapp/helpers_test.go` that creates a visible
|
||||
sibling task, cold-builds `/t/<missing-id>` with an active-workspace cookie,
|
||||
and asserts that top-level boot state contains the selected workspace,
|
||||
workflows, and a snapshot containing the sibling while route-specific task
|
||||
data remains absent.
|
||||
- **Anonymous bootstrap guard:** assert that an auth-enabled request without a
|
||||
request identity keeps only auth/features state and does not load workspace,
|
||||
workflow, repository, or Kanban snapshots.
|
||||
- **Successful task boot remains lean:** extend the backend coverage to assert
|
||||
that a valid task route still provides `routeData.taskDetail` and does not
|
||||
add fallback workspace/Kanban data to the top-level initial state.
|
||||
- **Client route failure:** extend
|
||||
`apps/web/src/task-detail-route.test.tsx` so a rejected task-data request
|
||||
reaches the null-task shell without a state hydrator; this preserves the
|
||||
boundary between route failure and the unavailable surface.
|
||||
- The new overview action is component markup with no new branching helper; its
|
||||
destination, accessibility, and responsive behavior are covered by the
|
||||
desktop and mobile Playwright scenarios below.
|
||||
|
||||
## E2E Tests
|
||||
|
||||
- **Desktop scenario:** extend
|
||||
`apps/web/e2e/tests/task/task-loading-state.spec.ts`. Seed a sibling task,
|
||||
cold-load a missing `/t/:id`, assert `task-load-error-state`, assert the global
|
||||
sidebar shows the sibling and not **No tasks yet**, then select the sibling
|
||||
and verify its valid task route opens.
|
||||
- **Mobile scenario:** extend
|
||||
`apps/web/e2e/tests/task/mobile-task-loading-state.spec.ts`. Cold-load a
|
||||
missing task route, assert the unavailable state and overview action fit the
|
||||
Pixel 5 viewport with a touch-sized target and no document horizontal
|
||||
overflow, tap the action, and verify the seeded task appears on mobile Home.
|
||||
- Both scenarios use the production Go-served Vite build through the managed
|
||||
E2E runner so the route-aware boot payload—not only client navigation—is
|
||||
exercised.
|
||||
|
||||
## Verification Results
|
||||
|
||||
- Backend contracts: `cd apps/backend && go test ./internal/backendapp -run '^(TestBootPayloadAnonymousMissingTaskDoesNotLoadHomeState|TestBootPayloadMissingTaskFallsBackToHomeKanbanState|TestBootPayloadValidTaskKeepsRouteSpecificState)$' -count=1` — 3 passed.
|
||||
- Anonymous regression: `cd apps/backend && go test ./internal/backendapp -run '^TestBootPayloadAnonymousMissingTaskDoesNotLoadHomeState$' -count=1` — 1 passed.
|
||||
- Client route boundary: `cd apps && pnpm --filter @kandev/web exec vitest run src/task-detail-route.test.tsx` — 3 passed.
|
||||
- Recovery-link component contract: `cd apps && pnpm --filter @kandev/web exec vitest run components/task/task-page-content.test.tsx` — 2 passed.
|
||||
- Frontend: `cd apps/web && pnpm run typecheck` — passed; `pnpm run i18n:check && pnpm run i18n:ratchet` — passed.
|
||||
- Desktop E2E: `cd apps/web && pnpm e2e:run tests/task/task-loading-state.spec.ts -- --grep 'keeps sibling tasks available'` — 1 passed.
|
||||
- Mobile E2E: `cd apps/web && pnpm e2e:run --no-build --project mobile-chrome tests/task/mobile-task-loading-state.spec.ts -- --grep 'returns to task overview'` — 1 passed.
|
||||
- The managed runner rebuilt the production Go-served Vite bundle and cleaned temporary reports.
|
||||
|
||||
## Implementation Waves And Parallel Candidates
|
||||
|
||||
Wave 1:
|
||||
|
||||
- [x] [Task 01: Restore fallback boot context](task-01-fallback-boot-context.md)
|
||||
|
||||
Wave 2 (depends on Task 01):
|
||||
|
||||
- [x] [Task 02: Add unavailable-route recovery UI and E2E](task-02-recovery-ui-and-e2e.md)
|
||||
|
||||
Both tasks are sequential. The rendered regression depends on the backend boot
|
||||
payload change, and no subagent execution is authorized by this plan.
|
||||
|
||||
## Risks and out of scope
|
||||
|
||||
- The fallback intentionally loads the same snapshots as Home only after task
|
||||
route-data failure; accidentally running it for successful routes would add
|
||||
duplicate work and could briefly select the wrong workspace.
|
||||
- Authorization must remain request-scoped. Tests should assert selected
|
||||
authorized state, not weaken task `NotFound` behavior to distinguish missing
|
||||
from inaccessible IDs.
|
||||
- Office task routes, task API semantics, sidebar presentation, and retry policy
|
||||
remain out of scope.
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
id: "01-fallback-boot-context"
|
||||
title: "Restore fallback boot context"
|
||||
status: completed
|
||||
wave: 1
|
||||
depends_on: []
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/missing-task-route-recovery/spec.md"
|
||||
---
|
||||
|
||||
# Task 01: Restore fallback boot context
|
||||
|
||||
Use the existing Home Kanban boot builder only when a task-detail route cannot
|
||||
produce task-specific route data, so the app shell retains its authorized
|
||||
workspace and sidebar snapshots without changing successful task boot.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- A missing or inaccessible `/t/:id` boot payload contains the selected
|
||||
authorized Kanban workspace, workflows, repositories, and workflow snapshots,
|
||||
including visible sibling tasks.
|
||||
- The invalid task ID is not synthesized into boot state and
|
||||
`routeData.taskDetail` remains absent.
|
||||
- A valid task route still uses only its route-specific task initial state and
|
||||
does not receive duplicate fallback Home state.
|
||||
- An auth-enabled anonymous request keeps its auth-only bootstrap and does not
|
||||
invoke the fallback loaders.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/backend/internal/backendapp/helpers.go`
|
||||
- `apps/backend/internal/backendapp/helpers_test.go`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`. Task 02's production-build E2E assertions depend on this boot
|
||||
payload behavior.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Spec sections **Desired behavior**, **Regression scenarios**, and
|
||||
**Constraints**.
|
||||
- `apps/backend/internal/backendapp/boot_state.go`:
|
||||
`taskDetailRouteData` and `addHomeKanbanRouteState`.
|
||||
- `apps/backend/internal/backendapp/helpers.go`: `bootPayload`.
|
||||
- ADR 0021 (Go-served SPA boot state) and ADR 0023 (active-workspace cookie
|
||||
precedence).
|
||||
|
||||
## TDD sequence
|
||||
|
||||
1. Add a failing `helpers_test.go` regression for a missing task route with a
|
||||
real sibling task and selected workspace.
|
||||
2. Add the valid-route non-duplication and anonymous-bootstrap assertions.
|
||||
3. Implement the minimal route-data-aware fallback in `bootPayload`.
|
||||
4. Refactor only if needed to keep `bootPayload` small and explicit.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
cd apps/backend && go test ./internal/backendapp -run '^(TestBootPayloadAnonymousMissingTaskDoesNotLoadHomeState|TestBootPayloadMissingTaskFallsBackToHomeKanbanState|TestBootPayloadValidTaskKeepsRouteSpecificState)$' -count=1
|
||||
```
|
||||
|
||||
## Risks
|
||||
|
||||
- Running the fallback for successful task routes would duplicate boot work and
|
||||
could expose a briefly incorrect active workspace before task hydration.
|
||||
- Fallback workspace listing must keep the request identity; bypassing the
|
||||
scoped task service would leak cross-user workspace metadata.
|
||||
- Auth-enabled anonymous requests must not reach any fallback loader because
|
||||
identity-free task-service calls are intentionally unscoped internal calls.
|
||||
|
||||
## Output contract
|
||||
|
||||
Report the RED failure, GREEN result, exact files changed, authorization and
|
||||
workspace-precedence evidence, remaining risks, and synchronized task/plan
|
||||
status. Record every exact command and outcome in `## Results`.
|
||||
|
||||
## Results
|
||||
|
||||
Implemented the conditional fallback in
|
||||
`apps/backend/internal/backendapp/helpers.go` and added focused coverage in
|
||||
`apps/backend/internal/backendapp/helpers_test.go`.
|
||||
|
||||
- RED: `cd apps/backend && go test ./internal/backendapp -run 'TestBootPayload(MissingTaskFallsBackToHomeKanbanState|ValidTaskKeepsRouteSpecificState)$' -count=1` — 1 passed, 1 failed; the missing-task assertion reported absent fallback `workspaces` state.
|
||||
- GREEN: the same command after the production change — 2 passed.
|
||||
- Regression check: `cd apps/backend && go test ./internal/backendapp -run '^TestBoot(RouteDataTaskDetailIncludesTaskPageData|Payload(MissingTaskFallsBackToHomeKanbanState|ValidTaskKeepsRouteSpecificState))$' -count=1` — 3 passed.
|
||||
|
||||
The fallback reuses the existing request-scoped Home Kanban builder, so the
|
||||
active-workspace cookie/query/settings/default precedence and authorization
|
||||
boundary remain unchanged. Successful task routes retain their lean
|
||||
route-specific initial state.
|
||||
|
||||
- Security regression: `cd apps/backend && go test ./internal/backendapp -run '^TestBootPayloadAnonymousMissingTaskDoesNotLoadHomeState$' -count=1` — 1 passed after the initial RED failure; the auth-enabled anonymous payload contains no workspace, workflow, repository, or Kanban state.
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
id: "02-recovery-ui-and-e2e"
|
||||
title: "Add unavailable-route recovery UI and E2E"
|
||||
status: completed
|
||||
wave: 2
|
||||
depends_on: ["01-fallback-boot-context"]
|
||||
plan: "plan.md"
|
||||
spec: "../../specs/missing-task-route-recovery/spec.md"
|
||||
---
|
||||
|
||||
# Task 02: Add unavailable-route recovery UI and E2E
|
||||
|
||||
Give the unavailable task surface a workspace-preserving overview action and
|
||||
prove that a cold missing-task route keeps desktop sibling navigation and a
|
||||
phone-safe recovery path.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- The unavailable state keeps its generic error semantics and exposes an
|
||||
accessible overview link scoped to the resolved workspace when one exists.
|
||||
- A desktop cold load lists and can navigate to a visible sibling task in the
|
||||
global sidebar instead of showing **No tasks yet**.
|
||||
- On Pixel 5, the overview action is touch-sized and viewport-contained, does
|
||||
not introduce horizontal document overflow, and opens Home where the sibling
|
||||
task is visible.
|
||||
|
||||
## Files likely touched
|
||||
|
||||
- `apps/web/components/task/task-page-content.tsx`
|
||||
- `apps/web/src/locales/en/common.json`
|
||||
- `apps/web/src/locales/pseudo/common.json`
|
||||
- `apps/web/src/task-detail-route.test.tsx`
|
||||
- `apps/web/components/task/task-page-content.test.tsx`
|
||||
- `apps/web/e2e/tests/task/task-loading-state.spec.ts`
|
||||
- `apps/web/e2e/tests/task/mobile-task-loading-state.spec.ts`
|
||||
|
||||
## Dependencies
|
||||
|
||||
- Task 01 must be complete so production-build E2E receives fallback workspace
|
||||
snapshots from the Go boot payload.
|
||||
|
||||
## Parallelism
|
||||
|
||||
`sequential`. This task consumes Task 01 and its desktop/mobile E2E files share
|
||||
the same production build and task-route behavior.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Spec sections **Desired behavior** and **Regression scenarios**.
|
||||
- Plan sections **Unavailable task recovery action**, **Mobile design
|
||||
contract**, and **E2E Tests**.
|
||||
- `apps/web/components/task/mobile/session-mobile-top-bar.tsx` as the existing
|
||||
task-overview navigation exemplar.
|
||||
- `apps/web/e2e/tests/task/task-loading-state-helpers.ts` and current desktop/
|
||||
mobile loading specs as fixture and routing patterns.
|
||||
|
||||
## TDD sequence
|
||||
|
||||
1. Extend the client route unit test and desktop/mobile E2E specs, then run the
|
||||
focused tests against the current implementation to capture the expected
|
||||
failures.
|
||||
2. Add the localized overview action and workspace-scoped destination.
|
||||
3. Run focused frontend checks.
|
||||
4. Rebuild through the managed E2E runner, pass the desktop regression, then
|
||||
reuse the unchanged build for the mobile regression.
|
||||
|
||||
## Verification
|
||||
|
||||
Run the workspace install once first if `apps/node_modules` is absent:
|
||||
|
||||
```bash
|
||||
cd apps && pnpm install --frozen-lockfile
|
||||
cd apps && pnpm --filter @kandev/web exec vitest run src/task-detail-route.test.tsx
|
||||
cd apps/web && pnpm run typecheck
|
||||
cd apps/web && pnpm run i18n:check && pnpm run i18n:ratchet
|
||||
cd apps/web && pnpm e2e:run tests/task/task-loading-state.spec.ts -- --grep 'keeps sibling tasks available'
|
||||
cd apps/web && pnpm e2e:run --no-build --project mobile-chrome tests/task/mobile-task-loading-state.spec.ts -- --grep 'returns to task overview'
|
||||
```
|
||||
|
||||
Confirm Playwright discovers and runs one intended test for each focused E2E
|
||||
command; `No tests found` is not passing evidence.
|
||||
|
||||
## Risks
|
||||
|
||||
- The overview action must use the fallback store workspace rather than the
|
||||
missing task ID, or it can navigate Home into a different workspace.
|
||||
- The mobile assertion must measure the actual link hitbox and document
|
||||
overflow after the unavailable state settles; visibility alone would miss
|
||||
the touch and containment contract.
|
||||
|
||||
## Output contract
|
||||
|
||||
Report RED and GREEN evidence, exact files changed, desktop and Pixel 5 user
|
||||
outcomes, i18n/typecheck results, generated artifact paths, cleanup, remaining
|
||||
risks, and synchronized task/plan status. Record every exact command and outcome
|
||||
in `## Results`.
|
||||
|
||||
## Results
|
||||
|
||||
Implemented the workspace-aware unavailable-task recovery action and localized
|
||||
the loading/error copy in
|
||||
`apps/web/components/task/task-page-content.tsx` plus the English and pseudo
|
||||
common catalogs. Added route-boundary, desktop, and Pixel 5 E2E coverage.
|
||||
|
||||
- RED: `cd apps/web && pnpm e2e:run --project mobile-chrome tests/task/mobile-task-loading-state.spec.ts -- --grep 'returns to task overview'` — 1 failed as expected because `task-unavailable-overview-link` did not exist.
|
||||
- Client contract: `cd apps && pnpm --filter @kandev/web exec vitest run src/task-detail-route.test.tsx` — 3 passed.
|
||||
- Frontend checks: `cd apps/web && pnpm run typecheck` — passed; `pnpm run i18n:check` — passed (2423 referenced keys, pseudo in sync); `pnpm run i18n:ratchet` — passed.
|
||||
- Desktop GREEN: `cd apps/web && pnpm e2e:run tests/task/task-loading-state.spec.ts -- --grep 'keeps sibling tasks available'` — 1 passed.
|
||||
- Mobile GREEN: `cd apps/web && pnpm e2e:run --no-build --project mobile-chrome tests/task/mobile-task-loading-state.spec.ts -- --grep 'returns to task overview'` — 1 passed.
|
||||
- Targeted Prettier and ESLint checks passed with no errors or warnings; typecheck and the i18n ratchets are clean.
|
||||
- `cd apps && pnpm exec prettier --check web/components/task/task-page-content.tsx web/components/task/task-page-content.test.tsx web/src/task-detail-route.test.tsx web/e2e/tests/task/task-loading-state.spec.ts web/e2e/tests/task/mobile-task-loading-state.spec.ts web/src/locales/en/common.json web/src/locales/pseudo/common.json` — passed.
|
||||
- `cd apps && pnpm --filter @kandev/web exec eslint components/task/task-page-content.tsx components/task/task-page-content.test.tsx src/task-detail-route.test.tsx e2e/tests/task/task-loading-state.spec.ts e2e/tests/task/mobile-task-loading-state.spec.ts` — passed with no errors or warnings.
|
||||
- The managed E2E runner rebuilt the Go backend and Vite bundle, and cleaned its temporary test-results/blob-report directories after each run; no generated artifacts are part of the change.
|
||||
|
||||
The desktop flow keeps sibling rows usable after a cold missing-task route. The
|
||||
Pixel 5 flow keeps the recovery link in the viewport at a 44px hitbox with no
|
||||
horizontal overflow and returns to Home scoped to the resolved workspace.
|
||||
@@ -202,6 +202,7 @@ System pages (Radarr/Sonarr-style) for status, disk usage, database maintenance,
|
||||
| [workflow-settings-autosave](workflow-settings-autosave/spec.md) | archived; superseded by settings-manual-save |
|
||||
| [mobile-quick-chat-topbar](mobile-quick-chat-topbar/spec.md) | building |
|
||||
| [native-code-review](native-code-review/spec.md) | building |
|
||||
| [missing-task-route-recovery](missing-task-route-recovery/spec.md) | draft |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
title: Missing task route recovery
|
||||
status: draft
|
||||
created: 2026-08-03
|
||||
owner: kandev
|
||||
---
|
||||
|
||||
# Missing task route recovery
|
||||
|
||||
## Problem
|
||||
|
||||
A cold navigation to a Kanban task-detail URL whose task is missing, deleted,
|
||||
or inaccessible renders the intended **Task unavailable** state, but the same
|
||||
failed task lookup also prevents the app shell from hydrating its authorized
|
||||
workspace and workflow context. The desktop sidebar therefore reports that
|
||||
there are no tasks even when the selected workspace contains other tasks, and
|
||||
the unavailable route offers no direct recovery action on phone viewports where
|
||||
the desktop sidebar is hidden.
|
||||
|
||||
## Desired behavior
|
||||
|
||||
- A failed cold load of `/t/:taskId` or the compatibility `/tasks/:taskId`
|
||||
renders the existing generic unavailable state without revealing whether the
|
||||
task was deleted, never existed, or is outside the current user's access.
|
||||
- Failure to load the requested task does not prevent the app shell from
|
||||
resolving the user's authorized Kanban workspace with the existing explicit
|
||||
URL parameter, active-workspace cookie, user-setting, and default-workspace
|
||||
precedence.
|
||||
- When the resolved workspace contains other visible tasks, the desktop task
|
||||
sidebar shows them and its rows remain usable navigation targets. The invalid
|
||||
route ID is not synthesized as a sidebar task.
|
||||
- The unavailable state includes a visible action back to the task overview for
|
||||
the resolved workspace. The action is keyboard accessible, touch reachable,
|
||||
and usable when no workspace can be resolved.
|
||||
- A valid task-detail boot continues to hydrate from that task's workspace and
|
||||
does not additionally load or briefly expose fallback workspace data.
|
||||
- Workspace and task authorization remain unchanged. Fallback state contains
|
||||
only workspaces, workflows, repositories, and task snapshots already visible
|
||||
to the current request identity.
|
||||
- Loading feedback remains visible while the task result is unresolved; the
|
||||
unavailable state and recovery controls appear only after failure is known.
|
||||
|
||||
## Regression scenarios
|
||||
|
||||
- **GIVEN** an authorized workspace containing another visible task, **WHEN**
|
||||
the user cold-loads `/t/<missing-id>`, **THEN** the unavailable state appears,
|
||||
the desktop sidebar lists the other task instead of **No tasks yet**, and
|
||||
selecting that row opens the valid task.
|
||||
- **GIVEN** the active-workspace cookie and user settings identify different
|
||||
authorized workspaces, **WHEN** a missing task route is cold-loaded, **THEN**
|
||||
fallback task data comes from the cookie-selected workspace according to the
|
||||
existing boot precedence.
|
||||
- **GIVEN** a valid task-detail URL, **WHEN** it is cold-loaded, **THEN** its
|
||||
route-specific boot data remains authoritative and no fallback workspace
|
||||
bootstrap is added to the top-level initial state.
|
||||
- **GIVEN** a user cannot access the requested task but can access another
|
||||
workspace, **WHEN** the task route loads, **THEN** the generic unavailable
|
||||
state and only that user's authorized fallback workspace tasks are shown.
|
||||
- **GIVEN** no Kanban workspace is available, **WHEN** a missing task route
|
||||
loads, **THEN** the unavailable state remains visible, the sidebar stays
|
||||
empty, and the task-overview action remains safe to activate.
|
||||
- **GIVEN** a phone viewport on a missing task route, **WHEN** the unavailable
|
||||
state renders, **THEN** the task-overview action fits the viewport, has a
|
||||
touch-sized target, and opens the mobile task overview without relying on the
|
||||
desktop sidebar.
|
||||
|
||||
## Constraints
|
||||
|
||||
- The Go-served SPA boot payload remains the first-paint source of workspace
|
||||
context, consistent with ADRs 0021 and 0023.
|
||||
- The repair reuses the existing task, workspace, workflow, and snapshot
|
||||
contracts; it adds no API, persistence, WebSocket, or permission contract.
|
||||
- Fallback hydration is conditional on unavailable task-detail route data so a
|
||||
successful task boot does not duplicate its route-specific resource loading.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Changing task lookup status codes or disclosing why a task is unavailable.
|
||||
- Creating a placeholder task for the invalid route ID.
|
||||
- Redesigning the desktop task sidebar, task switcher, or Kanban overview.
|
||||
- Changing Office task-detail not-found behavior under `/office/tasks/:id`.
|
||||
- Retrying backend failures beyond the existing task-detail client retry.
|
||||
|
||||
## Implementation plan
|
||||
|
||||
- [Missing task route recovery](../../plans/missing-task-route-recovery/plan.md)
|
||||
Reference in New Issue
Block a user