From a1ebaba9c11c1fdc7f65c9f9da1b844b14ef9490 Mon Sep 17 00:00:00 2001 From: Bryce Seefieldt Date: Tue, 30 Jun 2026 14:36:06 -0400 Subject: [PATCH 1/3] docs(phase2c4): Tech Stack Keyboard design-system + feature catalog entry - Update design-system source map: add TechStackKeyboard, TechStackIcons entries - Add Phase 2C.4 implementation notes: keycap sizing fix, proportional keyboard, radiogroup+CRT pattern - Create feature page: tech-stack-keyboard.md with full validation, testing, and troubleshooting - Document 16 vendored Simple Icons + 5 text legend fallbacks - Include CSS --keycap-unit variable fix for proportional key sizing (1u/1.25u/1.5u/2u) - Reference ADRs (Visual Identity, Dark Mode, React2Shell hardening) - Link to E2E tests (smoke tests cover home page render) - Accessibility standards: keyboard navigation, aria-live, radiogroup roles, reduced-motion --- .../tech-stack-keyboard.md | 168 ++++++++++++++++++ docs/20-engineering/design-system/index.md | 12 ++ 2 files changed, 180 insertions(+) create mode 100644 docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md diff --git a/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md b/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md new file mode 100644 index 0000000..452bee9 --- /dev/null +++ b/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md @@ -0,0 +1,168 @@ +--- +title: 'Tech Stack Keyboard' +description: 'Interactive radiogroup keyboard MODULE 01 showcasing curated tech stack with proportional key sizing, category color-coding, and CRT detail panel.' +tags: [feature, design-system, interaction, accessibility, theming] +sidebar_position: 6 +--- + +## Purpose + +- Feature name: Tech Stack Keyboard (MODULE 01) +- Why this feature exists: Demonstrate depth-language keycap styling, proportional key sizing, and accessible radiogroup interaction patterns in a production home-page context. The module showcases the curated tech stack with category-based color-coding and interactive detail feedback. + +## Scope + +### In scope + +- Interactive keyboard interface with 21 curated tech-stack keys +- 6 category groupings (languages, frontend, backend, data, cloud, tooling) with distinct color roles +- Radiogroup + CRT detail pattern for accessible keyboard navigation and feedback +- Proportional key sizing (1u/1.25u/1.5u/2u) with accurate CSS width ratios +- Keyboard navigation (arrows, Home/End, Enter/Space) and focus-visible states +- Dark and light theme support using design-system tokens + +### Out of scope + +- Real-time tech-stack updates or dynamic key generation +- Mobile/responsive layout optimization beyond current Tailwind grid behavior +- Animation or typing effects (reduced-motion compliant) + +## Prereqs / Inputs + +- Design system tokens (color, type scale, depth language) locked in Phase 2C +- Keycap and Keypad primitives available and working +- OperatingPrinciplesPanel radiogroup + CRT pattern to replicate +- 21 curated tech-stack blurbs from portfolio content +- Simple Icons SVG library (or text legends for missing icons) + +## Procedure / Content + +### Feature summary + +- Feature name: Tech Stack Keyboard (MODULE 01 / MY TOOLBOX) +- Feature group: Theming and Accessibility +- Technical summary: Interactive keyboard component using React radiogroup pattern with client-side state management, keyboard event handling (arrows, Home, End, Enter/Space), and aria-live region for detail feedback. Built from reusable Keycap/Keypad primitives with proportional sizing (1u/1.25u/1.5u/2u) via CSS `--keycap-unit` variable. +- Low-tech summary: Click a tech-stack key to see its name and a one-sentence description in the detail panel. Use arrow keys or click to navigate. Each category has a color to help organize the stack. + +### Feature in action + +- Where to see it working: `/` (home page, MODULE 01 / TECH STACK section) +- Visual treatment: Proportional keycap board with 6 category sections; selected key shows backlit state; detail panel on the right displays tech name and blurb +- Interaction: Click or keyboard navigate to select a key; detail panel updates with `aria-live=polite` feedback +- Theme behavior: Both light (warm beige keycaps) and dark (phosphor-green accents) modes fully supported + +### Confirmation Process + +#### Manual + +- **Steps:** + 1. Navigate to `/` + 2. Scroll to MODULE 01 / TECH STACK section + 3. Click on any key (e.g., TypeScript, React, PostgreSQL) + 4. Verify the detail panel updates with the key's name and blurb + 5. Use arrow keys to navigate; verify all keys are reachable + 6. Press Home/End to jump to first/last key + 7. Press Enter or Space to select the currently focused key + 8. Toggle light/dark theme; verify keycap colors and contrast are correct + +- **What to look for:** + - Selected key has backlit appearance (raised top-face glow) + - Detail panel updates immediately and reads aloud (if screen reader present) + - All 21 keys are rendered in correct proportions (1u, 1.25u, 1.5u, 2u widths are visually accurate) + - Category labels (LANGUAGES, FRONTEND, etc.) are visible above/below key groups + - Keyboard navigation wraps (End → Home, Home → End on arrows) + - No JavaScript errors in browser console + - Reduced-motion CSS media query respected (no unnecessary transitions) + +- **Artifacts or reports to inspect:** + - E2E test: `/portfolio-app/tests/e2e/smoke.spec.ts` → smoke tests cover home page render + - Design tokens: `/portfolio-app/src/app/globals.css` → `--keycap-unit`, keycap size classes, category color roles + - Component: `/portfolio-app/src/components/home/TechStackKeyboard.tsx` → radiogroup setup, keyboard handler, aria-live detail + +#### Tests + +- Unit tests: none (component is light on logic; mostly UI/state) +- E2E tests: + - [smoke.spec.ts](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/tests/e2e/smoke.spec.ts#L69) — home page renders without errors + - [smoke.spec.ts: Resilience checks › Reduced motion](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/tests/e2e/smoke.spec.ts#L55) — reduced-motion CSS respected + +### Potential behavior if broken or misconfigured + +- **Keycap widths all equal**: `--keycap-unit` CSS variable not set or size classes using `grid-column: span` instead of explicit widths → all keys render same size, defeating proportional sizing. +- **Detail panel doesn't update**: `selectedId` state not wired to CRT region, or radiogroup role missing → interaction feels broken. +- **Keyboard navigation broken**: `onKeyDown` handler not attached, or `tabIndex` logic incorrect → arrow/Home/End do nothing, focus not managed. +- **Color contrast fails**: keycap cap/legend pair not validated against depth-language endpoints; verify AA compliance in `globals.css` token pairs. +- **Missing icons**: Simple Icons CDN down or SVG path incorrect → fallback text legend should still render legibly. +- **Accessibility failure**: radiogroup role missing, aria-checked not managed, aria-live region not present → fails WCAG 2.1 AA keyboard and screen-reader testing. + +### Long-term maintenance notes + +- **Keycap sizing**: If `--keycap-unit` or gap size changes, update all four `.keycap--*u` width calc() expressions to keep ratios accurate. +- **Icon asset drift**: Text legends (Java, AWS, Azure, REST, SQL Server) are fallbacks; if Simple Icons adds these in future releases, consider updating to SVG glyphs. +- **Blurb updates**: Tech-stack blurbs are hardcoded in `STACK_KEYS` array; update there, not in the template files. +- **Category colors**: Each category uses a `--key-*` or `--key2-*` role; if design-system color palette changes, verify category contrast still meets AA. +- **Keyboard nav**: Home/End and arrow behavior relies on `STACK_KEYS.length` and modulo math; keep test coverage on edge cases (wrap, single key, etc.). + +### Dependencies, libraries, tools + +- React 19 (hooks: `useState`, `useMemo`, `useId`) +- TypeScript (union types for StackKeyCategory) +- Tailwind CSS (grid, responsive utilities) +- Design tokens from `src/app/globals.css` (color roles, depth language, keycap size classes) +- Simple Icons library (16 vendored SVG files as React components) + +### Source code references (GitHub URLs) + +- [TechStackKeyboard component](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/src/components/home/TechStackKeyboard.tsx) +- [Vendored tech stack icons](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/src/icons/TechStackIcons.tsx) +- [Home page integration](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/src/app/page.tsx#L90-L95) +- [Keycap width-ratio fixes in globals.css](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/src/app/globals.css#L1160-L1165) +- [ESLint config override for safe object injection](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/eslint.config.mjs#L10-L14) + +### ADRs + +- [ADR-0021: Visual Identity and Design-System Direction](/10-architecture/adr/adr-0021-visual-identity.md) — design direction and materiality principles +- [ADR-0014: Class-Based Dark Mode](/10-architecture/adr/adr-0014-class-based-dark-mode.md) — theme switching mechanism +- [ADR-0018: React2Shell Hardening Baseline](/10-architecture/adr/adr-0018-react2shell-hardening-baseline.md) — input validation and security defense-in-depth + +### Runbooks + +- Performance Troubleshooting: [rbk-portfolio-performance-troubleshooting](/50-operations/runbooks/rbk-portfolio-performance-troubleshooting.md) — if MODULE 01 render time regresses + +### Additional internal references + +- Design System: [/20-engineering/design-system/index.md](/20-engineering/design-system/index.md) — full source map and phase implementation notes +- UX Engineering Standards: [/20-engineering/ux-engineering-standards.md](/20-engineering/ux-engineering-standards.md) — accessibility, motion, responsive, and testing standards +- Keycap and Keypad primitives: [/20-engineering/design-system/components.md](/20-engineering/design-system/components.md) (when created) +- OperatingPrinciplesPanel pattern: source in portfolio-app at `src/components/home/OperatingPrinciplesPanel.tsx` + +### External reference links + +- [Simple Icons](https://simpleicons.org/) — SVG icon library used for tech stack glyphs +- [MDN: aria-live](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live) — screen-reader feedback pattern +- [MDN: Keyboard events](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent) — arrow key handling + +## Validation / Expected outcomes + +- **Visual:** All 21 keys render in correct proportions; category colors match design-system roles; both light and dark themes are readable and AA-compliant. +- **Interaction:** Clicking selects a key; detail panel updates immediately; keyboard navigation (arrows, Home/End, Enter/Space) works smoothly. +- **Accessibility:** radiogroup role and aria-checked states properly set; detail region has aria-live=polite; all keys reachable by keyboard; no focus traps. +- **Performance:** MODULE 01 renders within 16ms budget; no layout shifts after initial paint; reduced-motion CSS respected. +- **Tests:** E2E smoke tests pass; all 21 keys render without console errors; no contrast violations detected. + +## Failure modes / Troubleshooting + +| Symptom | Root Cause | Fix | +|---------|-----------|-----| +| All keys same size | `--keycap-unit` not set or `.keycap--Xu` classes using `grid-column: span` | Update `globals.css` size classes to use `width: calc(X * var(--keycap-unit))` | +| Detail panel blank | `selectedId` state not synced to CRT region; aria-live not present | Add aria-live="polite" to detail region; wire onClick to `setSelectedId` | +| Keyboard nav broken | `onKeyDown` handler not attached or `tabIndex` logic broken | Verify all keys have `tabIndex={isActive ? 0 : -1}` and `onKeyDown` prop | +| Color contrast fails | depth-language endpoints not validated; keycap + legend pair fails AA | Run contrast checker on cap color against legend color; adjust `--depth-*` tokens if needed | +| Missing icon renders as broken image | SVG path incorrect or icon doesn't exist in Simple Icons | Use text legend fallback (Java, AWS, Azure, REST, SQL Server) instead | +| Screen reader doesn't announce updates | aria-live missing or value is "off" not "polite" | Verify detail panel has `role="status"` and `aria-live="polite"` | + +## References + +- Phase 2C.4 Tech Stack Keyboard specification: `portfolio-app/tmp/phase2c4-tech-stack-keyboard-spec.md` +- Keycap and Keypad component documentation: [/20-engineering/design-system/index.md](/20-engineering/design-system/index.md) +- Design-system source map: [/20-engineering/design-system/index.md#source-map-where-the-design-lives-in-portfolio-app](/20-engineering/design-system/index.md#source-map-where-the-design-lives-in-portfolio-app) diff --git a/docs/20-engineering/design-system/index.md b/docs/20-engineering/design-system/index.md index b11b99f..efab2c7 100644 --- a/docs/20-engineering/design-system/index.md +++ b/docs/20-engineering/design-system/index.md @@ -71,6 +71,8 @@ The interface presents one coherent hardware fiction in two states: light mode i | Keycap primitive | `src/components/Keycap.tsx` | Reusable raised key with tokenized cap/legend variants, sidewalls, and state classes | | Keypad primitive | `src/components/Keypad.tsx` | Inset panel keypad composition built from `Keycap` descriptors | | Career era cards | `src/components/home/CareerEraCards.tsx` | Four channel-strip style cards for era highlights | +| Tech stack keyboard | `src/components/home/TechStackKeyboard.tsx` | Interactive radiogroup keyboard with 21 curated tech-stack keys and CRT detail panel | +| Tech stack icons | `src/icons/TechStackIcons.tsx` | 16 vendored Simple Icons SVGs + 5 text legends (Java, AWS, Azure, REST, SQL Server) | | Design preview | `src/app/design-tokens-preview/page.tsx` | Canonical rendered component gallery (keep complete and in-sync with component PRs) | ### Phase 2C.2 implementation notes @@ -93,6 +95,16 @@ The interface presents one coherent hardware fiction in two states: light mode i - `Keycap` and `Keypad` are currently **design-tokens preview primitives only** (see `src/app/design-tokens-preview/page.tsx`). - Home-page integration was intentionally deferred in this phase; `src/app/page.tsx` contains TODO markers for future copy and lead-slot promotion decisions. +### Phase 2C.4 implementation notes + +- TechStackKeyboard is the first production home-page module using Keycap/Keypad as interactive components (not preview-only). +- Keycap width-ratio bug fixed: proportional sizing now uses `--keycap-unit` CSS variable (`3.78rem = base 3.2rem + gap 0.58rem`) with explicit width calcs on `.keycap--1u|1-25u|1-5u|2u` size classes. +- TechStackKeyboard implements the OperatingPrinciplesPanel radiogroup + CRT pattern (accessible annunciator with `aria-live=polite` detail region) for interactive tech-stack browsing. +- 21 curated keys organized into 6 categories (languages, frontend, backend, data, cloud, tooling), each with hardcoded blurbs. +- Icon assets: 16 Simple Icons SVGs vendored as React components; 5 text legends used for missing or semantically incorrect icons (Java, AWS, Azure, REST, SQL Server). +- Keyboard navigation: arrows/Home/End for traversal, Enter/Space for selection; focus-visible states on all keys. +- Reduced-motion safe: no animation complexity, only CSS state-based styling. + ## Sub-references - **Components** (`components.md`) — each primitive's purpose, props, accessibility behavior, and source path. From b4c0fe5a697c71acfe263466269d71c3161b893a Mon Sep 17 00:00:00 2001 From: Bryce Seefieldt Date: Tue, 30 Jun 2026 14:54:20 -0400 Subject: [PATCH 2/3] docs(phase2c4): update lint-security reference to inline scoped suppressions --- .../features/04-theming-accessibility/tech-stack-keyboard.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md b/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md index 452bee9..5b316b6 100644 --- a/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md +++ b/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md @@ -117,7 +117,7 @@ sidebar_position: 6 - [Vendored tech stack icons](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/src/icons/TechStackIcons.tsx) - [Home page integration](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/src/app/page.tsx#L90-L95) - [Keycap width-ratio fixes in globals.css](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/src/app/globals.css#L1160-L1165) -- [ESLint config override for safe object injection](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/eslint.config.mjs#L10-L14) +- [Inline scoped suppressions for object-injection lint rule](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/src/components/home/TechStackKeyboard.tsx#L276) ### ADRs From f1b94178867025d1fa6c11591261fde38b515068 Mon Sep 17 00:00:00 2001 From: Bryce Seefieldt Date: Tue, 30 Jun 2026 14:56:03 -0400 Subject: [PATCH 3/3] docs(phase2c4): align troubleshooting table for clarity --- .../tech-stack-keyboard.md | 18 +++++++++--------- docs/20-engineering/design-system/index.md | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md b/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md index 5b316b6..26689ad 100644 --- a/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md +++ b/docs/00-portfolio/features/04-theming-accessibility/tech-stack-keyboard.md @@ -82,7 +82,7 @@ sidebar_position: 6 #### Tests - Unit tests: none (component is light on logic; mostly UI/state) -- E2E tests: +- E2E tests: - [smoke.spec.ts](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/tests/e2e/smoke.spec.ts#L69) — home page renders without errors - [smoke.spec.ts: Resilience checks › Reduced motion](https://github.com/bryce-seefieldt/portfolio-app/blob/feat/phase2c4-tech-stack-keyboard/tests/e2e/smoke.spec.ts#L55) — reduced-motion CSS respected @@ -152,14 +152,14 @@ sidebar_position: 6 ## Failure modes / Troubleshooting -| Symptom | Root Cause | Fix | -|---------|-----------|-----| -| All keys same size | `--keycap-unit` not set or `.keycap--Xu` classes using `grid-column: span` | Update `globals.css` size classes to use `width: calc(X * var(--keycap-unit))` | -| Detail panel blank | `selectedId` state not synced to CRT region; aria-live not present | Add aria-live="polite" to detail region; wire onClick to `setSelectedId` | -| Keyboard nav broken | `onKeyDown` handler not attached or `tabIndex` logic broken | Verify all keys have `tabIndex={isActive ? 0 : -1}` and `onKeyDown` prop | -| Color contrast fails | depth-language endpoints not validated; keycap + legend pair fails AA | Run contrast checker on cap color against legend color; adjust `--depth-*` tokens if needed | -| Missing icon renders as broken image | SVG path incorrect or icon doesn't exist in Simple Icons | Use text legend fallback (Java, AWS, Azure, REST, SQL Server) instead | -| Screen reader doesn't announce updates | aria-live missing or value is "off" not "polite" | Verify detail panel has `role="status"` and `aria-live="polite"` | +| Symptom | Root Cause | Fix | +| -------------------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | +| All keys same size | `--keycap-unit` not set or `.keycap--Xu` classes using `grid-column: span` | Update `globals.css` size classes to use `width: calc(X * var(--keycap-unit))` | +| Detail panel blank | `selectedId` state not synced to CRT region; aria-live not present | Add aria-live="polite" to detail region; wire onClick to `setSelectedId` | +| Keyboard nav broken | `onKeyDown` handler not attached or `tabIndex` logic broken | Verify all keys have `tabIndex={isActive ? 0 : -1}` and `onKeyDown` prop | +| Color contrast fails | depth-language endpoints not validated; keycap + legend pair fails AA | Run contrast checker on cap color against legend color; adjust `--depth-*` tokens if needed | +| Missing icon renders as broken image | SVG path incorrect or icon doesn't exist in Simple Icons | Use text legend fallback (Java, AWS, Azure, REST, SQL Server) instead | +| Screen reader doesn't announce updates | aria-live missing or value is "off" not "polite" | Verify detail panel has `role="status"` and `aria-live="polite"` | ## References diff --git a/docs/20-engineering/design-system/index.md b/docs/20-engineering/design-system/index.md index efab2c7..942cbd5 100644 --- a/docs/20-engineering/design-system/index.md +++ b/docs/20-engineering/design-system/index.md @@ -71,8 +71,8 @@ The interface presents one coherent hardware fiction in two states: light mode i | Keycap primitive | `src/components/Keycap.tsx` | Reusable raised key with tokenized cap/legend variants, sidewalls, and state classes | | Keypad primitive | `src/components/Keypad.tsx` | Inset panel keypad composition built from `Keycap` descriptors | | Career era cards | `src/components/home/CareerEraCards.tsx` | Four channel-strip style cards for era highlights | -| Tech stack keyboard | `src/components/home/TechStackKeyboard.tsx` | Interactive radiogroup keyboard with 21 curated tech-stack keys and CRT detail panel | -| Tech stack icons | `src/icons/TechStackIcons.tsx` | 16 vendored Simple Icons SVGs + 5 text legends (Java, AWS, Azure, REST, SQL Server) | +| Tech stack keyboard | `src/components/home/TechStackKeyboard.tsx` | Interactive radiogroup keyboard with 21 curated tech-stack keys and CRT detail panel | +| Tech stack icons | `src/icons/TechStackIcons.tsx` | 16 vendored Simple Icons SVGs + 5 text legends (Java, AWS, Azure, REST, SQL Server) | | Design preview | `src/app/design-tokens-preview/page.tsx` | Canonical rendered component gallery (keep complete and in-sync with component PRs) | ### Phase 2C.2 implementation notes