You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Patch SVG Previews (mirror of rack JPEG previews, but for the patch graph)
Status and ownership
Blocked — local and linked-remote backend/storage plus image-only timestamp-preservation checkpoints are in place; broader linked migration/typegen drift remains, so preview UI/data-service wiring remains gated until safe typegen/operational verification is complete.
Priority: MEDIUM
TODO section: INFRA
Owner persona on pickup: coordinator-loop → planner → frontend-dev → code-reviewer.
Problem and user intent
Generate and persist a visual preview of each patch — saved as an SVG file in
Supabase storage — and surface it everywhere a patch is listed/teased, the same
way rack JPEG previews work today. The user explicitly asked for:
The same UX as racks: an inline preview component with a "generate / update
preview" button, stale-detection badge, and a placeholder when no preview
exists yet.
The same backend shape: a dedicated storage bucket (different folder), a *.svg filename stamped with a generation timestamp persisted on the patch
row, cache-busted on update, cleaned up on replace.
Output is a graph representation (nodes + connections), not a rasterized
rack panel. SVG is required (not JPEG) so previews remain crisp at any size
and are tiny on disk.
Product / roadmap fit
Phase 2 (community platform). Patches are the headline shared artefact of
Phase 2 and currently render as text-only cards in lists. Adding a real
visual preview makes patch browsing, search results, and embedded patch teasers
(module pages, racks → linked patches, user profile activity) instantly
scannable.
Compounding effect: every later surface that lists patches inherits this
preview for free — public profiles, marketplace listings that reference a demo
patch, manufacturer pages showing patches involving their modules, future SEO
OG-image generation (see on-seo-og-image-generation.md, which can reuse the
same stored SVG as input).
Not a roadmap gate; does not block any tier. Pure UX/SEO polish on an already
shipped surface — safe to pick up between gated work.
Does not require ROADMAP.md edits.
Current system and context
The rack preview pipeline already exists and is the explicit template to copy.
Rack pipeline (reference implementation):
DB: racks.imagetext | null — stores the timestamped filename only.
uploadRackImage(file, name) — stamps filename with _YYYY-MM-DDhh-mm-ssms.<ext> then uploads; busts rackWithId cache.
deleteRackImage(name) — busts rackWithId cache.
Data service (src/app/components/rack-parts/rack-detail-data.service.ts,
around L716–L810): captures the rendered DOM with modern-screenshot
(domToJpeg), uploads, updates the row, deletes the previous file, and
surfaces snackbars + analytics events
(rack.image_downloaded, rack.preview_image_updated). Owner-or-admin gated.
Pure helpers previewGeneratedAt() and isPreviewStale() extract the
timestamp from the filename and compare it against rack.updated.
Renders: image, placeholder for "Preview unavailable", placeholder for "New
rack" (no image yet), and a stale badge + refresh button when relevant.
Consumers: rack-minimal (and through it, rack-list, rack-micro).
Patch side today (gaps):
patches table in src/backend/database.types.ts has no image column
and there is no patches storage bucket — needs a schema change.
src/app/components/patch-parts/patch-graph/ renders the graph using Sigma.js over WebGL/canvas (graph.component.ts imports Sigma + graphology). Sigma does not natively export SVG, so we can't reuse a domToSvg(graphHost) trick the way the rack pipeline can rip the DOM.
The underlying graph data model is already extracted: buildPatchGraphData() / extractPatchGraphModuleInstances() / computePatchGraphSizeConstant() in patch-graph-build.utils.ts give us
pure nodes/edges. Layout primitives live in patch-graph-layout.utils.ts.
This is enough to render an SVG ourselves without Sigma.
Patch list UI (patch-list, patch-micro, patch teasers in module/rack
pages) currently shows no visual at all — there is no equivalent of rack-image for patches.
Cache keys: 'patches' already exists in supabase.cache.ts; a patchWithId-style key may need to be added (a publicPatchWithId getter
exists in SupabaseService but no matching cache tag yet — confirm during
implementation).
Coupling points to honour:
Component → Data Service → API Service → Supabase (AGENTS.md §4 layering).
DatabaseStrings.ts is the only file allowed to know storage bucket names
(lint R2).
Re-run pnpm updateBackendTypes after the migration.
RLS changes require explicit manual user approval (AGENTS.md §5); the
storage bucket policies for patches must be reviewed by the maintainer
before going live.
Future strategy
Reuse the stored SVG as the input for SEO OG image generation (rasterize
on demand server-side or in an edge function for og:image). Removes the
need to recompute graph layout for SEO previews.
A future "patch thumbnail strip on module/manufacturer pages" feature becomes
trivial: just an <app-patch-preview> in a <patch-micro> card.
If Patcher ever ships an animated/live patch preview (signal flow, see patch-graph-flow.utils.ts), the static SVG remains the still fallback and
the loading placeholder.
The SVG export utility itself is reusable: rack analysis overlays, module
signal graphs, and the future module-IO graph could all share it.
Goals
Patches gain a persistent, owner-generated SVG preview stored in a
dedicated storage bucket and referenced by a new patches.image column.
A reusable app-patch-preview (or equivalently named) component renders the
stored SVG, a "no preview yet" placeholder, a "preview unavailable"
placeholder, and an owner-only "Generate / Update preview" button with a
stale indicator — visually consistent with app-rack-image.
Patch detail page exposes the generate/update flow with the same snackbar +
analytics treatment as racks (patch.preview_image_updated, patch.image_downloaded if download is also wired).
Patch list cards (patch-list, patch-micro) display the preview as their
primary visual.
Non-goals
No live/animated preview, no server-side rasterization, no OG-image
generation (separate plan).
No change to Sigma renderer for the interactive patch graph — Sigma stays for
the canvas viewer; the SVG export is an independent code path that reads the
same data model.
No change to rack previews.
No new permission model beyond owner-or-admin (matches racks).
Not building a thumbnail-cache CDN layer; rely on Supabase storage cacheControl.
Assumptions
A new public Supabase storage bucket patches is acceptable (matches racks
semantics). Bucket creation + RLS / policy is manual maintainer work, not
an autonomous agent action.
patches.imagetext | null is the right shape (matches racks.image); no
separate preview_generated_at column needed — the filename timestamp + patches.updated reproduce the rack stale-detection trick exactly.
SVG preview storage visibility should stay simple: privacy is about whether
private patches are listed in public registries, while link-based access to a
known preview URL is acceptable for now, matching the current rack-preview
posture. No special owner-only SVG read restriction is required.
The Sigma graph data model in patch-graph-build.utils.ts is sufficient to
produce a meaningful static SVG without re-running force-atlas2; we can
either (a) snapshot Sigma's current node positions when the user clicks
"Update preview" from the open patch view, or (b) run a deterministic
headless layout (see Risks).
SVG files are well under 1 MB; gzipped delivery is fine.
Dependencies and sequencing
Hard prerequisite: schema-change preflight read remains required before writing SQL. Product owner approved the backend/storage direction; migrations/storage/RLS still must be proposed/reviewed and must not be applied autonomously.
No dependency on marketplace, manufacturer pages, profiles, or any Tier
1/2 work.
Soft dependency: if on-seo-og-image-generation.md is scheduled, land
this first so OG generation can consume the stored SVGs.
Should land after the current in-flight bug fixes don't touch patch-detail-data.service.ts's mutation pipeline — coordinate via CURRENT_FEATURE.md at pickup time.
Approval queue
Approved 2026-06-18T20:58+02:00: backend/storage direction is approved with a new patches.image column for the SVG URL/path, a dedicated patches storage bucket, RLS writes limited to the patch owner, reads aligned with patch visibility, and a deterministic filename based on patch id/version. Read-visibility direction was simplified by the 2026-06-18T21:24+02:00 decision below. Do not apply migrations/storage/RLS from this docs-only approval checkpoint.
Approved 2026-06-18T21:24+02:00: preview storage visibility stays simple: public-registry listing is the privacy boundary, link-based access to a known SVG URL is acceptable like rack previews, and no special owner-only SVG read restriction is required for now. Do not apply migrations/storage/RLS from this docs-only approval checkpoint.
Approved 2026-06-18T22:43+02:00: Apply the exact additive SQL/storage shape below: add nullable public.patches.image, create/use a public patches storage bucket for link-readable SVG previews, and add authenticated owner/admin insert/update/delete storage policies. No unrelated RLS/policy changes are approved.
Approval requested 2026-06-19T09:05+02:00: May a maintainer reconcile the linked Supabase migration history before typegen by repairing/applying the local-vs-remote drift listed below? Default if not approved: keep pnpm updateBackendTypes, remote migration apply, and preview UI wiring blocked.
Approved 2026-06-19T09:05+02:00: Product owner approved maintainer reconciliation of the linked Supabase migration/typegen drift only, so the patch preview migration and pnpm updateBackendTypes can proceed safely after reconciliation. This does not approve unrelated RLS/policy changes, timestamp-preservation SQL, or pushing.
Approval requested 2026-06-19T09:05+02:00: Because public.patches currently has handle_updated_auto BEFORE UPDATE ... moddatetime('updated'), image-only row updates will bump patches.updated and break preview stale detection. May the next SQL checkpoint include a trigger/RPC-style preservation strategy for image-only updates (draft below), with no broad patch RLS changes? Default if not approved: do not wire updatePatchPreview$.
Approved 2026-06-19T09:05+02:00: Product owner approved a narrow image-only timestamp-preservation SQL strategy for public.patches.image updates, so preview row writes do not alter graph-edit freshness semantics. No broad patch RLS changes, unrelated migration/policy changes, or push are approved; preview UI/data-service wiring may proceed only after migration drift is reconciled and this SQL checkpoint exists.
Implemented locally 2026-06-19T09:12+02:00: Added local migration 20260619091200_preserve_patch_preview_image_updated.sql and narrow backend method update.patchPreviewImage(id, image). This checkpoint was not applied remotely, and remote typegen/migration reconciliation remains blocked/remaining.
Blocked/pivoted 2026-06-19T09:21+02:00: Read-only Supabase MCP inspection reconfirmed the linked project still lacks patches.image and the patches storage bucket, still has only handle_updated_auto/trg_patches_public_id triggers on public.patches, and still has divergent migration history. Keep UI/data-service wiring blocked until a maintainer completes approved migration/typegen reconciliation; no remote DDL/storage/RLS changes were applied.
Applied remotely 2026-06-19T09:45+02:00: With explicit operational approval, applied only the approved Patch preview storage and timestamp-preservation migrations to linked Supabase project sozmatmywjpstwidzlss. Production uses UUID patches.authorid, so the storage policies were applied with p.authorid::text = auth.uid()::text; local migration SQL was updated to match. Verified patches.image, public patches bucket, and tg_preserve_patch_preview_image_updated now exist remotely. pnpm updateBackendTypes remains blocked by unrelated linked migration drift.
Proposal-only SQL/storage checkpoint
This section was approved by the product owner at 2026-06-18T22:43+02:00 for
the next implementation checkpoint. Apply only this additive column, bucket, and
storage-policy shape; stop any remote/backend step if tooling reveals migration
drift, advisor failures, or a breaking production risk.
Proposed additive migration
-- Draft only: do not apply until maintainer approval is recorded.-- Adds the patch preview filename/path without touching existing rows.-- `add column ... null` has no backfill and must not fire `patches.updated`-- triggers on existing rows.altertablepublic.patches
add column if not exists image textnull;
comment on column public.patches.image is'Supabase Storage filename for the generated SVG patch preview in the patches bucket.';
Proposed storage bucket
-- Draft only: do not apply until maintainer approval is recorded.-- Public bucket keeps preview reads simple/link-readable, matching rack preview-- posture. Privacy remains the public-listing boundary, not secret SVG URLs.insert intostorage.buckets (id, name, public, file_size_limit, allowed_mime_types)
values ('patches', 'patches', true, 1048576, array['image/svg+xml'])
on conflict (id) do updateset public =excluded.public,
file_size_limit =excluded.file_size_limit,
allowed_mime_types =excluded.allowed_mime_types;
Proposed filename convention
Store only the basename in public.patches.image, not a full URL.
<patch_updated_stamp> is derived from the patch row's current updated
value at generation time, normalized to lowercase UTC yyyymmddtHHMMSSmmmz with non-alphanumerics removed.
This keeps the path deterministic for a patch/version while still changing
when the patch graph changes. Regenerating the same patch version may upsert
the same object path; once a later patch edit changes updated, the next
preview writes a new filename and can delete the previous object after the DB
row update succeeds.
Proposed storage policy shape
The bucket is public, so no owner-only SVG read policy is proposed. The app must
still gate generation through the owner/admin backend flow, and storage writes
remain constrained by policies that parse the patch id from the filename.
-- Draft only: RLS/storage policy changes require explicit maintainer approval.
drop policy if exists "patch_previews_insert_owner_or_admin"onstorage.objects;
create policy "patch_previews_insert_owner_or_admin"onstorage.objects
for insert
to authenticated
with check (
bucket_id ='patches'andlower(storage.extension(name)) ='svg'andstorage.filename(name) ~ '^patch_[0-9]+_v[0-9]{8}t[0-9]{9}z\.svg$'and (
coalesce(auth.jwt() ->'app_metadata'->>'role', '') ='admin'or exists (
select1frompublic.patches p
wherep.id=substring(storage.filename(name) from'^patch_([0-9]+)_')::integerandp.authorid=auth.uid()::text
)
)
);
drop policy if exists "patch_previews_update_owner_or_admin"onstorage.objects;
create policy "patch_previews_update_owner_or_admin"onstorage.objects
for update
to authenticated
using (
bucket_id ='patches'and (
coalesce(auth.jwt() ->'app_metadata'->>'role', '') ='admin'or exists (
select1frompublic.patches p
wherep.id=substring(storage.filename(name) from'^patch_([0-9]+)_')::integerandp.authorid=auth.uid()::text
)
)
)
with check (
bucket_id ='patches'andlower(storage.extension(name)) ='svg'andstorage.filename(name) ~ '^patch_[0-9]+_v[0-9]{8}t[0-9]{9}z\.svg$'and (
coalesce(auth.jwt() ->'app_metadata'->>'role', '') ='admin'or exists (
select1frompublic.patches p
wherep.id=substring(storage.filename(name) from'^patch_([0-9]+)_')::integerandp.authorid=auth.uid()::text
)
)
);
drop policy if exists "patch_previews_delete_owner_or_admin"onstorage.objects;
create policy "patch_previews_delete_owner_or_admin"onstorage.objects
for delete
to authenticated
using (
bucket_id ='patches'and (
coalesce(auth.jwt() ->'app_metadata'->>'role', '') ='admin'or exists (
select1frompublic.patches p
wherep.id=substring(storage.filename(name) from'^patch_([0-9]+)_')::integerandp.authorid=auth.uid()::text
)
)
);
Visibility reconciliation
The earlier owner/admin write direction still stands for generating, replacing,
and deleting previews. It is enforced twice: UI/data-service owner/admin
checks and storage policies for authenticated writes.
The later visibility simplification supersedes owner-only reads. The proposed patches bucket is public, so anyone with the exact SVG URL can read it, as
with rack previews. Private patch privacy is handled by not listing private
patches publicly, not by making preview URLs secret.
Remote gate: applying the bucket/policies and running typegen remains blocked
until the linked migration history is reconciled; this checkpoint only adds
local files and tests.
Non-breaking validation plan
Before applying: confirm the target Supabase project has no existing public.patches.image column or patches bucket/policies that would require
a rename/migration merge.
Apply only after approval; then run pnpm updateBackendTypes and confirm patches.Row/Insert/Update include optional nullable image.
Verify the column-only migration does not update existing public.patches.updated
values (no backfill/update statement is present).
Verify the row update used by the app to persist image does not
unintentionally bump patches.updated; local migration 20260619091200_preserve_patch_preview_image_updated.sql now implements the
approved image-only trigger strategy, but it has not been applied remotely.
Run Supabase advisors after the approved storage/RLS change and record any
warnings in this plan before implementation proceeds.
Remote migration/typegen drift inventory
Read-only inspection on 2026-06-19T09:05+02:00 confirmed the linked project sozmatmywjpstwidzlss is still not safe for remote typegen or additional
migration application:
supabase migration list --linked shows timestamp drift where remote history
has old versions for equivalent local migration names:
public-id/checkpoint family: remote 20260515092813… 20260515110711; local 20260515112000…20260515130700.
module usage/admin/module-discovery family: remote 20260609212909, 20260610124954, no local 20260611130000; local has 20260609212500, 20260610144900, 20260611130000.
generate-public-id grant and module snapshot: remote 20260611193541, 20260616135552; local 20260611193200, 20260616155154.
taxonomy split/correction: remote 20260618101252, 20260618101259, 20260618171152; local 20260618105927, 20260618121100, 20260618190400.
Remote is missing local migration families for manufacturer verification,
manufacturer claims, module availability tags, manufacturer owner policies,
manufacturer logo storage policies, and user module acquisitions.
Patch SVG preview objects were applied remotely on 2026-06-19T09:45+02:00: public.patches.image, the public patches storage bucket, and the
image-only timestamp-preservation trigger now exist.
Do not run pnpm updateBackendTypes, supabase db push, Supabase MCP
migrations, or migration repair until the maintainer explicitly approves the
reconciliation plan.
Preview update timestamp semantics gate
Read-only inspection on 2026-06-19T09:05+02:00 confirmed public.patches has:
CREATETRIGGERhandle_updated_auto
BEFORE UPDATEONpublic.patches
FOR EACH ROW EXECUTE FUNCTION moddatetime('updated')
moddatetime updates patches.updated for every row update, including an
image-only update. That would make a freshly persisted preview look non-stale
because the graph edit timestamp changes to the preview write time. The UI/data
service must not wire preview generation until this local checkpoint can be
applied after remote migration drift is reconciled.
Implemented local preservation strategy:
-- Local migration added in 20260619091200; do not apply remotely until drift is reconciled.-- Runs after handle_updated_auto by trigger-name ordering and restores OLD.updated-- only when the preview filename is the only logical row change.create or replacefunctionpublic.tg_preserve_patch_preview_image_updated()
returns trigger
language plpgsql
set search_path = public
as $$
begin
if NEW.image is distinct fromOLD.imageand (to_jsonb(NEW) -'image'-'updated') = (to_jsonb(OLD) -'image'-'updated') then
NEW.updated :=OLD.updated;
end if;
return NEW;
end;
$$;
droptrigger if exists zz_preserve_patch_preview_image_updated onpublic.patches;
createtriggerzz_preserve_patch_preview_image_updated
before updateonpublic.patches
for each row execute function public.tg_preserve_patch_preview_image_updated();
This is paired with SupabaseService.update.patchPreviewImage(id, image), which
updates only patches.image, filters non-admin writes by id and current authorid, allows the existing admin update path, selects id,image,updated,
and cache-busts patches plus patchesWithModule. No dedicated patchWithId
cache key exists yet; single-patch reads observe the patches cache key.
Proposed solution
MVP layer
Smallest end-to-end slice that proves the loop works on the patch detail page.
Migration: add patches.image text null + approved public patches storage bucket; capture both in the plan's Decision log.
Update generated types and Patch / PatchMinimal models with image?: string | null (manual type patch only; pnpm updateBackendTypes
is gated by remote migration drift).
Register DbStoragePaths.patches = 'patches' and StorageUrls.patches = ... in DatabaseStrings.ts.
Add uploadPatchPreview(file, name) (svg) and deletePatchPreview(name)
to supabase-storage.ts, mirroring the rack methods 1:1, content-type image/svg+xml, cache-bust whatever key reads patch detail.
Add local image-only timestamp-preservation trigger migration and narrow update.patchPreviewImage(id, image) backend method; remote apply/typegen
remains blocked by migration drift.
New pure utility patch-graph-svg.utils.ts (alongside patch-graph-build.utils.ts) that turns already-built {nodes, edges}
graph data into a self-contained SVG string. Unit-tested with a
co-located patch-graph-svg.utils.spec.ts.
Wire updatePatchPreview$ action into PatchDetailDataService,
modelled on RackDetailDataService.updateRackImagePreview$
(owner/admin gate, snackbars, analytics event patch.preview_image_updated, previous-file cleanup with 404 tolerance).
Owner-only "Generate/Update preview" button on the patch detail page
that emits into updatePatchPreview$.
Structural layer
Reusable surfaces + list consumers.
app-patch-preview component (sibling of rack-image) with:
@Input() data: PatchMinimal
@Input() canUpdatePreview = false
@Output() updatePreviewClick = new EventEmitter<void>()
placeholders: Preview unavailable (load failure) and New patch (no
image yet), matching rack copy and styling.
exported helpers patchPreviewGeneratedAt() and isPatchPreviewStale()
(consider extracting the rack ones to a shared utility instead of
duplicating — see Decision log on first non-obvious choice).
Embed app-patch-preview in patch-micro and patch-list cards.
Embed app-patch-preview on the patch detail page header area.
Add the rack-style stale badge and refresh button (gated by canUpdatePreview).
Polish layer
Consistent placeholder typography/colors with rack-image-fallback.
Loading skeleton while the SVG is fetched (mirror rack image transition
class).
Optional: download-preview button on patch detail page
(patch.image_downloaded analytics event), gated like rack's downloader.
Optional: visual tuning of the static SVG (legend, signal-flow colour
coding pulled from patch-graph-flow.utils.ts palette so the static
preview reflects the live view's colours).
DESIGN_LANGUAGE pass — pull a designer-agent review before locking
colours/typography.
Surfaces and files
Schema/migration
new SQL migration adding patches.image text null (preserve updated
semantics — see preflight doc, don't accidentally bump updated on
backfill).
local SQL migration preserving patches.updated for image-only preview
filename updates.
Owner (or admin) of a patch can click "Generate preview" on the patch
detail page; within a few seconds an SVG is uploaded to the patches
storage bucket, patches.image is updated to the new filename, and the
preview component re-renders showing the SVG.
Re-clicking the button regenerates the SVG, replaces the storage object,
and deletes the previous file (404 on delete is tolerated, matching
rack behaviour).
Patches without an image show a "New patch" placeholder. Patches whose
stored SVG fails to load show a "Preview unavailable" placeholder.
When patches.updated is newer than the timestamp encoded in patches.image's filename, a stale badge + refresh button appear (owner
only).
patch-list and patch-micro cards now show the preview as the primary
visual.
E2E: extend an existing patch detail E2E (or add a focused one under e2e/ if the patch detail has no media spec yet) — generate preview, reload,
assert SVG renders; assert non-owner sees no button.
Manual via patcher-ui-debug skill: capture a viewport snapshot of patch
list before and after generation to confirm the preview appears.
Sentry/Supabase MCP: after staging deploy, watch Sentry for new errors
tagged patch.preview_image_updated and Supabase logs for storage 4xx/5xx
on the patches bucket.
Risks and mitigations
Sigma is canvas, not SVG. The static SVG export must either snapshot
Sigma's current node positions at click time (needs the component to expose
positions) or run a deterministic headless layout (e.g. dagre / elkjs /
simple grid by extractPatchGraphModuleInstances). Open question for
planner: which approach gives a more recognisable preview, and is the
~30 KB dagre/elkjs cost acceptable in the patch detail bundle?
Backfill/update behaviour. The additive column migration has no backfill.
Local migration 20260619091200_preserve_patch_preview_image_updated.sql
restores OLD.updated only when image is the sole logical row change; this
still needs remote application after migration drift is reconciled.
Storage visibility for patches bucket. Direction is approved: owner-only writes and simple link-readable SVG access, with privacy handled by avoiding public-registry listing for private patches. No owner-only SVG read restriction is required for now. Exact public-bucket + owner/admin write policy SQL is now drafted above; do not apply it without maintainer approval.
Shared helper extraction.previewGeneratedAt / isPreviewStale exist
in rack-image.component.ts. Duplicating them is the smaller, safer change;
extracting to a shared util is cleaner but touches rack tests. Decision
belongs to the planner — record in Decision log.
Caching.uploadRackImage uses cacheControl: '360' (6 minutes).
Confirm whether SVGs (typically tiny + content-hashed via filename
timestamp) want longer; suggested cacheControl: '31536000' since the
filename changes on every regeneration.
patchWithId cache key. No dedicated single-patch cache key exists yet; publicPatchWithId and patchByPublicId use the broader patches cache
buster, so preview image writes bust patches and patchesWithModule.
Coordinator-loop handoff
When coordinator-loop picks this up:
Read the schema-change preflight doc and propose the migration as a
reviewable diff — do not apply autonomously.
Delegate the SVG renderer utility to frontend-dev first (pure function,
easy to TDD with golden snapshots) — this de-risks the whole feature.
Then delegate the storage + data-service layer (frontend-dev).
Then the UI component + list embeds (frontend-dev + designer pass for
the placeholders).
code-reviewer validates layering + that no shortcuts were taken on the
owner/admin gate.
Final manual checkpoint: maintainer approves bucket creation + RLS before
merge to production.
Decisions and open questions
Open questions
Finalize the approved migration/type-generation reconciliation and preview UI/data-service wiring sequence before implementation.
Confirm the final deterministic layout and designer-approved visual treatment during the implementation pass.
Decision log
2026-06-18T22:50+02:00 — Added local migration 20260618224500_add_patch_svg_previews_storage.sql for patches.image, public patches SVG bucket, and owner/admin storage write policies. Remote Supabase apply and pnpm updateBackendTypes were skipped because MCP migration inspection showed the linked remote is behind current local migrations; manually patched database.types.ts only for the additive patches.image shape.
2026-06-18T22:55+02:00 — Reviewer approved the local backend/storage slice after fixes. Validation passed with pnpm test-headless --include="**/supabase-service/storage*.spec.ts", pnpm lint, node scripts/checks/check-docs.cjs, and git diff --check; Supabase advisors were skipped because remote DDL/RLS was not applied.
2026-06-18T22:58+02:00 — Staged the next safe gate: reconcile remote migration/typegen drift, then verify whether persisting patches.image changes patches.updated before any PatchDetailDataService generation wiring.
2026-06-19T09:05+02:00 — Read-only Supabase inspection confirmed linked remote migration/typegen drift remains unresolved, remote lacks patches.image/patches bucket, and public.patches.handle_updated_auto uses moddatetime('updated'), so image-only row updates would bump patches.updated. Added maintainer approval gates for migration reconciliation and an image-only timestamp preservation strategy; no remote schema/storage/RLS changes were applied.
2026-06-19T09:05+02:00 — Product owner approved maintainer reconciliation of the linked Supabase migration/typegen drift only, allowing safe follow-up for the patch preview migration and pnpm updateBackendTypes after reconciliation. Timestamp-preservation SQL remains separately pending; no unrelated RLS/policy changes or push are approved.
2026-06-19T09:05+02:00 — Product owner separately approved a narrow image-only timestamp-preservation SQL strategy for public.patches.image updates, so preview row writes do not alter graph-edit freshness semantics. Preview UI/data-service wiring remains blocked until migration drift is reconciled and the approved SQL checkpoint exists; no broad patch RLS changes, unrelated migration/policy changes, or push are approved.
2026-06-19T09:12+02:00 — Added local migration 20260619091200_preserve_patch_preview_image_updated.sql plus update.patchPreviewImage(id, image) for owner/admin image-only persistence. The backend method updates only image, selects id,image,updated, busts patches/patchesWithModule, and is covered by focused specs; no remote Supabase changes or typegen were run.
2026-06-19T09:21+02:00 — Read-only Supabase MCP inspection reconfirmed the linked remote is still unsafe for patch preview UI/data-service wiring: migration history remains divergent, public.patches.image and the patches storage bucket are absent, and public.patches still lacks the local image-only timestamp-preservation trigger. Pivoted the coordinator loop to the next safe planning task; no remote DDL/storage/RLS changes were applied.
2026-06-18T22:43+02:00 — Product owner approved applying the drafted Patch SVG preview storage/RLS checkpoint: additive nullable patches.image, public link-readable patches SVG bucket, and authenticated owner/admin insert/update/delete storage policies. No unrelated RLS/policy changes are approved.
2026-06-18T20:58+02:00 — Product owner approved the Patch SVG previews backend/storage direction: add patches.image for SVG URL/path, use a dedicated patches storage bucket, limit RLS writes to the patch owner, align reads with patch visibility, and use a deterministic filename based on patch id/version; no migrations/storage/RLS were applied in this docs-only checkpoint.
2026-06-18T22:17+02:00 — Drafted the exact proposal-only SQL/storage checkpoint: nullable patches.image, public patches SVG bucket, deterministic patch_<id>_v<updated>.svg filenames, and authenticated owner/admin insert/update/delete storage policies; no migrations/storage/RLS were applied.
2026-06-18T21:24+02:00 — Product owner decided Patch SVG preview storage visibility should stay simple: privacy is about avoiding public-registry listing, link-based access to a known SVG URL is acceptable like current rack previews, and no special owner-only SVG read restriction is required for now; no migrations/storage/RLS were applied in this docs-only checkpoint.
2026-06-18T20:18+02:00 — First autonomous implementation slice intentionally avoids schema, storage, RLS, Supabase calls, model fields, UI components, and upload flows; it lands only the pure SVG renderer/test foundation while screenshot refresh remains credential/approval gated.
2026-06-18T20:23+02:00 — Reviewer findings on duplicate marker IDs and long-label clipping were fixed by replacing SVG marker IDs with inline arrowhead polygons and estimating label bounds in the generated viewBox.
2026-06-18 — Plan filed by feature-notetaker from a verbatim user
indication ("visual previews of the patches, saving SVG files… same
behavior as racks… different folder… preview placeholders"). Classified
as INFRA / MEDIUM because it polishes an already-shipped surface
(patches) without blocking or being blocked by any Tier 1/2 roadmap item,
but has high compounding value for browse/SEO surfaces.
Production and release gates
Remote schema, storage, RLS, migration, and type-generation work remains subject to the approval and migration-drift gates documented above. Preview UI/data-service wiring must wait for safe reconciliation and verification of the approved timestamp-preservation strategy. No unrelated policy changes, production-branch work, release, or push is authorized by this plan.
Related issues
N/A. No verified GitHub relationship was recorded for this plan.
Documentation impact
This issue preserves the complete migrated planning document. Public documentation remains deferred until confirmed production publication and visibility under the repository documentation lifecycle.
Source and migration metadata
Source: internaldocs/workflow/plans/patch-svg-previews.md
Migration status: migrated from local documentation
Patch SVG Previews (mirror of rack JPEG previews, but for the patch graph)
Status and ownership
coordinator-loop→planner→frontend-dev→code-reviewer.Problem and user intent
Generate and persist a visual preview of each patch — saved as an SVG file in
Supabase storage — and surface it everywhere a patch is listed/teased, the same
way rack JPEG previews work today. The user explicitly asked for:
preview" button, stale-detection badge, and a placeholder when no preview
exists yet.
*.svgfilename stamped with a generation timestamp persisted on the patchrow, cache-busted on update, cleaned up on replace.
rack panel. SVG is required (not JPEG) so previews remain crisp at any size
and are tiny on disk.
Product / roadmap fit
Phase 2 and currently render as text-only cards in lists. Adding a real
visual preview makes patch browsing, search results, and embedded patch teasers
(module pages, racks → linked patches, user profile activity) instantly
scannable.
preview for free — public profiles, marketplace listings that reference a demo
patch, manufacturer pages showing patches involving their modules, future SEO
OG-image generation (see
on-seo-og-image-generation.md, which can reuse thesame stored SVG as input).
shipped surface — safe to pick up between gated work.
ROADMAP.mdedits.Current system and context
The rack preview pipeline already exists and is the explicit template to copy.
Rack pipeline (reference implementation):
racks.imagetext | null— stores the timestamped filename only.racks(DbStoragePaths.racks,StorageUrls.racks).src/app/features/backend/supabase-storage.ts):uploadRackImage(file, name)— stamps filename with_YYYY-MM-DDhh-mm-ssms.<ext>then uploads; bustsrackWithIdcache.deleteRackImage(name)— bustsrackWithIdcache.src/app/components/rack-parts/rack-detail-data.service.ts,around L716–L810): captures the rendered DOM with
modern-screenshot(
domToJpeg), uploads, updates the row, deletes the previous file, andsurfaces snackbars + analytics events
(
rack.image_downloaded,rack.preview_image_updated). Owner-or-admin gated.src/app/components/rack-parts/rack-image/rack-image.component.ts):@Input() data: Rack,@Input() canUpdatePreview,@Output() updatePreviewClick.previewGeneratedAt()andisPreviewStale()extract thetimestamp from the filename and compare it against
rack.updated.rack" (no image yet), and a stale badge + refresh button when relevant.
rack-minimal(and through it,rack-list,rack-micro).Patch side today (gaps):
patchestable insrc/backend/database.types.tshas noimagecolumnand there is no patches storage bucket — needs a schema change.
src/app/components/patch-parts/patch-graph/renders the graph usingSigma.js over WebGL/canvas (
graph.component.tsimportsSigma+graphology). Sigma does not natively export SVG, so we can't reuse adomToSvg(graphHost)trick the way the rack pipeline can rip the DOM.buildPatchGraphData()/extractPatchGraphModuleInstances()/computePatchGraphSizeConstant()inpatch-graph-build.utils.tsgive uspure nodes/edges. Layout primitives live in
patch-graph-layout.utils.ts.This is enough to render an SVG ourselves without Sigma.
patch-list,patch-micro, patch teasers in module/rackpages) currently shows no visual at all — there is no equivalent of
rack-imagefor patches.'patches'already exists insupabase.cache.ts; apatchWithId-style key may need to be added (apublicPatchWithIdgetterexists in
SupabaseServicebut no matching cache tag yet — confirm duringimplementation).
Coupling points to honour:
AGENTS.md§4 layering).DatabaseStrings.tsis the only file allowed to know storage bucket names(lint R2).
pnpm updateBackendTypesafter the migration.internaldocs/patterns/BACKEND_METHODS.md§"Schema-change preflight".
AGENTS.md§5); thestorage bucket policies for
patchesmust be reviewed by the maintainerbefore going live.
Future strategy
on demand server-side or in an edge function for
og:image). Removes theneed to recompute graph layout for SEO previews.
trivial: just an
<app-patch-preview>in a<patch-micro>card.patch-graph-flow.utils.ts), the static SVG remains the still fallback andthe loading placeholder.
signal graphs, and the future module-IO graph could all share it.
Goals
dedicated storage bucket and referenced by a new
patches.imagecolumn.app-patch-preview(or equivalently named) component renders thestored SVG, a "no preview yet" placeholder, a "preview unavailable"
placeholder, and an owner-only "Generate / Update preview" button with a
stale indicator — visually consistent with
app-rack-image.analytics treatment as racks (
patch.preview_image_updated,patch.image_downloadedif download is also wired).patch-list,patch-micro) display the preview as theirprimary visual.
Non-goals
generation (separate plan).
the canvas viewer; the SVG export is an independent code path that reads the
same data model.
cacheControl.Assumptions
patchesis acceptable (matchesrackssemantics). Bucket creation + RLS / policy is manual maintainer work, not
an autonomous agent action.
patches.imagetext | nullis the right shape (matchesracks.image); noseparate
preview_generated_atcolumn needed — the filename timestamp +patches.updatedreproduce the rack stale-detection trick exactly.private patches are listed in public registries, while link-based access to a
known preview URL is acceptable for now, matching the current rack-preview
posture. No special owner-only SVG read restriction is required.
patch-graph-build.utils.tsis sufficient toproduce a meaningful static SVG without re-running force-atlas2; we can
either (a) snapshot Sigma's current node positions when the user clicks
"Update preview" from the open patch view, or (b) run a deterministic
headless layout (see Risks).
Dependencies and sequencing
1/2 work.
on-seo-og-image-generation.mdis scheduled, landthis first so OG generation can consume the stored SVGs.
patch-detail-data.service.ts's mutation pipeline — coordinate viaCURRENT_FEATURE.mdat pickup time.Approval queue
patches.imagecolumn for the SVG URL/path, a dedicatedpatchesstorage bucket, RLS writes limited to the patch owner, reads aligned with patch visibility, and a deterministic filename based on patch id/version. Read-visibility direction was simplified by the 2026-06-18T21:24+02:00 decision below. Do not apply migrations/storage/RLS from this docs-only approval checkpoint.public.patches.image, create/use a publicpatchesstorage bucket for link-readable SVG previews, and add authenticated owner/admin insert/update/delete storage policies. No unrelated RLS/policy changes are approved.pnpm updateBackendTypes, remote migration apply, and preview UI wiring blocked.pnpm updateBackendTypescan proceed safely after reconciliation. This does not approve unrelated RLS/policy changes, timestamp-preservation SQL, or pushing.public.patchescurrently hashandle_updated_auto BEFORE UPDATE ... moddatetime('updated'), image-only row updates will bumppatches.updatedand break preview stale detection. May the next SQL checkpoint include a trigger/RPC-style preservation strategy for image-only updates (draft below), with no broad patch RLS changes? Default if not approved: do not wireupdatePatchPreview$.public.patches.imageupdates, so preview row writes do not alter graph-edit freshness semantics. No broad patch RLS changes, unrelated migration/policy changes, or push are approved; preview UI/data-service wiring may proceed only after migration drift is reconciled and this SQL checkpoint exists.20260619091200_preserve_patch_preview_image_updated.sqland narrow backend methodupdate.patchPreviewImage(id, image). This checkpoint was not applied remotely, and remote typegen/migration reconciliation remains blocked/remaining.patches.imageand thepatchesstorage bucket, still has onlyhandle_updated_auto/trg_patches_public_idtriggers onpublic.patches, and still has divergent migration history. Keep UI/data-service wiring blocked until a maintainer completes approved migration/typegen reconciliation; no remote DDL/storage/RLS changes were applied.sozmatmywjpstwidzlss. Production uses UUIDpatches.authorid, so the storage policies were applied withp.authorid::text = auth.uid()::text; local migration SQL was updated to match. Verifiedpatches.image, publicpatchesbucket, andtg_preserve_patch_preview_image_updatednow exist remotely.pnpm updateBackendTypesremains blocked by unrelated linked migration drift.Proposal-only SQL/storage checkpoint
This section was approved by the product owner at 2026-06-18T22:43+02:00 for
the next implementation checkpoint. Apply only this additive column, bucket, and
storage-policy shape; stop any remote/backend step if tooling reveals migration
drift, advisor failures, or a breaking production risk.
Proposed additive migration
Proposed storage bucket
Proposed filename convention
public.patches.image, not a full URL.patch_<patch_id>_v<patch_updated_stamp>.svg.patch_42_v20260618t201530123z.svg.<patch_updated_stamp>is derived from the patch row's currentupdatedvalue at generation time, normalized to lowercase UTC
yyyymmddtHHMMSSmmmzwith non-alphanumerics removed.when the patch graph changes. Regenerating the same patch version may upsert
the same object path; once a later patch edit changes
updated, the nextpreview writes a new filename and can delete the previous object after the DB
row update succeeds.
Proposed storage policy shape
The bucket is public, so no owner-only SVG read policy is proposed. The app must
still gate generation through the owner/admin backend flow, and storage writes
remain constrained by policies that parse the patch id from the filename.
Visibility reconciliation
and deleting previews. It is enforced twice: UI/data-service owner/admin
checks and storage policies for authenticated writes.
patchesbucket is public, so anyone with the exact SVG URL can read it, aswith rack previews. Private patch privacy is handled by not listing private
patches publicly, not by making preview URLs secret.
until the linked migration history is reconciled; this checkpoint only adds
local files and tests.
Non-breaking validation plan
public.patches.imagecolumn orpatchesbucket/policies that would requirea rename/migration merge.
pnpm updateBackendTypesand confirmpatches.Row/Insert/Updateinclude optional nullableimage.public.patches.updatedvalues (no backfill/update statement is present).
imagedoes notunintentionally bump
patches.updated; local migration20260619091200_preserve_patch_preview_image_updated.sqlnow implements theapproved image-only trigger strategy, but it has not been applied remotely.
warnings in this plan before implementation proceeds.
Remote migration/typegen drift inventory
Read-only inspection on 2026-06-19T09:05+02:00 confirmed the linked project
sozmatmywjpstwidzlssis still not safe for remote typegen or additionalmigration application:
supabase migration list --linkedshows timestamp drift where remote historyhas old versions for equivalent local migration names:
20260515092813…20260515110711; local20260515112000…20260515130700.20260609212909,20260610124954, no local20260611130000; local has20260609212500,20260610144900,20260611130000.20260611193541,20260616135552; local20260611193200,20260616155154.20260618101252,20260618101259,20260618171152; local20260618105927,20260618121100,20260618190400.manufacturer claims, module availability tags, manufacturer owner policies,
manufacturer logo storage policies, and user module acquisitions.
public.patches.image, the publicpatchesstorage bucket, and theimage-only timestamp-preservation trigger now exist.
Do not run
pnpm updateBackendTypes,supabase db push, Supabase MCPmigrations, or migration repair until the maintainer explicitly approves the
reconciliation plan.
Preview update timestamp semantics gate
Read-only inspection on 2026-06-19T09:05+02:00 confirmed
public.patcheshas:moddatetimeupdatespatches.updatedfor every row update, including animage-only update. That would make a freshly persisted preview look non-stale
because the graph edit timestamp changes to the preview write time. The UI/data
service must not wire preview generation until this local checkpoint can be
applied after remote migration drift is reconciled.
Implemented local preservation strategy:
This is paired with
SupabaseService.update.patchPreviewImage(id, image), whichupdates only
patches.image, filters non-admin writes byidand currentauthorid, allows the existing admin update path, selectsid,image,updated,and cache-busts
patchespluspatchesWithModule. No dedicatedpatchWithIdcache key exists yet; single-patch reads observe the
patchescache key.Proposed solution
MVP layer
Smallest end-to-end slice that proves the loop works on the patch detail page.
patches.image text null+ approved publicpatchesstorage bucket; capture both in the plan's Decision log.Patch/PatchMinimalmodels withimage?: string | null(manual type patch only;pnpm updateBackendTypesis gated by remote migration drift).
DbStoragePaths.patches = 'patches'andStorageUrls.patches = ...inDatabaseStrings.ts.uploadPatchPreview(file, name)(svg) anddeletePatchPreview(name)to
supabase-storage.ts, mirroring the rack methods 1:1, content-typeimage/svg+xml, cache-bust whatever key reads patch detail.update.patchPreviewImage(id, image)backend method; remote apply/typegenremains blocked by migration drift.
patch-graph-svg.utils.ts(alongsidepatch-graph-build.utils.ts) that turns already-built{nodes, edges}graph data into a self-contained SVG string. Unit-tested with a
co-located
patch-graph-svg.utils.spec.ts.updatePatchPreview$action intoPatchDetailDataService,modelled on
RackDetailDataService.updateRackImagePreview$(owner/admin gate, snackbars, analytics event
patch.preview_image_updated, previous-file cleanup with 404 tolerance).that emits into
updatePatchPreview$.Structural layer
Reusable surfaces + list consumers.
app-patch-previewcomponent (sibling ofrack-image) with:@Input() data: PatchMinimal@Input() canUpdatePreview = false@Output() updatePreviewClick = new EventEmitter<void>()image yet), matching rack copy and styling.
patchPreviewGeneratedAt()andisPatchPreviewStale()(consider extracting the rack ones to a shared utility instead of
duplicating — see Decision log on first non-obvious choice).
app-patch-previewinpatch-microandpatch-listcards.app-patch-previewon the patch detail page header area.canUpdatePreview).Polish layer
rack-image-fallback.class).
(
patch.image_downloadedanalytics event), gated like rack's downloader.coding pulled from
patch-graph-flow.utils.tspalette so the staticpreview reflects the live view's colours).
colours/typography.
Surfaces and files
patches.image text null(preserveupdatedsemantics — see preflight doc, don't accidentally bump
updatedonbackfill).
patches.updatedfor image-only previewfilename updates.
patchesstorage bucket + storage policies (owner write,simple link-readable SVG access; no owner-only SVG read restriction for now).
src/backend/database.types.ts(regenerated).src/app/features/backend/DatabaseStrings.ts— addpatchesbucketconstant +
StorageUrls.patches.src/app/features/backend/supabase-storage.ts— adduploadPatchPreview+deletePatchPreview.src/app/features/backend/supabase-update.ts— addpatchPreviewImage(id, image)for narrow preview filename persistence.src/app/features/backend/__tests__/supabase-service/storage.spec.tsandstorage-cache.spec.ts— extend with patch counterparts.src/app/models/patch.ts— addimage?: string | nulltoPatchMinimal.src/app/components/patch-parts/patch-detail-data.service.ts— addupdatePatchPreview$subject + reactive pipeline.src/app/components/patch-parts/__tests__/(or co-located spec) —cover happy path, owner/admin gate, previous-file 404 tolerance, snackbar
success/error.
src/app/components/patch-parts/patch-graph/patch-graph-svg.utils.tspatch-graph-svg.utils.spec.tswith golden-snapshot tests.src/app/components/patch-parts/patch-preview/(component + html +scss + spec), mirroring
rack-imagestructure and tests.src/app/components/patch-parts/patch-details/patch-details.component.*—add the preview + button.
src/app/components/patch-list/patch-list.component.*,src/app/components/patch-micro/patch-micro.component.*— embed.Acceptance criteria
detail page; within a few seconds an SVG is uploaded to the
patchesstorage bucket,
patches.imageis updated to the new filename, and thepreview component re-renders showing the SVG.
and deletes the previous file (404 on delete is tolerated, matching
rack behaviour).
imageshow a "New patch" placeholder. Patches whosestored SVG fails to load show a "Preview unavailable" placeholder.
patches.updatedis newer than the timestamp encoded inpatches.image's filename, a stale badge + refresh button appear (owneronly).
patch-listandpatch-microcards now show the preview as the primaryvisual.
(UI hidden + backend gate + storage RLS).
pnpm lintandpnpm test-headlesspass; the new SVG renderer haspassing golden-snapshot tests; new storage methods have spec coverage.
Validation strategy
pnpm lint(includescheck-layering.cjs,check-route-module-imports.cjs,check-px-ts.sh,check-docs.cjs).pnpm test-headless --include="**/patch-preview/**" --include="**/patch-graph-svg.utils.spec.ts" --include="**/supabase-service/storage*.spec.ts" --include="**/patch-detail-data.service*.spec.ts".e2e/if the patch detail has no media spec yet) — generate preview, reload,assert SVG renders; assert non-owner sees no button.
list before and after generation to confirm the preview appears.
tagged
patch.preview_image_updatedand Supabase logs for storage 4xx/5xxon the
patchesbucket.Risks and mitigations
Sigma's current node positions at click time (needs the component to expose
positions) or run a deterministic headless layout (e.g. dagre / elkjs /
simple grid by
extractPatchGraphModuleInstances). Open question forplanner: which approach gives a more recognisable preview, and is the
~30 KB dagre/elkjs cost acceptable in the patch detail bundle?
Local migration
20260619091200_preserve_patch_preview_image_updated.sqlrestores
OLD.updatedonly whenimageis the sole logical row change; thisstill needs remote application after migration drift is reconciled.
patchesbucket. Direction is approved: owner-only writes and simple link-readable SVG access, with privacy handled by avoiding public-registry listing for private patches. No owner-only SVG read restriction is required for now. Exact public-bucket + owner/admin write policy SQL is now drafted above; do not apply it without maintainer approval.previewGeneratedAt/isPreviewStaleexistin
rack-image.component.ts. Duplicating them is the smaller, safer change;extracting to a shared util is cleaner but touches rack tests. Decision
belongs to the planner — record in Decision log.
uploadRackImageusescacheControl: '360'(6 minutes).Confirm whether SVGs (typically tiny + content-hashed via filename
timestamp) want longer; suggested
cacheControl: '31536000'since thefilename changes on every regeneration.
patchWithIdcache key. No dedicated single-patch cache key exists yet;publicPatchWithIdandpatchByPublicIduse the broaderpatchescachebuster, so preview image writes bust
patchesandpatchesWithModule.Coordinator-loop handoff
When
coordinator-looppicks this up:reviewable diff — do not apply autonomously.
frontend-devfirst (pure function,easy to TDD with golden snapshots) — this de-risks the whole feature.
frontend-dev).frontend-dev+designerpass forthe placeholders).
code-reviewervalidates layering + that no shortcuts were taken on theowner/admin gate.
merge to
production.Decisions and open questions
Open questions
Decision log
20260618224500_add_patch_svg_previews_storage.sqlforpatches.image, publicpatchesSVG bucket, and owner/admin storage write policies. Remote Supabase apply andpnpm updateBackendTypeswere skipped because MCP migration inspection showed the linked remote is behind current local migrations; manually patcheddatabase.types.tsonly for the additivepatches.imageshape.pnpm test-headless --include="**/supabase-service/storage*.spec.ts",pnpm lint,node scripts/checks/check-docs.cjs, andgit diff --check; Supabase advisors were skipped because remote DDL/RLS was not applied.patches.imagechangespatches.updatedbefore anyPatchDetailDataServicegeneration wiring.patches.image/patchesbucket, andpublic.patches.handle_updated_autousesmoddatetime('updated'), so image-only row updates would bumppatches.updated. Added maintainer approval gates for migration reconciliation and an image-only timestamp preservation strategy; no remote schema/storage/RLS changes were applied.pnpm updateBackendTypesafter reconciliation. Timestamp-preservation SQL remains separately pending; no unrelated RLS/policy changes or push are approved.public.patches.imageupdates, so preview row writes do not alter graph-edit freshness semantics. Preview UI/data-service wiring remains blocked until migration drift is reconciled and the approved SQL checkpoint exists; no broad patch RLS changes, unrelated migration/policy changes, or push are approved.20260619091200_preserve_patch_preview_image_updated.sqlplusupdate.patchPreviewImage(id, image)for owner/admin image-only persistence. The backend method updates onlyimage, selectsid,image,updated, bustspatches/patchesWithModule, and is covered by focused specs; no remote Supabase changes or typegen were run.public.patches.imageand thepatchesstorage bucket are absent, andpublic.patchesstill lacks the local image-only timestamp-preservation trigger. Pivoted the coordinator loop to the next safe planning task; no remote DDL/storage/RLS changes were applied.patches.image, public link-readablepatchesSVG bucket, and authenticated owner/admin insert/update/delete storage policies. No unrelated RLS/policy changes are approved.patches.imagefor SVG URL/path, use a dedicatedpatchesstorage bucket, limit RLS writes to the patch owner, align reads with patch visibility, and use a deterministic filename based on patch id/version; no migrations/storage/RLS were applied in this docs-only checkpoint.patches.image, publicpatchesSVG bucket, deterministicpatch_<id>_v<updated>.svgfilenames, and authenticated owner/admin insert/update/delete storage policies; no migrations/storage/RLS were applied.feature-notetakerfrom a verbatim userindication ("visual previews of the patches, saving SVG files… same
behavior as racks… different folder… preview placeholders"). Classified
as INFRA / MEDIUM because it polishes an already-shipped surface
(patches) without blocking or being blocked by any Tier 1/2 roadmap item,
but has high compounding value for browse/SEO surfaces.
Production and release gates
Remote schema, storage, RLS, migration, and type-generation work remains subject to the approval and migration-drift gates documented above. Preview UI/data-service wiring must wait for safe reconciliation and verification of the approved timestamp-preservation strategy. No unrelated policy changes, production-branch work, release, or push is authorized by this plan.
Related issues
N/A. No verified GitHub relationship was recorded for this plan.
Documentation impact
This issue preserves the complete migrated planning document. Public documentation remains deferred until confirmed production publication and visibility under the repository documentation lifecycle.
Source and migration metadata
Source: internaldocs/workflow/plans/patch-svg-previews.md
Migration status: migrated from local documentation