Skip to content

Dark mode: fix highlight text + add theme preference setting#94

Merged
HamptonMakes merged 5 commits intomainfrom
dark-mode-improvements
Apr 21, 2026
Merged

Dark mode: fix highlight text + add theme preference setting#94
HamptonMakes merged 5 commits intomainfrom
dark-mode-improvements

Conversation

@HamptonMakes
Copy link
Copy Markdown
Collaborator

@HamptonMakes HamptonMakes commented Apr 20, 2026

Changes

1. Fix comment highlight text color in dark mode

The <mark> element used for inline comment highlights has a browser-default color: black. Added color: inherit to .anchor-highlight so highlighted text is readable in dark mode.

2. Add theme preference setting (System / Light / Dark)

New Appearance section on the Settings page with three instant-switching options:

  • System — follows the device/OS preference (default)
  • Light — always light, even if the OS is dark
  • Dark — always dark, even if the OS is light

Theme switches instantly via Stimulus — no page reload. Preference is persisted to the user's metadata JSON column.

How it works

  • CSS: @media (prefers-color-scheme: dark) now targets :root:not([data-theme]) so it only applies for "System" mode. :root[data-theme="dark"] and :root[data-theme="light"] handle forced overrides.
  • JS: Stimulus theme_controller sets data-theme on <html> for instant transition, updates the color-scheme meta tag, and PATCHes /settings/theme to persist.
  • Backend: User#theme_preference getter/setter backed by metadata JSON. PATCH /settings/theme endpoint validates against allowed values.

Files changed

File Change
engine/app/assets/stylesheets/coplan/application.css color: inherit on highlights, forced theme selectors, theme option styles
engine/app/models/coplan/user.rb THEME_PREFERENCES, getter/setter
engine/config/routes.rb patch "theme" route
engine/app/controllers/coplan/settings/settings_controller.rb update_theme action
engine/app/views/coplan/settings/settings/index.html.erb Appearance section UI
engine/app/views/layouts/coplan/application.html.erb Conditional data-theme + color-scheme meta
engine/app/javascript/controllers/coplan/theme_controller.js New Stimulus controller

All 687 specs pass.

HamptonMakes and others added 2 commits April 20, 2026 15:58
The <mark> element used for anchor highlights has a browser-default
color: black, making highlighted/commented text unreadable in dark mode.
Adding color: inherit to .anchor-highlight ensures the text color follows
the page theme instead of the browser default.

Amp-Thread-ID: https://ampcode.com/threads/T-019dac90-a21f-721a-96de-81ec6c0ec184
Co-authored-by: Amp <amp@ampcode.com>
Adds a user-facing Appearance section to Settings with three theme
options: System (follows device), Light, and Dark. Theme switches
instantly via a Stimulus controller that sets data-theme on <html>
and persists the preference to the user's metadata JSON column.

CSS changes:
- Media query dark tokens now target :root:not([data-theme]) so they
  only apply when no explicit theme is set
- Added :root[data-theme="dark"] with the same dark tokens for forced
  dark mode regardless of system preference
- Added :root[data-theme="light"] to force light mode
- Added .theme-option component styles for the settings UI

Backend:
- User model: theme_preference getter/setter backed by metadata JSON
- Settings controller: PATCH /settings/theme endpoint
- Routes: added theme route in settings namespace

Frontend:
- Stimulus theme_controller.js: instant DOM update + async PATCH
- Layout: conditional data-theme attribute + dynamic color-scheme meta

Amp-Thread-ID: https://ampcode.com/threads/T-019dac90-a21f-721a-96de-81ec6c0ec184
Co-authored-by: Amp <amp@ampcode.com>
@HamptonMakes HamptonMakes changed the title Fix comment highlight text color in dark mode Dark mode: fix highlight text + add theme preference setting Apr 20, 2026
HamptonMakes and others added 2 commits April 20, 2026 16:13
Replace the large card-based radio buttons with a sleek inline
segmented control: monitor icon for System, sun for Light, moon
for Dark. Uses a settings-row layout pattern for clean label/control
alignment.

Amp-Thread-ID: https://ampcode.com/threads/T-019dac90-a21f-721a-96de-81ec6c0ec184
Co-authored-by: Amp <amp@ampcode.com>
Wrap the segmented control in a card so it reads as its own settings
section rather than a floating header element. Bump button padding
from xs/sm to sm/md for better touch targets.

Amp-Thread-ID: https://ampcode.com/threads/T-019dac90-a21f-721a-96de-81ec6c0ec184
Co-authored-by: Amp <amp@ampcode.com>
@HamptonMakes
Copy link
Copy Markdown
Collaborator Author

@codex

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 56bec22c11

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


def theme_preference=(value)
self.metadata ||= {}
self.metadata["theme_preference"] = value
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Persist theme outside auth-synced metadata

Saving theme_preference inside metadata will be clobbered in common host setups because authenticate_coplan_user! reassigns attrs[:metadata] on every request (and the integration guide’s example returns metadata: {}). In that flow, a user can select Dark, but the next request overwrites metadata and resets to System, so the new setting is not actually persistent. This preference needs to live in a field that is not rewritten by auth sync (or merge preserved keys before assignment).

Useful? React with 👍 / 👎.

@HamptonMakes
Copy link
Copy Markdown
Collaborator Author

Good catch! The authenticate_coplan_user! method was doing assign_attributes(attrs.slice(..., :metadata, ...)) which would overwrite the entire metadata hash — including the stored theme_preference — on every request if the auth callback returned metadata: {}.

Fixed by extracting a sync_user_attrs helper that merges incoming metadata into the existing hash instead of replacing it. This preserves user-set keys like theme_preference while still allowing the auth callback to set its own metadata fields.

See commit 56bec22 → fixed in the next push.

The authenticate_coplan_user! method was using assign_attributes with
the full :metadata key from the auth callback, which would overwrite
user-set preferences like theme_preference on every request.

Extract sync_user_attrs helper that merges incoming metadata into the
existing hash, preserving keys the user has set independently.

Amp-Thread-ID: https://ampcode.com/threads/T-019dac90-a21f-721a-96de-81ec6c0ec184
Co-authored-by: Amp <amp@ampcode.com>
@HamptonMakes HamptonMakes merged commit 076266f into main Apr 21, 2026
5 checks passed
@HamptonMakes HamptonMakes deleted the dark-mode-improvements branch April 21, 2026 14:54
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