Blog Visual Design Improvement — Visual-Only Design Polish (Palette+Scout Joint Evaluation)
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 stylestyle={{ borderColor: 'var(--border)' }}creating a dual path bypassing tokens. - Choice: One-to-one substitution rules for full conversion:
Remove all 14 inline
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-brandstyle={{ borderColor/backgroundColor: 'var(--...)' }}occurrences and unify toborder-border/bg-border-strongTailwind token classes. Explicitly addbg-surface text-textto 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 withmb-10, breaking visual rhythm into the body (post card list). - Choice: Header
py-4 → py-6, home subtitlemb-10 → mb-8. Body containermax-w-3xland post listspace-y-6are 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-2title +text-sm text-gray-500date +text-xs bg-brand-50 py-0.5tags — 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:
- h1:
text-3xl mb-2→text-4xl mb-4 leading-tight tracking-tight(letter-spacing + line height + weight) - Tag container:
mt-2 gap-2→mt-4 gap-3(breathing room) - Tag chip:
py-0.5→py-1 font-medium(readability) - Add
border-b border-border pb-8 mb-10at bottom of header block → visual separator from body (prose)
- h1:
- 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-smdefault +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-subtlemeta - 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
- Container:
- 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/typographyplugin's.prosedefaults. 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, hovertext-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, backgroundsurface-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 introducingshadow-smto PostCard (72-2), body cards felt relatively flat in comparison. - Choice: Full normalization of all 11 types is excessive scope (L workload) → Apply
shadow-smdefault to 4 representative types (Callout, MetricCard, ServiceCard, ArchService) only. ArchService maintains existinghover:shadow-mdkeeping interactive intensity. In dark mode shadow is weak, but existingborder-bordertoken auto-inverts in.darkto 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.
transitionclasses scattered or missing per component, and focus-visible handling only explicitly set in PostCard (72-2). - Choice: Add global
atransition: color 150ms ease+a/button:focus-visible2-step shadow (2px surface offset + 2px brand ring) as ring + offset expression to globals.css@layer base. Implements ring + offset equivalent to Tailwindring-offsetwith CSS only, auto-adapts in dark mode as--surfaceauto-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.tsplugin, but direct CSS variable usage is more intuitive.
- Code Paths:
blog/src/app/globals.css(@layer baseextension) - 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.mdcalled 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. Inlinestyle={{ 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-200maximizes 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/typographyand want to apply brand color/dark mode/readability to body, directly defining.prose child selectorsin globals.css@layer componentsis 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 asvar(--brand)/var(--text-muted)for dark mode auto-adaptation. Applyline-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)b272b7achore(blog): color/border tokenization + header/home spacing rhythm readjustment (72-1)cf120f1style(blog): post header typographic/spacing redesign (72-3)02907c2style(blog): PostCard visual hierarchy/interaction reinforcement (72-2)2c23302style(blog): prose body element style extension (72-4)24a4253style(blog): card component shadow consistency cleanup (72-5)bdc4cd3style(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)
- Pages (3):
- 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 buildpassed 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 withreact/forbid-component-propsor custom rule. - nginx trailing slash pattern enhancement — Enhance
blog/nginx.conftry_filespattern to$uri $uri.html $uri/index.html =404. Sprint 70 remaining item, already registered in MEMORY.md.