react
Technical notes on web development, DevOps, and AI integration.
53 articles
- 22:43frontend
The Nav Accent That Moved on Its Own Turned Out to Be Modulo
The AI entry in my navbar changed color after a menu reorder. A lesson in modulo index color assignment and fixing it with data identity.
TL;DR: Styling nav entries with a modulo index ties colors to render position, so reordering the menu silently reshuffles accents. Pinning the special "Ask AI" entry to its href identity keeps its color stable. Adding an idle pill also satisfies WCAG 1.4.1, since shape reinforces hue for users who can't distinguish colors.
#tailwind#react#css - 12:43frontend
A Separate Mobile Chat Component With One Answer Logic
My mobile chat needed its own messaging-app-style frame, but the answer logic stays single-sourced by reusing the exported TurnBody.
TL;DR: A desktop three-column chat layout doesn't survive shrinking to phones, so the author built a separate mobile thread component using the familiar messaging-app pattern. Sticky header, chat bubbles, composer, all done with CSS sticky and backdrop-filter. Only the frame is duplicated; shared TurnBody logic keeps desktop and mobile in sync.
#react#frontend#ux - 12:40frontend
Duplicate Order 1: When My History Sidebar Vanished at lg
My chat history sidebar vanished at the lg breakpoint: a CSS order tie-break and a details element forced into flex duty, two roots in one commit.
TL;DR: A missing sidebar at lg breakpoints came down to two bugs from a mobile accordion refactor. Equal flex order values let the center wrapper win the source-order tie-break, fixed with an explicit lg:order-2. A details element can't flex-size children in Chromium, so it became state-driven with aria-expanded instead.
#css#react#tailwind - 12:35frontend
44px Buttons, 16px Fonts: Standard Numbers in Mobile Chat
A responsive pass on my AI chat panel: 44px send targets per WCAG, 16px input fonts against iOS zoom, and a 65vh transcript with overscroll containment.
TL;DR: A desktop-tidy chat panel broke on mobile in three unrelated ways, each fixed by a standardized number. Buttons grew to 44px per WCAG target-size guidance, and the textarea got a 16px font to stop Safari's forced zoom. A 65vh transcript with overscroll containment stops scroll chaining and pull-to-refresh.
#css#tailwind#react - 16:57frontend
Badge [2] in the Sidebar Isn't the Second Card You See
Citation badges in the sidebar list are computed with indexOf from the original array, not the filtered iteration position, so numbers stay truthful.
TL;DR: While refactoring a chat sources sidebar, badge numbers were computed over a filtered array, so dropping knowledge entries shifted every web source's number and broke reference links. The fix: compute numbers with indexOf on the original turn.sources, so badges always match answer references. Lesson: reference numbers must come from original data, not filtered display order.
#react#frontend#ux - 16:41frontend
It's Never Just a Re-Render
Knowledge cards now render inline below the chat answer; reusing the card component takes a named export, unique per-panel ids, and its own expand state.
TL;DR: Moving knowledge cards inline into the chat answer panel sounded trivial but broke two hidden contracts. SourceCard became a named export, and card ids got a prefix to avoid duplicate ids across the document. Expanded state now lives locally per panel, keyed by turn and slug, so opening a card in one panel doesn't leak into the other.
#react#frontend#ux - 03:07frontend
I Moved the Chat Mode Toggle to the Top of the Sidebar
Mode chips at the bottom of a sidebar stay invisible until you scroll. Moved to the top, with type and aria-pressed kept intact.
TL;DR: Moving the chat mode switch to the sidebar's top fixed a real usability bug: users only found the search-versus-about toggle after scrolling past the decision point. Core lesson: layout order is decision order, so mode-setting controls belong above scrollable content. The move kept accessibility attributes like aria-pressed and type=button intact, shrinking the form to one responsibility.
#react#accessibility#tailwind - 00:46frontend
Conditional Footer in Next.js: Keep the Client Small
Hiding the footer on a chat route without giving up server rendering, using the composition pattern from the Next.js docs.
TL;DR: Hiding a site footer on a full-height chat page, the author rejected CSS display hacks and demoting the whole layout to a Client Component. Instead, a tiny ConditionalFooter client wrapper uses usePathname to return null on /ai routes while the server-rendered Footer passes through as children. Lesson: keep client conditions in small leaves and slot Server Components in as children.
#nextjs#react#typescript - 00:39frontend
React Duplicate Keys from localStorage: My Two-Layer Fix
Legacy localStorage ids collided in my React chat. Here is the reindex plus seeding fix that silenced the duplicate key error.
TL;DR: A React "duplicate key" warning came from legacy localStorage chat data holding duplicate turn ids, plus a counter effect that read stale values and minted collisions. The fix: reindex every restored turn to 0..n-1 and seed the next id inside the restore effect itself. Lesson: treat persisted data as untrusted and normalize it at the boundary.
#react#localstorage#typescript