OpenPost is the all-in-one content team for solo founders. It turns their work into channel-ready content, publishes it, and brings outcomes back into one workspace. Hosted is the primary product; self-hosting is a deployment option.
- A Publication is the user-visible post. It owns the source idea, schedule, status, and destination outputs.
- A Rendition is one destination-specific version with its own account, text, media, format, timing, and provider settings.
Product copy calls these posts and variants. Publication and Rendition remain the internal model names.
- Start with the founder's launches, updates, lessons, and ideas. Remove repeat work without hiding decisions.
- Preserve provider truth. Show real capabilities, limits, review needs, readiness, and failures.
- Keep every outcome inspectable: draft, scheduled, queued, published, failed, or retrying.
- Use the same terms, permissions, and workspace boundaries across web, mobile, API, CLI, MCP, Hosted, and self-hosted surfaces.
- Treat UX consistency as a product requirement. Reuse established patterns and preserve keyboard access, visible focus, readable contrast, reduced motion, localization, and touch targets.
Load only the branch the task needs:
| Task | Read or use |
|---|---|
| Substantial, ambiguous, or multi-ticket work | agent-workflow; the OpenPost Vikunja project for the current spec, priority, and execution state |
| Product scope, public copy, provider support, or capability claims | README.md for current public claims and readiness; PRODUCT.md for purpose, terms, and scope |
| UI, visual, or user-facing copy changes | ux-consistency and DESIGN.md; add impeccable for design critique |
| Public documentation structure, prose, examples, or images | documentation and unslop; preserve complete tasks, useful images, and old routes |
| Public SEO, search discovery, or agent access audits | openpost-seo; verify live findings against the marketing and documentation source owners |
| Interaction sounds | cuelume |
| Repository ownership or an unfamiliar seam | docs/agents/repository-map.md, then confirm its paths and symbols with rg |
| Go HTTP or OpenAPI work | huma; for TypeScript consumers, also use openapi-typescript |
| Server-state reads, cache keys, invalidation, or loading boundaries | docs/specs/server-state-query-migration.md |
| Deployment, runtime configuration, release workflows, or revision proof | docs/agents/deployable-inventory.md |
| n8n package release or package-only recovery | docs/agents/n8n-package-release.md |
| Video Editor, Quick Cut, or Recorder | docs/specs/video-editors-rebuild.md and the relevant rows in docs/specs/freecut-parity-audit.md |
For reference implementations, read docs/references/README.md before inspecting a checkout. Use postiz or shoutrrr for publishing and durable automation, miniPaint for the Image Editor, and the named video references for recording or editing. Keep reference checkouts shallow and Git-ignored. Audit source and license before porting code; OpenPost's architecture, security, accessibility, provider rules, and product language remain authoritative.
- Vikunja owns private work, specs, priorities, and execution state. GitHub Issues and pull requests own public reports and contributor discussion.
- Hindsight bank
rodrigoowns durable product terms, decisions, constraints, and history. Before substantial work, recallproject:openpost, then verify the result against current code, Vikunja, or the live system. Retain only verified durable facts taggedproject:openpostandsource:<agent>. - This repository owns code, tests, generated contracts, build and security rules, and public technical documentation. Keep project state out of repo-local memory files and mutable task boards.
Keep deployable projects in apps/, shared runtime code in packages/, browser suites in tests/, deployment files in deploy/, and repository manifests in config/. Public Go module names and root task scopes do not change when source folders move. Keep community policies in .github/, launch templates in docs/launch-kit/, social artwork in assets/social/, and browser configurations with their test suites. Keep PRODUCT.md and DESIGN.md at the root for design-tool discovery. Local review reports and screenshots are ignored; commit maintained acceptance inputs with their owning tests or design sources.
Keep third-party deployment packaging under deploy/<platform>/. If a platform requires its manifest at the repository root, use a dedicated wrapper repository instead of adding another root file here.
Public marketing speaks to Cloud customers creating content and running a business. Features navigation points to /#features; automation and self-hosting setup belong in the documentation, not duplicate marketing pages. Keep removed public routes out of the route manifest and maintain their deliberate redirects in apps/marketing/static/_redirects.
Public buying comparisons belong to the shared guide catalogue in packages/social-images/src/. Reuse the guide renderer and discovery manifest. Derive OpenPost prices and limits from @openpost/plan-catalog; keep competitor facts with official source links and an explicit review date. Change that date only after checking those sources, and state where the competitor fits better.
Comparison metadata owns the product name, local official mark, category, and optional free-tool link. The Resources comparison directory and guide hub share one directory component; retain existing guide URLs. Creative-tool comparisons describe free local editing separately from Hosted storage and publishing. Keep third-party mark sources in assets/logos/comparisons/SOURCES.md and preserve original colors.
Install marketing module-download recovery in the client init hook, before hydration imports run. A layout mount callback cannot recover failures that precede its own mount.
App module-download recovery clears its persisted retry budget only after a successful SvelteKit navigation. Keep failed routes bounded across reloads.
Public OG images use the shared renderer in scripts/social-images/render.mjs. Keep generated homepage artwork and its prompt in assets/brand/social/, topic icons from Lucide, full titles, stable image URLs, and matching launch-kit PNGs. Verify the full catalogue and inspect cards at 320px preview width.
Public media-tool routes, format pairs and metadata come from packages/social-images/src/media-tools.js. Directory thumbnails use static format and operation previews, public Lucide icons, and the paired marketing palette. Keep their palette assignments in the shared media-tool catalogue. Browser video/audio conversion stays behind the marketing local-media.ts adapter, loads only on tool routes, bounds inputs and output writes, and rejects failed selected tracks. The UI and guides explicitly disclose primary-track selection and omitted subtitles or attachments.
Public documentation uses Fumadocs in apps/docs/, with authored MDX under content/docs/ and a static export in out/. Keep customer guides, built-in Workflows, external automation, self-hosting, and the generated API reference separate. Workflow tutorials and run controls belong in content/docs/workflows/; SDK, HTTP API, CLI, and n8n guides belong in content/docs/automate/. Engineering documentation belongs in docs/development/; operator reference details belong in docs/reference/. Generate API operation pages from apps/web/openapi.json on every docs build, never edit generated MDX.
The public Cloudflare site composes marketing at / and documentation at /docs through bun run build -- public-site; openpost-marketing publishes dist/public-site. Keep docs base-path aware, reject output collisions, and keep the retired docs host only as a direct path-and-query-preserving redirect.
Keep static redirects before wildcard or placeholder rules in the composed public site. Cloudflare counts every rule after the first dynamic rule against its 100-rule dynamic limit.
Generate the media-limits guide with scripts/sync-docs-openapi.mjs; edit the capability catalogue or apps/server/cmd/openpost-media-limits, never the generated MDX.
Verify docs reader interactions against the static export with bunx playwright test --config tests/docs/playwright.config.ts after bun run build -- docs. Use shared Fumadocs controls and SVG icons for page actions, search, and navigation. Documentation copy controls belong to apps/docs/components/documentation-copy.tsx; retain the exact source text for recovery and show success only after the clipboard write completes.
Each social network has one self-hosting integration guide. Keep shared credential setup in the integration index and link provider-specific steps to it. Use SetupScreenshot for expandable setup screenshots, preserve source attribution and licenses for reused portal images, and label edited example values. Capture OpenPost connection dialogs through tests/app/product-screenshots.spec.ts in both schemes. Generated screenshots illustrate setup, never provider approval.
- SvelteKit builds the interface; Go embeds it into one binary. Echo serves HTTP, Huma owns OpenAPI, and Bun ORM owns database access. SQLite is the self-host default; PostgreSQL supports Hosted.
- Publications are the canonical authored-content inventory. Page reads use stored state; provider calls occur only in explicit sync or durable job flows. Planned views use authored
scheduled_at; queuedactual_run_atmay include random dispatch delay and is not the authored schedule. Published views use the recorded actual time. - Rendition array order is authored inventory state. Persist input order in
renditions.position, and project it consistently in reads and actions. Creation timestamps and generated IDs never define destination order. - Publication revision domains compare stored authorship before and after the transaction. Ignore delivery IDs, timestamps and inherited output projections; preserve explicit empty overrides and effective schedule changes.
- Hydrate the composer session and visible content from the same Publication response. Never refresh only the save revision behind an open editor; stale content must trigger a revision conflict rather than overwrite newer thread segments.
- Joined rendition segments retain authored per-source text and media decisions in
source_overrides, keyed by canonical Publication segment IDs. Project them into one provider output; never hydrate the flattened output as a first-source override. Missing source overrides inherit, explicit empty values omit, and legacy joined overrides remain a whole-output edit. Deduplicate output media by ID in canonical source order, retaining the first occurrence's item metadata and each source's authored selection. - Shared polls belong to canonical segment
settings.poll; destination choices are independent of text and media overrides. The shared composer shows a compact summary and edits common content in a cancellable dialog; account tabs own poll versions, supported voting settings, and unresolved-account notices. Text versions retain authored language without adding engagement prompts. Resolve throughservices/publicationpollfor persistence and delivery. Preserve explicit text/omit choices and legacy polls; seedocs/development/publication-polls.mdbefore changing poll storage or projection. - Social Set account formats and reusable destination/post settings are defaults for new publications. Copy them into renditions and rendition segments on create, let explicit post values win, and never rewrite existing publications when a set changes. Keep attachment-specific settings on the post. Preset editing and saving use the same account-resolved settings common to every media shape and authoring variant of the selected format; Automatic has no format-specific presets. Account-resolved settings are authoritative even when empty; use provider catalog fields only before account resolution.
- Workflows owns automation discovery, templates, editing, and run inspection. Existing repost policies and executions stay in the repost service, retaining IDs, grants, snapshots, and per-post overrides. First comments remain destination settings; never duplicate either system into generic workflow runs.
- Workflow node categories and symbols come from the shared workflow catalog. Use
workflows/node-icon.svelteacross the canvas, picker, and previews; category colors never replace labels or run-status indicators. - Workflow reference paths distinguish literal JSON keys with quoted brackets from nested dot paths across discovery, chips, validation, copying and execution. Preserve existing dot-path bindings; see
docs/development/workflows.md. - Native post imports use the configured provider registry and canonical OAuth grant permissions. X has no import reader, even with an operator budget override. Keep imports in their read-only inventory, retain activation watermarks and unfinished cursors, reserve provider request costs before I/O, and fence checkpoints against disable or reactivation. See
docs/development/native-post-imports.md. - Persistent work uses database jobs rather than in-memory goroutines. Media crosses the
BlobStorageboundary. Provider adapters live underapps/server/internal/platform/. - Meta comment collection follows outer and nested continuation pages through the shared bounded Graph walker. Share its request budget across one collection, validate continuation hosts, preserve reply parents and ownership, and retain typed recovery errors. A failed later page must fail collection rather than record a partial result as successful.
- S3 download bodies follow the caller's cancellation and close lifecycle. Bound connection and response headers through the transport; never apply the short object-operation deadline to the entire media stream.
- Workflows persist structured definitions and immutable run snapshots in
services/workflows/; the canvas is a projection. Canvas placement is device-local, scoped to the Workspace and workflow, and must not change authored definitions or run snapshots. Native effects use publication, Builder, and provider-write owners with stable run/step identity. Readdocs/development/workflows.mdbefore changing admission, approval, recovery, destinations, canvas actions, editor history, recipes, custom tools, node tests, AI decisions, usage, or workflow credentials. Workflow destinations reuse the composer's Social Set controls; native publication and Builder creation own default snapshots. Stored Builder inspection remains actor- and Workspace-authorized without AI configuration; generation and mutation retain their runtime guards. JavaScript stays inside the bounded WASM adapter; HTTP and AI dispatch require a fenced durable receipt. - The binary roles are
all,web,worker, andmigrate. Self-hostedallauto-migrates; Hosted migrates once before startingwebandworkeragainst that schema. - Svelte code uses runes, the typed API client, and shared UI/page controls. Visible form fields use shared primitives.
- Social post cards and page shells belong to
packages/social-preview, shared by the composer and public tools. Account tabs supply editing snippets through the composer mutation owners; All stays shared and Full preview stays read-only. Canonical segmentsettings.linkowns account URL choices resolved byservices/publicationlink; preserve legacy URLs and exclude generated URI projections from authorship. Preview support is separate from publishing readiness. Composer text and preview paragraphs detect their own direction without changing stored text or surrounding controls. Keep layout responsive to its container and explicit appearance independent of the host theme; seedocs/development/social-previews.mdfor the rendering contract and references. - Composer sync controls follow
docs/development/social-previews.md. Read its inheritance contract before changing linked state or unlink/relink actions. bun run fallowgates introduced dead code through the maintained audit JSON attribution and reports complexity separately withhealth --report-only. The thin gate adapter preserves full audit JSON and fails closed on tool or protocol errors; combined audit exit codes also enforce health thresholds. Repository build-script dependencies belong to root devDependencies and the Fallow tooling framework.- Frontend checks generate Paraglide declarations before
svelte-check; source-only generated messages can hide diagnostics behind TypeScript's file-size limit. Turbo must forwardOPENPOST_PARAGLIDE_PRECOMPILEDso browser checks can reuse those declarations without Vite overwriting them. CI usescheck -- frontend-marketingto prepare translations once for both readers; standalone scopes prepare their own output. - Shared
Select.Rootdefaults to a scalar single selection. Passtype="multiple"only for array values. - The client-only app sets its initial scheme and canvas in
apps/web/src/app.htmlbefore external resources load. Keep its storage key aligned with ModeWatcher; verify entry and reload withtests/app/startup-theme.spec.ts. - Reorder previews stay local to the view until drop. Commit through the owning editor or composer mutation once, preserve selection by key, and let Escape cancel without autosaving. Video Editor clip moves share visual tracks across clip kinds, retain linked audio timing, and reject locked, incompatible, or occupied targets.
- PWA registration uses an absolute root URL through
SvelteKitPWAandpwa-manager.svelte. Updates wait for open windows to close. Cache only public app resources; exclude API, authorization, query-bearing navigations, and original media. Test service workers against the production build withtests/app/pwa.spec.ts, not Vite dev. - App browser tests use full Chromium's headless backend. Their servers disable external diagnostics and product telemetry regardless of inherited environment settings; diagnostics regressions use local HTTP receivers. PWA installability checks require an isolated temporary persistent profile because Chrome disallows installation in private contexts.
- Authored color fields use
$lib/components/color-picker.svelte. Keep canvas sampling, grading tools, CSS color expressions, and provider color categories in their specialized owners. @openpost/query-catalogowns cache-safe remote reads across web and mobile. Mutations reconcile through its affected keys; each route owns one cold loading boundary and keeps cached state visible while refreshing. Media history snapshots retain only discovery controls, guard restoration by actor and Workspace, and reload results through the query owner.- Workspace navigation keeps Posts, Inbox, Analytics, and Media visible; More owns secondary tools. Calendar and List share the Posts destination and remembered view. Calendar keeps a compact date picker with a selected-day agenda on narrow screens; List owns status tabs and search. The List view also remembers its status tab; an explicit tab in the URL takes precedence. Workspace management belongs in the workspace switcher; personal preferences belong in the profile menu. Keep mobile More drill-in views within one menu rather than sideways flyouts.
- Notification bells open the shared unread panel. Opening a notification marks it read before navigation; failed read updates leave it available to retry. The full page defaults to Unread and keeps Read and All history, with
?status=allfor the panel history link. Workspace shell owns notification polling once per active Workspace. - App page headers use
PageHeaderand the compactdata-app-titlescale, including List and Calendar. UseheaderActionLayout="inline"when one compact action fits beside the title on phones. Keep page actions, section navigation, and filters outside content loading boundaries. UsePageContainer'snavigationsnippet for persistent controls andcontentLayout="fill"when the page owns scrolling; Settings skeletons describe only the selected panel.navigation-progress.svelteowns delayed BProgress feedback for SvelteKit navigation, never background queries or measured operation progress. - Native mobile tabs share
WorkspaceHeader; keep repeated page titles and introductory copy out of it. UseNativeTextfor iOS display text so enlarged Dynamic Type owns the line box. Native analytics uses the shared query catalogue and server aggregates; never rank posts by aggregating only a destination page. - Mobile server, token, and Workspace persistence is one transaction boundary owned by
apps/mobile/src/lib/identity-store.ts. Keep all three behind its queue and crash marker; a committed server change clears the server-scoped session. packages/plan-catalogowns sellable plans, prices, and limits for marketing, signup, billing settings, and the generated Go catalog. Readdocs/development/billing-and-usage.mdbefore changing pricing, quotas, checkout, or first-Workspace confirmation.- API, CLI, MCP, and product surfaces share terms, authorization, and workspace boundaries. For a contract change, edit its source and regenerate every consumer.
- Maintainer diagnostics send allowlisted error kinds and compiled app stack locations, never raw error messages or concrete browser routes. Preserve opt-out and privacy signals, and deploy receiver schema support before new senders.
- OpenAPI declares session-cookie authentication for browser sessions and session-only security for admin operations that reject API tokens.
- Cookie banners use direct Decline (all optional browser analytics off) and Accept (first-party analytics cookies) actions. Keep cookie-free analytics under More options, and public English copy in
@openpost/telemetry; the app localizes the same choices. - Hosted waitlist admission uses
OPENPOST_HOSTED_WAITLIST_ENABLED. Keep password registration and OIDC provisioning closed while it is enabled, including callbacks started earlier; existing accounts retain sign-in. Seedocs/development/billing-and-usage.mdfor reopening and notification recovery. - Put Huma request size limits on
huma.Operation.MaxBodyBytes. Do not read and replace an HTTP request body before Huma, because Huma's body-read deadline can then cancel a long-running handler after the body is already buffered. - External application authorization is separate from social-provider OAuth. Keep delegated client identity, consent, grants, credentials, and scope policy in
apps/server/internal/services/externalapps/; keep durable signed delivery inapps/server/internal/services/externalwebhooks/. - MCP clients may request REST scopes from shared OAuth discovery. Keep their consent on the MCP path and show and submit only MCP scopes. Registered
op_app_clients use delegated application consent. - AI features use maintained SDKs behind
apps/server/internal/ai/and the shared model and configuration choices. - Publication Builder keeps nonempty authored direction fields in native input state. Omit those fields from provider response schemas and restore their trimmed values before validation; models generate only unlocked choices. Drafts and review replacements use destination output limits and native text validation. Repair invalid generated output at most once per stage within the build deadline, retaining every call's usage; exhausted repairs fail validation.
- Keep feature SVGs in
assets/brand/features/for the README and public materials. Use recognizable silhouettes with transparent backgrounds and sparse dithering, without a Converge frame. The application uses the theme icon system for page, navigation, and action icons; do not render or distribute feature artwork to the app. Preserve Converge geometry: the app logo follows its theme's focal color, public marks use paired light/dark artwork throughThemeImage, and artwork with a colored background keeps its existing colors. - Keep marketing illustration colors in paired public light/dark tokens. Preserve its original green, blue, and lilac palette with the shared neutral public controls. Do not change authenticated organization themes to style public campaigns.
- Modal scrims darken the page in both schemes. Keep their theme defaults independent of text colors and synchronize the web, server, and native built-in catalogs.
- Keep the built-in theme catalog out of startup imports. Settings loads only the selected panel, through its existing cold loading boundary; failed module downloads must leave navigation usable and offer a page refresh.
- Keep authenticated navigation and dialogs in
workspace-shell.svelte, loaded once after sign-in. Public entry routes must not download that shell; retain its mounted instance across workspace navigation. - Unconfigured organizations start with the orange-brown Dither palette; saved organization and Workspace choices take precedence. Public profiles use the signed-in viewer's Workspace theme, including direct entry and reload, and use Dither for anonymous visitors. Anonymous app entry pages also use Dither. Workshop remains the complete recovery theme. Theme management reads unpublished drafts from the organization catalog; workspace assignments use published themes. Apply server-confirmed settings immediately and keep background refreshes outside the mutation busy state.
- Theme tests use the request-scoped application preview context and the existing theme runtime. Restore saved appearance on preview exit; persist the scheme only after assignment succeeds.
- Theme chart tokens are categorical data colors. Keep
chart1throughchart5chromatic and pairwise distinct in every built-in scheme; reserve neutral gray for aggregated data outside the top series. - Dithered buttons share the binary Bayer mask and default 16% ink tint from
@openpost/dither, including generated README SVGs. Native theme controls resolve a fixed ink and opacity around that default to satisfy contrast across authored palettes. Keep texture contrast between 1.18:1 and 1.8:1 and text at least 4.5:1 on both colors; hover and press change density only. - Dither rendering belongs to
@openpost/ditheracross web, marketing, docs, and native mobile. Native controls import the DOM-free@openpost/dither/paintentry and render SVG patterns, with press feedback and no idle animation. Keep native content sections on the canvas instead of nesting cards. Measure surfaces to keep Bayer cells at 2 CSS pixels; hover changes density with no idle animation. Keep chart geometry and control semantics in their owners, validate composited action contrast, and preserve the package attribution. - Cloud Video Project copies create independent asset references with stable media IDs and shared originals in one transaction. Media status changes refresh every referencing project in the Workspace; wait for unlinked uploads before copying.
- Render queues and completed exports use project export storage: browser storage scoped to the Workspace for Cloud Video Projects, the selected folder for local projects. Recording imports must verify their destination is still open before committing timeline changes.
- Local folder permission loss disconnects the active Video Editor root synchronously. Enter reconnect using the mounted gate's folder identity, without waiting for IndexedDB reads.
- Video audio stays embedded until detached through the timeline action. Persist audio ownership independently of edit links so unlinking, moving, or deleting detached audio cannot restore the video's sound. Recorder microphone audio belongs to the camera, or to the screen when no camera is selected; mix system and microphone audio into one recorded stream.
- Recorder scratch writes append and flush in a dedicated OPFS worker, retaining the atomic-file fallback where synchronous access is unavailable. Acknowledged chunks must survive a lost tab. Finalization and preview preparation are progress states; recovery appears only outside an active recording handoff.
- Screenshot templates own versioned structured documents in
lib/screenshot-templates/and workspace drafts inscreenshot_template_designs. Preview and browser PNG export use the same authored DOM; keep its appearance independent of the app theme and preserve Unicode shaping. Fixed frames must reject overflowing exports. Generated PNGs retain immutableMediaGenerationRecipesnapshots. Chat images and participant photos use workspace Media IDs; validate and lock their sources when saving, retain normalized draft and recipe references for the media lifecycle, and reject exports with unloaded images. Reuse image return tokens for composer attachment, and register the workspace switch guard before discarding unsaved edits. Memes use the same draft editor and library with their existing server renderer; keep AI browsing separate from authoring. Meme overlays and remake parents retain media references, including GIFs. Embedded editors must flush before dismissal or mode changes. - Image and Video Editor headers share
editor-header.sveltefor layout and theme surfaces. Each editor owns its save, history, navigation, and export behavior; keep save feedback visible at narrow widths. Editor property sections use the neutral panel surface; reserve hover and selection colors for interactive states. - Quick Cut and Video Editor share source-time speech and signal analysis in
lib/video-editor/audio/. Keep cleanup in cancellable workers with bounded PCM chunks, and intersect removable ranges across retained audio channels and streams. Transcript engines own decoding and channel mixing; Quick Cut stores source words for cuts without creating subtitles. Cloud source transcripts are browser-local derived data scoped to the signed-in actor and Workspace; capture the storage target before asynchronous analysis and preserve local folder paths. Keep creation choices invideo-editor-choice.svelteand source handoffs behindmedia/workspace-source.ts. - Repurpose clip suggestions persist immutable caller-supplied transcript snapshots in
services/repurpose/. The source revision is a caller fingerprint, never proof of server-verified media contents. Resolve AI word indexes into source times, preserve actor/Workspace scope and generation-fenced cancellation, and require source-fingerprint review before editor changes. Suggestions never create or publish posts. - Repurpose reviews source-word suggestions explicitly before creating independent Quick Cut projects through existing asset and storage owners. Keep review receipts, adjusted bounds, and completed outputs scoped to the actor, Workspace, project, and source revision; keep derived cloud transcripts in the shared browser-local transcript owner.
- Recording cleanup reviews bind to an immutable timeline fingerprint. Require signal evidence for every pause, even with captions, and intersect it across linked audio before offering cuts, exempt only confirmed video-only sources, and leave repeated-start suggestions unchecked. Apply cuts and optional voice effects in one history transaction; caption generation remains an explicit action.
- Active Video Editor sequences and the Edit return destination from Motion are device-local view state, scoped to the project and Workspace. They never dirty the authored project. Recorded-project entry requests Edit explicitly; ordinary project entry keeps the remembered workspace.
- Connected editor agent requests use
services/editoragentfor scoped browser sessions and retry receipts. The open editor applies typed operations through its existing timeline actions or Image Editor controller, checks the authored revision and stable target IDs, and reports live changes separately from save and render completion. Never let MCP or Hosted assistant handlers patch editor documents directly. Future Workflow editing nodes must reuse versioned domain operations through a durable headless executor; browser sessions cannot execute durable Workflow steps. - Video Editor property diamonds author keys through timeline actions. Keep auto-key mode separate, capture first keys with
timeline/keyframe-value.ts, and resolve animated inspector values at the playhead. - Timeline history reconciles out-of-range playheads after committed duration reductions, including Undo and Redo. The editor session aligns its clock; duration reconciliation leaves live and cancelled gestures alone.
- Parented text keeps its local raster layout and inherits world dimensions when composited.
textLayoutSizeis derived render state, never an authored document field. Preview and export must scale the raster rather than reflowing text at the parent's size. - Video Editor static property edits use
updateItemProperties, which rejects effective track and group locks. Trigger save feedback only after an admitted edit; locking authored content does not disable browsing or inspector disclosures. Transform-parent admission checks the child's lock, not the controller's; parent removal still preserves surviving children in world space. - Video Editor sidebar visibility and full-height docking are independent per-device settings. Keep docking in CSS so panels and preview stay mounted; the timeline spans only columns without a full-height sidebar. Verify changes with
tests/app/video-editor-panels.spec.ts. - Video Editor library recipes are device-local, scoped to the Workspace, and own copied media and fonts. Import those files through the destination project asset boundary before inserting independent instances. Timers share frame-derived preview/export pixels and normal audio mixing; duration includes a separate optional completion hold. Reusable block duration edits preserve a quiet hold between authored entrance and exit events.
- Connected editor MCP and Assistant use the existing timeline actions and Image Editor controller. Browser sessions are interactive, never durable Workflow executors. Saved styles pin immutable versions; recipe application creates independent content. Personal rules and favorite metadata stay user/workspace scoped, shared rules stay project scoped. Only explicit save instructions create rules/styles; only admitted manual choices count toward learned suggestions. Keep provider-attempt usage durable before parsing and unknown counters nullable.
- Built-in Video Editor galleries use shipped posters from
scripts/generate-video-editor-posters.mjs. Regenerate them when catalog defaults or rendering change. Keep live previews limited to deliberate hover or keyboard focus, and stop them offscreen, on hidden pages, and with reduced motion. - Paper shaders share the
effects/paper/adapter across background and GPU effect compositors. Drive them with sequence time; keep logo preparation worker-compatible and derived from the current effect input. Preserve saved presets and Paper licensing, and fail export when exact rendering is unavailable. Readdocs/development/editor-shaders.mdbefore changing this integration. - Editor start pages share
editor-start.svelteandeditor-format-button.svelte. Keep blank project creation direct, with optional custom settings. Each editor retains its own storage and creation boundaries. Project libraries show cloud and local work together; storage choices affect new projects only. Useproject-storage-status.sveltefor storage and sync feedback, and claim offline availability only from verified local assets. - Image layers without
color_grade_versionretain legacy Fabric adjustments. Version 1 routes layer and page-output grading through the shared editor color pipeline and the Fabric preview/export adapter. Never migrate legacy layers implicitly. - Image Editor schema 2 adds per-page dimensions and grapheme-indexed text runs. Resolve page sizes through
imageEditorPageDimensions; document dimensions remain defaults for inherited and new pages. Keep text-run offsets aligned with Fabric grapheme segmentation through editing, rendering, and export. Curved text uses native SVG textPath runs through the Fabric adapter; IText owns editing and selection. Embed fonts by asset identity and retain the SVG source for scaled preview/export drawing. - Composer cover edits reuse Image Editor return tokens with
destination_coverpurpose and a revision-bound local target. Attach the export only to the original account cover setting after checking the source and current provider capability. Timestamp-only covers never enter Image Editor. Cover text creation uses per-editor readable defaults; preserve existing authored text and ordinary Image Editor defaults. - Media editing reopens only the active design associated through
design_documents.source_media_id. General media references do not establish that relationship. Duplicated and restored designs leave it unset; trashing a design permits a new source edit. Restoration preserves the design ID, content, media references and version history without replacing a newer Media editing association. - Workspace Image Editor effect presets use per-preset mutations and appear in the brand-kit read model. Applying a preset copies its effects into the document; editing or deleting the preset never rewrites existing layers.
- Image Editor inspector text ranges follow focused textarea selection changes, including collapsed ranges. Inactive inspector inputs must not replace canvas text selections.
- Image Editor controller mutations preserve unchanged document, page, and layer references; shared undo entries depend on those objects remaining immutable. Apply document edits through the controller, and defer page-strip rendering until a color gesture ends. Key document shells by design ID. Keep template documents raw and clone persisted JSON at renderer boundaries so reactive proxies cannot reach Fabric.
- Image authored properties, structural mutations, pixel edits and color gestures use the controller’s effective lock, including locked ancestors. Rename, visibility and lock management remain available; pending intrinsic image sizing completes through its dedicated import boundary.
- Local portable Image projects route assets by document role. Font imports validate declared type, file signature, size and browser decoding, normalize the filename to the actual format, and retain the font asset kind for migration. Font assets stay out of the raster media picker. Image font registration and Fabric rendering use the shared asset-specific runtime family; authored family names remain unchanged.
- Image Editor thumbnails may downsample grading inputs using crop and display size. Keep live editing and export pixels at full resolution because selection and erase tools sample the live canvas. Bounded brush masks must preserve pressure and evaluate roughness in page coordinates.
- Editors and the free color picker share
editor-color-magnifier.svelte. Pass the sampled pixels and window coordinates, and mount it outside transformed canvas containers so zoom cannot offset the preview. Each caller owns sampling and commits. - Image and Video Editors share wheel and curve controls, scope presentation, and curve math under
lib/components/editor-color-*andlib/editor-color-grade. Each editor owns its gestures, selection, persistence, and sampled frames. - Image Editor tool-family interactions and responsive Color docking follow
DESIGN.md. Keep Edit and Color pane sizes as separate device preferences; Color controls must leave the live canvas visible on phones. - Editor workstation controls (scrub field, slider row, disclosure, menu, knob, toolbar group, status line) live in
lib/components/editor-density/on theme tokens at 22px fields, 25px menus and bars, and 32px primary actions. Scope 44px minimums to coarse pointers with[@media(pointer:coarse)]; the[(pointer:coarse)]form is not a valid variant. Every new production file needs a production importer or the knip reachability gate fails. - Video Color palettes share the selected Clip or Sequence target and comparison/auto-key controls. Derive the target ID and list together; Sequence mode preserves clip selection, and playhead auto-selection skips sequence grades. Palette, scopes and Keyframes visibility are view state; switching them never authors an effect. Preserve picker and spatial overlays when hiding transform tools in Color. Photo expanded pages render the ordered strip, and narrow-screen menus dispatch through the same command registry as desktop.
- A Video Editor sequence grade is one
sequenceColorGradeadjustment item on its dedicated locked track. The timeline store keeps it over the full sequence range, and preview/export apply it once after compositing. Never treat it as an item-scoped adjustment layer. - Uploaded media is untrusted. Validate declared and detected types before deduplication. Image admission validates decoded pixels before reuse, with a 64-million-pixel allocation bound; keep uploader limits and the Media Library guide aligned. ICO decoding stays in its dedicated seekable driver; do not globally register its format, because opaque readers can allocate untrusted directory sizes. Serve only explicitly allowed passive formats inline through
setMediaResponseHeaders; active documents and unknown types must download, including legacy records. Preserve cookie-authenticated audio/video playback when changing the media CSP. - Keep secrets out of code and logs. Stored provider tokens remain encrypted.
- Braces security admission requires the identical root/mobile upstream patch and verified installed index/lib hashes before ignoring its unpatched-version advisory. Remove that admission when a fixed upstream release is available; never add an unconditional exception.
- Provider certification identifies output profiles. Hash all of an output's authoring formats into its contract, and use the same production requirements when recording and evaluating evidence.
- Connected-account limits revalidate with saved destination settings; publishing readiness uses the canonical output contract rather than account-adjusted preview limits.
- Media limits shared by the capability catalogue and provider validators belong in
apps/server/internal/providerlimits/. Record the official source and verification date there, and test both validation boundaries against the same independently specified limits. @openpost/platform-textowns public platform text limits and counting rules shared by the composer and marketing tools. Keep account-specific limit resolution in the web app.- Telegram authorization comes from its verified installation for the same account, workspace, and chat. Do not require a user OAuth grant for bot-token publishing.
- Establish the boundary. For broad, browser, or release work, run
devenv shell -- doctor. Inspect the worktree and the owning code, tests, docs, and current task. Preserve unrelated changes. The step is complete when every intended file belongs to the requested concern and the expected behavior is explicit. - Change the owner. Work through the owning service or abstraction instead of reaching around it. Use Devenv and root
bun runtasks. The step is complete when the smallest coherent implementation satisfies the behavior without a parallel source of truth. - Prove behavior. Reproduce bugs through the closest practical user-facing boundary and add the smallest stable regression test when one can fail on a plausible regression. Prefer focused practical verification over circular tests of private helpers, literals, or implementation branches. The step is complete when the changed behavior has independent evidence and the nearest relevant gate passes.
- Synchronize surfaces. Update affected contracts, docs, product copy, and generated outputs from their sources. Add user-visible behavior, migration, or operator notes to
changes/<issue>.mdunder the right changelog group. The step is complete when no affected surface describes the old behavior and contract checks pass when applicable. - Finish cleanly. Run the scoped root gate from
docs/agents/repository-map.md; scale up tobun run release -- checkfor broad release proof andbun run verifyonly for high-risk production-build proof. SetVITEST_MAX_WORKERS=2when test workers exhaust local memory; root tasks forward this limit. Re-runux-consistencyfor UI or copy. Once required checks pass, broaden or repeat them only for new edits, failures, or unresolved risks. The step is complete when relevant checks pass and residual risks are reported.
- Commit directly to
mainwith Conventional Commits, one concern per commit. Stage only files owned by the current work. - Release assets are user downloads. Keep scan reports in Actions; use Git SHAs, image digests, OCI labels, and native signatures instead of custom release manifests. Validate workflows with
bun run check -- workflows. - Push only when asked. A push is not a deployment.
- A request to release authorizes the required commits, pushes, tags, artifact publication, and deployment. State the revision and effect, run the required checks, and finish without another approval prompt. Ask separately for destructive live actions outside that release workflow.
- Before pushing a release, run the configured checks from Devenv. On macOS, the system Bash is version 3, so
scripts/changed-files-check.shmust use indexed arrays rather than associative arrays. - The stable Video Editor media regression command is
cd apps/web && bun run sync && bunx vitest run --project server ...; plainbun testbypasses the configured Svelte/Vitest setup and can report$state is not defined. - MCP OAuth responses must echo the normalized requested
mcp:readandmcp:fullscope set. Store the canonical server grant separately, and test the real Executor handshake so a response that only saysmcp:fullcannot trigger a false missing-mcp:readerror. - Discord bot analytics is an account-read operation. Do not inherit the global publishing certification gate when deciding its readiness; infer bot capability from the provider account state.
- Publish timeline item arrays atomically. Production Svelte effects can observe partially mutated proxy arrays; keep the mounted preview regression in the production-mode browser suite. Range cuts union timeline intervals, preserve intentional audio/adjustment overlap, and reconcile captions through source clip identity.
- Keep the phonemizer's generated Emscripten module as a verbatim runtime asset. Bundler tree-shaking can remove its voice filesystem initialization even when imports succeed.
- The DOM preview prepares a bounded set of nearby video layers before cuts; inactive layers stay hidden, paused, and silent. Timeline renderers share source tracks, keep streaming decoders per clip, and release inactive clips and nested compositions. Bound reverse-frame caches; defer
Input.dispose()until in-flight frame reads finish. Decoders own returned video frames until their next read or disposal; compositors borrow them.ItemRasterizerowns text, caption, and shape buffers until compositing consumes them. Invalidate cached pixels for resolved style, cue, animation, and font changes. - For editor throughput or reactive-update profiling, use
docs/development/profiling.md. Keep measurements opt-in, local, and free of user content; propagate the profiling choice to workers. - Release candidates prove themselves on their own tag: tag CI forces the full core gates (backend, frontend, security, image, browser suites) while distributions follow changes since the last published stable release, so failed tags do not become baselines. Main CI is development feedback, never release proof. Never infer coverage from a green path-filtered or canceled main run.
- Release preparation validates cheap inputs first, proves the candidate builds releasable notes from
CHANGELOG.mdpluschanges/fragments, pushesmain, then selects one immutable SHA. Tag CI generates the draft notes from the tag; a post-publish bot commit records the shipped section and consumes exactly the tagged fragments. Nevergit add --all, never stash someone else's work, never restart the whole release for a narrow failure: classify it, fix it in its owner, create a new candidate only when source changes, otherwise resume the failed job. New work either explicitly supersedes the candidate or ships next release. - A broken production never blocks shipping its fix: readiness gates deployment health, not the release decision. Product screenshots refresh deliberately with public imagery changes, never as a release gate. Android and n8n keep their own cadence; a server-only release bumps neither and waits for neither.
- Shared checkouts can advance while a release is running. Inspect
git status --short, stage only owned paths, and rebase onorigin/mainbefore pushing. Preserve unrelated work in its own branch or stash rather than deleting it.
Product images are generated by bun run capture:product-screenshots from deterministic real app flows at 2x resolution. Refresh them deliberately when public product imagery changes, never as a release gate. Capture detail images through owning UI locators, never hand-crop coordinates; keep the framed README hero out of the landing tour.
Star-history updates keep the pinned action's data and curve. scripts/style-star-history.mjs adds the Dither area fill and regenerates both SVGs and the 2x PNG with transparent backgrounds. Preserve the star-count guard.
README badges are generated into assets/badges/ by scripts/generate-readme-badges.mjs from public GitHub counts and the main CI run. Keep light and dark variants together. The README assets workflow refreshes badges and star history together once every two days in a single commit; failed data reads leave the previous assets intact.