Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
-- Sai moved the two remaining public-cycle sessions to 14:00 Argentina
-- (America/Argentina/Cordoba, UTC-3), which is 17:00 UTC. Historical sessions
-- keep their actual times. Access, facilitator and room state remain intact.
BEGIN;

WITH corrected_sessions ("id", "scheduled_at") AS (
VALUES
('50000000-0000-4000-8000-202609050001'::uuid, '2026-09-05 17:00:00'::timestamp),
('50000000-0000-4000-8000-202609120001'::uuid, '2026-09-12 17:00:00'::timestamp)
)
UPDATE "scheduled_sessions" AS session
SET
"scheduled_at" = corrected_sessions."scheduled_at",
"updated_at" = CURRENT_TIMESTAMP
FROM corrected_sessions
WHERE session."id" = corrected_sessions."id"
AND session."status" = 'SCHEDULED'::"ScheduledSessionStatus";

DO $$
DECLARE
initialized_count integer;
corrected_count integer;
BEGIN
SELECT count(*) INTO initialized_count FROM "users";

SELECT count(*) INTO corrected_count
FROM "scheduled_sessions"
WHERE ("id", "scheduled_at") IN (
('50000000-0000-4000-8000-202609050001'::uuid, '2026-09-05 17:00:00'::timestamp),
('50000000-0000-4000-8000-202609120001'::uuid, '2026-09-12 17:00:00'::timestamp)
)
AND "status" = 'SCHEDULED'::"ScheduledSessionStatus";

IF initialized_count = 0 AND corrected_count <> 0 THEN
RAISE EXCEPTION 'Empty installation contains a partial remaining Umbral cycle';
ELSIF initialized_count > 0 AND corrected_count <> 2 THEN
RAISE EXCEPTION 'Remaining Umbral sessions must both start at 17:00 UTC';
END IF;
END $$;

COMMIT;
8 changes: 3 additions & 5 deletions src/app/__tests__/page.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,9 @@ describe('landing page', () => {
});

it.each([
['third legacy', REMAINING_PUBLIC_ID, '2026-09-05T16:00:00.000Z'],
['third canonical', REMAINING_PUBLIC_ID, '2026-09-05T17:00:00.000Z'],
['fourth legacy', FINAL_PUBLIC_ID, '2026-09-12T16:00:00.000Z'],
['fourth canonical', FINAL_PUBLIC_ID, '2026-09-12T17:00:00.000Z'],
])('shows the remaining public cycle at 14:00 Argentina with the %s stored schedule', async (_state, id, storedAt) => {
['third', REMAINING_PUBLIC_ID, '2026-09-05T17:00:00.000Z'],
['fourth', FINAL_PUBLIC_ID, '2026-09-12T17:00:00.000Z'],
])('shows the canonical %s public-cycle schedule at 14:00 Argentina', async (_state, id, storedAt) => {
const scheduledAt = new Date(storedAt);
mountDb(vi.fn().mockResolvedValue([{
...SATURDAY,
Expand Down
21 changes: 1 addition & 20 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,31 +31,12 @@ type WeekendEvent = {
};

const INTERNAL_NEXT = /^\/session(\/[A-Za-z0-9_-]+)*$/;
// Rollout-safe editorial override: normalize only the legacy stored times.
// Once scheduled_at is migrated to the canonical value, this becomes a no-op
// so the app cannot show 15:00 while old and new revisions overlap.
const LANDING_1400_ART_SCHEDULES = new Map([
['50000000-0000-4000-8000-202609050001', {
legacy: '2026-09-05T16:00:00.000Z',
canonical: '2026-09-05T17:00:00.000Z',
}],
['50000000-0000-4000-8000-202609120001', {
legacy: '2026-09-12T16:00:00.000Z',
canonical: '2026-09-12T17:00:00.000Z',
}],
]);
// A missed lifecycle transition must not leave an old LIVE row advertising the
// current checkout indefinitely. Weekend sessions last hours, not days; 24
// hours keeps a delayed/extended live room discoverable while failing closed
// before a stale row can be presented as the next paid event.
const PUBLIC_LIVE_DISCOVERY_MAX_AGE_MS = 24 * 60 * 60 * 1000;

function landingDisplayTime(event: Pick<WeekendEvent, 'id' | 'scheduledAt'>): string {
const stored = event.scheduledAt.toISOString();
const schedule = LANDING_1400_ART_SCHEDULES.get(event.id);
return schedule?.legacy === stored ? schedule.canonical : stored;
}

function publicScheduleNow(): Date {
const pinned = process.env.E2E_DASHBOARD_ENABLED === '1'
? process.env.E2E_CLOCK_NOW?.trim()
Expand Down Expand Up @@ -184,7 +165,7 @@ export default async function LandingPage({
)}
</>
)}
<EventLocalTime at={landingDisplayTime(event)} />
<EventLocalTime at={event.scheduledAt.toISOString()} />
</div>
<div className="text-right">
<p className="text-xs font-mono text-[var(--text-secondary)]">
Expand Down
19 changes: 18 additions & 1 deletion src/lib/__tests__/public-cycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('public four-Saturday cycle', () => {
expect(isPublicCycleSession('10000000-0000-4000-8000-000000000001')).toBe(false);
});

it('keeps the final migration contract at the confirmed 16:00 UTC', () => {
it('preserves the historical four-session correction at 16:00 UTC', () => {
const migration = readFileSync(
new URL(
'../../../prisma/migrations/20260821194000_correct_four_saturday_cycle_start/migration.sql',
Expand All @@ -31,6 +31,23 @@ describe('public four-Saturday cycle', () => {
expect(migration).not.toContain('14:00:00');
});

it('moves only the two remaining sessions to 14:00 Argentina / 17:00 UTC', () => {
const migration = readFileSync(
new URL(
'../../../prisma/migrations/20260901180000_move_remaining_umbral_sessions_to_1400_argentina/migration.sql',
import.meta.url,
),
'utf8',
);

expect(migration).toContain("'2026-09-05 17:00:00'::timestamp");
expect(migration).toContain("'2026-09-12 17:00:00'::timestamp");
expect(migration).not.toContain('2026-08-22 17:00:00');
expect(migration).not.toContain('2026-08-29 17:00:00');
expect(migration).toContain('corrected_count <> 2');
expect(migration).toMatch(/BEGIN;[\s\S]*UPDATE[\s\S]*RAISE EXCEPTION[\s\S]*COMMIT;/);
});

it('recognizes only an entirely anonymous COMP entitlement for a reviewed public room', () => {
const candidate = {
staffUser: null,
Expand Down
Loading