Skip to content

feat(frontend): UI/UX improvements - dark mode, accessibility, SEO - #21

Merged
junhoyeo merged 8 commits into
mainfrom
feat/frontend-ui-improvements
Dec 17, 2025
Merged

feat(frontend): UI/UX improvements - dark mode, accessibility, SEO#21
junhoyeo merged 8 commits into
mainfrom
feat/frontend-ui-improvements

Conversation

@junhoyeo

@junhoyeo junhoyeo commented Dec 17, 2025

Copy link
Copy Markdown
Owner

Summary

This PR delivers comprehensive frontend improvements including dark-only theme enforcement, accessibility enhancements, SEO metadata, and code quality improvements.

Changes

πŸŒ™ Dark Mode Only

  • Removed light mode support entirely - app is now dark-only
  • Deleted ThemeToggle.tsx component (was unused)
  • Removed .light CSS class and all light theme variables
  • Simplified useSettings.ts to only manage color palette
  • Force colorMode="night" in Primer provider
  • Migrated 200+ hardcoded hex colors to CSS variables for consistency

β™Ώ Accessibility Improvements

  • TabBar: Added role="tablist", role="tab", aria-selected, keyboard navigation (Arrow keys, Home/End)
  • Navigation: Added aria-label to nav element and interactive buttons
  • GraphControls: Added aria-pressed, aria-label to all controls and selects
  • BreakdownPanel: Added role="region" and aria-label to close button
  • Tooltip: Added role="tooltip"
  • Profile components: Added tablist pattern with keyboard navigation, aria-labels on buttons

πŸ” SEO Metadata

  • Added generateMetadata function to user profile pages for dynamic SEO
  • Added static metadata to Settings, Local, and Device pages
  • Split pages into Server/Client components to support Next.js 15 metadata exports
  • Created: ProfilePageClient.tsx, SettingsClient.tsx, LocalClient.tsx, DeviceClient.tsx

🧹 Code Quality

  • Removed stubbed account deletion button from settings page
  • Removed unused TrashIcon import
  • Cleaner component architecture with server/client separation
  • Simplified theme management code (~250 lines removed)

Files Changed

Theme (Dark Mode Enforcement)

  • globals.css - Removed .light class
  • useSettings.ts - Removed theme preference logic
  • PrimerProvider.tsx - Force dark mode
  • ThemeToggle.tsx - Deleted

Components (CSS vars + Accessibility)

  • TabBar.tsx, Navigation.tsx, GraphControls.tsx, BreakdownPanel.tsx
  • Tooltip.tsx, Skeleton.tsx, DataInput.tsx
  • GraphContainer.tsx, StatsPanel.tsx, TokenGraph3D.tsx
  • profile/index.tsx (60+ color replacements + ARIA improvements)

Pages (SEO)

  • (main)/page.tsx, settings/page.tsx, local/page.tsx, device/page.tsx
  • u/[username]/page.tsx (with generateMetadata)

Testing

  • TypeScript compilation passes
  • Dark theme displays correctly
  • Keyboard navigation works on TabBar and ProfileTabBar
  • All ARIA attributes properly applied
  • No broken functionality

…me support

- Update globals.css with theme-aware CSS variables
- Update themes.ts with theme-aware utility functions
- Replace hardcoded hex colors (#141415, #FFFFFF, #696969, etc.) with var(--color-*) in:
  - Skeleton.tsx: loading state components
  - DataInput.tsx: JSON input form
  - GraphContainer.tsx: graph wrapper
  - StatsPanel.tsx: statistics display
  - TokenGraph3D.tsx: 3D visualization
  - Tooltip.tsx: hover tooltips

This enables proper light/dark theme switching across the application.
Add ARIA attributes, keyboard navigation, and focus management:

TabBar.tsx:
- Add role="tablist" and role="tab" semantic roles
- Add aria-selected for active state
- Add keyboard navigation (Arrow keys, Home/End)
- Add visible focus ring styles

Navigation.tsx:
- Add aria-label="Main navigation" to nav element
- Add aria-labels to avatar and sign-in buttons
- Add aria-hidden to decorative icons

GraphControls.tsx:
- Add role="group" and aria-labels to control groups
- Add aria-pressed for toggle buttons
- Add aria-labels to selects and filter buttons

BreakdownPanel.tsx:
- Add role="region" to panel container
- Add aria-label to close button

profile/index.tsx:
- Add tablist pattern to ProfileTabBar with keyboard navigation
- Add aria-labels to Share and GitHub link buttons
- Replace 60+ hardcoded colors with CSS variables

Also includes CSS variable migration for theme support in these components.
- Remove non-functional "Danger Zone" section from settings page
- Remove unused TrashIcon import
- Extract client-side logic to SettingsClient.tsx for SSR metadata support
- Add static SEO metadata: title "Settings - Token Usage"

The account deletion feature was showing an alert saying "not yet implemented".
Removing the non-functional button improves UX by not misleading users.
Add SEO metadata to all pages following Next.js 15 App Router patterns:

User Profile (/u/[username]):
- Add generateMetadata function for dynamic SEO per user
- Include OpenGraph and Twitter card metadata
- Extract client logic to ProfilePageClient.tsx

Local Page (/local):
- Add static metadata: "Local Data - Token Usage"
- Extract client logic to LocalClient.tsx

Device Page (/device):
- Add static metadata: "Device Authorization - Token Usage"
- Extract client logic to DeviceClient.tsx

The server/client split is required because Next.js 15 doesn't allow
metadata exports from Client Components ("use client").
- Replace hardcoded colors with CSS variables in leaderboard page
- Update package.json and lockfile with dependencies
…iables

Document all required environment variables for local development:
- DATABASE_URL: PostgreSQL connection string
- GITHUB_CLIENT_ID/SECRET: GitHub OAuth credentials
- NEXT_PUBLIC_URL: Application URL for OAuth redirects

This helps developers understand what's needed to run the frontend locally.
@junhoyeo
junhoyeo marked this pull request as ready for review December 17, 2025 11:47
@junhoyeo junhoyeo changed the title feat(frontend): comprehensive UI/UX improvements - theme, accessibility, SEO feat(frontend): UI/UX improvements - dark mode, accessibility, SEO Dec 17, 2025
@junhoyeo
junhoyeo merged commit e7f687a into main Dec 17, 2025
2 checks passed
@junhoyeo
junhoyeo deleted the feat/frontend-ui-improvements branch December 17, 2025 22:53
haunchen added a commit to haunchen/tokscale that referenced this pull request Aug 5, 2026
… went unidentified

`SessionModels::sole_model` served rows carrying neither `junhoyeo#19` nor `junhoyeo#21` whenever the file named exactly one model. That counted named ids, not models: a conversation where one row carries a display label no row ever identifies ran at least two models while naming only one, and an unlabelled row there could be either. It inherited the single named id and billed a model switch under the wrong model.

Gate the fallback on every label in the file resolving to a machine id, so those rows stay `unknown` β€” the same rule the label path already followed, now applied to the unlabelled one.

Also mark `junhoyeo#21` optional in the field table. It was present on every row observed so far, but the parser has never required it and the `sole_model` path exists precisely for rows that carry neither field.
junhoyeo pushed a commit that referenced this pull request Aug 6, 2026
…1051)

parse_gen_metadata read the model from chatModel.#19 and fell back to the
literal string "unknown", which inferred_provider_from_model cannot place,
producing unpriceable antigravity/unknown rows. Antigravity CLI drops a
whole group of chatModel fields (1, 2, 8, 10, 16, 19) on some continuation
and tool turns, but #21 β€” the model display label β€” survives on every one,
and sibling rows carry the machine id next to the identical label.

Turns are now attributed by resolving that label against labels seen
elsewhere in the same database.

Rejected: fall back to the conversation's sole model whenever a row lacks
a label | a conversation containing a model switch would bill the wrong
model instead of staying unknown (cubic P1, fixed in 73d5937 by
withholding the fallback when any label is unresolved)
Confidence: high
Scope-risk: narrow
Directive: post-#1053 these rows no longer abort a submission β€” they are
excluded silently, so a regression here is invisible rather than loud
Not-tested: a database where the same display label maps to two different
machine ids
junhoyeo pushed a commit that referenced this pull request Aug 18, 2026
…1131)

* fix(antigravity-cli): resolve routing-label rows from display labels

Antigravity CLI writes the generic routing label `gemini-default` as
`chatModel.responseModel` (#19) whenever the router picked the model, leaving
the concrete tier only in the sibling `#21` display label (e.g. "Gemini 3.5
Flash (Low)"). The parser preserved the label verbatim, so those rows were
excluded from `tokscale submit` as unpriced despite carrying a tier-identifying
display label. Across real databases this silently dropped ~218M tokens / $74
of Gemini usage.

Resolve such rows in three steps: use a concrete `#19` as before; otherwise
recover the machine id from a sibling row sharing the display label (the
existing `SessionModels` mechanism); otherwise map the display label itself via
a verified lookup table. A routing label is never treated as evidence of a
concrete model, so it no longer poisons the `by_display` ambiguity check when a
real sibling id exists. Rows that still resolve to nothing keep `gemini-default`
verbatim, so the submission-time exclusion (`is_generic_routing_label` in
lib.rs) continues to apply rather than guessing.

Display labels are server-supplied and could be renamed or localized, so only
labels verified against real user databases are mapped; anything unknown returns
None and the routing label is preserved. Fixes #1116.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(antigravity-cli): map "Gemini 3.5 Flash (Low)" to gemini-3.5-flash-extra-low

Align the display-label mapping with the existing alias table
(`model_placeholder_m187` β†’ `gemini-3.5-flash-extra-low` in
`aliases.rs:85`) so IDE and CLI Low-tier usage resolves to the same
model id. The previous value (`gemini-3.5-flash`) split the tier across
two model rows. Prices are identical today, but per-tier rows would
diverge β€” the exact scenario `aliases.rs:76-79` was written to guard
against.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant