Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## Evidence

<!-- For feature PRs and UI bug fixes, attach a video or images. Show UI changes in both light and dark themes. For non-UI features, show relevant API or command output. Add before-and-after evidence for visual fixes when useful. -->
<!-- For feature PRs and UI bug fixes, attach a video or images. Show UI changes in both light and dark mode. For non-UI features, show relevant API or command output. Add before-and-after evidence for visual fixes when useful. -->

## How to try it

Expand All @@ -17,5 +17,5 @@
- [ ] Added or updated real JavaScript-enabled browser specs in `spec/system/` for changed UI behavior, covering the user interaction and visible result; or explained why no browser UI is involved.
- [ ] Added model, service, or request specs where needed for domain behavior, authorization, or API cases.
- [ ] Ran `bundle exec rspec`, or explained why it could not run and listed the specs that did run.
- [ ] Tested affected UI behavior in both light and dark themes, or explained why theme testing does not apply.
- [ ] Tested affected UI behavior in both light and dark mode, or explained why mode testing does not apply.
- [ ] Attached a video or images demonstrating the change, if this PR adds or changes a feature or fixes a UI bug.
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Follow [the design guidance](docs/DESIGN.md) across document, rich preview,
expanded table/diagram, and editing surfaces:

- **Blue means interaction:** subtle blue for hover, blue for keyboard focus,
selection, active discussions, and comment-composer quotes, in both themes.
selection, active discussions, and comment-composer quotes, in both modes.
- **Selection supports reading first:** clicking table cells or selecting text
must not open a composer. Offer Comment and the C shortcut. Intentional comment
actions focus the comment or reply field immediately. Diagrams browse by
Expand Down Expand Up @@ -131,7 +131,7 @@ a way for the person who requested it to try the changed behavior:

- Run the relevant automated specs. Exercise UI behavior in a real browser,
using a built-in browser when the agent environment provides one. For UI
changes, check light and dark themes and capture screenshots or a short
changes, check light and dark mode and capture screenshots or a short
recording of the result. For visual bugs, include before-and-after evidence
when useful.
- Start the development server (`bin/dev`; use `bin/setup --skip-server` and
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Thanks for helping improve CoPlan. Keep pull requests focused and make it easy f
- Add **real browser specs** for new or changed UI behavior in `spec/system/`. These are RSpec system specs using Capybara and Selenium with headless Chrome, so the page's JavaScript runs. Drive the feature through the UI as a user would and assert the visible result and, where relevant, the persisted change. Cover meaningful success, failure, and edge cases. A request spec, mocked JavaScript call, or assertion that an element merely exists does not replace a browser spec for an interactive feature.
- Add model, service, or request specs where they help cover domain behavior, authorization, and API cases. Keep specs focused on behavior rather than implementation details. For a feature without a browser UI, use the appropriate non-browser specs and explain why a browser spec does not apply.
- Run the relevant browser specs with `bundle exec rspec spec/system/<feature>_spec.rb`, then run the full suite with `bundle exec rspec`. If you cannot run it, say why and identify exactly what you did run.
- For every feature, test the affected UI in both **light and dark themes**. Check the complete interaction, including loading, success, error, and empty states that apply. For a feature without a UI, explain in the PR why theme testing does not apply.
- Include **a video or images** in feature PRs and UI bug fixes so reviewers can see the result. Show both themes for UI changes; a short video or labeled screenshots are both fine. For features without a UI, include images of the relevant API or command output, or a short video demonstrating it. Include before-and-after evidence for visual fixes when useful.
- For every feature, test the affected UI in both **light and dark mode**. Check the complete interaction, including loading, success, error, and empty states that apply. For a feature without a UI, explain in the PR why mode testing does not apply.
- Include **a video or images** in feature PRs and UI bug fixes so reviewers can see the result. Show both modes for UI changes; a short video or labeled screenshots are both fine. For features without a UI, include images of the relevant API or command output, or a short video demonstrating it. Include before-and-after evidence for visual fixes when useful.

## Shared development examples

Expand Down
3 changes: 2 additions & 1 deletion docs/DEMO_LIBRARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ shipped types, including Scratchpad, PRD, Test Plan, and Handoff.
| reference-gallery | All five reference types, a resolved CoPlan target, explicit/extracted references, named citations, repeated URLs, footnotes |
| table-gallery | Precise cell discussion, repeated labels, empty values, links, wide expandable data |
| formatting-gallery | Headings, editable template notes hidden while reading, emphasis, quotes, nested lists, tasks, two code languages, image, mention, footnote |
| reading-specimen | A realistic design doc for judging reading in both modes: prose, headings, lists, tasks, quote, table, footnote, image, SQL/Ruby/diff/YAML code, a long scrolling line, flowchart and sequence diagrams, plus open, agent-answered, resolved, diagram-node, table-cell, and general discussions |
| image-resizing | Resize and restore image widths, wrap prose from the top, Shift+Enter beside, Enter below |

Flowcharts currently support source-backed node and edge comments. Other
Expand Down Expand Up @@ -203,7 +204,7 @@ can open that example from the demo index and try the new behavior.
3. Seed the real relationships or versions needed by the feature.
4. Add assertions for the initial state, repeatability, and preservation of
local changes in `spec/lib/development_seed_spec.rb`.
5. Run the seed and relevant specs, then try the example in both themes.
5. Run the seed and relevant specs, then try the example in both modes.

The browsable index includes every definition and refreshes as examples are
added. If you edit the index yourself, re-seeding preserves your version.
Expand Down
2 changes: 1 addition & 1 deletion docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Reading is the primary activity. Apply these rules consistently to document
text, tables, diagrams, rich previews, expanded views, and editors, in light
and dark themes.
and dark mode.

Selecting a table cell is a browsing action. Keep its composer closed until
the reader presses C or Enter, chooses Comment, or opens a discussion badge.
Expand Down
13 changes: 8 additions & 5 deletions docs/HOST_APP_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ CoPlan is a Rails engine that manages collaborative planning documents. It owns

## User settings and host integrations

CoPlan owns the settings shown on its Settings page. Theme and push-to-talk
preferences live in `coplan_users.theme_preference` and `coplan_users.voice_hotkey`.
CoPlan owns the settings shown on its Settings page. Light/dark mode and
push-to-talk preferences live in `coplan_users.theme_preference` and `coplan_users.voice_hotkey`.
Notification preferences have their own column; browser subscriptions and API
tokens have engine-owned tables. User `metadata` is available for host directory
data and is not a settings API.
Expand All @@ -19,7 +19,7 @@ bin/rails co_plan:install:migrations
bin/rails db:migrate
```

`MoveUserSettingsOutOfMetadata` copies valid existing theme and hotkey values,
`MoveUserSettingsOutOfMetadata` copies valid existing mode and hotkey values,
including explicit System and Off choices. Missing or invalid values use System
and Ctrl+Space. Preferences erased by an earlier directory sync cannot be recovered.
Rollback copies the latest settings back into metadata while preserving host fields.
Expand Down Expand Up @@ -65,7 +65,7 @@ config.exceptions_app = CoPlan::ExceptionsApp.new(Rails.root.join("public"))
```

This page uses the regular application layout, including the signed-in viewer's
navigation and theme. Visitors without a session can still use it. Other error statuses and
navigation, mode and theme. Visitors without a session can still use it. Other error statuses and
non-HTML errors retain Rails' public error handling. Development still shows
Rails' detailed routing exceptions; use the preview URL to try the design.
Keep a static `public/404.html` as a fallback for web servers serving errors
Expand Down Expand Up @@ -217,7 +217,7 @@ The engine manages a `coplan_users` table with these columns:
| `name` | String | Display name |
| `admin` | Boolean | Admin flag |
| `metadata` | JSON | Host directory data; not CoPlan settings |
| `theme_preference` | String | Engine-owned theme: system, light, or dark |
| `theme_preference` | String | Engine-owned light/dark mode: system, light, or dark |
| `voice_hotkey` | String | Engine-owned push-to-talk preference |

`CoPlan::User` is a normal ActiveRecord model. Host apps can reference it directly:
Expand Down Expand Up @@ -314,6 +314,9 @@ CoPlan.configure do |config|
config.track_event = ->(event, payload) {
AnalyticsEvent.create!(name: event, payload: payload)
}

# App theme (optional). One of CoPlan::Themes.keys; see docs/THEMES.md.
config.default_theme = "classic" # default
end
```

Expand Down
2 changes: 1 addition & 1 deletion docs/SOURCE_COMMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ checks a 120-row, 24-column table and commenting on its far corner. Diagram
rendering coverage is distinct from the source-comment syntax described below.

Hover, keyboard focus, active selections, and new-comment context use blue
interaction styling in both themes. Open discussions keep small muted badges.
interaction styling in both modes. Open discussions keep small muted badges.
Unresolved does not mean unread: orange is reserved for a viewer's outstanding
task or review attention, as defined in [the design guidance](DESIGN.md).

Expand Down
98 changes: 98 additions & 0 deletions docs/THEMES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# App themes

A theme changes how CoPlan looks, never what it does. It is a named set of
CSS variable values. Light and dark mode are a separate choice, so a theme
defines both.

## Terms

| Term | Means | In code |
| --- | --- | --- |
| Mode | Light, dark or system | `data-theme`, `theme_preference`, `theme_controller.js`. These names predate app themes and stay. |
| Theme | The app's look: Classic, … | `data-app-theme`, `CoPlan::Themes`, `config.default_theme` |
| Deck theme | A slide look: `coplan`, `graphite` | the `theme` attribute on a presentation region. Always say "deck theme". |

If the mode attribute is ever renamed, move `data-theme` to `data-mode` and
accept both for a release, since host CSS may target it.

Classic is the built-in look: Lexend, glass panels and the document card. It
sets no variables. Every rule that a theme can change reads a variable with
Classic's value as the fallback, `var(--hook, <Classic value>)`. A theme sets
only the hooks it changes.

## How a theme is applied

The layout renders `<html data-app-theme="classic">`. The value is the host's
`config.default_theme` (see [HOST_APP_GUIDE.md](HOST_APP_GUIDE.md)). An
unknown name stops boot in development and test. In other environments it
logs a warning and falls back to `classic`. Light and dark stay on
`data-theme`.

The themes are listed in `CoPlan::Themes::ALL`
(`engine/app/models/coplan/themes.rb`).

## Adding a theme

1. Add an entry to `CoPlan::Themes::ALL`.
2. Add `engine/app/assets/stylesheets/coplan/themes/<key>.css` and import it
last in `coplan/application.css`.
3. Set tokens and hooks under `:root[data-app-theme="<key>"]`. For dark mode,
repeat them under `:root[data-app-theme="<key>"][data-theme="dark"]` and
inside `@media (prefers-color-scheme: dark)` under
`:root[data-app-theme="<key>"]:not([data-theme])`.
4. Set custom properties only. A hook can also be set on a component, for
example `:root[data-app-theme="<key>"] .plan-document { --color-surface: … }`.

A hook set on `:root` resolves any `var()` it uses at the root. To follow a
token that changes further down the page, set the hook on that component.

Decks are fixed artifacts. `.deck` sets every document hook to `initial`,
which brings back the built-in value, so themes never restyle slides. Code
panels on slides pin their own colors in `.deck-slide .deck-content pre`.
`spec/lib/deck_theme_isolation_spec.rb` checks that every variable a theme
file sets is reset there or allowlisted with a reason.

## Tokens

Every token in `foundation.css` `:root` can be overridden, for example
`--color-bg`, `--color-text`, `--font-sans`, `--font-mono`,
`--code-block-bg`, the `--code-*` syntax colors, `--glass-*` and
`--shadow-*`. Keep blue for interaction and orange for attention
([DESIGN.md](DESIGN.md)).

## Hooks

| Area | Hooks |
| --- | --- |
| Page | `--page-backdrop` (the body's background image) |
| Glass | `--glass-blur` (panels), `--window-blur` (comment window, review tray, voice button), `--overlay-blur` (modal backdrops) |
| Floating windows | `--window-bg`, `--window-edge`, `--window-shadow`, `--composer-input-bg` |
| Overlays | `--overlay-tint` (new-document and expanded views), `--dialog-backdrop` (review dialog) |
| Panels | `--panel-bg`, `--panel-edge`, `--panel-shadow`, `--panel-row-bg`, `--document-support-radius` |
| Document card | `--document-bg`, `--document-border`, `--document-radius`, `--document-shadow`, `--document-padding`, `--document-padding-narrow` (phones) |
| Plan title | `--plan-title-size`, `--plan-title-size-narrow`, `--plan-title-weight`, `--plan-title-leading`, `--plan-title-tracking`, `--plan-title-wrap` |
| Reading type | `--reading-measure`, `--reading-leading`, `--reading-paragraph-gap`, `--reading-heading-weight`, `--reading-heading-tracking`, `--reading-heading-wrap`, `--reading-h1-size`, `--reading-h1-leading`, and for h2 and h3: `-size`, `-leading`, `-space-above`, `-space-below` |
| Emphasis | `--strong-weight` |
| Code blocks | `--code-block-border`, `--code-block-radius`, `--code-block-padding`, `--code-block-font-size`, `--code-block-leading`, `--code-block-scrollbar` |
| Code label | `--code-label-height`, `--code-label-margin`, `--code-label-padding`, `--code-label-bg`, `--code-label-dots`, `--code-label-border`, `--code-label-radius`, `--code-label-font`, `--code-label-leading`, `--code-label-tracking`, `--code-label-align` |
| Inline code | `--code-chip-padding`, `--code-chip-radius`, `--code-chip-size` |
| Diagrams | `--diagram-bg`, `--diagram-border`, `--diagram-radius` |
| Tables | `--table-radius`, `--table-numerals` |
| Other | `--blockquote-rule`, `--image-outline` |

Reading type applies to the document body in the reader and the editor
only. Comments, previews and decks keep their own type.

## Diagram palette

A theme can give Mermaid diagrams its own colors. Set all of these as hex
values (Mermaid's color math can't read `oklch()`), per scheme:
`--diagram-bg`, `--diagram-node`, `--diagram-node-border`, `--diagram-text`,
`--diagram-line`, `--diagram-cluster`, `--diagram-cluster-border`,
`--diagram-actor-line`, `--diagram-alt`, `--diagram-note`,
`--diagram-note-border`, `--diagram-note-text`.

Each diagram reads the palette from its own container. If any of them is
missing, documents keep Mermaid's built-in themes. Decks always do, because
`.deck` resets `--diagram-bg`. An expanded deck diagram keeps the slide's
background, so its built-in colors stay readable in either mode.
4 changes: 2 additions & 2 deletions docs/human-document-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ unique index.
Rich mode shares reading-view typography. The compact toolbar provides selected
states, accessible labels and shortcut hints. The link button uses the same
Lucide outline convention as the app; style controls and native options follow
light/dark themes. Cmd/Ctrl+B/I format, Cmd/Ctrl+Z undoes, Cmd/Ctrl+Shift+Z redoes,
light and dark mode. Cmd/Ctrl+B/I format, Cmd/Ctrl+Z undoes, Cmd/Ctrl+Shift+Z redoes,
and Cmd/Ctrl+K adds a link. Lists support Enter, Tab and Shift+Tab.
At the start of a paragraph, typing `* ` creates a bullet. Immediate Backspace
restores the literal asterisk and space. After typing an item and pressing Enter,
Expand Down Expand Up @@ -289,7 +289,7 @@ large structural rewrites can move the selection to the changed range.
Run `bundle exec rspec`. Concurrent worktrees need distinct test databases via
`DATABASE_URL`, so one suite cannot reset another suite’s schema or data.
`spec/system/editor_lists_spec.rb` covers bullet typing, immediate reversal,
empty-item exit and caret alignment in both themes, repeated Backspace without
empty-item exit and caret alignment in both modes, repeated Backspace without
new bullets, blank lines after lists, citation metadata, and saved/reopened lists.
`spec/system/document_editor_modes_spec.rb` covers mode
fidelity, table/Mermaid previews, Close editor success/error/conflict/in-flight behavior,
Expand Down
13 changes: 11 additions & 2 deletions engine/app/assets/stylesheets/coplan/controls.css
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@
box-shadow: var(--shadow-glass);
}

/* The plan document's card (theme hooks; docs/THEMES.md). */
.plan-document.card {
padding: var(--document-padding, var(--space-lg));
border: var(--document-border, 1px solid var(--card-edge));
border-radius: var(--document-radius, var(--radius-lg));
background: var(--document-bg, var(--glass-bg-strong));
box-shadow: var(--document-shadow, var(--shadow-glass));
}

/* Status badges */
.badge {
display: inline-block;
Expand Down Expand Up @@ -172,7 +181,7 @@
.plan-type-icon--lg { width: 36px; height: 36px; border-radius: 9px; }
.plan-type-icon--lg svg { width: 19px; height: 19px; }

/* Saturated mid-tones read on both themes (like language dots). */
/* Saturated mid-tones read in both modes (like language dots). */
.plan-type-icon--0 { background: color-mix(in srgb, #5b8def 16%, transparent); color: #5b8def; }
.plan-type-icon--1 { background: color-mix(in srgb, #3fa876 16%, transparent); color: #3fa876; }
.plan-type-icon--2 { background: color-mix(in srgb, #d99a2b 16%, transparent); color: #d99a2b; }
Expand All @@ -197,7 +206,7 @@
.avatar--xl { width: 4rem; height: 4rem; font-size: 1.3rem; }

.avatar--initials {
/* White initials need a deeper blue than the dark theme's link color. */
/* White initials need a deeper blue than dark mode's link color. */
background: #2563eb;
color: var(--color-text-inverse);
font-weight: 600;
Expand Down
Loading
Loading