From ac44d513634cf6a9494f2da67d3f0a6537fa400f Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 24 Apr 2026 02:32:25 +0200 Subject: [PATCH] =?UTF-8?q?feat(calendar):=20M4.a=20=E2=80=94=20events=20a?= =?UTF-8?q?dopt=20the=20unified=20visibility=20system?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Third consumer of @mana/shared-privacy. Calendar events now carry a VisibilityLevel the owner flips from the EventDetailModal via ; a new calendar.events embed source lets the user drop a moduleEmbed block on their website that pulls their public events in. This unblocks concrete use-cases the Website-Builder audit surfaced: band tour dates, public workshops, public rehearsals on a team-space website, meeting-with-the-host pages. Changes: - calendar/types: visibility + unlistedToken + visibilityChangedAt + visibilityChangedBy on LocalEvent; CalendarEvent (UI type) requires visibility. timeBlockToCalendarEvent forwards the field; cross-module TimeBlocks (tasks, habits, time entries) without an owning LocalEvent fall back to 'space' so they stay off the public embed - calendar/stores/events: createEvent stamps defaultVisibilityFor(activeSpace.type); createDraftEvent seeds a 'private' draft until the user explicitly opts in; new setVisibility(id, level) mints/clears the unlisted token on the transition boundary and emits cross-module VisibilityChanged - calendar/components/EventDetailModal: sits in the modal-actions row left of copy/edit/delete website embed: - website-blocks/moduleEmbed/schema: EmbedSourceSchema adds 'calendar.events'; the filter shape gains optional `upcomingDays` (1-365) and `tagIds` (up to 16). Old filters (isFavorite/status/kind) remain — each source uses only its own subset - website/embeds: resolveCalendarEvents gates hard on canEmbedOnWebsite(event.visibility ?? 'private'), joins each event to its LocalTimeBlock for the real start/end, applies the optional upcomingDays window and tag-id AND-filter, sorts upcoming-first with id as stable tiebreaker Redaction is whitelist-per-design (plan §2): the inlined snapshot carries only title, formatted date range, and location — NOT description, reminders, tag labels, or the guest list. Fields that typically hold private context stay out of the public blob regardless of the visibility toggle. Verified: - pnpm check (web): 7450 files, 0 errors - pnpm test calendar + website: 26/26 - pnpm run validate:all green Next: M4.b — Todo, M4.c — Goals. Same pattern; split out because goals lives under $lib/companion/goals/ with its own structure and Todo has a complex view-column/filter surface that warrants its own PR. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../components/EventDetailModal.svelte | 6 ++ .../modules/calendar/stores/events.svelte.ts | 50 +++++++++ .../web/src/lib/modules/calendar/types.ts | 10 ++ .../web/src/lib/modules/website/embeds.ts | 102 ++++++++++++++++++ .../website-blocks/src/moduleEmbed/schema.ts | 12 ++- 5 files changed, 177 insertions(+), 3 deletions(-) diff --git a/apps/mana/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte b/apps/mana/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte index d6689da3f..f1e244ba6 100644 --- a/apps/mana/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte +++ b/apps/mana/apps/web/src/lib/modules/calendar/components/EventDetailModal.svelte @@ -1,6 +1,7 @@