Files
webmail/lib/demo/fixtures/files.ts
T
Linus Rath 4c0017d0c1 feat: rework the demo around one persona with two accounts and a shared mailbox
The demo user becomes Nora Lindqvist, engineering lead at Meridian Labs,
in the week of a product launch. The fixtures tell one story across mail,
contacts, calendar, tasks and files, and exercise the features the
screenshots and the public demo should show:

- a second, mail-only login (her personal domain) and the shared Support
  mailbox delegated to the work account, so the account switcher, the
  shared section and the merged inbox have real content
- raw headers the way Stalwart writes them: the sender check fails on
  the phishing mail, newsletters carry a one-click unsubscribe, one mail
  asks for a read receipt, and spam scores show in the details
- an iMIP invitation with a served .ics, an event waiting for the answer
  on the calendar, and RSVP patches applied in the demo client
- @mentions as mailto links in a received mail, a verification code
  twelve minutes old, nested folders, named tags on the mail, contact
  groups, a scheduled message and tasks switched on
- every attachment and file backed by real bytes under public/demo,
  generated by `scripts/demo-assets/generate.mjs`; contact photos are
  bundled portraits instead of requests to randomuser.me
- folder counts computed from the mail instead of hand-kept numbers

Demo login seeds the tags, templates and merged-inbox settings once per
device and switches the cross-account gate on for the session; "Reset"
restores them. `bulwark-demo-simulate=off` in localStorage stops the
simulated incoming mail for screenshot runs.
2026-10-18 19:48:11 +02:00

59 lines
2.8 KiB
TypeScript

import type { FileNode } from '@/lib/jmap/types';
import { demoDate } from '../demo-utils';
import { DEMO_ASSETS, type DemoBlobId } from './assets';
const FOLDER = {
documents: 'demo-file-documents',
beacon: 'demo-file-beacon',
photos: 'demo-file-photos',
travel: 'demo-file-travel',
} as const;
function folder(id: string, name: string, parentId: string | null, createdDays: number, modifiedDays: number): FileNode {
return { id, parentId, name, type: 'd', blobId: null, size: 0, created: demoDate(-createdDays), modified: demoDate(-modifiedDays) };
}
/** A file backed by one of the demo assets; name and size come from the asset. */
function file(id: string, parentId: string | null, blobId: DemoBlobId, createdDays: number, modifiedDays = createdDays, name?: string): FileNode {
const asset = DEMO_ASSETS[blobId];
return { id, parentId, name: name ?? asset.name, type: asset.type, blobId, size: asset.size, created: demoDate(-createdDays), modified: demoDate(-modifiedDays) };
}
export function createDemoFileNodes(): FileNode[] {
return [
folder(FOLDER.documents, 'Documents', null, 400, 1),
folder(FOLDER.beacon, 'Beacon', FOLDER.documents, 60, 1),
folder(FOLDER.photos, 'Photos', null, 400, 5),
folder(FOLDER.travel, 'Travel', null, 24, 2),
// Documents
file('demo-file-meeting-notes', FOLDER.documents, 'demo-blob-meeting-notes', 7, 1),
file('demo-file-todo', FOLDER.documents, 'demo-blob-todo', 3, 0),
file('demo-file-quarterly-report', FOLDER.documents, 'demo-blob-quarterly-report', 3, 2),
file('demo-file-service-agreement', FOLDER.documents, 'demo-blob-service-agreement', 9, 1),
file('demo-file-budget', FOLDER.documents, 'demo-blob-budget', 40, 4),
// Documents / Beacon
file('demo-file-launch-plan', FOLDER.beacon, 'demo-blob-launch-plan', 2),
file('demo-file-runbook', FOLDER.beacon, 'demo-blob-runbook', 1, 1),
file('demo-file-soak-metrics', FOLDER.beacon, 'demo-blob-soak-metrics', 1),
file('demo-file-soak-dashboard', FOLDER.beacon, 'demo-blob-soak-dashboard', 1),
file('demo-file-launch-deck', FOLDER.beacon, 'demo-blob-launch-deck', 5, 2),
// Photos
file('demo-file-team-photo', FOLDER.photos, 'demo-blob-team-photo', 120),
file('demo-file-lake', FOLDER.photos, 'demo-blob-vacation', 95),
file('demo-file-wedding-1', FOLDER.photos, 'demo-blob-wedding-1', 1),
file('demo-file-wedding-2', FOLDER.photos, 'demo-blob-wedding-2', 1),
file('demo-file-wedding-3', FOLDER.photos, 'demo-blob-wedding-3', 1),
// Travel
file('demo-file-eticket', FOLDER.travel, 'demo-blob-boarding-pass', 3, 3, 'E-ticket-E7KQ2R.pdf'),
file('demo-file-hotel', FOLDER.travel, 'demo-blob-hotel-voucher', 3),
file('demo-file-summit-ticket', FOLDER.travel, 'demo-blob-summit-ticket', 2),
// Root
file('demo-file-prototype', null, 'demo-blob-prototype', 1),
];
}