v0.3 Phase 3: status grammar — LumotiaNotice left-bar refactor

Phase 3 audit found that both LumotiaStatusPill and LumotiaNotice
already shipped in v0.2 Phase 5. Phase 3 reduced to an audit-and-adapt
pass.

LumotiaStatusPill ($lib/components/StatusPill.svelte) is already
correct for quietware. Pattern: neutral pill background + neutral
label text + role colour confined to a 6x6 px dot. Colour is
supplementary, label is always perceivable. Works unchanged across
all three quietware modes. No changes shipped.

LumotiaNotice ($lib/ui/LumotiaNotice.svelte) refactored to the
left-bar accent pattern. Previously the role colour rendered on the
icon outline and on the entire surround border at 30% opacity; in
quietware light mode that left the bright #FFCD00 caution colour
faded against cream paper. New pattern:

  - 4-px solid LEFT border in the tone colour, 100% opacity.
  - Soft tone-tinted background (10% opacity) for ambient cue.
  - Subtle neutral outer border on the other three sides.
  - Icon, title and body text all use --color-text.

Visible signal lives on the large bar (works at any contrast level);
foreground text uses the always-legible neutral. Matches Material
Design "banner with leading accent" and IBM Carbon "inline
notification" guidance. ARIA semantics (role="alert" for danger,
role="status" for the rest) preserved.

Design-system-v2 preview route already imports both primitives. The
updated Notice renders correctly with the existing preview content;
no additional wiring required.

Caution remains a fill-only convention on cream by design. Any
darkened yellow reads as olive ("looks like poop", per Jake), so the
fill-only rule stays in force and this pattern delivers it cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-15 13:03:00 +01:00
parent a9972f76bb
commit db9c50c548
2 changed files with 37 additions and 21 deletions

View File

@@ -208,6 +208,18 @@ Each phase ships as its own PR. Each is flag-gated by `html[data-design="quietwa
- Combine with `data-theme="light"` and `data-contrast="high"` (already wired by v0.2) to reach the light and high-contrast quietware modes. - Combine with `data-theme="light"` and `data-contrast="high"` (already wired by v0.2) to reach the light and high-contrast quietware modes.
- No new dependencies. No bundle-size impact in the off path. - No new dependencies. No bundle-size impact in the off path.
### Phase 3 — Status grammar primitives. Landed 2026-05-15.
Architectural finding: both `LumotiaStatusPill` and `LumotiaNotice` already shipped in v0.2 Phase 5. Phase 3 became an audit-and-adapt task rather than a new-component build.
- `LumotiaStatusPill` ([src/lib/components/StatusPill.svelte](../../src/lib/components/StatusPill.svelte)) audited. Pattern is already accessibility-correct: neutral pill background + neutral label text + role colour confined to a 6×6 px dot. Colour is supplementary, label is always perceivable. Works unchanged across all three quietware modes. No changes shipped.
- `LumotiaNotice` ([src/lib/ui/LumotiaNotice.svelte](../../src/lib/ui/LumotiaNotice.svelte)) refactored to the left-bar accent pattern. Old version put the role colour on the icon + outline; in quietware light mode that rendered a bright `#FFCD00` caution icon faded against cream paper. New version places the role signal on a 4-px solid left border (visible at any contrast level), with the rest of the surround as a soft tone-tinted background plus subtle neutral outer border. Icon, title and body text all use `--color-text`. Matches Material "banner with leading accent" and IBM Carbon "inline notification" guidance. ARIA semantics (`role="alert"` for danger, `role="status"` for the rest) preserved.
- Design-system-v2 preview route already imports `LumotiaNotice` and `LumotiaStatusPill`. The updated Notice renders correctly there with the existing preview content; no additional preview wiring needed.
- All four tones (info / caution / danger / success) verified by the pattern itself: the visible signal is the left bar, which is `border-l-{tone}` at 100% opacity, working at any colour intensity. Caution remains a fill-only convention on cream by design.
--- ---
## Regression diary ## Regression diary

View File

@@ -1,15 +1,22 @@
<script lang="ts"> <script lang="ts">
// v0.2 Phase 5 primitive — inline notice block. Replaces the ~10 // v0.2 Phase 5 primitive — inline notice block. Replaces the ~10
// ad-hoc <div class="bg-{danger,warning,success}/10 …"> patterns // ad-hoc <div class="bg-{danger,warning,success}/10 …"> patterns
// scattered across pages. Four tones map to the semantic tokens // scattered across pages. Four tones map to the semantic tokens.
// introduced in Phase 3: //
// info — soft blue-grey (--color-info) // v0.3 Phase 3 update — left-bar accent pattern. The visible status
// caution — tuned amber (--color-caution) // signal now lives on a 4-px solid left border in the tone colour
// danger — tuned red (--color-danger) // (100% opacity, fill-grade chroma); the surrounding container uses
// success — moss (--color-success) // a soft tone-tinted background and a subtle outer border. Icon,
// The icon and aria-role are tone-aware. Dismissible variant is // title and body text all use --color-text so they remain legible
// opt-in for transient toasts inline; persistent notices stay // across every theme combination, including quietware light mode
// visible. // where --color-caution is the bright #FFCD00 fill (would fail as
// text on cream). Same accessibility role semantics retained.
//
// Pattern matches Material Design "banner with leading accent" and
// IBM Carbon "inline notification" guidance: status colour on a
// large fill surface (the bar), neutral foreground text. Works for
// any tone in any theme regardless of WCAG-text contrast on the
// role colour itself.
import { Info, AlertTriangle, AlertOctagon, CheckCircle, X } from "lucide-svelte"; import { Info, AlertTriangle, AlertOctagon, CheckCircle, X } from "lucide-svelte";
import type { Snippet } from "svelte"; import type { Snippet } from "svelte";
@@ -33,29 +40,25 @@
classes = "", classes = "",
}: Props = $props(); }: Props = $props();
const palette: Record<Tone, { bg: string; border: string; fg: string; icon: typeof Info }> = { const palette: Record<Tone, { bar: string; bg: string; icon: typeof Info }> = {
info: { info: {
bar: "border-l-info",
bg: "bg-info/10", bg: "bg-info/10",
border: "border-info/30",
fg: "text-info",
icon: Info, icon: Info,
}, },
caution: { caution: {
bar: "border-l-caution",
bg: "bg-caution/10", bg: "bg-caution/10",
border: "border-caution/30",
fg: "text-caution",
icon: AlertTriangle, icon: AlertTriangle,
}, },
danger: { danger: {
bar: "border-l-danger",
bg: "bg-danger/10", bg: "bg-danger/10",
border: "border-danger/30",
fg: "text-danger",
icon: AlertOctagon, icon: AlertOctagon,
}, },
success: { success: {
bar: "border-l-success",
bg: "bg-success/10", bg: "bg-success/10",
border: "border-success/30",
fg: "text-success",
icon: CheckCircle, icon: CheckCircle,
}, },
}; };
@@ -69,10 +72,11 @@
<div <div
role={ariaRole} role={ariaRole}
class="px-4 py-3 rounded-lg border text-[13px] flex items-start gap-2.5 class="px-4 py-3 rounded-r-lg rounded-l-sm border border-l-4
{tonePal.bg} {tonePal.border} {classes}" border-border-subtle text-[13px] flex items-start gap-2.5
{tonePal.bar} {tonePal.bg} {classes}"
> >
<ToneIcon size={16} class="mt-0.5 shrink-0 {tonePal.fg}" aria-hidden="true" /> <ToneIcon size={16} class="mt-0.5 shrink-0 text-text" aria-hidden="true" />
<div class="flex-1 min-w-0 leading-relaxed text-text"> <div class="flex-1 min-w-0 leading-relaxed text-text">
{#if title} {#if title}
<p class="font-semibold text-text mb-1">{title}</p> <p class="font-semibold text-text mb-1">{title}</p>