Frontend: correct day-cell icon meanings and add a conflicts filter (#48) #95

Merged
claude-bot merged 1 commit from feat/conflict-icon-and-filter into main 2026-09-01 00:01:03 +00:00
Contributor

Batch C items 1 and 2. Both were blocked on #93 — now that the backend reports conflict per day, neither needs resolution logic in the client.

Icons

Spec §10.1 names four states — approved, candidate, no scheme, conflict. Two were bound to the wrong thing: meant rejected and meant stale. So a day with no scheme rendered nothing at all, and nothing anywhere indicated a conflict.

glyph now means
approved
candidate
no scheme (per spec)
conflict (reserved)
stale — approved but needs regenerating
rejected

stale and rejected are real states the spec's list doesn't cover. They keep distinct glyphs rather than collapsing into , because "I rejected this" and "nothing was generated" call for different actions. Every icon now carries a title and aria-label; previously they were bare unlabelled glyphs.

Conflict renders beside the scheme status, not instead of it. It describes the day (two events tied at the top), while status describes the scheme — and a conflicted day can perfectly well be approved. It's suppressed once the day is pinned, since the pin settles the ambiguity. The backend reports the two flags independently precisely so the UI can make that call.

Filter

"Conflicts" joins the toolbar. Two details that would otherwise have bitten:

  • isVisible checks conflict before the "no scheme" guard. A conflicted day with no scheme yet is exactly what this filter should surface; the existing guard would have hidden it.
  • CalendarPage narrows filteredSchemeIds for the new filter too. Toolbar falls back to approving all candidates when that list is empty — so a filter that narrowed the view but not the list would bulk-approve far beyond what the user could see. That's a data-loss-shaped bug, not a cosmetic one.

A test-harness fix worth noting

makeDay in the DayCell tests didn't supply the new required fields. Because test files aren't in the tsc -b project, nothing flagged it — and every conflict assertion would have read undefined, gone falsy, and passed for the wrong reason. Fixed by adding the fields to the helper.

That's the second time this session that gap has mattered; still worth a follow-up.

Verification

In a node:22 container matching CI:

tsc -b --noEmit    exit 0
eslint             exit 0  (--max-warnings 0)
vitest             47 passed  (was 40)
npm run build      succeeded

7 new tests: for no scheme, no longer used for stale, for rejected, conflict shown / absent / suppressed-when-pinned, and conflict rendering alongside status rather than replacing it.

Batch C after this

Remaining: 3 (gradient swatches), 4 (month backdrop bar), 7 (import panel), 8 (quick-push arbitrary colour).

Batch C items **1 and 2**. Both were blocked on #93 — now that the backend reports `conflict` per day, neither needs resolution logic in the client. ## Icons Spec §10.1 names four states — `✓` approved, `⏳` candidate, `✕` no scheme, `⚠` conflict. **Two were bound to the wrong thing**: `✕` meant *rejected* and `⚠` meant *stale*. So a day with **no scheme rendered nothing at all**, and nothing anywhere indicated a conflict. | glyph | now means | |---|---| | `✓` | approved | | `⏳` | candidate | | `✕` | **no scheme** (per spec) | | `⚠` | **conflict** (reserved) | | `↻` | stale — approved but needs regenerating | | `⊘` | rejected | `stale` and `rejected` are real states the spec's list doesn't cover. They keep **distinct** glyphs rather than collapsing into `✕`, because *"I rejected this"* and *"nothing was generated"* call for different actions. Every icon now carries a `title` and `aria-label`; previously they were bare unlabelled glyphs. **Conflict renders beside the scheme status, not instead of it.** It describes the *day* (two events tied at the top), while status describes the *scheme* — and a conflicted day can perfectly well be approved. It's suppressed once the day is `pinned`, since the pin settles the ambiguity. The backend reports the two flags independently precisely so the UI can make that call. ## Filter "Conflicts" joins the toolbar. Two details that would otherwise have bitten: - **`isVisible` checks `conflict` before the "no scheme" guard.** A conflicted day with no scheme yet is exactly what this filter should surface; the existing guard would have hidden it. - **`CalendarPage` narrows `filteredSchemeIds` for the new filter too.** Toolbar falls back to approving *all* candidates when that list is empty — so a filter that narrowed the view but not the list would bulk-approve far beyond what the user could see. That's a data-loss-shaped bug, not a cosmetic one. ## A test-harness fix worth noting `makeDay` in the DayCell tests didn't supply the new required fields. Because **test files aren't in the `tsc -b` project**, nothing flagged it — and every conflict assertion would have read `undefined`, gone falsy, and *passed for the wrong reason*. Fixed by adding the fields to the helper. That's the second time this session that gap has mattered; still worth a follow-up. ## Verification In a `node:22` container matching CI: ``` tsc -b --noEmit exit 0 eslint exit 0 (--max-warnings 0) vitest 47 passed (was 40) npm run build succeeded ``` 7 new tests: `✕` for no scheme, `⚠` no longer used for stale, `⊘` for rejected, conflict shown / absent / suppressed-when-pinned, and conflict rendering alongside status rather than replacing it. ## Batch C after this Remaining: **3** (gradient swatches), **4** (month backdrop bar), **7** (import panel), **8** (quick-push arbitrary colour).
Frontend: correct day-cell icon meanings and add a conflicts filter (#48)
All checks were successful
CI / Alembic migration check (pull_request) Successful in 1m12s
CI / Python lint & type-check (pull_request) Successful in 1m45s
CI / Frontend lint, test & build (pull_request) Successful in 2m1s
CI / Python tests (pull_request) Successful in 2m52s
CI / Docker build, health smoke & E2E (pull_request) Successful in 5m4s
ae6ad04ff0
Batch C items 1 and 2. Both were blocked on #93; now that the backend reports
conflict per day, neither needs resolution logic in the client.

Icons. Spec 10.1 names four states -- approved, candidate, no scheme, conflict
-- and two glyphs were bound to the wrong one: X meant "rejected" and the
warning triangle meant "stale". So a day with no scheme rendered nothing at all,
and nothing anywhere indicated a conflict.

  X   now means no scheme, per spec
  !   is reserved for conflict
  ~   stale (approved but needs regenerating)
  0   rejected

stale and rejected are real states the spec's list does not cover. They keep
distinct glyphs rather than collapsing into X, because "I rejected this" and
"nothing was generated" call for different actions. Every icon now carries a
title and aria-label; previously they were unlabelled glyphs.

Conflict renders as a separate indicator beside the scheme status, not instead
of it: it describes the DAY (two events tied at the top) while the status
describes the scheme, and a conflicted day can perfectly well be approved. It is
suppressed once the day is pinned, since the pin settles the ambiguity -- the
backend reports the two flags independently precisely so the UI can decide this.

Filter. "Conflicts" joins the toolbar. Two details that would otherwise bite:

  - isVisible checks conflict BEFORE the "no scheme" guard. A conflicted day
    with no scheme yet is exactly what the filter should surface, and the guard
    would have hidden it.
  - CalendarPage narrows filteredSchemeIds for the new filter too. Toolbar falls
    back to approving ALL candidates when that list is empty, so a filter that
    narrowed the view but not the list would bulk-approve far beyond what the
    user could see.

Also fixes makeDay in the DayCell tests to supply the new required fields.
Without that every conflict assertion would read undefined and pass for the
wrong reason -- test files are not in the tsc project, so nothing would flag it.

Verified in a node:22 container matching CI:

  tsc -b --noEmit   exit 0
  eslint            exit 0 (--max-warnings 0)
  vitest            47 passed, up from 40
  npm run build     succeeded

Refs #48, #93.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-bot deleted branch feat/conflict-icon-and-filter 2026-09-01 00:01:04 +00:00
Sign in to join this conversation.
No description provided.