Skip to content

feat(calendar): recompose the golf calendar on Fairway primitives with a scheduling task board - #1930

Merged
njrini99-code merged 16 commits into
mainfrom
agent/calendar-makeover
Sep 10, 2026
Merged

feat(calendar): recompose the golf calendar on Fairway primitives with a scheduling task board#1930
njrini99-code merged 16 commits into
mainfrom
agent/calendar-makeover

Conversation

@njrini99-code

@njrini99-code njrini99-code commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Summary

The golf calendar recomposed on Fairway's own primitives, plus an interaction-first Find a time workspace. Three passes, oldest first:

  1. Material makeover + colored-edge removal (fe575fd9c, 7a385e9d9) — superseded in most places by the passes below; the owner ban on colored card edges stands.
  2. Phone home recomposed on Fairway primitives (89e4b6051) — Phase B slice for owner review.
  3. Scheduling task board, one acceptance rule, real gesture contract (0ea60406b) — the four source issues from the workspace review, resolved.

What is reused (shared kit, unchanged)

PressTarget for every multi-child row · Button/IconButton for labeled actions only · Segmented (view selector) · PopoverPanel (More, People menus) · Avatar/AvatarGroup · StatusPill · Surface/Elevated · CalendarSurface (DayPicker) for the phone month · EmptyState · ModalShell · the shell's fw-glass-chrome.

What improved in the shared kit

  • Segmented gains an opt-in quiet presentation (no sunken track shadow, flat pill). Default rendering unchanged for the 72 other consumers.
  • ModalShell gains presentation="workspace" (phone: edge-to-edge full height, keyboard-aware; sm+: a wide stage). Default dialog presentation unchanged; existing ModalShell tests pass.

What disappeared

Portrait member rail · header counters · glass hero/dock/float pills in the workspace · every local material recipe except the functional calendar vocabulary (.ground, .chipFloat, .avatarChip, .teamChip, .buttonRow, .paper, .well, .chrome, .dock, .float, .selected, .glow, .check, .attention, .row, .rowIcon, .press, .rise — consumers now use Fairway tokens directly) · per-dialog !important geometry · the workspace's inline status block and glowing dock (replaced by one task board) · the drawer's four sunken Responses tiles.

The four workspace issues

  • Button-layout coupling: .buttonRow > span removed; people, conflict, busy-time, document and attendance rows, the scheduling name column, ruler slots and suggestion cards are PressTarget compositions.
  • Workspace sizing: ModalShell presentation="workspace"; CalendarSchedulingDialog no longer overrides the frame.
  • Confirmation policy: acceptProposal (every schedule verified, every REQUIRED person free) is the single rule behind the board, the confirm action and the dialog's final recheck. Test fixture with an optional busy participant proves allAvailable=false while acceptance passes and onChoose fires.
  • Final drag frame: release position is committed (pending frame flushed, never dropped); lane taps distinguish tap from pan (8px slop). Tests: release-before-frame, tap places, horizontal pan and vertical scroll do not.

Task board (SchedulingTaskBoard, Fairway Elevated)

accepted → date · time, summary, Use this time · required busy → names + Next open time · unverified → names + Retry · fetch/recheck failure → "Could not verify schedules" + Retry, confirm disabled. The frame reserves its space below the scrolling body.

Compact date exploration and the drawer (Phase C, after the owner checkpoint)

  • Title is the date-jump: the hero title is a PressTarget opening a PopoverPanel with the shared CalendarSurface; picking a day selects it. No ambiguous toggle.
  • Month-scoped agenda: the agenda window is the title's calendar month, prev/next step by month, the empty state names the period.
  • Visual pass: Week titles its exact range and names the week when empty; agenda day headings carry "in N days" within the coming week; the Find a time name column shows whole first names on phones.
  • Detail drawer (owner review of the phone capture): Responses is one centred stat strip (figure over label, hairline dividers) instead of off-centre tiles; neutral icon discs that washed out on the cream card are bare emerald glyphs; the Accepted count carries the accent; the date line is the sans body face.

Native phone pass (owner review of the phone captures, Fable 5.1)

  • Masthead: one sticky glass band. Big month/week/day title with a quiet year (the title is still the date-jump), a labelled Today control carrying today's number, the More menu, and the full-width view switcher below. No arrows on the phone; they remain from md up. The masthead publishes its height so the list's day headings pin beneath it.
  • Page turn: a horizontal touch swipe across the schedule steps the period with a short slide and a light haptic. Vertical drags, short flicks and mouse drags do nothing. Range fetches show as a thin progress line at the masthead's foot instead of a band.
  • Floating "+": the coach's New event is a floating action above the tab bar on the phone; the masthead keeps the labelled button from md up.
  • List: each day is one grouped, raised card with hairline dividers; day headings stick under the masthead; today's group carries a now-line at its sorted position and names its date beside "Today"; times show meridiem and end time on every width.
  • People: one raised row, one control. The summary (avatars, "Team schedule", count) opens the People menu, which offers "Compare schedules…"; from md up a labelled Compare button stands beside the row, never inside it.
  • Depth: raised cards and Segmented's floating thumb everywhere; the Month grid uses the sans face.

Shared kit: raised buttons (Button, IconButton)

Primary and secondary now sit on the page at rest (lit top edge plus the resting shadow), lift a step on hover, settle flat on press. Ghost stays chrome-light; danger keeps its flat whisper. The calendar's local shadow overrides were dropped in favour of the shared variants. Covered by button.test.tsx.

Verification

  • ESLint on the calendar tree and the changed controls: clean. tsc --noEmit: exit 0.
  • Vitest: src/components/fairway/controls, src/components/fairway/pages/calendar, messages, TeamSwitcher — 77 files, 733 tests pass (new: masthead, now-line, member-row, swipe/FAB, button depth).
  • Chromium iPhone 15 emulation against the dev server (coach + player): swipe September → October, Today, sticky headings under the masthead, floating "+" opens the editor, raised Today/More shadows read from computed style, people row with no nested control; desktop 1280 keeps arrows and the labelled New event.
  • Not measured: physical iPhone, dark theme, large text, VoiceOver.

Not in this PR

Event-to-detail continuity · desktop context rail · wheel picker, resizable panes, Gantt (deferred by brief). Not deployed; production deploy needs explicit authorization. The drawer's People section exposes no buttons on the phone (pre-existing, reported).

🤖 Generated with Claude Code

https://claude.ai/code/session_011zGybqwusmeKC2jJhsU9JC

@njrini99-code njrini99-code changed the title feat(calendar): frosted-glass material makeover for the golf calendar feat(calendar): recompose the golf calendar on Fairway primitives with a scheduling task board Sep 9, 2026
@njrini99-code
njrini99-code force-pushed the agent/calendar-makeover branch from f362049 to 64377e6 Compare September 10, 2026 02:02
njrini99-code and others added 16 commits September 9, 2026 22:23
Rebuild the calendar's look and feel around one material vocabulary
(CalendarSurfaces.module.css): a warm ground wash the glass refracts,
frosted champagne chrome and docks with token blur, matte ivory paper
cards, and an emerald focused plane for selection and the primary action.

Home: sticky glass hero (month, nav, week strip, view switcher, one
primary action, secondary actions as pills / phone More menu) that agenda
rows, the member rail and the grids scroll under; time-gutter event rows
with type-tinted icon discs; compact phone month grid with density dots
and whole-cell taps; week-strip swipe to move a week; honest conflict
count on the home.

Find a time: full-screen glass workspace with a finger-smooth selection
lens (free follow with no transition while dragging, 200ms settle on
release, grab-offset preserved, readout kept in view, lane tap to place,
timeline scrolls the band into view), suggestion cards, glowing dock CTA,
and an opening slot where everyone is free.

Person schedule, class detail, event detail, editor, conflicts,
availability, attendance and subscriptions get the same material pass.
AppShell publishes --fw-hub-subnav-offset so page chrome can sit under
the hub sub-nav without guessing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
…red card edges

Phone home now matches the reference screens: month title toggles the
compact month grid, ghost prev/next + More menu + glowing primary action,
rounded-square selected day in the week strip, portrait member rail with a
Team chip first and Compare last, stacked "9:00 / AM" time gutter, and a
richer amber attention banner.

Event cards lose the colored left rule everywhere (owner ban): the type
reads from the tinted icon disc and the card is distinguished by material
(specular rim, hairline, layered grounded shadow). Pending attendance rows
become an amber wash + ring. "Show N earlier events" is a floating sand chip.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
The Fairway Button wraps children in one bare <span>, so every row built
from sibling elements inside it (people picker, conflicts, availability,
file picker, proposal rows, attendance row, Find-a-time name column)
stacked or overflowed on phone. `.buttonRow` makes that span the row and
inherits the button's gap, alignment and justification.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
Phase B slice for owner review. The calendar stops carrying its own
material system on the home screen and composes shared Fairway parts:

- Hero: compact title row + explicit view selector (Segmented, new
  opt-in `quiet` presentation) + prev/next; secondary actions behind
  More until xl; the week strip only in Day view; no header counters.
- People: compact summary (AvatarGroup + status) with a People menu
  (Everyone / open a schedule) and the existing compare picker; the
  portrait rail is gone.
- Agenda: day heading + one grouped Surface of PressTarget rows with
  hairline dividers; history is a quiet ghost action with a real count.
- Month on phone: CalendarSurface (DayPicker) overview with the selected
  day's events beneath; the desktop grid keeps a single day target.
- Player: "Needs your reply" contextual row instead of a header CTA.
- Removed local recipes: .ground, .chipFloat, .avatarChip, .teamChip.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
…ure contract

Resolves the four source issues from the workspace review and gives Find a
time its contextual floating board:

- ONE acceptance rule: `acceptProposal` (every schedule verified, every
  required person free; optional people may be busy) drives the workspace
  board, its confirm action and the dialog's final recheck. Before, the
  workspace enabled the action on its own predicate and the dialog rejected
  the same selection on `allAvailable`. Fixture with an optional busy
  participant added.
- Gesture contract: releasing a drag commits the release position (a pending
  animation frame is flushed, never dropped); lane taps place the window
  only when the pointer stayed put (8px), so pans and page scrolls do not.
- `SchedulingTaskBoard` (Fairway Elevated): accepted / required busy with
  names and "Next open time" / unverified with names and Retry / could not
  verify. The frame reserves its space; nothing floats over the last row.
- `ModalShell` gains `presentation="workspace"` (phone: edge-to-edge,
  keyboard-aware; sm+: wide stage); the scheduling dialog drops its
  !important geometry overrides.
- `.buttonRow` is gone: people, conflict, busy-time, document and
  attendance rows, the scheduling name column, ruler slots and suggestion
  cards are `PressTarget` compositions; Button stays for labeled actions.
- Week strip marks every day a multi-day event runs (`eventDaySpan`), the
  same interpretation as the agenda and month overview.
- Feature doc updated to the composition contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
…lendar surface

Phase C, first pass: the detail drawer, person dialog, editor (essentials,
people & time, verification, review, stages), class detail, conflicts
center/detail, availability, attendance, files, subscriptions and the
people picker now compose on Fairway classes instead of CalendarSurfaces
recipes. `.paper`/`.row` → bordered bg-surface card with --fw-shadow-card;
`.rowIcon` → neutral sunken disc, or a semantic warning/success tone where
one was meant; `.float` → bordered surface; `.chrome`/`.dock` → the shell's
`fw-glass-chrome`; `.check`/`.selected` → accent-650 fill; `.attention` →
warning-bg/ink/ring; `.press`/`.rise`/`.glow` removed (PressTarget and
Button already carry the press response; the primary Button is the green
treatment). `--row-tint` custom-property plumbing is gone.

CalendarSurfaces.module.css keeps only functional vocabulary: colour scope,
grid hairlines, interval tints, hatch, pending, reference, lens + motion,
inspector, enter. The reduced-motion contract test follows.

Also regenerates DOCUMENT_AUTHORITY_INVENTORY.md (knowledge_check fell
over the feature doc's new heading count).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
…responses

- The hero title is now the date-jump: a PressTarget trigger opens a
  PopoverPanel with the shared CalendarSurface (frameless inside the panel);
  picking a day selects it and closes the popover. Hero test added.
- Agenda is month-scoped: its window is the title's calendar month, prev/next
  step by month, and the empty state names the period.
- Detail drawer per owner review: the Responses block is one centred stat
  strip (figure over label, hairline dividers) instead of four sunken tiles
  whose figures read off-centre; neutral row icons are bare glyphs so cream
  discs no longer wash out on the cream card; the date line uses the sans
  body face. Semantic warning/success discs keep their fill.
- calendar-events.md: retired-recipe list, date-jump, month-scoped agenda,
  drawer contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
Owner review of the drawer: "Don't be afraid to use the green as accent."
Row icons (location, notes, responses, people) are bare accent-700 glyphs,
the header clock is emerald, and the Accepted count in the Responses strip
carries the accent when above zero. Warning/success discs are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
…le first names

- Week view titles its exact Sunday-start range ("Sep 6 – 12, 2026",
  cross-month and cross-year forms) and its empty state names the week
  instead of "No upcoming events".
- Agenda day headings within the coming week carry a quiet "in N days" cue;
  Today / Tomorrow / Yesterday remain the heading itself.
- Find a time: the phone name column is 116px and shows the first name whole
  (full name from `sm` and in the accessible label) instead of "Aud…".

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HHqSV9CHn8zRWuWt57JELZ
…the pre-land review

Four defects a fresh-context review of this branch confirmed, each verified
against the code rather than the PR's own summary.

CalendarSchedulingDialog: ModalShell's `presentation="workspace"` is full-bleed
on phones (top 0, square corners) and delegates safe areas to its child.
SchedulingWorkspace honours that in its header and footer; the sibling
loading/error branch did not, so the Back button and the `role="alert"` retry
sat under the notch. `useScheduleWindow` nulls `snapshot` on every request-key
change, so this was not a first-mount state — every date jump landed there, and
the error case stayed until a retry succeeded. ModalShell's own comments record
the identical incident already biting the `dialog` path once.

SchedulingWorkspace: the hour-ruler slots are real start-time controls and their
hit area had regressed from main's `min-h-14` (56px) to `h-8` (32px). They now
take the kit's 44px floor on a coarse pointer only, so the compact ruler the
makeover designed survives for a mouse. Layout-safe: the selection lens spans
`gridRow: 3 / span N`, so growing row 2 does not move it.

FairwayMonthGrid: below `sm` each cell offered two tab stops, not one. `sr-only`
clips a native button visually but leaves it in the tab order and the a11y tree,
and `pointer-events-none` does not block keyboard activation — so the grid was
84 focus stops instead of 42, half of them painting no focus ring. `max-sm:hidden`
is layout-neutral (`sr-only` is already absolutely positioned) and makes the code
match the comment above it.

FairwayCalendarHero: Day view announced "Previous/Next day" while navigate()
steps `addDays(d, dir * 7)` for both Day and Week by design — the day-strip is
the single-day picker and the arrows turn the page. The label now says what the
button does; an AT user cannot see the strip to infer it. Covered by a new
parameterised test that fails when the old label is restored.

Verified: tsc --noEmit clean; eslint --max-warnings=0 clean on all four files;
228 test files / 1760 tests pass — the same baseline as before these changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zGybqwusmeKC2jJhsU9JC
Two multi-touch defects from the pre-land review. A phone is a multi-touch
device: a resting thumb, a palm, or a second finger lands on the same band and
lanes the gesture is already using, and neither handler cluster asked which
pointer it was hearing from.

The band and the drag handle share `dragging` (one boolean) and `dragRef` (one
record). A second contact re-entered `beginDrag`, overwrote `grabOffsetPx` and
captured itself on the same element, after which the band tracked whichever
finger moved last — and whichever finger lifted first committed ITS position
and set `dragging` false, stranding the finger still down: its moves were then
ignored and its own release was a no-op. The gesture now has an explicit owner.
A second pointerdown is refused rather than allowed to take over, and move, up
and cancel act only for the pointer that started it. The lane-tap path already
compared pointer ids; the drag cluster it sits beside did not.

The lane rows share one pending-tap record but attach the same three handlers
to every row, and the up handler cleared that record BEFORE checking whether it
owned it. Two overlapping contacts therefore lost both taps: the first release
discarded the second contact's still-valid record on its way to rejecting
itself, and the second release then found nothing. The record is now scoped by
pointer id — the first contact owns it, and only that pointer's up or cancel
consumes it.

Also drops a tautological assertion in the workspace test that re-checked the
`data-testid` value `getByTestId` had just queried by, so it could not fail.

Both fixes are covered by tests proved to fail without them: reverting the drag
owner fails only the second-touch test, reverting the tap scoping fails only
the stray-contact test. 17 tests pass in the file; tsc --noEmit clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zGybqwusmeKC2jJhsU9JC
…d list details

The phone calendar read as a stack of flat strips: six chrome bands before
the first event, a view switcher squeezed beside two arrows, a coach people
line with ghost-text buttons under it, and a flat list. This gives it the
structure and material of a native calendar.

Masthead (FairwayCalendarHero)
- Large period title with the year quiet beside it; the day short on a phone
  (full form kept for assistive tech); a settle-in on change.
- Full-width Segmented in its shared depth presentation (sunken track,
  floating thumb). Prev/next only from md up: on a phone the schedule
  swipes, like every native calendar.
- "Today" is a lifted secondary pill carrying today's number in a calendar
  glyph (decorative, outside the name); More is a lifted disc.
- Warm bottom rim + resting whisper so the bar sits ON the list; a 2px
  progress line along its edge replaces the loading banner.
- Publishes --fw-calendar-hero-h so day headings can pin beneath it.

Schedule (FairwayCalendar / FairwayAgendaView / FairwayEventCard)
- The body swipes horizontally to turn the period (touch/pen, >=56px,
  <=40px drift) and slides in from the direction of time, derived from the
  period keys; a view change is a cut; reduced motion disables it.
- The coach's phone primary action is a floating "+" above the tab bar; the
  empty state's own New event and the masthead's labelled button stay md+.
- Day headings pin under the masthead; Today/Tomorrow carry their date.
- Each day is one raised group card of hairline-divided rows (0.5px on 2x).
- Today's group carries a now-line at its sorted position, ticking by the
  minute after mount, seeded from nowRef for hydration.
- Rows show start + meridiem and the end time on every width; touch press
  tints the row. The range-empty state is compact, never a monolith card.

People row (FairwayCalendarMemberRail)
- One raised row: the summary IS the People trigger; Compare is a lifted
  control (icon on a phone, labelled from md), and the People menu also
  offers "Compare schedules…".

Month / strip / skeleton
- Phone month is one raised card with sans tabular numerals (not mono); the
  selected day pill in the strip is lifted; the route skeleton now mirrors
  the real first paint (masthead, people row, grouped day cards).

Tests: masthead naming/glyph/height var/progress line; swipe paging and the
floating action; now-line placement; the one-row people entry; plus the
coverage tests from the PR review (Segmented quiet, ModalShell workspace,
scheduling recheck, relative day cue, agenda window). Feature doc updated.
Review scripts live under docs/reviews/calendar-mobile-review (captures
are left untracked).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zGybqwusmeKC2jJhsU9JC
Primary and secondary buttons now sit ON the page: a lit top edge plus the
resting card whisper at rest, a step up on hover, settled flat on press.
Flat controls read as disabled chips beside raised cards and the Segmented
switcher's floating thumb. Ghost stays chrome-light by contract; danger
keeps its flat whisper. The calendar's local shadow overrides are dropped
now that the shared variants carry the depth.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zGybqwusmeKC2jJhsU9JC
…view

The push hook's diff --check rejects trailing spaces; finding titles now
stand as their own paragraph instead of relying on a two-space line break.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zGybqwusmeKC2jJhsU9JC
A raised circle inside a raised row, same cream, two stacked hairlines and
an icon nobody reads. On a phone the row is one control (it opens People,
which already offers "Compare schedules…"); from md the labelled Compare
button stands beside the row, never inside it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zGybqwusmeKC2jJhsU9JC
Every finding in it was applied in this branch; as a docs/ file it added
178 markdownlint violations to the repo-wide ratchet, which blocks Review
Gate. The review lives in the PR conversation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011zGybqwusmeKC2jJhsU9JC
@njrini99-code
njrini99-code force-pushed the agent/calendar-makeover branch from 1d57a46 to d18d77b Compare September 10, 2026 02:24
@njrini99-code
njrini99-code merged commit cea5649 into main Sep 10, 2026
28 of 29 checks passed
@njrini99-code
njrini99-code deleted the agent/calendar-makeover branch September 10, 2026 02:44
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