Blog Visual Design Improvement — Visual-Only Design Polish (Palette+Scout Joint Evaluation)

Date
ImpactCritical

Context

blog.algo-su.com (Next.js 15 SSG + Tailwind 3 + MDX) had its content reorganized into 6 topic-based posts in Sprint 68, and in Sprint 70 the 36 ASCII diagrams in the body were converted to 11 React visual components — making the structure, content, and components stable. However, following user feedback ("improve the screen"), visual depth (shadow/hover), typographic hierarchy, spacing rhythm, and token consistency had not been touched.

PM clearly separated the nature of the work: "Not content improvement, but visible design improvement". That is, content additions or new feature creation — MDX body/copy, Hero metric additions, code block copy buttons, dark mode toggle, post navigation, etc. — are out of scope. Only "how existing markup looks" is addressed.

Oracle performed a joint evaluation by two agents instead of deciding alone:

  • Palette — Before→After proposals from design token/style perspective (10 sections)
  • Scout — Scouting visual weaknesses from a first-time visitor perspective (TOP 12+)

The intersection of both evaluations was adopted as core priority. Items Scout found but with "content/feature addition" character (Hero section, Back nav, dark mode toggle, code block copy button) were excluded from this sprint by Oracle authority and deferred. The result was refined to 6 work units (72-1 ~ 72-6) and ADR (72-D), all processed as 6 local commits.

This sprint is "pure design polishing" unlike "Sprint 71 bug fix", and is also the first case validating that the Palette+Scout joint evaluation can become the standard entry flow for design work in the AlgoSu 12-Agent system.

Decisions

D1: Design work entry flow — Oracle → Palette + Scout joint evaluation

  • Context: Design work is difficult to determine by a single agent. Palette knows token/style SSoT but not user experience; Scout claims to know user experience but not the token system. Asking only one side risks (a) arbitrary hardcoding ignoring tokens or (b) token cleanup the user doesn't feel.
  • Choice: When a design work trigger occurs, invoke Oracle skill to delegate Palette and Scout in parallel. Palette does code-based evaluation (file:line citation + Before→After description), Scout does build output/markup-based user perspective evaluation (first visitor's impression). Oracle aggregates both results into priority candidates, blocking scope (content/feature addition) violating items, then submits to PM.
  • Alternatives:
    • PM directly narrows scope with AskUserQuestion and proceeds with single work unit — high PM cognitive load, low agent system utilization. Actually attempted at start of this sprint, then rejected.
    • Palette evaluation alone — risk of "token cleanup done but first impression unchanged" without user experience validation.
  • Code Paths: agents/commands/palette.md, agents/commands/scout.md, .claude/commands/algosu-oracle.md
  • Note: This decision is permanently recorded in memory feedback_design_workflow.md as the standard workflow for future design work.

D2: Full tokenization of hardcoded colors/borders (72-1)

  • Context: Tailwind palette color classes used directly — text-gray-500/600/400, border-gray-200, dark:border-gray-800, text-brand-500/600/700, bg-brand-50 — scattered across pages/cards/visual components. Also, all 11 visual components had inline React style style={{ borderColor: 'var(--border)' }} creating a dual path bypassing tokens.
  • Choice: One-to-one substitution rules for full conversion:
    text-gray-500/600     → text-text-muted
    text-gray-400         → text-text-subtle
    border-gray-200       → border-border
    dark:border-gray-800  → (remove, --border auto-inverts in .dark)
    text-brand-600        → text-brand
    text-brand-700        → text-brand-strong
    bg-brand-50           → bg-brand-soft
    hover:border-brand-500 → hover:border-brand
    
    Remove all 14 inline style={{ borderColor/backgroundColor: 'var(--...)' }} occurrences and unify to border-border / bg-border-strong Tailwind token classes. Explicitly add bg-surface text-text to body.
  • Alternatives: Gradual substitution — risk of hardcoding recurring in new work units. Safer to bring grep to 0 occurrences at once so subsequent work units start from a clean state.
  • Code Paths: blog/src/app/layout.tsx, blog/src/app/page.tsx, blog/src/app/posts/[slug]/page.tsx, blog/src/components/post-card.tsx, blog/src/components/blog/{architecture-map,hierarchy-tree,kv,mermaid,metric-grid,phase-timeline,pipeline,service-grid,tier-matrix,tier-stack}.tsx

D3: Spacing/rhythm redistribution (72-1)

  • Context: Header felt cramped with py-4, and home subtitle had excessive spacing with mb-10, breaking visual rhythm into the body (post card list).
  • Choice: Header py-4 → py-6, home subtitle mb-10 → mb-8. Body container max-w-3xl and post list space-y-6 are already good, kept as-is.
  • Alternatives: Redefine all page spacing at once — high regression risk. Only touch the two most impactful points, leave the rest for 72-2/72-3 per-component realignment.
  • Code Paths: blog/src/app/layout.tsx, blog/src/app/page.tsx

D4: Post header visual hierarchy reinforcement (72-3)

  • Context: First screen when entering post detail: text-3xl font-bold mb-2 title + text-sm text-gray-500 date + text-xs bg-brand-50 py-0.5 tags — visual hierarchy nearly flat. Hard for users to grasp "what this post is, when written, and what category" at a glance.
  • Choice: 4 simultaneous improvements in single component change:
    1. h1: text-3xl mb-2text-4xl mb-4 leading-tight tracking-tight (letter-spacing + line height + weight)
    2. Tag container: mt-2 gap-2mt-4 gap-3 (breathing room)
    3. Tag chip: py-0.5py-1 font-medium (readability)
    4. Add border-b border-border pb-8 mb-10 at bottom of header block → visual separator from body (prose)
  • Alternatives: Add hero image — content/asset addition in scope. Add signature — same reason, deferred.
  • Code Paths: blog/src/app/posts/[slug]/page.tsx

D5: Home PostCard multi-axis interaction (72-2)

  • Context: Home card has border-gray-200 p-5 hover:border-brand-500 — on hover only color changes, no depth/movement/shadow change. Hard for first visitors to feel "this is a clickable card", and 6 cards are monotonous in the same tone.
  • Choice: 3-axis hover feedback (border + shadow + translate) + internal hierarchy realignment in PostCard:
    • Container: shadow-sm default + hover:shadow-md hover:border-brand hover:-translate-y-0.5 transition-all duration-200
    • Padding: p-5 → p-6
    • Date promoted to top text-xs uppercase tracking-wide text-text-subtle meta
    • Title: text-lg font-semibold → text-xl font-bold leading-snug + group-hover:text-brand (card is <a>, use group)
    • Excerpt: line-clamp-2 leading-relaxed (stable area)
    • Tags reuse same tokens as 72-3 (intra-sprint consistency)
    • focus-visible ring for keyboard focus visualization
  • Alternatives:
    • Distribute accent colors per card — Palette recommended, but meaning attribution is weak without "latest post highlight". 6 posts all same date makes it unnecessary. Hold.
    • Introduce thumbnail images — public directory is empty and asset creation needed, out of scope.
  • Code Paths: blog/src/components/post-card.tsx

D6: Token-based prose body element customization extension (72-4)

  • Context: Almost entirely dependent on @tailwindcss/typography plugin's .prose defaults. globals.css had minimal corrections for only 3 types (table/inline code/blockquote), while h2/h3 hierarchy, link underline, strong/em weight, ul/ol markers, hr spacing, etc. were all plugin defaults. Body appeared as "standard markdown with no distinctiveness".
  • Choice: Add 8 element token-based rules to globals.css @layer components:
    • h2/h3/h4: Redefine spacing/size/letter-spacing/weight (emphasize section boundaries)
    • p: line-height 1.75
    • a: text-brand + underline-offset 2 + decoration 2 + transition, hover text-brand-strong
    • strong/em: Specify weight/color
    • ul/ol/li: Spacing/padding/marker color alignment
    • hr: 2.5rem spacing + border-border
    • Inline code: padding 0.375rem → 0.5rem, radius/font-size reinforced
    • blockquote: Left bar 4px → 6px, background surface-muted → brand-soft (contrast reinforced)
  • Alternatives:
    • Switch to shiki/Prism for code block syntax enhancement — build tool change, out of scope.
    • Introduce dedicated CJK font for prose — web font loading/performance, out of scope.
  • Code Paths: blog/src/app/globals.css (8 additions in @layer components, reinforce existing table/code/blockquote)
  • Note: All colors reuse existing CSS variables — 0 new token definitions.

D7: Apply shadow-sm default to 4 representative card types (72-5)

  • Context: Of 11 visual components, almost no depth. Only ArchService has hover:shadow-md, no default shadow. After introducing shadow-sm to PostCard (72-2), body cards felt relatively flat in comparison.
  • Choice: Full normalization of all 11 types is excessive scope (L workload) → Apply shadow-sm default to 4 representative types (Callout, MetricCard, ServiceCard, ArchService) only. ArchService maintains existing hover:shadow-md keeping interactive intensity. In dark mode shadow is weak, but existing border-border token auto-inverts in .dark to maintain separation via brightness difference.
  • Alternatives:
    • Apply to all 11 — L workload + need to verify visual consistency for each diverse component (Pipeline arrows, TierStack matrix, HierarchyTree tree, etc.). Separate to next sprint.
  • Code Paths: blog/src/components/blog/{callout,metric-grid,service-grid,architecture-map}.tsx

D8: Interaction base layer (72-6)

  • Context: Link transitions and keyboard focus visualization not defined globally. transition classes scattered or missing per component, and focus-visible handling only explicitly set in PostCard (72-2).
  • Choice: Add global a transition: color 150ms ease + a/button:focus-visible 2-step shadow (2px surface offset + 2px brand ring) as ring + offset expression to globals.css @layer base. Implements ring + offset equivalent to Tailwind ring-offset with CSS only, auto-adapts in dark mode as --surface auto-inverts.
  • Alternatives:
    • Introduce shadcn/ui — blog doesn't use shadcn, overkill to introduce framework for single utility addition.
    • Tailwind base config extension with utility-only — expressible via tailwind.config.ts plugin, but direct CSS variable usage is more intuitive.
  • Code Paths: blog/src/app/globals.css (@layer base extension)
  • Note: PostCard already explicitly specified focus-visible ring in 72-2 with same expression as global rule — harmless duplication, kept.

Patterns

P1: Oracle → Palette + Scout joint evaluation workflow

  • Where: This entire sprint. Role division in agents/commands/palette.md + agents/commands/scout.md called in parallel for design work.
  • When to Reuse: When design/UI/visual improvement work arrives. Especially when (a) work scope spans multiple components/pages, (b) it's unclear "where to start", and (c) it's not trivial work like a single typo/color fix. When invoking Oracle skill, assign clear roles to each agent (Palette: code-based token evaluation / Scout: user perspective visual weaknesses) with common constraints (no content changes, no feature additions, plan mode read-only). Oracle organizes the intersection of both results as priority, cross-section as candidate alternatives, blocks scope-violating items, and returns final candidate list to PM. PM only needs to approve priority to immediately proceed with work unit division.

P2: Token SSoT grep 0-occurrence guarantee

  • Where: Full substitution across blog/src/** (D2)
  • When to Reuse: In projects with design tokens, "do all colors go through tokens?" can only be verified with grep. At work unit end, rg "text-gray-|border-gray-|dark:border-gray-|brand-(50|500|600|700|900)" src/ result must be 0 occurrences for token SSoT to be intact. Inline style={{ borderColor/backgroundColor: 'var(--...)' }} must also be 0 occurrences (becomes a path bypassing Tailwind tokens). This sprint unified it into work unit 1 so subsequent work units start from a clean state.

P3: Multi-axis hover feedback (border + shadow + translate)

  • Where: blog/src/components/post-card.tsx (D5)
  • When to Reuse: When giving hover feedback to clickable cards/panels/list items, single-axis change (color only) is weak for users to perceive "this is interactive". Bundling 3-axis simultaneous changes (border color + shadow intensity + translate position) into transition-all duration-200 maximizes interactive intensity with depth/movement/highlight happening simultaneously. Even when shadow weakens in dark mode, other 2 axes maintain feedback, stable in both light/dark. Adding focus-visible ring provides same intensity for keyboard users.

P4: prose customization — token reuse first

  • Where: blog/src/app/globals.css @layer components (D6)
  • When to Reuse: When using @tailwindcss/typography and want to apply brand color/dark mode/readability to body, directly defining .prose child selectors in globals.css @layer components is more intuitive and advantageous for token reuse than touching prose's variable system (--tw-prose-*). Key principle: no new CSS variable definitions, use existing tokens only. Colors reference directly as var(--brand)/var(--text-muted) for dark mode auto-adaptation. Apply line-height 1.75, letter-spacing -0.01em. Reinforce blockquote with thicker left bar (4px → 6px) and background (surface-muted → brand-soft) for contrast.

Metrics

  • Commits (AlgoSu): 6 (b272b7a..bdc4cd3)
    • b272b7a chore(blog): color/border tokenization + header/home spacing rhythm readjustment (72-1)
    • cf120f1 style(blog): post header typographic/spacing redesign (72-3)
    • 02907c2 style(blog): PostCard visual hierarchy/interaction reinforcement (72-2)
    • 2c23302 style(blog): prose body element style extension (72-4)
    • 24a4253 style(blog): card component shadow consistency cleanup (72-5)
    • bdc4cd3 style(blog): global transition/focus-visible addition (72-6)
    • (+ 1 ADR commit planned)
  • Files changed (AlgoSu): 16 files
    • Pages (3): blog/src/app/layout.tsx, blog/src/app/page.tsx, blog/src/app/posts/[slug]/page.tsx
    • Card (1): blog/src/components/post-card.tsx
    • Visual components (11): blog/src/components/blog/{architecture-map,callout,hierarchy-tree,kv,mermaid,metric-grid,phase-timeline,pipeline,service-grid,tier-matrix,tier-stack}.tsx
    • Global CSS (1): blog/src/app/globals.css
    • Documentation (1): docs/adr/sprints/sprint-72.md (this file)
  • Files changed (aether-gitops): 0 (no blog manifest changes — only image rebuild needed)
  • Lines: ~+153 / -84 (code only; excluding ADR)
  • Build: cd blog && npm run build passed after each work unit end (10 pages SSG, First Load JS 103-104 kB maintained)
  • New external dependencies: None
  • New CSS variables / Tailwind tokens: None (100% existing token reuse)

Follow-up Recommendations (outside Sprint 72 scope)

Add to MEMORY.md follow-up section:

  • Dark mode toggle UI introduction — Dark mode is technically implemented but no toggle entry point, users cannot activate it. Introduce next-themes + sun/moon icon toggle in header + localStorage persistence. New Client Component required.
  • Code block language label + copy button — New Client Component (code-block.tsx). Can also enrich syntax highlighting if reviewed together with shiki/Prism transition.
  • Hero / overview summary section — "67 sprints / 2,432 tests / 12 agents / 6 services" metric cards on home first screen. Content addition so separate PM decision needed. MetricGrid component can be reused.
  • Post → list back nav, next/previous post card — "← Blog Home" at top + "Next post" card at bottom of post detail page. Information architecture change so separate work unit.
  • Visual component 11 types full shadow normalization — This sprint only processed 4 types (Callout/Metric/Service/ArchService). Remaining 7 types (Pipeline/TierStack/TierMatrix/PhaseTimeline/HierarchyTree/Mermaid/KV) deferred to next sprint. Visual consistency verification needed for each component.
  • inline style={{ }} ESLint rule introduction — Block token system bypass (G1 lesson). Enforce with react/forbid-component-props or custom rule.
  • nginx trailing slash pattern enhancement — Enhance blog/nginx.conf try_files pattern to $uri $uri.html $uri/index.html =404. Sprint 70 remaining item, already registered in MEMORY.md.