Skip to content

Added dark mode cuz your app is extremely nice (I use it for myself) …#5

Open
msjabata25 wants to merge 6 commits into
beltromatti:developerfrom
msjabata25:Dark-mode
Open

Added dark mode cuz your app is extremely nice (I use it for myself) …#5
msjabata25 wants to merge 6 commits into
beltromatti:developerfrom
msjabata25:Dark-mode

Conversation

@msjabata25

@msjabata25 msjabata25 commented Jun 11, 2026

Copy link
Copy Markdown

Added dark mode cuz your app is extremely nice (I use it for myself) but the lack of a dark mode (and my allergy to light mode) is an ick i needed to fix.

Fix cta buttons, quiz/flashcards feedback colors, added chat markdown support (small change with md suppport but honestly very nice to have). Also added detection for if the user already has dark mode in general or not

  • Added --button-primary-bg CSS var so CTA buttons stay dark in both themes

  • Added --feedback-correct/wrong CSS vars for adaptive green/rose indicators

  • Fixed all CTA buttons (Generate/Reveal/Next/Send/Start) to use --button-primary-bg

  • Fixed quiz option feedback, VerdictBadge, DeckComplete, FeedbackCard colors

  • Darkened flashcard rating tones (50→200, 200→400) for dark mode contrast

  • Fixed chat user bubble background in dark mode

  • Added react-markdown rendering to chat Bubble component

Note: The Feynman session where the bubble thing is is i think still light mode but i'll try and fix it in a later contribution


Summary by cubic

Adds a system-aware dark mode with SSR application and a Settings toggle, with live theme changes across the app. Also hardens the visualizer sandbox and fixes settings persistence races.

  • New Features

    • Dark mode via html.dark tokens and ThemeProvider; follows saved or system preference, reacts to matchMedia and SETTINGS_EVENT, and prevents FOUC with SSR + inline script.
    • Theme-aware visualizers and PDFs: Graph observes theme changes and redraws; ThreeDView updates clearColor on theme toggles without teardown; right-pane, modals, and PDFs use surface/shadow tokens.
    • Chat bubbles render Markdown via react-markdown, preserve newlines, block images, and sanitize https?:// links.
  • Bug Fixes

    • Settings: POST preserves theme; restored hydration guard to prevent stale overwrites (theme toggle is exempt for instant apply); ThemeProvider no-ops on fetch failures; listens to OS preference changes.
    • Sandbox: fixed constructor-chain bypass — original constructors are no longer reachable from model code; eval remains shadowed; threat model documented.
    • Reliability: atomic writes with unique tmp suffixes for settings, docs, KG, and work context; KG builder returns an errored state instead of throwing; added docId validation (regex widened to allow hyphens).
    • UI polish: error toasts/badges and flashcard dots use feedback tokens; VoiceBlob keeps a 1:1 aspect ratio.

Written for commit ca54643. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR adds a system-aware dark mode to the app, including SSR theme injection to prevent FOUC, a ThemeProvider for live theme reactivity, and a settings toggle with system-preference detection. It also backfills theme tokens across all UI components, adds Markdown rendering to chat bubbles, and includes several reliability improvements (atomic file writes, docId validation, sandbox hardening in viz-runtime, KG builder returning errored state instead of throwing).

  • Dark mode system: theme is read at SSR time and injected as html.dark; a blocking inline script handles the system-preference fallback before first paint; ThemeProvider reacts to SETTINGS_EVENT and matchMedia changes without async fetch on mount.
  • Sandbox fix (lib/viz-runtime.ts): ORIG_FUNC/ORIG_ASYNC/ORIG_GEN are captured at module scope (outside new Function reach) and constructors are nulled in a wrapper closure before model code runs, then restored in finally — correctly closes the closure-escape vector flagged in the previous review.
  • Reliability fixes: atomic writes with UUID tmp suffixes across settings-store, kg, store, and work-context; docId regex validation on every path helper; KG build returns an errored graph state instead of rejecting to prevent unhandled rejections.

Confidence Score: 5/5

Safe to merge — the dark-mode implementation is thorough and the reliability fixes are sound. The three flagged items are minor cosmetic regressions.

The SSR + inline-script FOUC prevention is correctly implemented, the sandbox closure-escape fix is structurally sound (module-scope constants are genuinely unreachable from new Function bodies), atomic writes with UUID tmp suffixes are a clear improvement, and the docId validation closes a real path-construction gap. All three open comments are visual/UX nits: the Electron background flash only affects pre-render milliseconds for light-mode users, the amber error banner loss is cosmetic, and the missing follows-system reset path is a feature gap rather than broken behaviour.

electron/main.js (hardcoded dark titleBarOverlay/backgroundColor) and components/SettingsButton.tsx (no UI path back to system preference) are worth a second look before merge.

Important Files Changed

Filename Overview
components/ThemeProvider.tsx New component that applies theme reactively via custom events and system matchMedia; avoids async fetch on mount by deferring to inline script + SSR class injection.
app/layout.tsx Reads saved theme at SSR time, injects it as a class on html, and adds a blocking inline script to handle system-preference fallback before first paint — eliminates FOUC.
components/SettingsButton.tsx Adds dark mode toggle with system-preference detection; fixes the hydration guard via skipHydrationCheck. One gap: once an explicit theme is saved, there is no UI path back to follows-system.
electron/main.js Changes BrowserWindow backgroundColor and Windows titleBarOverlay to hardcoded dark values, causing a dark flash before page render and a persistently dark title bar in Windows light mode.
lib/viz-runtime.ts Sandbox hardening: ORIG_FUNC/ORIG_ASYNC/ORIG_GEN captured at module scope (not in new Function scope), preventing closure escape; constructor nulling wrapped in try/finally. Solid fix.
lib/kg-runner.ts Refactored to return errored KnowledgeGraph state instead of throwing, preventing unhandled promise rejections; logic is otherwise unchanged.
components/RightPane/ChatView.tsx Adds react-markdown rendering to chat bubbles with blocked images, sanitized links, and custom component styling; whitespace-pre-wrap retained for pulsing states.
components/RightPane/index.tsx bg-white replaced with theme tokens throughout; the activeTagError banner was changed from amber/warning colors to neutral grey, losing the semantic warning signal.
lib/paths.ts Adds assertValidDocId with /^[a-z0-9-]{1,64}$/ validation on every path-constructing export, closing a path-traversal vector.
lib/settings-store.ts Persists optional theme field; uses unique UUID tmp suffix for atomic writes; safe round-trip for undefined/null theme.
components/Visualizer/GraphView.tsx Adds MutationObserver on html.dark class to trigger a canvas redraw with theme-aware colors; skipResetRef prevents error/state reset on theme-only rerenders.
components/Visualizer/ThreeDView.tsx Adds MutationObserver to update WebGL renderer clear color on theme toggle without tearing down the context; rendererRef null check prevents stale updates.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Server as Next.js Server
    participant Script as Inline Script
    participant HTML as html element
    participant TP as ThemeProvider
    participant SB as SettingsButton

    Server->>HTML: Apply html.dark class via SSR
    Server->>Script: Embed inline script with saved theme
    Script->>HTML: Add/remove .dark before first paint
    Note over Script,HTML: No FOUC — runs synchronously before CSS

    TP->>HTML: Listen to SETTINGS_EVENT
    TP->>HTML: Listen to matchMedia change

    SB->>SB: User opens settings panel
    SB->>Server: GET /api/settings to hydrate saved theme
    SB->>SB: User clicks dark mode toggle
    SB->>HTML: classList.toggle dark
    SB->>Server: POST /api/settings with theme
    SB-->>TP: Dispatch SETTINGS_EVENT
    TP->>HTML: applyTheme from event detail
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Server as Next.js Server
    participant Script as Inline Script
    participant HTML as html element
    participant TP as ThemeProvider
    participant SB as SettingsButton

    Server->>HTML: Apply html.dark class via SSR
    Server->>Script: Embed inline script with saved theme
    Script->>HTML: Add/remove .dark before first paint
    Note over Script,HTML: No FOUC — runs synchronously before CSS

    TP->>HTML: Listen to SETTINGS_EVENT
    TP->>HTML: Listen to matchMedia change

    SB->>SB: User opens settings panel
    SB->>Server: GET /api/settings to hydrate saved theme
    SB->>SB: User clicks dark mode toggle
    SB->>HTML: classList.toggle dark
    SB->>Server: POST /api/settings with theme
    SB-->>TP: Dispatch SETTINGS_EVENT
    TP->>HTML: applyTheme from event detail
Loading

Comments Outside Diff (1)

  1. components/RightPane/ChatView.tsx, line 439-451 (link)

    P2 Fenced code blocks without a language tag render as inline code

    isInline is derived from !className. This works for labelled fences (```ts) because react-markdown injects language-ts. For an unlabelled fence (``` with no language), react-markdown passes no className, so isInline is true and the content is wrapped in the small inline <code> span instead of the styled <pre> block. LLM responses frequently omit language labels, so multi-line code will collapse into a single inline chip. A safer guard is to additionally check that the content contains a newline before treating it as inline.

Reviews (5): Last reviewed commit: "fix: re-add hydration guard to persist()..." | Re-trigger Greptile

…but the lack of a dark mode (and my allergy to light mode) is an ick i needed to fix.

Fix cta buttons, quiz/flashcards feedback colors, added chat markdown support (small change with md suppport but honestly very nice to have). Also added detection for if the user already has dark mode in general or not

- Add --button-primary-bg CSS var so CTA buttons stay dark in both themes
- Add --feedback-correct/wrong CSS vars for adaptive green/rose indicators
- Fix all CTA buttons (Generate/Reveal/Next/Send/Start) to use --button-primary-bg
- Fix quiz option feedback, VerdictBadge, DeckComplete, FeedbackCard colors
- Darken flashcard rating tones (50→200, 200→400) for dark mode contrast
- Fix chat user bubble background in dark mode
- Add react-markdown rendering to chat Bubble component

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a dark mode theme across the application by introducing a ThemeProvider, updating global CSS variables, and replacing hardcoded colors with theme-aware design tokens. It also adds a dark mode toggle in the settings panel and integrates markdown rendering in the chat view. The feedback highlights a critical bug in the settings API where updating other preferences wipes out the saved theme, and suggests loading the theme server-side to prevent a Flash of Unstyled Content (FOUC). Additionally, recommendations are made to use Tailwind's dark: modifier for flashcard rating colors and to replace hardcoded code block backgrounds with design system tokens to ensure proper contrast in dark mode.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread app/api/settings/route.ts
Comment thread app/layout.tsx
Comment thread components/RightPane/FlashcardsView.tsx Outdated
Comment thread components/ThemeProvider.tsx Outdated
Comment thread components/RightPane/ChatView.tsx Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 issues found across 18 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread components/SettingsButton.tsx
Comment thread app/api/settings/route.ts
Comment thread components/RightPane/index.tsx Outdated
Comment thread components/ThemeProvider.tsx
Comment thread components/RightPane/ChatView.tsx Outdated
Comment thread components/RightPane/ChatView.tsx
Comment thread components/RightPane/ChatView.tsx Outdated
Comment thread components/RightPane/ChatView.tsx Outdated
@7Mik 7Mik changed the base branch from main to developer June 12, 2026 00:47
@7Mik

7Mik commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

@msjabata25 thanks for your submission.
Could you please look into the issues that the AI reviewers found before pulling again? I will take care of the conflicts with the developer branch afterwards.
Also, please ensure that the dark mode is applied to the whole website, including the feynman session, before applying for review again

@7Mik 7Mik marked this pull request as draft June 12, 2026 16:27
@beltromatti

Copy link
Copy Markdown
Owner

@greptile

Comment thread components/ThemeProvider.tsx Outdated
Comment thread components/PdfViewer.tsx Outdated
Comment thread components/RightPane/FlashcardsView.tsx
Comment thread components/WelcomePopup.tsx Outdated
…rk mode gaps

- lib/viz-runtime.ts: block sandbox escape via Function constructor chain
- lib/work-context.ts, lib/kg.ts, lib/store.ts: atomic write (tmp+rename)
- lib/kg-runner.ts: fix TOCTOU race, catch unhandled promise rejection
- lib/paths.ts: add docId validation regex to prevent path traversal
- electron/main.js: fix dark-mode white flash on startup
- app/api/settings/route.ts: preserve theme on partial settings POST
- app/layout.tsx + ThemeProvider.tsx: SSR dark class via server-side settings read
- components/SettingsButton.tsx: remove hydration guard that dropped toggles
- components/RightPane/ChatView.tsx: block img, hardcoded colors → CSS vars, href sanitize
- components/RightPane/FeynmanView.tsx: gradient/rose → surface/feedback tokens
- components/RightPane/KnowledgeGraphView.tsx: deduplicate API fetch
- components/RightPane/VoiceBlob.tsx: add maxWidth:100% for responsive sizing
- components/Visualizer/GraphView.tsx: canvas/axis colors adapt to html.dark
- components/Visualizer/ThreeDView.tsx: clearColor adapts to html.dark
- components/PdfViewer.tsx: bg-white → surface tokens
- components/UploadCard.tsx: unmount race guard, feedback tokens, keyboard handler
- components/CodexHealthBanner.tsx: rose/amber → feedback/tag tokens
- app/globals.css: refine dark palette surface/ink/border/accents

@msjabata25 msjabata25 left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the issues that were in the ai reviews.
All details in the commit message .
And don't worry I did update the voice blob and made it match the theme

@7Mik

7Mik commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

@greptile-apps
@cubic-dev-ai

@cubic-dev-ai

cubic-dev-ai Bot commented Jun 16, 2026

Copy link
Copy Markdown

@greptile-apps
@cubic-dev-ai

@7Mik I have started the AI code review. It will take a few minutes to complete.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown

The review has been posted above. Here's a quick summary of the key findings:

High priority:

  1. FOUC (Flash of Unstyled Content)ThemeProvider applies the dark class only after an async API fetch, so dark-mode users see a light flash on every page load. The fix is to emit an inline <script> in the document <head> that reads localStorage/a cookie and sets html.dark synchronously before paint.
  2. PDF scroll containerscrollRef div in PdfViewer.tsx still has bg-white; the area around PDF pages stays white in dark mode.
  3. Flashcard rating buttons — still using hardcoded Tailwind shades (-200, -400) instead of the --feedback-correct/wrong-* CSS variables used everywhere else in this PR.

Lower priority:

  • WelcomePopup drop shadow is hardcoded to a light-mode rgba value — nearly invisible in dark mode.
  • Unlabelled fenced code blocks in chat will render as inline <code> chips instead of <pre> blocks because isInline relies solely on !className.

The PR is solid overall — the token design in globals.css is well-structured and the quiz/flashcard feedback migration is consistent. These gaps are the remaining rough edges for the dark-mode experience.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 issues found across 29 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread lib/viz-runtime.ts Outdated
Comment thread lib/paths.ts Outdated
Comment thread lib/work-context.ts Outdated
Comment thread components/RightPane/VoiceBlob.tsx
Comment thread components/Visualizer/GraphView.tsx
Comment thread components/RightPane/ChatView.tsx Outdated
Comment thread components/ThemeProvider.tsx Outdated
Comment thread components/SettingsButton.tsx Outdated
Comment thread components/UploadCard.tsx Outdated
Comment thread components/AccountButton.tsx Outdated
Comment thread lib/viz-runtime.ts Outdated
- lib/viz-runtime.ts: null AsyncFunction/GeneratorFunction constructors
- lib/work-context.ts, kg.ts, store.ts: use unique tmp suffix per write
- lib/paths.ts: widen docId regex to accept non-hex IDs (sample-anatomy)
- components/Visualizer/GraphView.tsx: add MutationObserver for theme
  changes; use ink/inkMuted for grid, axes, legend, axis labels
- components/ThemeProvider.tsx: always reapply() on system pref change
- components/SettingsButton.tsx: guard stale fetch from overwriting
  user theme toggle (userToggledRef)
- components/RightPane/ChatView.tsx: unconditional whitespace-pre-wrap
  so non-pulsing bubbles preserve newlines
- components/RightPane/FlashcardsView.tsx: hardcoded Tailwind shades →
  --feedback-wrong/tag-amber/--feedback-correct/tag-sky tokens
- components/RightPane/VoiceBlob.tsx: add aspectRatio:1 to prevent
  canvas distortion when maxWidth clamps width
- components/AccountButton.tsx: logout hover uses feedback-wrong tokens
- components/WelcomePopup.tsx + globals.css: add --shadow-modal token
- components/UploadCard.tsx: add e.preventDefault() on Space/Enter
@msjabata25

Copy link
Copy Markdown
Author

Fixed the remaining issues (the ai reviewer is pointing out already solved issues) so if it comes out with another similar review report then i suggest you review the codebase with something else to avoid fixing the same issue over and over again

@msjabata25 msjabata25 marked this pull request as ready for review June 17, 2026 12:32

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 29 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread components/Visualizer/GraphView.tsx
Comment thread components/ThemeProvider.tsx Outdated
Comment thread components/SettingsButton.tsx
Comment thread components/RightPane/ChatView.tsx Outdated
@msjabata25 msjabata25 marked this pull request as draft June 21, 2026 13:19
…-runtime cleanup, kg-runner return path

- lib/settings-store.ts: unique tmp suffix via randomUUID (prevents write race)
- lib/viz-runtime.ts: hoist constructor refs to module scope; document threat model
- lib/kg-runner.ts: startBuild returns errored KG instead of re-throwing (no
  unhandled rejection risk from API route)
- components/Visualizer/ThreeDView.tsx: add MutationObserver for html.dark class
  to update renderer clearColor without WebGL teardown; error toast + badge use
  CSS variables for theme adaptation
- components/Visualizer/TwoDAnimView.tsx: error toast uses --feedback-wrong-* tokens
- components/RightPane/FlashcardsView.tsx: StatusDot uses --feedback-correct-icon
- components/RightPane/ChatView.tsx: destructure node from code props (internal
  react-markdown AST node no longer spreads onto DOM <code>)
- components/SettingsButton.tsx: add osPrefersDark state + matchMedia listener for
  reactive effectiveTheme computation
- components/ThemeProvider.tsx: fetch failure no-op (catch(() => {})) so current
  DOM class is authoritative on network error
- components/Visualizer/GraphView.tsx: skipResetRef prevents error/reportedRef reset
  on theme-only redraws so onRuntimeError isn't re-emitted
@msjabata25 msjabata25 marked this pull request as ready for review June 21, 2026 13:37

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 30 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread lib/viz-runtime.ts Outdated
Comment thread components/SettingsButton.tsx
…model code closure

The model code runs inside an IIFE nested in the `new Function` body. That
IIFE captures the outer function's parameter scope. Previously,
__origFunc/__origAsync/__origGen (the original Function/AsyncFunction/
GeneratorFunction constructors) were passed as parameters to new Function,
making them directly accessible from the model code via closure. The model
code could call __origFunc("return eval")() to regain eval in global scope.

Fix: move constructor nulling/restoring to the wrapper returned by compileFn,
outside the new Function body entirely. The original constructor references
are captured at module scope and live only in the wrapper's closure — the
new Function body (and thus the model code) never sees them.
Without the guard, opening the settings panel and immediately toggling
autoGenerate or maxRetries before the /api/settings fetch resolves
persists a value derived from the build-time default
(AUTO_GENERATE_VIZ / MAX_VIZ_GEN_RETRIES), silently overwriting whatever
was saved on disk.

The guard was previously removed to allow the theme toggle to work before
hydration. Fix: add it back with a skipHydrationCheck parameter, passing
true only from onThemeToggle.
@msjabata25

Copy link
Copy Markdown
Author

Cubic reviews and greptile are all clean I think. Everything seems to be working now. I can safely say dark mode and md support for chat are done.

@7Mik @beltromatti

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.

3 participants