Skip to content

Commit 52d3a58

Browse files
authored
docs(handoff): add files for 7-10-2026 (#1994)
* feat: add handoff skill and plans * docs: add handoff files for today
1 parent 1cba9b5 commit 52d3a58

19 files changed

Lines changed: 1448 additions & 1 deletion

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ blob-report/
2222
build/
2323
buildcache/
2424
logs/
25-
/handoff/
2625
node_modules/
2726
packages/backend/logs
2827
packages/backend/build

docs/Config/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ SuperTokens handles user-sessions for us.
5454
| `supertokens.postgres.database` | Self-host | Postgres database name for SuperTokens. |
5555

5656
## Google
57+
5758
These values are only necessary if you want to enable Google Oauth and/or 2-way sync between Compass and Google Calendar
5859

5960
Both `google.clientId` and `google.clientSecret` must be real values for Google features to activate. Setting only one causes backend startup to fail.

handoff/20260709/plan.md

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# Plan — 2026-07-09
2+
3+
Five spec items, one PR each off `main`, shipped via the `ship` skill (auto-merge on green CI).
4+
Ordering below is the intended ship order: cheap/mechanical first, the hard nudge item mid-day
5+
with a fresh Opus/high context, forms last.
6+
7+
Answers locked at the morning gate:
8+
9+
- **Item 1:** Left-align everything (header, message, buttons); buttons ordered **Log out → Cancel**, left-aligned.
10+
- **Item 2:** **Broad** copy rewrite across the full audited list — but errors stay *functional* (colloquial ≠ vague; keep the "what to do next").
11+
- **Item 5:** **Shared form shell only** — extract the inner `<form>` wrapper; leave each form's floating/positioning wrapper as-is.
12+
13+
---
14+
15+
## Item 1 — fix(web): logout dialog + command-palette keycap
16+
17+
**Files:**
18+
19+
- `packages/web/src/components/OverlayPanel/OverlayPanel.tsx``OverlayPanelActions` currently `flex w-full justify-end`.
20+
- `packages/web/src/components/LogoutConfirmation/LogoutConfirmationDialog.tsx` — button order.
21+
- `packages/web/src/common/hooks/useLogoutCmdItems.ts``shortcut: "z"` (line 19).
22+
- `packages/web/src/components/CommandPalette/CommandPalette.tsx` (renders `item.shortcut`).
23+
24+
**Approach:**
25+
26+
- The panel root is `flex flex-col items-center` (OverlayPanel.tsx:65). `items-center` is why the
27+
message + actions don't share the header's left edge. The panel is **shared** with the auth
28+
status loader (`variant="status"`, centered spinner) — so I will **not** flip `items-center`
29+
globally. Instead:
30+
- Give the `message` `<p>` `w-full` so its text aligns to the same left edge as the full-width title. (Title already has `w-full`.)
31+
- Change `OverlayPanelActions` from `justify-end` to `justify-start` **only for the modal use** — cleanest is to keep the panel's `items-center` but make title/message/actions all `w-full`, and left-justify the actions. Verify the status loader still looks right (it has no actions row, so unaffected).
32+
- Actually simplest & safest: switch panel root to `items-start` for `variant === "modal"` only (status keeps `items-center`). Then title/message/actions naturally left-align; add `w-full` where needed for the buttons row. I'll pick whichever reads cleaner once I see it in the preview; both are contained to the modal variant.
33+
- Reorder buttons in `LogoutConfirmationDialog` to **Log out (primary) → Cancel**. No extra tests for ordering (per spec).
34+
- Remove `shortcut: "z"` from the Log Out command item so no keycap renders (the palette already conditionally renders `item.shortcut`, so dropping the field is sufficient). Confirm no code still binds `z` to logout.
35+
36+
**Deliberately not doing:** no new tests solely for button order; not touching the status/loader variant's alignment.
37+
38+
**Manual test:** open logout dialog (via command palette / account menu) → header, message, buttons share left edge; order is Log out then Cancel; Esc + backdrop still dismiss; focus trap intact. Command palette → Log Out row shows **no** `Z` keycap.
39+
40+
---
41+
42+
## Item 2 — fix(web): more human copy (broad)
43+
44+
**Primary:** `packages/web/src/views/GoogleAuthCallback/GoogleAuthCallback.tsx:65`
45+
`"Completing Google authorization..."``"Just finishing up …"`.
46+
47+
**Broad sweep (audited candidates, colloquial + still functional):**
48+
49+
- `auth/google/hooks/useConnectGoogle/useConnectGoogle.util.ts` — "Checking Google Calendar…", "Syncing Google Calendar…", "Syncing...", "Google Calendar needs a sync", "Google Calendar needs reconnecting".
50+
- `components/PlannerSidebar/PlannerAccountSummary/PlannerAccountSummary.tsx:87` — "Syncing changes…".
51+
- `auth/google/hooks/useConnectGoogle/useConnectGoogle.ts:78` — sync-failed toast.
52+
- `auth/google/authorization/google-authorization.constants.ts:15,17` — OAuth failure / missing-permissions.
53+
- `auth/google/util/google.auth.util.factory.ts:16,18` — local-sync failure / session-expiry-during-save.
54+
- `components/LoginAbsoluteOverflowLoader/LoginAbsoluteOverflowLoader.tsx:52,57` — login loader messages.
55+
- `common/utils/storage/db-errors.util.ts:61,69,77` — quota / version-mismatch / unexpected-close.
56+
- `common/utils/app-init.util.ts:43` — offline-storage-unavailable.
57+
- `components/AuthModal/hooks/useAuthFormHandlers.util.ts:9` — backend-unreachable.
58+
- `auth/compass/user/hooks/useLoadProfile.ts` (`showSessionExpiredToast`) — session expired.
59+
- `common/apis/util/api.util.ts:81` — "Login required, cuz security 😇" (already casual; keep or lightly tidy).
60+
- Leave already-friendly ones (`uff-dah…`, "Check your email") as-is.
61+
62+
**Guardrails:** replace jargon (authorization, quota, backend, database version) with plain words,
63+
keep the actionable instruction in every error. If a string is asserted in a test, update the test.
64+
65+
**Manual test:** trigger the Google auth redirect → spinner reads "Just finishing up …". Spot-check a couple of the reworded toasts (sync failure, session expiry) still make sense.
66+
67+
---
68+
69+
## Item 3 — fix(web): nudge lag + replay (the hard one; Opus/high)
70+
71+
**Root cause (confirmed by trace):** every keydown (incl. OS key-repeat) calls `mutate()`, which does
72+
an instant optimistic cache write **plus** a network PUT. PUTs are serialized per-event by
73+
`waitForPrecedingEventWrites` (`events/mutations/event.mutation.runtime.ts`) to avoid Mongo
74+
write-conflict 500s, with **no coalescing** — so a burst enqueues N PUTs that drain one-at-a-time
75+
after the user stops (the "replay"), and the queue can error under load.
76+
77+
**Files:**
78+
79+
- `packages/web/src/common/utils/event/event-nudge-shortcut.util.ts` (`nudgeEventFromKeyboard`).
80+
- `packages/web/src/views/Week/hooks/shortcuts/useWeekShortcuts.ts` (`moveFocusedCalendarEvent`, Shift+Arrow bindings).
81+
- `packages/web/src/views/Day/hooks/shortcuts/useDayEventNudgeShortcuts.ts`.
82+
- `packages/web/src/components/PlannerSidebar/SomedayEventSections/SomedayEvents/SomedayEventContainer/SomedayEventContainer.tsx` (`scheduleEvent`, someday→grid).
83+
- `packages/web/src/common/hooks/useUpdateEvent.ts` — already separates instant `draftActions.setEvent` from network `edit`; `saveImmediate=false` updates local state only.
84+
85+
**Approach (keep it simple):**
86+
87+
- Keep the visual **instant** on every keypress (optimistic/draft update — the mechanism already exists via `useUpdateEvent(payload, false)` / draft store).
88+
- **Trailing-debounce the network write** (~250 ms after the last nudge) so a burst collapses to a
89+
single PUT of the final position. Add one tiny debounce helper (none exists in the repo) — a
90+
minimal `useDebouncedCallback`/timeout, not a library.
91+
- Flush the pending write on blur / unmount / when a different event is nudged, so nothing is lost
92+
if the user navigates away quickly.
93+
- Do **not** touch the `waitForPrecedingEventWrites` serialization machinery — the debounce removes
94+
the burst before it reaches that queue, which is the simplest fix and leaves the write-conflict
95+
protection intact.
96+
- Scope: keyboard nudging (Shift+Arrow) + the someday→grid schedule path. Drag already routes
97+
through the draft layer and isn't part of the reported bug — leave it.
98+
99+
**Spike rule:** if the instant-visual-vs-debounced-write split gets ugly after ~2 attempts (e.g. the
100+
optimistic layer fights the debounce), stop, checkpoint in summary, and reassess coalescing at the
101+
queue instead.
102+
103+
**Manual test:** hold Shift+Right on a grid event → moves smoothly, network tab shows ~1 PUT after
104+
release (not one per step), no errors. Someday→grid via Shift+Right then nudge around → preview
105+
immediate, single settle, no trailing replay.
106+
107+
---
108+
109+
## Item 4 — style(week): now line only on the current day's column
110+
111+
**File:** `packages/web/src/common/calendar-grid/components/CalendarTimedGrid.tsx` (`CalendarNowLine`, lines 154–179; render gate at line 79).
112+
113+
**Approach:**
114+
115+
- Off-week guard already exists (`isTodayVisible ? <CalendarNowLine/> : null`, line 79) — no change there.
116+
- Currently the line is `absolute h-px w-full` → spans all columns. Scope it to the current day's
117+
column: compute `todayIndex = visibleDates.findIndex(date isSame today)` and pass it (+ column
118+
count) to `CalendarNowLine`; position via `left: calc(index * 100% / count)` and
119+
`width: calc(100% / count)` (matches the `repeat(count, 1fr)` grid). Keep the blue gradient + minute tick.
120+
121+
**Manual test:** current week → now line appears only under today's column. Navigate to a non-current
122+
week → no now line. Resize / different visible-day counts → line stays aligned to today's column.
123+
124+
---
125+
126+
## Item 5 — style(forms): shared form shell
127+
128+
**Files:**
129+
130+
- `packages/web/src/views/Forms/EventForm/EventForm.tsx` (grid `<form>`: `z-1 rounded-sm bg-(--event-form-bg) px-5 py-4.5 shadow-… transition-all duration-300`).
131+
- `packages/web/src/views/Forms/SomedayEventForm/SomedayEventForm.tsx` (someday `<form>`: same + `text-xl`).
132+
- New: `packages/web/src/views/Forms/EventFormShell.tsx` (or `components/`).
133+
134+
**Approach:**
135+
136+
- Extract the inner `<form>` wrapper into a content-agnostic `EventFormShell` that owns the shared
137+
container styling (padding, bg via `--event-form-bg`, shadow, rounding, transition) and takes
138+
`children`, `priority`, `onSubmit`, ref/props. Both forms render their existing sections inside it.
139+
- Reconcile the one real drift (`text-xl` on someday, title sizing) — decide a single consistent
140+
scale or expose a minimal prop; keep both forms visually intentional.
141+
- Leave floating wrappers (`FloatingEventForm`, `FloatingFormContainer`) untouched — they anchor to
142+
genuinely different things (grid event vs sidebar). Consolidating them was explicitly out of scope.
143+
144+
**Manual test:** open a grid event form and a someday event form → identical padding/margins/shadow/
145+
radius; fields and save/cancel behave as before; no visual regression in either.
146+
147+
---
148+
149+
## Cross-cutting
150+
151+
- One branch + PR per item, off `main`; `ship` skill validates → reviews → opens PR → watches CI →
152+
squash-merges on green. Merge gated on `gh pr checks` exit + `mergeStateStatus == CLEAN`.
153+
- Respect `.claude/settings.json` deny-list; any denied need → push-notify, not a workaround.
154+
- Live browser QA (`preview_*`) while screen is unlocked; otherwise CI is the gate and QA is
155+
sequenced for staging review.
156+
- Evening: run `simplify` over the day's diffs, land cleanup as its own PR(s).
157+
- Keep `summary.md` append-only through the day.
158+
159+
---
160+
161+
# Item 6 (new handoff) — feat: allow events to have empty title (#1871)
162+
163+
PO answers locked at the gate:
164+
- **Existing "untitled" events:** *leave them, fix going forward.* No inbound string-matching of
165+
"untitled"/"(No title)". Just stop creating the default; existing ones clear when edited.
166+
- **Empty display:** *fully blank block.* Keep the visible card textless; keep the "Untitled event"
167+
string only in the screen-reader aria-label (accessibility).
168+
169+
## Root cause (verified in code)
170+
171+
`packages/core/src/mappers/map.event.ts:168-169``gEventDefaults.summary = "untitled"`. The
172+
gcal→compass mapper does `mergeWith({}, gEventDefaults, gEvent)` then `title = event.summary!`.
173+
lodash merge skips only `undefined` sources, so when Google **omits** `summary` (which is what the
174+
API does for untitled events — "(No title)" is display-only, never sent), the `"untitled"` default
175+
lands in `title`. That single default is the bug.
176+
177+
## Scope — two one-line source changes, no frontend changes
178+
179+
1. **`packages/core/src/mappers/map.event.ts:169`**`summary: "untitled"``summary: ""`.
180+
Absent Google summary now maps to `""`. Empty/real summaries already pass through unchanged.
181+
182+
2. **`packages/core/src/types/event_new.types.ts:49`**`title: StringV4Schema``title: z.string()`.
183+
`StringV4Schema` is `z.string().nonempty()` and is **shared** (gEventId, rrule, etc.), so give
184+
`title` its own `z.string()` rather than relaxing the shared symbol. This v4 `EventSchema` is dead
185+
at runtime today — it only feeds a Mongo `$jsonSchema` validator via a migration
186+
(`packages/scripts/src/migrations/…new-events-collection.ts`) — but relaxing it now prevents a
187+
latent DB-level rejection of `""` if/when that collection goes live. Keeps intent consistent.
188+
189+
## Deliberately NOT changing (verified already correct)
190+
191+
- **Outbound compass→gcal** (`map.event.ts:105`, `if (event.title) gcalEvent.summary = …`): omitting
192+
`summary` is correct. `updateEvent` uses `gcal.events.update` (PUT / full-resource replace,
193+
`gcal.service.ts:218`), so an omitted summary **clears** the title in Google — the desired
194+
round-trip. No change.
195+
- **Frontend**: display already renders raw `event.title` (blank when empty); the "Untitled event"
196+
fallback is aria-label-only (`CalendarTimedEventCard.tsx:167`, `CalendarAllDayEventCard.tsx:87`) —
197+
matches PO's "fully blank block, keep a11y". Forms already accept empty titles; web schemas extend
198+
`CompassCoreEventSchema` (`title: z.string().optional()`) with no override. No web changes.
199+
- **Log-only fallbacks** (`gcal.event.parser.ts:121` `?? "unknown"`, `compass.event.parser.ts:545`)
200+
— not persisted, don't affect titles. Leave.
201+
- No inbound "untitled"/"(No title)" normalization (PO chose leave-existing).
202+
203+
## Tests
204+
205+
- Add cases to `packages/core/src/mappers/map.event.to-compass.test.ts` (or the to-compass test):
206+
gcal event with **absent** summary → compass `title === ""`; gcal event with real summary → unchanged.
207+
- Sweep google-to-compass propagation test utils/fixtures for reliance on the `"untitled"` default
208+
(`google-to-compass.event-propagation.test.util.ts`, upsert tests) and the driver default
209+
`event.driver.ts:64` (`|| "No Title"`); update only what the new behavior actually breaks.
210+
- Run `bun run type-check` (v4 schema edit) + web/core/backend unit suites.
211+
212+
## Ship
213+
214+
One PR off `main` via the `ship` skill (branch e.g. `feat/empty-event-title`), title
215+
`feat(core): allow events to have empty title`. Auto-merge on green CI. Manual Testing Steps in the
216+
PR: create an event with no title → saves + displays as a blank block; (staging, authed) it syncs to
217+
Google as "(No title)" and syncing back keeps it blank.
218+
219+
## Manual test (local + staging)
220+
221+
- Local preview: create/save an event leaving the title empty → no error, card renders blank, reopens
222+
with an empty title field (placeholder "Title" shows).
223+
- Staging (authed, sequenced): empty-title event → appears in Google Calendar as "(No title)"; edit
224+
its time in Google → title stays blank in Compass on next sync.

handoff/20260709/spec.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# July 9, 2026
2+
3+
## [x] feat: allow events to have empty title
4+
5+
<https://github.com/SwitchbackTech/compass-calendar/issues/1871>
6+
7+
## [x] fix(web): adjust logout menu and cmd
8+
9+
- see logout-msg.png in this dir
10+
- Problems:
11+
- The logout and cancel buttons are not positioned intuitively
12+
- The "You'll lose access.." text isn't aligned with the "Log out?" header
13+
- The buttons aren't aligned with the "Log out" header
14+
- The buttons are in an unintuitive order
15+
- The 'z' shortcut keycap appears in the cmd palette, event when we don't support it anymore
16+
- Solutions
17+
- remove the 'z' keycap preview from the "Log Out" option in the command palette
18+
- Align vertically
19+
- Reorder buttons to be 'Log out' and then 'Cancel' (don't add extra tests just for the ordering, that's overkill)
20+
21+
## [x] fix(web): make copy in loader and elsewhere more human
22+
23+
Problem: The spinner that shows after a redirect happens says "Completing Google authorization...". Users don't know what 'authorization means'. The language is developer-focused instead of user focus
24+
Solution: Rename that loading message to be "Just finishing up ..."
25+
26+
- For other user-facing message, make them more colloquial and functional rather than displaying developer-focused language
27+
28+
## [x] fix: when quickly nudging, the event lags behind
29+
30+
- for example, when moving a someday event to the grid with SHIFT+RIGHT, the event shows up successfully, but if the user keeps moving it around, it can't keep up and errors appear.
31+
- also, when a user moves a someday event from the sidebar to the grid, the draft preview shows up immediately. however, when the user finishes and doesn't do anything (they're finally happy with the time), then all their changes are 'replayed': they happen from the top and the user sees the event change one move (and API request) at a time.
32+
- the draft should always appear immediatly, but we shouldn't trigger a new API request for every move. We might need to throttle/debounce or introduce another mechanism to ensure the API is getting hammered and that we don't need to reply the changes after the user is done in order to keep state in sync
33+
- remember to keep this solution as simple as possible (it could get ugly if we're not careful)
34+
35+
## [x] style(week): adjust the now line to only appear on the current day's column
36+
37+
- problem: the now line is very visually noise
38+
- solution: instead of rendering the now line across the entire grid, just render it on the current day (if the user is viewing the current week). Do not render the now line if the current day is not visible
39+
40+
## [x] style(forms): consolidate form styling
41+
42+
- problem: the someday and regular event forms are both handling stylings slightly differently, resulting in inconsistencies (eg the layout or magin)
43+
- solution: create a shared form component that both the regular and someday event components use
44+
- this component should be agnostic of what the form contents are; the dev should just be able to use it to ensure things like layouts, margins, floating behavior, etc is consistent
45+
46+
### [x] feat(day): make the default task list width 600
47+
48+
- compass.day.task-list-width
49+
50+
### [x] refactor(web): simplify floating-ui implementations
51+
52+
- refactor custom code to use floating-ui, reducing maintenace burden
53+
- why: we don't need to reinvent the wheel
54+
- IMPORTANT: don't do this until after moving the form to the sidebar.
55+
- also update agent docs for guidance on this so its used going forward
56+
- when to use floating-ui:
57+
- tooltips
58+
- popovers
59+
- select menus
60+
- comboboxes
61+
- dropdown menus
62+
- dialogs
63+
64+
### [x] fix(day): user cannot migrate task when thumb icon is focused
65+
66+
- expected: when a user has focused on the thumb icon to the left of a task and types the cmd+ctrl+arrow hotkey, the task should be migrated
67+
- actual: nothing happens
68+
- reason: this is important because we want to give the user confidences that they can always migrate a task with the keyboard easily
69+
70+
#

0 commit comments

Comments
 (0)