/** * Web-app constants. Domain limits live in `@factory/shared/constants` * (LIMITS, TOKEN_LIFETIMES, RATE_LIMITS); this file holds web-specific values * plus the block/property/view type lists used by menus and pickers. */ import { BLOCK_TYPES, PROPERTY_TYPES, VIEW_TYPES, type BlockType, type PropertyType, type ViewType, type WorkspaceRole } from "./types"; // The `?? fallback` is a DEV-ONLY convenience (local two-process setup per // .env.example). In any deployment the env file MUST set these, because a // browser on a different origin than the page cannot reach localhost — the // silent fallback then produces opaque "Cross-Origin Request Blocked" errors // (observed 2026-10-07: a dev server started without NEXT_PUBLIC_API_URL // shipped a bundle that called http://localhost:4000 from notion.filps.com). // The warning below makes a misconfigured bundle visible in the browser // console instead of failing on first API call. const USING_ENV_API_BASE = process.env.NEXT_PUBLIC_API_URL !== undefined; export const API_BASE: string = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000/api/v1"; export const WS_URL: string = process.env.NEXT_PUBLIC_WS_URL ?? "ws://localhost:4001"; export const APP_URL: string = process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000"; if (!USING_ENV_API_BASE) { console.warn( "[web] NEXT_PUBLIC_API_URL is not set — using the localhost fallback " + `(${API_BASE}). If this page is served from another origin (tunnel/domain), ` + "API calls will be blocked by the browser. Source the .env file " + "before starting `next dev`." ); } /** * httpOnly refresh cookie — the server-side source of truth for the session * (set by the API on /auth/login and /auth/register, rotated on * /auth/refresh, cleared on /auth/logout). Name must match the API's * REFRESH_COOKIE (apps/api/src/constants.ts) — it is HttpOnly, so web JS can * never read it; the web client uses it ONLY as an existence signal for * render-gating (lib/auth-store.ts `hasSession()`). */ export const SESSION_COOKIE = "factory_refresh"; export { BLOCK_TYPES, PROPERTY_TYPES, VIEW_TYPES }; export type { BlockType, PropertyType, ViewType, WorkspaceRole }; /** Human labels for the type picker / slash menu (see components/editor/type-menu.tsx). */ export const BLOCK_TYPE_LABELS: Record = { paragraph: "Text", heading_1: "Heading 1", heading_2: "Heading 2", heading_3: "Heading 3", quote: "Quote", callout: "Callout", bulleted_list: "Bulleted list", numbered_list: "Numbered list", todo: "To-do list", toggle: "Toggle list", image: "Image", video: "Video", audio: "Audio", file: "File", bookmark: "Bookmark", embed: "Embed", code: "Code", equation: "Equation", column: "Column", column_list: "Columns", divider: "Divider", spacer: "Spacer", database: "Database", linked_database: "Linked database", table: "Table", chart: "Chart", form: "Form", synced_block: "Synced block", child_page: "Page (child)", child_database: "Database (child)", toc: "Table of contents" }; export const PROPERTY_TYPE_LABELS: Record = { title: "Title", text: "Text", rich_text: "Rich text", number: "Number", select: "Select", multi_select: "Multi-select", status: "Status", date: "Date", date_range: "Date range", person: "Person", email: "Email", phone: "Phone", url: "URL", checkbox: "Checkbox", files: "Files", created_time: "Created time", created_by: "Created by", last_edited_time: "Last edited time", last_edited_by: "Last edited by", relation: "Relation", rollup: "Rollup", formula: "Formula", button: "Button", unique_id: "Unique id" }; export const VIEW_TYPE_LABELS: Record = { table: "Table", board: "Board", calendar: "Calendar", timeline: "Timeline", list: "List", gallery: "Gallery", chart: "Chart", form: "Form", dashboard: "Dashboard" }; /** * Permission levels (PRODUCT-SPEC §17: full_edit > edit > comment > view). * "public" (anyone with the link, no sign-in) is a SHARE level, not a member * role: the share surfaces offer it (`pageApi.share` accepts it) and the API * is the enforcement (TASK-0133 org policy can disable it). PERM_LEVELS — * the member-grant ladder — therefore excludes it; SHARE_LEVELS is the * complete set a page share can be set to. */ export const PERM_LEVELS = ["view", "comment", "edit", "full_edit"] as const; export type PermLevel = (typeof PERM_LEVELS)[number]; export const PERM_RANK: Record = { view: 0, comment: 1, edit: 2, full_edit: 3 }; /** Levels a page share can be set to (member ladder + the guest link). */ export const SHARE_LEVELS = [...PERM_LEVELS, "public"] as const; export type ShareLevel = (typeof SHARE_LEVELS)[number]; export const STORAGE_LIMITS = { MAX_FILE_SIZE: 500 * 1024 * 1024, MAX_PAGE_TITLE: 200, MAX_WORKSPACE_NAME: 50, MAX_BLOCK_BATCH: 500 } as const; /** Debounce window (ms) before a queued block save flushes to /blocks/batch. */ export const BLOCK_SAVE_DEBOUNCE_MS = 500; /** Query cache staleness for structural data (pages, blocks, records). */ export const DEFAULT_STALE_TIME_MS = 30_000;