Overhaul Oz Styling #75
Labels
No labels
blocked
bug
duplicate
effort
high
effort
low
effort
medium
effort
minimal
enhancement
help wanted
invalid
pr
feedback
pr
reviewing
question
status
backlog
status
document
status
done
status
implement
status
pending
status
planning
status
reviewing
waiting
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
tepichord/milner#75
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Naina:
Updated on 2026-09-21 based on maintainer feedback.
Overview
Oz's console works but reads as unstyled: one hairline under each row, flat buttons, no motion, weak grouping, and every press is a full-page POST. This plan restyles the page on the existing shadcn-svelte CSS variables — bits-ui plus custom CSS only, per the maintainer's follow-up: no shadcn-svelte, no Tailwind, no new dependency — and moves the four
/api/*posts into+page.server.tsactions behinduse:enhance, so a press updates the table in place. The reported 501 on preview-mode activate is localised and recorded, not guessed at.Technical Approach
Move the four POST handlers into named actions in
oz/src/routes/+page.server.ts.1 2 Keep each body; replace the previewthrow redirect(303, '/?preview=…')with a returned notice, and replace everyhtmlError(...)withfail(status, { notice }).failand nothtmlError:use:enhanceposts withaccept: application/json, so kit devalue-serializes the return value — aResponsethrows "not serializable" and surfaces as a 500 error result (kit 2.70.3actions.js).3 4htmlErrorstays inoz/src/lib/http.tsforcallback/+server.ts.5 6Delete
oz/src/routes/api/{build,activate,delete,front-door}/;grep -rn 'action="/api/' oz/then finds nothing.Enhance every action form; drop the
?preview=/?pr=branch fromload; read the notice fromform.Real-mode Build still redirects to the Woodpecker UI;
use:enhancefollows an external redirect with a full page load (client.js→native_navigation), so that press keeps opening the pipeline.One top-level dialog state variable; close it once the action lands.
$statecannot be declared inside{#each}(state_invalid_placement).11Table: card, zebra, hover, and a sticky header that actually holds.
.table-wrapperneeds an explicit height orstickyhas no scrollport;separate+box-shadowkeeps row lines with the cells.14 15Button variants via the
data-varianthook the stylesheet already expects (dead today).7 bits-ui has no variant system.Leave
Showbyte-identical —tests/test_static.py:3231pins that exact string.13Complete the dialog. Keep no
Portal(the delete<form>lives insideContent, and a portal would move it out of the form); add the missing overlay and the two labelled parts.Motion on
data-state, gated on reduced motion. bits-ui waits ongetAnimations()before unmounting a closing part, so CSS keyframes alone hold it open.9 10 The keyframes repeat the basetranslate(-50%, -50%), so the dialog does not jump.Repaint the two notice colours — the only hex outside the variable set — and group the action cell.
Localise the 501 and record it; the fix is out of scope here. Nothing in the tree or in kit 2.70.3 emits 501, and every preview branch short-circuits before auth.16 unverified (in-repo half verified; the “kit 2.70.3 contains none” half has no cited source)
A log line means the press reached the console and the 501 came from a later hop (router/ingress); a non-501 from the port-forward localises it to the proxy. Fix wherever it lands, independently of the styling.
Update the one test that pins the markup —
tests/test_static.py:3215.12Implementation Details
oz/src/routes/+page.server.tsactions(build/activate/delete/frontDoor); drop the?preview=/?pr=branchoz/src/routes/+page.svelteuse:enhance,form?.notice,openPr,(row.number)keys, table/dialog/button markupoz/src/app.cssoz/src/routes/api/{build,activate,delete,front-door}/tests/test_static.pyConstraints:
app.cssagainsthsl(var(--…)).name="csrf"input; the preview branch returns before it is checked.Log outform a plain POST — it must navigate.oz/src/lib/selection.tsandexpiry.tsstay untouched (pure, shared with the router).Out-of-scope notes:
.darkis dead code — nothing ever adds the class. A theme toggle is its own issue.Acceptance Criteria
oz/src/routes/api/no longer exists, and nothing references/api/{build,activate,delete,front-door}.<th scope="col">and a<caption>are present.data-variant;Showis unchanged.Titleand aDescription, animates open and closed, and the motion is neutralised underprefers-reduced-motion: reduce.Testing Plan
cd oz && deno task test— unit suite green; extendsrc/lib/preview-mode.test.tsonly if a new pure helper lands.cd oz && deno task check— Svelte/TS types clean after the actions land.python3 -m pytest tests/test_static.py -q— the updated anchor plus:Manual, on a preview build showing the five fixture rows: press each action and watch its row change with no navigation.
Manual: keyboard-open the delete dialog and tab through it; the focus trap,
Esc, and the reduced-motion setting are what a screenshot cannot check.Related Issues/PRs
tests/test_static.pyis its markup contract.docs/preview-environment.md§11h — the status codes the preview router can answer, for step 9.Research Findings
getAnimations()before unmounting a closing part and emitsdata-state="open"|"closed"; CSS keyframes on those selectors hold the overlay and content open, so no Sveltetransition:directive is needed.Button.Rootexposes onlydata-button-root— sodata-variantis Oz's own hook.8use:enhanceneeds a same-page action and a devalue-serializable return: posting to a+server.tsendpoint, or returning aResponsesuch ashtmlError, makes kit parse non-JSON and error out.use:enhancefollows a redirect with a navigation, and an externallocationbecomes a full page load (kit 2.70.3client.js→native_navigation), which is how real-mode Build keeps opening Woodpecker.position: stickyon<th>needsborder-collapse: separate(collapsed borders stay behind) and a scroll container with an explicit height, or the header never holds.Pre-Mortem
Responsereturns survive the move, souse:enhancehits the error boundary in real mode. The plan only says to convert everyhtmlError(...)tofail(...), but each of the four handlers also returns a barenew Response(null, { status: 403 })for CSRF rejection and a302with aLocationheader on success, whichuse:enhancecannot JSON-parse. No rendered-page test and no cluster in CI means a rejection press shows the error boundary and a success press silently navigates, and it ships green.tests/test_static.pyreads the files rather than rendering them, so the sticky header (which needs.table-wrapperto actually be a scrollport — with five fixture rows undermax-height: 70vhit never scrolls and never visibly sticks) and the dialog's keyframetranslate(-50%, -50%)alignment are unverified. A plausible-looking diff passes every check while an acceptance criterion is quietly false.We deliberately avoided shadcn because of the package size, everything I requested I would like to be done with Bits UI and custom CSS classes. Not by bringing in shadcn and the large runtime expansion that necessitates.