Skip to content

Claude/admiring clarke 7sdyyq#253

Open
krpeacock wants to merge 32 commits into
lowercasename:mainfrom
krpeacock:claude/admiring-clarke-7sdyyq
Open

Claude/admiring clarke 7sdyyq#253
krpeacock wants to merge 32 commits into
lowercasename:mainfrom
krpeacock:claude/admiring-clarke-7sdyyq

Conversation

@krpeacock

Copy link
Copy Markdown

No description provided.

krpeacock and others added 30 commits June 8, 2026 12:55
supporting weekly, biweekly, and monthly frequencies with timezone-aware
time and duration fields.

Closes #1
Builds on every push to main and publishes to ghcr.io/krpeacock/gathio:latest.
Also removes the leftover exit 0 workaround in the Dockerfile now that the
TypeScript migration is complete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
pnpm install --prod skipped devDependencies including @types/* packages,
causing tsc to fail. Install everything, compile, then prune back to prod-only
before the final image stage. --ignore-scripts bypasses pnpm's supply-chain
policy prompt for build tools like esbuild.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
generate-timezones.js was crashing on pages without a #timezone element,
leaving #recurrenceTimezone empty. Now handles both selectors safely.
group-edit.js was gating on a generateTimezoneOptions function that was
never defined; replaced with direct select2 init on the populated element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
eventGroupURL was sending the string "undefined" when no URL was set,
failing URL validation. Default to empty string instead.

Timezone select2 was initializing before generate-timezones.js had
populated the options (document.ready race). Moved init to shown.bs.modal
so options are guaranteed to exist, and added dropdownParent so the
dropdown renders correctly inside the modal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After a successful image push, joins the Tailscale network via OAuth
and calls the Portainer stack webhook to trigger an automatic redeploy.
Requires TS_OAUTH_CLIENT_ID, TS_OAUTH_SECRET, and PORTAINER_WEBHOOK_URL
secrets, and tag:ci in the Tailscale ACL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Timezone: render options server-side with America/Los_Angeles default,
  remove select2 dependency that was causing empty dropdown
- Group URL: treat empty/undefined as optional in both JS and validation
- Dockerfile: lowercase BUILD_IMAGE stage name
- Actions: pin to Node 24-compatible versions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The UI uses recurrenceNthDayOfWeek for the nth-weekday day picker, but
both POST and PUT handlers were reading recurrenceDayOfWeek (the weekly
field) for all cases. This caused the wrong weekday to be stored — e.g.
Friday was stored as Monday. Now reads the correct field based on monthlyType.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously events were only generated on container startup and daily.
Now creating or editing a group with recurrence enabled immediately
generates upcoming events, so fixes take effect without a restart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pe field conflict

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…nces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Generated instances inherit the template's edit token so the
creator can edit any occurrence with a single token.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rror handling

Group events were failing validation (missing type, empty location string).
Per-entry try/catch prevents one broken series from blocking others.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- computeOccurrences: convert resolved date to rule timezone before
  setting hour/minute so 18:00 means local time, not UTC
- deleteevent route: skip ActivityPub broadcast for events without
  activityPubActor (auto-generated instances)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add excludedDates to IEventGroup interface and EventGroupSchema (was
  only in local working tree, not committed)
- Explicitly import IEventGroup type in recurrence.ts to make the type
  annotation unambiguous for TypeScript in all build environments
- Include packageManager field in package.json (pnpm corepack)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Conflicts with the pnpm version specified in the GitHub Actions workflow
config, causing ERR_PNPM_BAD_PM_VERSION during CI setup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Recurring event generation produced duplicate Event documents whenever
`generateRecurringEvents` ran concurrently or on different calendar days.
Three causes are addressed:

1. Application-level dedup with no DB guarantee. Replaced read-then-insert
   with `Event.updateOne(..., upsert: true)` keyed on a new compound unique
   index `(recurrenceId, occurrenceKey)`. Concurrent runs now collapse to a
   single document per occurrence; E11000 from the loser is swallowed.

2. Non-anchored biweekly stepping. `computeOccurrences` now steps in fixed
   units from a canonical `anchorDate` stored on the rule, never from `now`.
   The same series produces the same UTC instants regardless of when
   generation runs. Anchor is set explicitly to the template event's start
   on event-level rules and synthesized (and persisted) on first generation
   for legacy group-level rules.

3. Stale occurrences after rule edits. Group edits that move the rule
   (frequency / weekday / time / timezone / monthly mode change) now clear
   future auto-generated, attendee-free instances before regenerating, so
   edits update future instances instead of duplicating them.

Legacy group-level recurrence now also flows through the upsert path under
a synthetic `recurrenceId = "group:<groupId>"`, with one-shot backfill of
`recurrenceId`/`occurrenceKey` on existing rows so the unique index applies
to them too. Pre-existing duplicates are logged and skipped; a separate
dedup migration is still required before the index can fully apply on
databases that already contain duplicates.

Refs: #7
…eTimezone not sent

Two bugs prevented newly-created recurring events from generating occurrences:

1. recurrenceTime was never auto-populated from the event's start time.
   The form field started as "" and computeOccurrences() received rule.time = "",
   causing moment.hour(NaN) → invalid candidate moments → zero occurrences.
   Fix: watch recurrenceEnabled and eventStart in Alpine init() to auto-populate
   recurrenceTime from the event's start datetime substring.

2. The recurrenceTimezone <select> had no x-model, so Alpine's data.recurrenceTimezone
   stayed as "" regardless of the user's selection. The server stored rule.timezone = "",
   causing moment-timezone to silently use UTC for all occurrence stepping.
   Fix: read the select's DOM value in submitForm() (same pattern as editEventGroupForm).

Also fixes the same timezone bug in newEventGroupForm, which lacked both recurrence
fields in data{} and the DOM-read fix in submitForm(). Adds server-side validation
that rejects event creation with invalid/missing recurrenceTime or recurrenceTimezone
when recurrenceEnabled=true, surfacing the error clearly instead of silently storing
broken recurrence data.

https://claude.ai/code/session_018jCUGYAWLjrtP5525S32u4
kaiapeacock-eng and others added 2 commits June 9, 2026 14:22
…s test

When the user reports "I marked the event recurring but it isn't repeating,"
the previous form behavior gave them nothing to troubleshoot with:
- A blank recurrenceTime / recurrenceTimezone was silently accepted client-
  side, accepted server-side, and only manifested as zero generated
  occurrences hours later.
- Non-400 errors swallowed the actual server message and showed "An
  unexpected error has occurred."

This change:

1. validateRecurrenceFields (public/js/util.js) mirrors the server's
   validation rules and runs before submit. If any field is invalid, the
   form renders the field-level message and decorates the offending input
   with `is-invalid` — no network round-trip.

2. extractErrors (public/js/util.js) reads `response.errors`,
   `response.message`, or the raw response body in that order, so the
   user sees the real reason for a non-200, not a generic message.

3. newEventForm and newEventGroupForm both use the helpers and report
   network failures with the underlying error message.

4. New Cypress test cypress/e2e/recurringEvent.cy.ts covers two paths:
   a) Happy path — recurrenceTime auto-populates from eventStart, event
      submits, /api/event confirms recurrence.time and timezone are
      non-empty, /api/recurrence/generate produces multiple instances.
   b) Validation path — clearing recurrenceTime shows the inline error
      and decorates the field; no submit happens.

https://claude.ai/code/session_018jCUGYAWLjrtP5525S32u4
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.

3 participants