Skip to content

Release: The Forum 1.0 (forum.tigerapps.org) - #55

Merged
DIodide merged 82 commits into
mainfrom
staging
Sep 28, 2026
Merged

DIodide merged 82 commits into
mainfrom
staging

Conversation

@DIodide

@DIodide DIodide commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Promotes staging to production. Includes #43 (sparse ingested events), #48 (ranking, visibility, hardening, indexes), #49 (Princeton CAS, CI, hygiene), #50 (simpler UI), #51 (InboxEngine: MyPrincetonU orgs, logos, venues, events), #52/#54 (deployment + CI/CD), #53 (floating sidebar, Figma cards on real data).

Merging deploys https://forum.tigerapps.org (production DB theforum), then syncs from InboxEngine every five minutes. Staging stays at https://forumdev.tigerapps.org.

Release report: https://claude.ai/artifact/Vhjswni5iyjqiBjLtu87n3

rohanmatta11 and others added 30 commits August 20, 2026 21:28
…ty cap, seeded daily nudge, popularity/org-history signals, ranking docs
TIG-247.

ErrorState was sitting in the shared states file unused by anything, so
every server action failure was invisible. Wired it into Explore (retry
button instead of an empty feed, which reads as "no events" and is a
totally different message) and into the notification dropdown, which had
an actual `// silently fail` in it.

Worse than the missing UI: RSVP and save were updating optimistically and
never rolling back. If the server threw, the button stayed flipped while
the DB disagreed. Both revert now and toast.

Didn't toast on the notification failure — it polls every 60s so that
would just be spam. It shows the error inside the dropdown when you open
it instead.

Search: Explore needed you to press Enter, Orgs debounced as you typed.
No reason for them to differ, so Explore debounces now too, plus a result
count when a search or filter is active.

Ingested events: the server swaps in the literal string "TBD" when an
event has no location, and the card was rendering that next to a map pin,
which just looks broken. Treats it as absent now. Org, flyer, description
and tags were already conditional so a sparse event degrades fine.

Settings on a phone: TOP_BAR_CLEARANCE reserved 140px on the right to
clear the floating bell/avatar, which is over a third of a 375px screen.
Drops below the bar on phones instead, only insets from the right at sm+.
Fixes the Events page heading too since it uses the same constant.
Home feed now matches the Figma: event cards in a two-column grid with
the highlights rail pinned to the right, so only the feed shifts when the
nav rail expands. The shell runs full width instead of max-w-7xl. With a
capped shell the whole row re-centred and the rail travelled with it.

Card, on Explore and the map's expanded view alike:
- utility icons (save, share, hide, open) go coral, hovering to a coral
  wash rather than the ghost variant's full-strength turquoise --accent
- tags alternate yellow/turquoise, friends line reads "… added this event
  to their calendar!", description gains a See Details link
- footer wraps as two units with the redundant Users glyph dropped, so
  "4 attending" stops breaking across two lines in a narrow column
- the map modal picks up the shared date formatter and the "+ Calendar"
  action; buildGCalUrl was extracted for that call site and never wired up

Fixes from review:
- feed requests carry a monotonic id and a queued search is cancelled when
  filters change, so a debounce armed with the previous filters can no
  longer land last and overwrite the feed
- save/RSVP flip optimistically and genuinely revert on failure; the catch
  used to invert a value that was still correct. Handlers rethrow so the
  card announces success only once the request resolves, instead of
  showing a success toast beside the error one
- the result count uses the returned total, not the 20-row page length
- getFeedEvents carries rawDatetime through; it was declared on FeedEvent
  for calendar links but dropped with the sort key

Rail backdrop goes near-opaque over the map, where 50% let street names
read straight through the nav labels.
…-ingestion

Error states, consistent search, and handle sparse ingested events
scrape_listserv.yml failed daily and printed listserv subjects into public Actions logs; pipeline-poll.yml was disabled and broken.
…ta-layer

Brings in rohanmatta11's Explore ranking work as-is (smooth time decay,
org diversity cap, soon-event quota, seeded daily nudge, popularity and
past-RSVP org affinity, batched enrichment, docs/ranking.md). The branch
head does not compile yet; review fixes follow in the next commits.
Seed creates users with plausible real NetIDs. Abort when NODE_ENV=production or DATABASE_URL host is not localhost/127.0.0.1 unless ALLOW_REMOTE_SEED=1.
It scraped officer/member personal data using browser cookies and contained an execSync shell injection. Org data will come from the shared InboxEngine service.
Without it, bunx tsc from the repo root resolved typescript@latest (TS 7).
Centralize date helpers in lib/date-format.ts (zoned parts, date keys,
wall-clock to instant conversion, formatters) and use them for the
explore header, event detail, map timeline/labels/day bucketing, map
list cards, and notification timestamps. Server-rendered card strings
now also use Eastern time instead of the host's UTC.
Friends shown on event cards have RSVP'd; the app never reads anyone's
calendar. Also pluralize the '+ N others' overflow.
Copy-only change in explore-client.tsx, kept in its own commit so it can
be dropped cleanly if it conflicts with the data-layer work there.
Addresses the open review points on PR #38:
- Fix the missing closing brace in diversifyByOrg (compile error).
- Return the finalized, paginated page instead of the full enriched list.
- Apply scoring, the org-diversity cap and the soon-event quota to the whole
  candidate list in a single greedy pass (finalizeFeedOrder), then slice.
  Soon injection only picks events whose org is under the cap.
- Org cap is now a sliding window (max 3 per org in any 20 positions)
  rather than deferring over-cap events to the very end.
- Candidate pool: all published upcoming events within a 45-day horizon
  (cap 1000), plus a personalised second stage (followed orgs, friend
  RSVPs, interest tags) only if the first stage saturates.
- Enrichment is batched with one inArray query per signal across the
  candidate set; attendee rosters are loaded for the returned page only.
- Responses carry asOf; later pages reuse it as 'now' so every page is a
  slice of the same ranking.

Ranking logic moves to lib/feed-ranking.ts (pure) and lib/feed.ts (data),
getFeedEvents validates its params with zod, and docs/ranking.md is updated.

Co-authored-by: Rohan Matta <rohan.matta11@gmail.com>
Class years are computed from the current Princeton academic year
(rolling over after Commencement) plus Grad/Other, instead of a
hardcoded list that had already gone stale. Interest and campus-region
options are shared between onboarding and settings, keyed by the
existing event_tag / campus_region enum values. Friends list no longer
renders 'Grad' as "'ad".
- Remove the hardcoded 'Princeton TigerApps / Design Lead' rows, dead
  Edit Role buttons, the duplicate Friends/Organizations block with its
  non-working search, and the fake 'Suggested tags' (Jane Street etc.)
- Interests: add/remove from the real event_tag list with a filter;
  saving stays on the page, toasts success/failure, and refreshes from
  the server so the saved state survives a reload (ideas from PR #44,
  without its enum rename)
- Send the user's actual regions instead of regions: [] (which wiped
  them) and add a campus-region picker
- Editable name (updateProfile accepts displayName), read-only NetID,
  dynamic class years, major picker, org-leader toggle
- Organizations panel lists orgs the user actually manages
- Avatar upload checks the S3 response and surfaces errors via toast
- Links to Privacy, Terms and contact
It rendered a duplicate of Settings and nothing linked to it.
apps/web/src/lib/cas.ts: state generation + timing-safe compare, return-path sanitizing, service/login/serviceValidate/logout URL builders, service-URL pinning, and serviceValidate XML parsing (fast-xml-parser, removeNSPrefix, processEntities:false) with NetID validation. Adds fast-xml-parser, a 'test' script, and drops the unused @auth/drizzle-adapter dependency.
Entra derived netId from the UPN/preferred_username with no domain check, allowing account takeover. Login now goes through Princeton CAS (fed.princeton.edu/cas):

- /api/auth/cas/login stores a 32-byte state + sanitized return path in short-lived httpOnly SameSite=Lax cookies and redirects to CAS.
- /api/auth/cas/callback timing-safe checks state, then calls Auth.js signIn('cas') in-process.
- The 'cas' Credentials provider's authorize() re-validates the ticket with CAS serviceValidate (15s timeout), pins the service URL to our own callback/origin, and upserts the user by NetID without clobbering edited displayName/email. CAS is the sole identity authority.
- Direct POSTs to /api/auth/{signin,callback}/cas are rejected.
- Sign-out clears the Auth.js session, then ends the CAS SSO session via /api/auth/cas/logout.
- New shadcn auth error page (pages.error = /auth/error), public in middleware.
- jwt callback tolerates DB errors, keeping last known token values.
- Env: drop AUTH_AZURE_*; add optional AUTH_URL and CAS_BASE_URL; document AUTH_TRUST_HOST.
Next 16 deprecates middleware in favor of proxy, which always runs on Node.js and rejects a runtime segment config.
Adds lib/event-visibility.ts as the single definition of who may see an
event: (status='published' AND is_public) OR creator OR owner/officer of the
event's org. Applied to:
- Explore feed and search (published AND visible)
- Map, which also drops events at the (0,0) 'other' placeholder location
- getEvent: returns null (404) for drafts/private events the viewer can't see
- Similar events, friends' activity, saved/RSVP'd lists in My Events and the
  Explore sidebar
- Org page upcoming events (drafts/private labelled for officers); org
  recommendations only count published public events
- Notifications: reminders only for visible events, and notifications that
  reference an event the viewer can no longer see are hidden. Reminder
  generation is batched instead of one query per event.
- toggleRsvp/toggleSave no-op on events the viewer can't see
- createEvent only notifies followers for published public events
- Name is saved (completeOnboarding accepts displayName)
- NetID and email are displayed read-only from the user's record
  instead of editable inputs that were never saved
- Remove the Residential College picker (no column to store it in)
- Class years are computed dynamically; interests use the real
  event_tag values instead of lossy label aliases (Film -> visual arts)
- Save on Finish Setup and only show 'You are all set' after the
  server confirms; failures toast instead of a fake success
- Mobile: responsive padding/stacking, h-dvh, no clipped tall steps
turbo.json: declare build env (NEXT_PUBLIC_* hashed; SKIP_ENV_VALIDATION, DATABASE_URL, AUTH_*, CAS_BASE_URL, AWS_* passed through). Turbo 2 strict env mode otherwise strips them, so 'turbo build' could not validate or skip env validation.
DIodide and others added 29 commits September 28, 2026 03:10
Small flyer beside a 24px title, host link, a tight date/time/place
list, one row of actions (RSVP, Save, Add to calendar, Share),
attendance on one line, tags, description, and owner Edit/Delete as
quiet links. Similar events become a compact list. RSVP/save/delete now
roll back and toast on failure instead of claiming success.
The four-step 'timeline' sidebar was navigation for a one-screen form.
Narrow column, smaller title input and cover drop zone, tighter spacing.
Narrower panel, 13px sans title, 32px avatars, tight rows and
sentence-case action buttons instead of the 28px italic serif header and
56px tiles.
SearchInput gains an opt-in shortcut: '/' focuses it from anywhere
outside another field and Escape leaves it, with a small kbd hint.
Enabled on Explore, Orgs and Friends.
…e legacy pipeline

- packages/inbox-engine git submodule (TigerAppsOrg/InboxEngine)
- migration 0004: org MyPrincetonU identity/profile fields, event source_url and
  location_detail, sync_state; organizations.creator_id nullable for imported groups
- db:sync-engine: upserts 739 MyPrincetonU orgs (logos, descriptions, links),
  ~350 campus venues and the revisioned event feed (official + listserv)
- Org profile rebuilt: logo header, MyPrincetonU/website/social links, About,
  upcoming events, followers + friends who follow; rosters stay on MyPrincetonU
- Event detail shows where imported events came from and room details
- Remove backends/fastapi, apps/admin-web, apps/listserv-scraper, models/

Co-Authored-By: Claude <noreply@anthropic.com>
- Preset cover grid sits behind a 'Pick a preset cover' disclosure
- Smaller drop zone; Publish/Save use the regular button size
- My Events is only highlighted on /events itself, not on event pages
  opened from Explore
Hide/share (hover affordances on desktop) are left out below md so the
title isn't squeezed to a few characters; titles may wrap to two lines
on small screens.
Integrates #48 (data layer: ranking/asOf pagination, visibility,
validation, rate limits) and #49 (Princeton CAS auth, proxy.ts).

Conflict resolution:
- actions/map.ts: staging's visibility/validation imports and logic,
  plus the Eastern-time formatTime label
- actions/users.ts: staging's zod schemas, transactions and helpers;
  optional displayName re-added to the schema and both updates
- explore-client.tsx: staging's asOf/offset pagination and load-more
  state; compact row layout with a small 'Load more (N left)' button
- event-detail-client.tsx: compact layout; Edit gated on staging's
  canEdit (org officers), Delete on isOwner
- app/page.tsx: staging's CAS GET forms; landing previews + SiteFooter
- mobile-nav.tsx / sidebar.tsx: no Log out tab, sidebar stays deleted;
  the header account menu uses signOut({ redirectTo:
  '/api/auth/cas/logout' })
- proxy.ts: staging's file with /privacy, /terms and the OG/Twitter
  image routes added to the public list
Staging's httpsUrlSchema rejects http:// and credentialed URLs, which
the client accepted and then failed with a generic error.
Release: simpler, honest UI (compact shell, real settings, event forms, polish)
Adopt InboxEngine: MyPrincetonU orgs, logos, venues and events; retire legacy pipeline
Additive: FeedEvent gains optional orgLogoUrl (organizations.logo_url)
and locationDetail (events.location_detail), and EventDetail gains
orgLogoUrl, so cards can show MyPrincetonU logos and rooms. No query
filters or ordering change.
- FloatingSidebar: detached 28px-radius card, turquoise to blush
  gradient, icon-only nav (Home, My Events, Map, Friends, Organizations)
  with turquoise active tile, tooltips and aria-labels; Log out pinned
  at the bottom behind a confirmation, using the CAS sign-out redirect
- 'g' then h/e/m/f/o jumps between sections (shown in tooltips)
- Header becomes a light utility bar (New event, bell, account menu);
  the wordmark and menu Log out only appear on phones, which keep the
  bottom tab bar
- Faint ambient pastel blobs behind content
- Card: 24px radius; 40px org logo (object-contain on white) with a
  pastel initials fallback for the ~200 orgs without one; semibold org
  name; 20px serif title; room detail after the venue; free food tags
  yellow, others turquoise; hide is hover-revealed on desktop
- Event photo shows as a thumbnail only when it's real: MyPrincetonU's
  generic orange banner (shared by hundreds of events) is treated as
  no image
- Description preview drops raw links from listserv emails
- Search is the Figma pill (pale turquoise, blue icon); chips are
  outlined pills with a coral/peach active state and use the exact
  event_tag values
'Hello <Name>,' with today's Princeton date, the pill search and topic
chips, then events as a two-column card grid or compact rows (toggle
remembered per browser). The side column keeps Friends going and shows
Saved only when there is something saved (the old 'Coming up' repeated
the top of the feed). Pagination/asOf/load-more logic is unchanged.
- Layout preference is remembered per page (Home defaults to cards,
  My Events to the denser list)
- Full cards show Edit/Delete for your own events and always offer
  '+ Calendar', building the Google Calendar link themselves
…e sync timer

- next.config: output standalone traced from the repo root
- apps/database/src/migrate.ts (drizzle-orm migrator) for deploys without drizzle-kit
- deploy/: build-release.sh, run-release.sh (migrate, switch, health check, rollback),
  systemd units (server + 5-minute sync timer), nginx sites, SSM document
- .github/workflows/deploy.yml: CI then deploy (staging → forumdev, main → forum)
- turbo passes DB/engine env to db:* tasks; README deployment section

Co-Authored-By: Claude <noreply@anthropic.com>
Orgs list rows use the shared OrgAvatar (logo or initials fallback) and
a pill New organization button; list containers, panels and skeletons
move to 20px radii on white. Orgs stay a dense list: 739 groups read
better as rows than as a card grid.
Header, About and the side column sit on white 24px-radius cards; the
title is serif; link chips become pills; the logo uses the shared
OrgAvatar (MyPrincetonU logo or initials).
Content sits on a 24px-radius card, 'Hosted by' shows the org's logo,
and MyPrincetonU's generic banner falls back to the cover art.
Deployment: forumdev (staging) and forum (production) with CI/CD
'Add a tagline here' is MyPrincetonU's empty-field placeholder, not an
org's tagline.
Deploy fix: release directory permissions
Design: floating sidebar and Figma event cards, built on real InboxEngine data
@DIodide
DIodide merged commit 67de279 into main Sep 28, 2026
5 checks passed
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.

4 participants