167 lines
10 KiB
Markdown
167 lines
10 KiB
Markdown
---
|
|
spec: docs/specs/ui/webkit-task-dialog-rendering.md
|
|
created: 2026-08-01
|
|
status: complete
|
|
---
|
|
|
|
# Implementation Plan: WebKit Task Dialog Rendering
|
|
|
|
## Overview
|
|
|
|
Classify the active rendering engine before React mounts, then opt only the Create Task dialog into
|
|
a WebKit-safe CSS path. Prove engine classification with unit tests and prove both the unchanged
|
|
Chromium path and the WebKit desktop/mobile geometry through focused browser tests.
|
|
|
|
## Confirmed root cause
|
|
|
|
`DialogContent` combines fixed centering with translated positioning and a `scale(0.95)` enter/exit
|
|
animation on the same element that contains the form text. WebKit rasterizes transformed,
|
|
composited text-bearing layers differently from Blink, producing the reported soft dialog while
|
|
the same Go-served SPA and fonts remain sharp in Chrome. The overlay and content also share
|
|
`z-50`, leaving their relative composited-layer ordering implicit. The Tauri window is opaque and
|
|
does not configure vibrancy or transparency.
|
|
|
|
## Frontend
|
|
|
|
### Rendering-engine marker
|
|
|
|
- Add `apps/web/lib/browser/rendering-engine.ts` with a pure classifier and a small document-marker
|
|
helper.
|
|
- Classify a user agent as WebKit when it contains `AppleWebKit` and is not a desktop Blink runtime.
|
|
iPhone, iPad, and iPod UAs without Blink compatibility tokens remain on the WebKit path. Branded
|
|
iPadOS desktop-mode Chrome/Edge UAs that carry `Chrome/` or `Edg/` are intentionally treated as
|
|
`other`; this token-based classifier does not infer touch capability from a desktop UA.
|
|
- Treat desktop `Chrome`, `Chromium`, `HeadlessChrome`, `Edg`, `OPR`, and `SamsungBrowser` tokens as
|
|
Blink/non-WebKit compatibility UAs. Firefox and unknown engines fall back to `other`.
|
|
- In `apps/web/src/main.tsx`, apply `data-rendering-engine="webkit|other"` to the document root
|
|
before boot-payload loading and React rendering. Do not persist the value.
|
|
|
|
### Create Task dialog rendering
|
|
|
|
- Add a semantic opt-in data attribute to the Create Task `DialogContent` in
|
|
`apps/web/components/task-create-dialog.tsx`; do not modify the shared default motion in
|
|
`apps/packages/ui/src/dialog.tsx`.
|
|
- Add a semantic nested-confirmation marker to
|
|
`apps/web/components/discard-local-changes-dialog.tsx` so the WebKit stack can elevate that
|
|
confirmation without styling by test IDs.
|
|
- Allow the shared `AlertDialogContent` primitive to pass a scoped overlay class to its portaled
|
|
overlay, keeping the nested stack rule local to the discard confirmation.
|
|
- Add scoped WebKit selectors and opacity-only keyframes in `apps/web/app/globals.css`.
|
|
- Under `html[data-rendering-engine="webkit"]`, override the opted-in Create Task and nested
|
|
confirmation surfaces' animation names so their keyframes never write `transform`; replace
|
|
translated centering with `inset: 0`, automatic margins, and desktop `height: fit-content`; lower
|
|
only the task dialog's own overlay to `z-49` while keeping content at the shared `z-50` modal
|
|
level; and elevate the nested discard confirmation to an explicit `z-52` overlay and `z-53`
|
|
content layer with `height: fit-content` at every viewport width.
|
|
- Preserve the current mobile `width: 100%` and `height: 100%`, desktop 900px width, 85vh maximum
|
|
height, form overflow ownership, rounded corners, focus, and dismissal behavior.
|
|
- Pass `showCloseButton={false}` only to the Create Task dialog so the generic top-right close
|
|
control is absent in every browser while the footer Cancel action remains available.
|
|
- Add `pt-0` to the Create Task surface so it no longer reserves top padding for the removed
|
|
control while preserving horizontal and bottom padding.
|
|
- Leave the existing scale-and-fade utility classes active for non-WebKit engines.
|
|
|
|
### Mobile design contract
|
|
|
|
- **Desktop outcome:** Safari and WebKit-backed Tauri show the existing centered 900px Create Task
|
|
form without transformed text; Chromium retains its current motion.
|
|
- **Mobile entry point:** the existing Kanban floating New Task action opens the same full-height
|
|
dialog.
|
|
- **Nearest shipped exemplar:** the existing mobile Create Task surface and
|
|
`MobileKanbanPage.mobileFab` remain the composition and entry-point baseline.
|
|
- **Hierarchy and primary action:** task name, prompt, repositories, agent profile, and submit
|
|
controls remain in their existing order; task creation remains the primary action.
|
|
- **Presentation rationale:** this is a rendering workaround, so preserving the existing
|
|
full-height mobile dialog is less disruptive than introducing a new drawer or route.
|
|
- **Geometry:** the dialog remains the single viewport-bound surface, keeps its existing internal
|
|
form scroll owner and safe-area behavior, and introduces no document horizontal overflow.
|
|
- **Shared logic:** all task state, validation, selectors, and submit handlers remain unchanged;
|
|
only engine classification and presentation CSS differ.
|
|
- **Mobile proof:** Pixel 5 E2E forces the WebKit marker and verifies full-viewport containment,
|
|
accessible inputs, internal usability, and zero document horizontal overflow.
|
|
|
|
## Tests
|
|
|
|
- **What:** Safari/WKWebView/WebKitGTK, desktop Blink, Firefox, iOS branded browsers, iPadOS
|
|
desktop mode, and unknown-UA classification.
|
|
- **File:** `apps/web/lib/browser/rendering-engine.test.ts`
|
|
- **How:** table-driven Vitest cases against the pure classifier plus marker-helper assertions.
|
|
- **What:** the marker is applied before React boot without persistence.
|
|
- **File:** `apps/web/lib/browser/rendering-engine.test.ts`
|
|
- **How:** pass a detached document root and navigator-like input to the marker helper and assert
|
|
the exact `data-rendering-engine` value.
|
|
|
|
## E2E Tests
|
|
|
|
- **Scenario:** Chromium retains its existing Create Task scale motion and positioning when the
|
|
runtime marker is `other`.
|
|
- **File:** `apps/web/e2e/tests/task/create-task-webkit-rendering.spec.ts`
|
|
- **What to verify:** the early marker is `other`, the dialog keeps the existing animation name,
|
|
translated centering, dimensions, and content-over-overlay hit testing.
|
|
- **Scenario:** the WebKit override removes transforms while preserving centered desktop geometry.
|
|
- **File:** `apps/web/e2e/tests/task/create-task-webkit-rendering.spec.ts`
|
|
- **What to verify:** force the root marker to `webkit` before opening the dialog; assert the
|
|
opacity-only animation name, identity `transform`/zero `translate`, explicit overlay/content
|
|
stacking levels, 900px capped width, viewport-centered bounds, and focused task-name input.
|
|
- **Scenario:** a narrow WebKit Create Task dialog keeps its full-height usable composition.
|
|
- **File:** `apps/web/e2e/tests/task/mobile-create-task-webkit-rendering.spec.ts`
|
|
- **What to verify:** force the WebKit marker, open from `MobileKanbanPage.mobileFab`, assert
|
|
viewport containment, task-name and prompt reachability, internal scrolling when required, and
|
|
no document horizontal overflow.
|
|
- **Scenario:** a nested dirty-tree confirmation remains compact and above the task form.
|
|
- **File:** `apps/web/e2e/tests/task/create-task-branch-selector.spec.ts`
|
|
- **What to verify:** force the WebKit marker, trigger the discard confirmation, and assert
|
|
opacity-only motion, transform-free centering, the explicit `z-52` overlay / `z-53` content
|
|
stack, and preserved form state after Cancel.
|
|
|
|
## Implementation Tasks
|
|
|
|
- [x] [Task 01: Classify the rendering engine](task-01-rendering-engine-marker.md)
|
|
- [x] [Task 02: Apply WebKit-safe task dialog rendering](task-02-webkit-dialog-rendering.md)
|
|
|
|
Execution is sequential in the primary conversation. No subagent delegation is planned or
|
|
authorized.
|
|
|
|
## Validation
|
|
|
|
Run from `apps` unless noted otherwise:
|
|
|
|
- `pnpm --filter @kandev/web test -- --run lib/browser/rendering-engine.test.ts` — 14 passed,
|
|
including the desktop Mac Chrome Blink-token regression.
|
|
- `pnpm run typecheck` from `apps/web` — no errors.
|
|
- `pnpm --filter @kandev/web lint` — passed.
|
|
- `pnpm exec prettier --check app/globals.css components/discard-local-changes-dialog.tsx
|
|
components/task-create-dialog.tsx e2e/tests/task/create-task-webkit-rendering.spec.ts
|
|
lib/browser/rendering-engine.test.ts lib/browser/rendering-engine.ts
|
|
../../docs/plans/webkit-task-dialog-rendering/plan.md
|
|
../../docs/plans/webkit-task-dialog-rendering/task-02-webkit-dialog-rendering.md` from `apps/web`
|
|
— passed.
|
|
- `pnpm --filter @kandev/web build` — passed.
|
|
- `pnpm --filter @kandev/web e2e:run --no-build --host --project chromium --
|
|
tests/task/create-task-webkit-rendering.spec.ts --workers=1` — 2 passed.
|
|
- `pnpm --filter @kandev/web e2e:run --no-build --host --project mobile-chrome --
|
|
tests/task/mobile-create-task-webkit-rendering.spec.ts --workers=1` — 1 passed.
|
|
- `pnpm --filter @kandev/web e2e:run --no-build --host --project chromium --
|
|
tests/task/create-task-branch-selector.spec.ts --grep "confirm modal lists files" --workers=1` — 1
|
|
passed, confirming the nested WebKit discard confirmation's motion and explicit z-52/z-53
|
|
stacking.
|
|
- `pnpm --filter @kandev/web e2e:run --no-build --host --project chromium --
|
|
tests/task/dialog-body-lock.spec.ts --workers=1` — 1 passed.
|
|
- `pnpm --filter @kandev/web e2e:run --no-build --host --project mobile-chrome --
|
|
tests/task/mobile-dialog-body-lock.spec.ts --workers=1` — 1 passed.
|
|
- Native macOS Safari/Tauri visual comparison — not run; this Linux worktree cannot launch those
|
|
engines. PR screenshots document the desktop and mobile states for native review.
|
|
|
|
## Risks
|
|
|
|
- Browsers do not expose a standardized rendering-engine API. The classifier therefore needs
|
|
explicit compatibility-UA tests and must fail safe to the existing rendering path.
|
|
- Chromium E2E can prove selector behavior and geometry after forcing the root marker, but it
|
|
cannot reproduce WKWebView rasterization. Final acceptance requires a macOS Tauri/Safari visual
|
|
comparison at actual-size zoom (`Cmd+0`) after the automated checks pass.
|
|
- Transform-free absolute centering can accidentally stretch an auto-height desktop dialog. The
|
|
WebKit desktop selector must set `height: fit-content`, while the narrow layout retains the
|
|
existing full-height rule.
|
|
- The opt-in attribute deliberately limits blast radius. Other dialogs remain out of scope until
|
|
they show the same reproduced WebKit defect.
|