Commit Graph
37072 Commits
Author SHA1 Message Date
Shane cb9bb7abe0 Unify brand background to #121212, remove divider under Layout tiles
- All 4 brands' bg.primary now use the same #121212 (was a slightly
  different near-black per brand) — verified via pixel sampling of
  rendered OG images
- Content group no longer has a top divider, removing the line that sat
  directly under the Layout tiles (same noDivider pattern used for Layout
  itself against the Format buttons above it)
2026-07-07 22:12:50 -05:00
Shane cdd79e8437 Give Social its own Instagram / Twitter-X layout set
Same pattern as Newsletter: Social (id "twitter") now swaps the standard
4-tile Layout section for two dedicated tiles — Instagram (centered,
tighter box for square-cropped feeds) and Twitter / X (classic
bottom-left card layout, matching how X actually presents link
previews) — with the same reset-to-valid-default effect on format change.
2026-07-07 22:09:27 -05:00
Shane 78873e61fc Remove divider line between Format buttons and Layout section
Group now takes an optional noDivider prop; the Layout group (which
immediately follows Brand & format) uses it so there's no border line
sitting right under the Format button set.
2026-07-07 22:02:37 -05:00
Shane bbae8c4353 Rename Export button to green "Export", dual-theme Twitter/light-only LinkedIn
- "Export images" button renamed to "Export" and recolored to the brand
  green (matches Download's styling elsewhere)
- Twitter/X in-context preview now always shows both light and dark cards
  side by side instead of a toggle — no reason to hide either
- LinkedIn in-context preview is light mode only (LinkedIn's default and
  by far its most common surface); the light/dark theme selector is
  removed entirely
2026-07-07 21:58:44 -05:00
Shane f32044de81 Add Oriole/Supasquad brands, Newsletter/Luma formats, preview titles
- Two new placeholder brands (Oriole: orange accent, Supasquad: magenta
  accent) following the same pattern as Multigres — real colors TBD
- Format renames: "OG image" -> "Blog", "Twitter / X post" -> "Social"
- Two new formats: Newsletter (1200x600, no Thumb) and Luma (1600x900,
  Luma's standard event-cover size, no Thumb)
- Newsletter gets its own two-tile Layout set (Newsletter cover / Section
  header) instead of the standard 4 templates, swapped automatically when
  the format changes, with a reset effect so the selected template is
  always valid for the active format
- Fixed a latent bug where switching to a Thumb-less format while View was
  "Both" still rendered an empty Thumb card
- In-context previews (Twitter/X, LinkedIn, Blog listing) now show a title
  above the mockup, matching the OG/Thumb preview card labels
2026-07-07 21:51:47 -05:00
Shane 62c275e97d Add Supaimage logo as the Thumb's icon-less background
Drops the Supaimage/Supabase lightning-bolt mark into public/backgrounds/
as thumb-default.png — picked up automatically by
randomBackgroundDataUri() (single file today, so it's deterministic; more
can be added later without code changes).
2026-07-07 21:30:34 -05:00
Shane 46523a2908 Dropdown polish, matching toolbar corners, drop OG/layout backgrounds
- Icon dropdown now opens upward (bottom-full) instead of pushing the
  panel taller downward, and closes on outside click like a real dropdown
- Floating toolbars use rounded-md to match the button-set corners instead
  of rounded-full
- Remove the headline/WCAG info bar under the OG preview and the
  no-icon warning under the Thumb preview
- Remove the random background-image fallback from OG/layout renders
  entirely (flat color only, as templates originally worked) — kept only
  for the icon-less Thumb, whose asset folder is now empty pending a
  real image to drop in (see public/backgrounds/README.md)
2026-07-07 21:22:59 -05:00
Shane 057619cae0 Anchor toolbars, add accurate Twitter/X + LinkedIn light/dark previews
- View toggle and the Preview/safe-area toolbar are now siblings of the
  scrollable canvas (main), not children of it, so they stay anchored in
  place instead of scrolling away with the content
- Twitter/X and LinkedIn in-context mockups use each platform's real hex
  palette (not our app's semantic tokens) with a Light/Dark toggle —
  X's true-black "Lights out" dark mode, LinkedIn's #1b1f23 dark surface,
  accurate borders/secondary-text colors, verified badge, Follow button
- When a social preview is active, the OG/Thumb canvases are hidden and the
  mockup takes over the full canvas view instead of sitting below the OG
  card
2026-07-07 21:08:30 -05:00
Shane b8b92bd4f3 Move Copy URL / Download into an Export images modal
- Remove per-card Copy URL / Download buttons from the OG and Thumb
  previews; the header's "Export @2x" checkbox becomes an "Export images"
  button that opens a modal
- Modal holds the @2x toggle plus one row per image: a read-only URL field,
  a copy button (clipboard icon), and a download button (down-arrow icon)
- Bottom toolbar: add a "Preview" label ahead of the view-in-context
  segmented control, and move the safe-area guide button to the other end
2026-07-07 20:55:24 -05:00
Shane 91e60178d9 Fix editor not filling full viewport height in embedded/iframe previews
Chain height:100% from html -> body -> the app's root div instead of relying
on 100vh alone (min-h-screen on body, h-screen on the root). 100vh can end
up taller or shorter than the actual visible area in embedded iframe
previews (the same class of bug as mobile browser chrome), whereas an
unbroken height:100% percentage chain sizes correctly to whatever the host
gives the iframe.
2026-07-07 20:33:46 -05:00
Shane 2e27ff8a95 Toolbar/layout polish: icon-button safe area, drop template patterns
- Safe area toggle in the bottom toolbar is now a dotted-box icon button
  instead of a checkbox + label; the on-image guide is dotted to match
- Bottom toolbar now aligns to the same center line as the View toggle
  above it (both toolbars sit in main's padded content box, but absolute
  positioning ignores ancestor padding, so the offsets are repeated)
- Eyebrow style selector removed — always renders as a pill now
- Reduced the eyebrow pill's font weight one step (500 -> 400)
- Removed each template's default background pattern (dots/grid/hlines/
  vlines) entirely, along with the now-dead grid-snap alignment math;
  icon-less compositions still get the random image background
- Removed the "Template" label above the layout tiles
2026-07-07 20:29:21 -05:00
Shane a8304396ae Editor polish: pill eyebrows, always-on sentence case, random no-icon backgrounds
- Remove platform-crop previews (too small to be useful)
- View (OG/Thumb/Both) moves back to the top of the canvas, separate from
  the other tools; the floating bottom toolbar now holds the safe-area
  guide toggle and View-in-context
- Eyebrow defaults to the pill style, now white background / black text,
  set in IBM Plex Mono (self-hosted alongside Manrope for satori + fontkit)
- Sentence case is now always on; wrap text in [brackets] to preserve its
  exact casing instead of a manual on/off toggle
- Remove the manual font-size override — always auto-fit
- Remove the "No headline..." caption under the Thumb preview
- Icon-less compositions get a random background image from
  public/backgrounds/ (new asset folder, 3 starter SVGs) instead of a flat
  fill, on both the OG template root and the Thumb
2026-07-07 20:15:06 -05:00
Shane 221a9b50ab Simplify editor UI: button sets, tile layouts, floating view toolbar
- Brand and Format are now button sets instead of dropdowns
- Removed the AI art direction panel for now
- Templates render as visual tiles (like the icon grid) instead of a <select>
- Icon picker is now a dropdown with a thumbnail preview, moved to the end
  of Content (so it's still reachable in Thumb-only view)
- Removed the Thumb icon-size control and the Background pattern controls —
  Thumb uses the format default, patterns use each template's default
- View and View-in-context are now a single floating toolbar, bottom-center
  of the canvas, instead of split between the canvas top and the sidebar
- Safe-area guide is now a uniform 80px inset on every edge
2026-07-07 19:51:09 -05:00
Shane 3531b49911 Add Twitter/LinkedIn/blog-listing in-context preview mockups
Today's platform-crop preview only showed a cropped rectangle, not what the
image looks like in actual use. Adds a "View in context" selector (View
group) with rough mockups of a Twitter/X post, a LinkedIn post, and a
supabase.com blog listing card, each embedding the currently-rendered image
so switching Brand/Format shows how it reads in context.

Also fixes the local dev launch config's pnpm filter for the og-generator ->
supaimage package rename.
2026-07-02 14:12:15 -05:00
Shane 6025737ef4 Rename OG Image Generator to Supaimage
Reflects the tool's expanded scope beyond Supabase blog OG images (package
name, page title, sidebar header, README). The apps/og-generator folder path
is intentionally left as-is for now, to avoid turning this PR's diff into a
delete+add and losing review history — a clean fast-follow after merge.
2026-07-02 14:06:16 -05:00
Shane b8ff5965a8 Add multi-brand, multi-format support (Supaimage phase 1)
Splits design tokens into Brand (colors, illustration stroke) and Format
(canvas size, icon/thumb sizing) abstractions, threaded explicitly through
the render route, asset library, and editor UI instead of hidden globals.
Adds Multigres as a second (placeholder-color) brand and a Twitter/X post
format alongside the existing OG/Thumb. Brand/Format selectors now lead the
editor panel; uploaded assets are scoped per brand via a new `brand` column.
2026-07-02 14:04:00 -05:00
ShaneandClaude Opus 4.8 c50903c5f0 fix(og-generator): vertical grid-snap now aligns to the visible glyph, not the box
Confirmed via pixel measurement that grid-snap for top/bottom-anchored
headlines (stacked, bottom-left templates) was aligning to padY/H-padY — the
invisible CSS layout-box edge — while the visible glyph ink sat well inside
it due to line-height leading. Measured a 13px gap at fontSize=64 on the
'stacked' template (grid line at y=72, visible text top at y=85).

Fixed using the actual Manrope font metrics (already loaded via fontkit for
the auto-fit measurement) rather than a guessed pixel constant, so the
correction stays accurate at every font size auto-fit picks:
  visualTopInset = halfLeading + ascent - capHeight   (box top -> cap-height top)
  visualBottomInset = halfLeading + descent           (box bottom -> baseline)

Re-measured after the fix: top-anchor gap closed from 13px to 0px, bottom-anchor
to within 1px. Center-anchored templates (split-right, centered) were already
within 1.5-4px with no leading-driven bias -- left unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:24:39 -05:00
ShaneandClaude Opus 4.8 ed32f16fcd fix(og-generator): switching templates no longer resets the background pattern
changeTemplate() used to overwrite patternType/Scale/Opacity with the new
template's defaultPattern on every switch. Template and background are meant
to be independent controls, so it now only sets the template. AI suggestions
are unaffected — applySuggestion() already sets the pattern separately, right
after calling changeTemplate(), when the suggestion includes one. Verified:
set pattern to vlines, switched template to centered (whose default is dots),
pattern select stayed on vlines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:17:39 -05:00
ShaneandClaude Opus 4.8 463e88b160 fix(og-generator): scope the 65% preview width to single-panel view only
Round #19 applied 65% width unconditionally. Per follow-up: it should only
apply when viewing a single image (OG or Thumb alone) — "Both" reverts to
full width. Row width is now `view === 'both' ? 'w-full' : 'w-[65%]'`.
Verified via getBoundingClientRect at 1700px: Both = 100.0%, OG-only = 65.0%,
Thumb-only = 65.0%, zero console errors in all three states.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:06:44 -05:00
ShaneandClaude Opus 4.8 4e5ce33c59 fix(og-generator): match WCAG: label styling to Headline:, narrow preview to 65%
- Split "WCAG:" into its own font-medium text-foreground span, matching
  "Headline:" (previously the whole segment, including the label, was
  colored by pass/fail state)
- Preview row: w-full -> w-[65%], with the wrapper's horizontal centering
  made unconditional (was @4xl-only) so the narrower row stays centered in
  both stacked and side-by-side layouts. Verified via getBoundingClientRect:
  exactly 65.0% of the canvas's usable width, with equal left/right margins
  (225.4px each) at 1700px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:03:10 -05:00
ShaneandClaude Opus 4.8 da0c4eab9d fix(og-generator): actually vertically center the preview row + info panel labels
The previous round's items-start fix aligned the two cards relative to EACH
OTHER (proven equal, but only proves top===center===bottom trivially) — it
never addressed the real complaint: the whole OG+Thumb row sat pinned near
the top of the canvas with dead space below on wide/tall screens. Wrapped the
row in a flex-1 container that centers it (@4xl:items-center
@4xl:justify-center) within the space remaining below the view toggle. Since
flex items don't shrink below their content size, an overflowing row still
just grows the wrapper rather than clipping (avoids the classic
flex-center-clips-on-overflow bug). Verified via getBoundingClientRect: 186.1px
of space above and below the row, exactly equal.

OG info panel: prefixed "Headline:" at the start, moved the fit-mode
indicator to right after the font size and reworded it to "(auto on)" (shown
only when auto-fit is active), and prefixed "WCAG:" before the contrast
score. Reads as "Headline: 64px (auto on) · 2 lines · WCAG: 16.3:1 AAA".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:59:19 -05:00
ShaneandClaude Opus 4.8 75ce4a5dac fix(og-generator): truly floating panel, top-align previews, remove zoom, frame info pill
- Panel is now position:absolute (not a flex sibling) over a full-bleed
  absolute-inset canvas — one continuous dot-grid surface behind it, instead
  of two boxes split by a shared flex edge. Canvas content gets pr-[380px]
  so it doesn't render under the panel.
- Preview row: items-center -> items-start. OG and Thumb render at identical
  size (same aspect ratio, equal flex-basis) with identical-height label rows
  above them, so top-aligning is what actually keeps the IMAGES aligned;
  items-center was aligning the outer card blocks (image + variable-height
  caption below), which visibly offset the images from each other. Verified
  via computed styles: both cards now start at the exact same top regardless
  of caption height.
- Removed the zoom control entirely (state, buttons, ZOOM_MIN/MAX, the
  style={{zoom}} CSS zoom) — it scaled the whole card including buttons/
  labels, not just the rendered image.
- OG fit-info pill: added a real border + solid background (was
  bg-surface-200 with no border, blending into the canvas) so it reads as a
  distinct panel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:47:30 -05:00
ShaneandClaude Opus 4.8 b27bdcd51d feat(og-generator): floating tool panel on the right, centered preview row
- Package the top bar (title + Export @2x) into the sidebar itself, as a
  sticky header strip inside the panel rather than a separate full-width bar
- Move the panel from the left to the right, and restyle it as a floating
  card (rounded corners, shadow, inset margin) over the canvas instead of a
  full-height docked sidebar
- Preview row: add @4xl:items-center so the OG and Thumb cards align by
  vertical center when shown side by side on wide screens (they already
  stacked at medium widths); verified via computed styles (align-items:
  center, cards offset by exactly half their height difference)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:29:54 -05:00
ShaneandClaude Opus 4.8 166826620a fix(og-generator): logo fitBox() returned {w,h} instead of {width,height}
Spreading the result straight onto <img> as JSX props meant satori saw
w={..} h={..} instead of width/height — no intrinsic size, so the logo
filled its entire container instead of fitting a small box. Caught via a
real end-to-end upload test against the live Supabase project (secret key +
migrations now applied): a color logo rendered full-canvas instead of boxed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:12:42 -05:00
ShaneandClaude Opus 4.8 5ec0a89f5d feat(og-generator): custom color logos for partnerships/acquisitions
Line icons are stroke-only by brand rule, but a partner's logo (Grafana,
Multigres, etc.) needs to render in ITS OWN colors — a second asset kind
alongside the existing icon system, not a replacement for it.

- supabase/migrations/0002_logo_assets.sql: add width/height to `assets`
  (kind + storage_path already existed from 0001)
- lib/assets/sanitize-svg.ts: sanitizeLogoSvg() — a separate allowlist that
  PRESERVES fill/stroke/gradients/style color declarations (unlike the icon
  sanitizer, which strips them for the stroke-only treatment), while still
  blocking scripts, event handlers, and external references (internal `#id`
  refs are kept so gradient defs/<use> keep working). Unit-tested against a
  mixed color+attack payload.
- lib/supabase/assets.ts: insertLogoAsset() uploads to the og-assets Storage
  bucket (raster-safe, unlike inline SVG bodies) and records client-measured
  width/height; rowToIcon exposes kind/url/width/height for both kinds.
- app/api/assets/route.ts: POST branches on a `kind` field ('icon', default,
  or 'logo'); logo accepts SVG/PNG/JPEG/WebP up to 2MB, requires width+height.
- app/api/og/route.tsx: icon slots render logos as-is (no stroke
  normalization), fit to their natural aspect ratio via a new fitBox() helper.
- app/page.tsx: a second "+ Upload logo (color)" control measures the file's
  natural dimensions in-browser (Image().onload) before POSTing; the icon
  picker renders logo entries as real <img> (actual colors) instead of the
  forced-stroke SVG redraw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:03:02 -05:00
ShaneandClaude Opus 4.8 a15601fe9c docs(og-generator): simplify reference-examples intake to a single links file
Drop a plain figma-links.txt (one URL per line) instead of pairing every
image with a .figma.txt file — Claude Code pulls the screenshot/design
context per link directly via the Figma integration. Also notes this same
drop zone doubles as the intake for a future image-generation training set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:34:54 -05:00
ShaneandClaude Opus 4.8 6f94bbf697 feat(og-generator): sidebar cleanup — font size placement, sentence-case, background
- Move Font size out of Layout, under Headline in Content (auto-fit toggle +
  slider now sit right below the headline field)
- Rename the sentence-case checkbox to "Disable sentence-case" and invert it
  (unchecked = sentence-case on, the existing default) so its label matches
  its behavior
- Background: remove the color picker — patterns are white-only now
  (lib/ai/claude.ts + the Suggestion validators updated to match). Opacity
  range raised from 4-12% to 20-35% (baseline 20%), and the single source of
  truth (lib/design/patterns.ts PATTERN_OPACITY) drives both the editor slider
  and the /api/og server clamp
- Scale options: drop Small, rename Medium -> "Default" and Large -> "Bigger"
- Bump template/example defaultPattern.opacity from ~5% to 20% to match the
  new range; /api/og keeps accepting legacy sm/green query params so
  previously-generated URLs keep rendering unchanged

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:24:43 -05:00
ShaneandClaude Opus 4.8 227fa178dc revert(og-generator): remove DialKit dev tuning panel
Removes DialRoot from the root layout, the useDialKit "Render tuning" controls
from the editor, and the iconScale/strokePx tuning params from /api/og; uninstalls
dialkit + motion. The Ollama engine and expanded icon library are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:13:10 -05:00
ShaneandClaude Opus 4.8 b90a9f2b5f feat(og-generator): self-hosted LLM engine (Ollama) + expanded Lucide icon library
Two ways to keep smart art direction without a paid AI service:

B) Local LLM engine — lib/ai/ollama.ts calls a self-hosted Ollama server
   (opt-in via OLLAMA_URL), reusing the exact prompt + validation as the Claude
   engine. Nothing leaves your infra. /api/suggest engine order is now
   Claude -> Ollama -> keyword. Shared helpers exported from claude.ts.

A) Bigger on-brand palette — scripts/gen-lucide-icons.mjs generates a curated
   30-icon Lucide set (lib/assets/lucide-icons.ts) from the lucide-static dev
   dep, with Supabase-flavored tags. lib/assets/icon-library.ts combines seed +
   Lucide into ICON_LIBRARY/ICON_MAP; the picker, keyword matcher, LLM catalog,
   and /api/og icon resolver all use it, so a text prompt matches ~35 icons
   (semantic reasoning comes free once the local LLM is on).

Docs: .env.local.example + README (Ollama option). App still runs with no AI.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:08:04 -05:00
ShaneandClaude Opus 4.8 a27461cc34 feat(og-generator): DialKit render-tuning controls (icon scale + stroke)
- /api/og: accept optional iconScale + strokePx params (backward-compatible,
  clamped), applied to both the OG and Thumb icon rendering
- Editor: a dev-only useDialKit "Render tuning" panel (iconScale, strokePx)
  feeds live values into the render URLs; defaults (1, 2) leave the URL
  untouched so production output is unchanged

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:45:35 -05:00
ShaneandClaude Opus 4.8 ad0fe823d0 chore(og-generator): add DialKit dev tuning panel (dialkit + motion)
- Install dialkit ^1.3.0 + motion ^12.42.0
- Import dialkit/styles.css and mount <DialRoot/> as a sibling of {children} in
  the root layout (not wrapping it), gated to development so the tuning panel
  never ships to end users. Controls appear once components register dials via
  useDialKit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:39:08 -05:00
ShaneandClaude Opus 4.8 a582df7c7f feat(og-generator): custom SVG icon uploads (asset library)
- lib/assets/sanitize-svg.ts: conservative allowlist sanitizer → {viewBox, body}
  for line-art SVGs; strips script/foreignObject/image/event-handlers/hrefs and
  rejects DOCTYPE/entity and non-drawing input
- lib/supabase/assets.ts: listAssets() + resolveIcon() (seed → DB) via the anon
  client; insertAsset() via the admin (secret) client
- app/api/assets/route.ts: GET the library; POST an SVG (multipart) → sanitize →
  insert. Returns a clear 503 when SUPABASE_SECRET_KEY isn't configured
- /api/og: resolve the icon param through resolveIcon so uploaded assets render
  in OG + Thumb
- Editor Assets panel: load /api/assets, merge seed + uploaded in the grid, and
  replace the disabled button with a real file upload (loading + error states)

Reads work with the publishable key; uploads (writes) go through our server
route with the secret key, keeping the publishable key read-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:26:30 -05:00
ShaneandClaude Opus 4.8 5d315cf467 feat(og-generator): Supabase wiring — client, v1 schema, DB-backed examples
- Add @supabase/supabase-js (catalog) + lib/supabase/server.ts: anon
  (publishable, RLS-respecting reads) + admin (secret) clients that return null
  and no-op gracefully when the project isn't configured
- Add supabase/migrations/0001_init.sql: featured_examples / assets / posts
  tables, RLS (public read on the reference tables), the og-assets Storage
  bucket, and a seed of the featured-examples corpus. Idempotent.
- Read featured_examples from the DB when configured
  (lib/supabase/featured-examples.ts), falling back to the bundled corpus on
  missing table / query error / no config; thread the examples list through
  suggestArtDirection + suggestWithClaude + /api/suggest
- Document Supabase env vars (.env.local.example) + a README setup section

The app still runs fully with no backend — Supabase is opt-in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:11:08 -05:00
ShaneandClaude Opus 4.8 6e807b4ab7 feat(og-generator): Claude-backed AI art direction + featured examples
Replace the keyword-only "Describe this post" suggester with a real Claude
call, grounded in a curated featured-examples corpus.

- Add featured-examples corpus (lib/ai/examples.ts) as approved precedent, and
  match against it first in the keyword suggester
- Add Claude suggester (lib/ai/claude.ts) + Node-runtime /api/suggest route:
  claude-opus-4-8 reasons over the icon/template/pattern vocab + examples with
  structured JSON output; every field is validated back into the allowed set;
  falls back to the keyword matcher when ANTHROPIC_API_KEY is unset or on any
  error, so the route always returns a valid suggestion
- Editor: Generate now calls the route (async + "Generating…" state), adds the
  "ai" source with a "✨ AI suggestion" label; drop the client-side suggester
- Add "Save current as example" to grow the corpus
- Pin @anthropic-ai/sdk ^0.106.0; add .env.local.example + README AI section

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 13:47:25 -05:00
ShaneandClaude Opus 4.8 ed055b6bcd feat(og-generator): editor UX refinements + headline typography rules
- Headline weight 600→500; balanced 2-line wrapping (no full-width span) plus
  widow/orphan avoidance in the fit algorithm
- Grid-snap the background pattern to each template's content anchor on both axes
- Eyebrow "pill" style option alongside plain text
- Reorder sidebar (lead with AI → Layout → Assets → Content) with clearer
  section-header vs. label hierarchy; Plain/Pill eyebrow control; disabled
  "Upload SVG" affordance noting it lands with the Supabase asset library
- Condense the OG spec readout to a single-line info panel
- Both view: responsive side-by-side that fills the canvas on wide screens and
  stacks when narrow, plus canvas zoom
- Fix a horizontal scroll on the font-size slider
- AI art direction: explicit "Generate" button (sparkle icon) instead of live

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 11:07:05 -05:00
ShaneandClaude Opus 4.8 a4cd01ddeb feat(og-generator): light-mode editor, Both view, AI art-direction, grid-snap
- App chrome switches to light mode (the generated image stays dark by spec)
- Canvas gets a dot-grid workspace background
- Move the OG/Thumb view toggle onto the canvas; add a default "Both" view that
  renders OG + Thumb together, each with its own Copy URL / Download
- Illustrations default to a neutral 2px / #A0A0A0 stroke (eyebrow stays green)
- Add a lightweight, backend-free AI art-direction field: describe the post ->
  suggested icon + template + rationale (keyword/tag match over the seed icons)
- Grid-snap: phase the background pattern so a grid line lands on the safe-area
  inset, anchoring the composition to the background grid

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 10:12:58 -05:00
ShaneandClaude Opus 4.8 6037f56642 feat(og-generator): internal OG/Thumb image generator (v1)
Self-serve internal tool to generate on-brand OG (1200x630) and Thumb
images for supabase.com/blog posts. New App Router app at apps/og-generator.

- Render via next/og (satori + resvg), self-hosted Manrope, fontkit text
  measurement; all colors/fonts resolve from a design-tokens config (no
  hardcoded hex) per the non-destructive recipe model
- Headline: auto sentence-case + acronym allowlist, auto-fit to <=2 lines
  (64->40px) with overflow blocking, manual line-break/size override
- 4 guardrailed templates + background pattern library (grid/dots/rules,
  opacity locked 4-12%), each template with a default pattern
- Line-art icon system, stroke normalized to 1.22-1.88px; OG + Thumb share it
- Guardrails: char counter (60/70), WCAG contrast check, safe-area overlay,
  platform-crop preview
- Stateless /api/og URL + 1x/2x PNG export

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 15:51:05 -05:00
Joshen Lim 0038f303f2 Track is initialized in feature preview context (#47309)
## Context

Noticed that while default opted into unified logs, if you refresh while
on the page, you'll get redirected back to the old logs URL
(logs/explorer)

Happening due to a inconsistent tracking of loading states for feature
flags and feature previews. Just need to track whether the feature
previews have been initialized

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved the loading behavior for unified logs preview so it only
finishes loading after preview settings are fully initialized.
* Added a more reliable initialization state to better reflect when
feature-based defaults are ready.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 15:13:25 +00:00
Charis 16526bd6bf refactor(studio): extract SQL editor save mechanism + model folder lifecycle (4/9) (#47276)
## What

PR 4 of a stacked refactor of the SQL editor snippet/folder state. It
pulls the persistence logic out of the store into an injectable
mechanism, and replaces the folder `'new-folder'` id sentinel with an
explicit lifecycle — plus a concurrency bug fix that surfaced along the
way.

### Save mechanism (`sql-editor-save.ts`)

`createSaveMechanism({ state, upsertContent, createSQLSnippetFolder,
updateSQLSnippetFolder, invalidate, notify, debounceMs })` → `{
saveSnippet, createFolder, updateFolder }`. The store's subscribe now
dispatches to it; *when* to save still lives in the subscribe (the
scheduler/provider move is PR 5). Per-id debounce cache lives in the
factory closure (no module-global leak).

- **`saveSnippet`** reads the live store snippet, guards
`isLoadedSnippet` so a content-less snippet can **never PUT an empty
body** (directly unit-tested), then builds the payload + drives status
transitions + gated invalidation.
- **`toast` is injected** as a `Notifier` (new generic DI contract in
`lib/notifier.ts`) — the mechanism no longer imports sonner.
- **create vs rename are two named-arg functions**, not an `isNew`
branch; rollback is deterministic per operation instead of matching on
`error.message` text.
- **caught errors are `unknown`**, narrowed via the existing
`getErrorMessage` util with a generic fallback — no `any`.

### Folder lifecycle (replaces the `NEW_FOLDER_ID` sentinel)

- **`FolderStatus`** enum (`new_editing | new_saving | editing | saving
| idle`) collapses the persistence and progress axes into one enum —
same pattern as `SnippetStatus` — with `isNewFolder` / `isFolderEditing`
/ `isFolderSaving` predicates. Tagging a folder as new/persisted is now
an explicit field, not an id convention.
- New placeholders get a **unique local id** (`crypto.randomUUID`);
`NEW_FOLDER_ID` is deleted, which also lifts the accidental
one-unsaved-folder-at-a-time limit.

### Bug fix: folder-rename rollback race

The shared `lastUpdatedFolderName` field let two in-flight renames
clobber each other's rollback target (and a shared `finally` could wipe
it). Replaced by a **per-folder `previousName`** on
`StateSnippetFolder`, so concurrent renames of different folders are
isolated. A new test runs two failing renames concurrently and asserts
each restores its own previous name.

## Tests

`sql-editor-save.test.ts` (mechanism — fakes + fake timers, incl.
content-less no-PUT and concurrent-rename isolation) and
folder-lifecycle predicate tests. `pnpm --filter studio typecheck`
clean; 82 state/sql-editor unit tests pass.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Improved SQL editor folder handling with clearer create, rename, and
save states.
* Added a more consistent notification flow for successful and failed
save actions.

* **Bug Fixes**
* Improved rollback handling when folder renames fail, helping restore
the previous name reliably.
* Updated save behavior to better protect against duplicate or
out-of-order updates.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 11:08:26 -04:00
obchain 7007a8e5e8 fix(studio): raise body size limit for saving SQL snippets (#47032)
## What kind of change does this PR introduce?

Bug fix — closes #45060.

## What is the current behavior?

Saving a large SQL snippet from the SQL Editor fails when the content
exceeds ~1 MB. The content API route (`PUT
/platform/projects/{ref}/content`) relies on Next.js's default API
body-parser limit of `1mb`, so large snippets — for example a
multi-thousand-line RPC — are rejected with a `413 Payload Too Large`
before the handler runs, and the snippet can't be saved.

## What is the new behavior?

The route now sets an explicit body size limit of `5mb`, matching the
limit already used by the AI SQL endpoint
(`pages/api/ai/sql/generate-v4.ts`). Large SQL snippets save
successfully, and the value is consistent with existing SQL-handling
routes in the app.

```ts
export const config = {
  api: {
    bodyParser: {
      sizeLimit: '5mb',
    },
  },
}
```

## Additional context

- Only the content route's `PUT` handler accepts the snippet body; the
sibling `item/[id].ts` route doesn't take a content body, so no change
is needed there.
- Supersedes the stale #45101 (no activity in ~8 weeks); this version
documents the rationale and aligns the limit with the existing precedent
in the codebase.

---

- [x] I have read the
[CONTRIBUTING](https://github.com/supabase/supabase/blob/master/apps/studio/CONTRIBUTING.md)
guidelines.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Fixed an issue preventing users from uploading or processing large
content, such as SQL snippets, which would previously result in
rejection errors.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 17:03:12 +02:00
Joshen Lim ccf84da968 Ensure that pathname in unified logs is solely server side filtered (#47307)
## Context

Filtering on pathname in unified logs shows no data despite the network
request returning some data

Happening due to missing `filterFn` on pathname in `Columns.tsx` (should
just return true so that the react table doesn't bother with client side
filtering, since filtering is done on the server side)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Updated log table filter handling for several always-visible columns,
with no change to the displayed data or user experience.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 14:39:06 +00:00
Joshen LimandAlaister Young 2bac064adf Joshen/fe 3697 progressively default opt in to unified logs (#47296)
## Context

We're progressively opting in users to use the new Unified Logs UI 🙂🙏

## Changes involved

- [ ] Removed flag for controlling visibility of unified logs feature
preview
- [ ] Added flag for controlling default opt in behaviour of unified
logs
- [ ] Small tweak to Unified Logs banner is default opted in (Just show
"New" and more info CTA)
- Disabling, then enabling again will thereafter show the existing "Go
back to old logs CTA"
<img width="290" height="166" alt="image"
src="https://github.com/user-attachments/assets/a2c46ce1-63c3-490c-bc7d-fc1254982dbe"
/>


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Unified Logs preview now derives default opt-in state from a new
default-opt-in flag and exposes `isDefaultOptIn`.

* **Bug Fixes**
* Removed eligibility-based gating so the “Beta” badge and Unified Logs
banner render consistently across logs screens.
* Unified Logs banner was refactored to handle enable/disable and
navigation internally, while remaining shown unconditionally.

* **Tests**
* Updated mocks and assertions to reflect the revised preview/banner
enablement and dismissal logic.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
2026-06-25 21:45:40 +08:00
Pamela Chia 0a797ef4ea feat(studio): add creation funnel telemetry (#47291)
## Summary

Adds frontend funnel telemetry to the organization-creation and
project-creation flows in Studio, so each is measurable as a funnel
(form exposed → completed) entirely from frontend events. Feeds the KPI
3 FE Benchmark Friction dashboard. Org creation had zero frontend funnel
events before this (only a backend event that fires across every
surface), and project creation had no clean form-view impression.

## Changes

- Define `organization_creation_form_exposed`,
`organization_creation_completed`, and `project_creation_form_exposed`
in the telemetry constants.
- Fire `organization_creation_form_exposed` when the new-org form
renders, gated on the profile resolving so pre-auth redirects are not
counted. Fire `organization_creation_completed` from the create success
callback, covering both the free and the paid pending-payment-intent
paths, attaching the new org slug as the organization group.
- Fire `project_creation_form_exposed` once the org and the
create-project permission have resolved, so it anchors on the form being
visible rather than the route loading. Project completion reuses the
existing client-side success event, so no duplicate completion event was
added.

## Notes

I chose exposed → completed over exposed → submitted. The org slug only
exists after the create API resolves, so the completion event is the
only org-funnel event that can carry the organization group; a
submit-time event cannot, which would break org-level segmentation. A
pageview is not a sufficient exposure anchor either: pageview capture is
off, and the manual pageview fires on route change before the form is
interactive (pre-auth redirect, async permission load, the no-org
redirect).

The `completed` verb follows the repo's approved-verb list
(`.claude/skills/telemetry-standards`); the repo previously migrated
`branch_merge_succeeded` to `branch_merge_completed` for the same
reason.

## Testing

Tested on the preview deploy:

- [x] `/dashboard/new` while signed in →
`organization_creation_form_exposed` fires once.
- [x] Create a free org → `organization_creation_completed` fires with
the organization group set.
- [x] `/dashboard/new/[slug]` with create permission →
`project_creation_form_exposed` fires once with `surface=main` and the
organization group.
- [x] No event re-fires on re-render or tab refocus.

Post-deploy: confirm in PostHog prod (project 34344) via HogQL that each
event fires with the expected properties and the organization / project
group set.

## Linear

- fixes FE-3690


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added improved tracking for organization and project creation flows,
including when forms are shown and when organization creation completes.
* Captures creation metadata to support better reporting on onboarding
and setup progress.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 13:17:11 +00:00
Pamela Chia 2d0bcd4714 feat(telemetry): classify funnel creation errors (#47293)
## Summary

The KPI-3 friction dashboard needs to know *why* users hit errors on the
signup, project-creation, and org-creation funnels, not just that they
did. The existing `dashboard_error_created` event already fires for
these paths (10% sampled, with `$pathname`), but carries no reason:
~98.5% of events have no `errorType` and no property carries an error
message. This adds PII-safe classification computed client-side from a
controlled vocabulary, so raw error text never leaves the browser.
Validation errors (previously invisible, since they are inline form
errors that never raise a toast) are now captured on invalid submit.

## Changes

- Extend `dashboard_error_created` with `origin`, `errorCategory`,
`errorReason`, `errorCode`, and a `form` source value
- Add a pure, unit-tested classifier (`funnel-errors.ts`) and a
10%-sampled tracking hook (`use-track-funnel-error.ts`); the classifier
maps errors to stable slugs and emits only slugs + HTTP status, never
raw message text
- Classify signup errors (API failures + validation) in `SignUpForm`
- Classify project-creation errors (API failures, OrioleDB guard,
validation) in the new-project wizard
- Classify org-creation errors (API failures, payment/card declines,
confirm-subscription, validation) in `NewOrgForm`

## Testing

13 unit tests cover every classifier branch (validation / api / network
/ payment, status-code handling, message-pattern matching, and
fallbacks).

To verify on the Vercel preview (events are 10% sampled; set the sample
rate to 1 locally to observe each fire):
- Signup with a weak but non-empty password: `origin=signup,
source=form, errorCategory=validation, errorReason=password_invalid`
- Signup with an already-registered email: `origin=signup, source=toast,
errorCategory=api, errorReason=email_already_registered`
- New project with an empty name: `origin=project_creation, source=form,
errorReason=project_name_invalid`
- New org with an empty name: `origin=org_creation, source=form,
errorReason=org_name_missing`
- New org with a declined test card: `origin=org_creation,
errorCategory=payment`

PII: raw `error.message` is never sent; only controlled slugs and HTTP
status. Dashboard consumers must filter `origin IS NOT NULL` so these do
not collide with the generic toast events the global tracker still
emits.

## Linear

- fixes FE-3691


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added improved, categorized telemetry for signup, project creation,
and organization creation errors, including payment,
subscription-change, and validation failures.
* Extended dashboard error events with optional structured diagnostics
(origin, category, reason, and optional error code) and support for
form-origin reporting.

* **Bug Fixes**
* Improved project-creation handling to record a validation telemetry
event when an Oriole image is unavailable.
* Ensured payment-related and subscription-change failures are captured
consistently alongside existing user toasts.

* **Tests**
* Added unit tests covering API/network/validation/Stripe error
classification and reason mapping.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 20:56:23 +08:00
Jordi EnricandClaude Opus 4.8 e0ba04caf4 feat(studio): migrate per-service log pages to OTEL endpoint behind a flag DEBUG-145 (#47264)
## Problem

The legacy per-service log pages (postgres, auth, api, edge functions,
storage, realtime, cron, etc.) and the single-log detail panel query the
BigQuery-backed `logs.all` analytics endpoint. We are moving these reads
onto the OTEL ClickHouse endpoint (`logs.all.otel`).

## Fix

- Add `Logs.utils.otel.ts`: ClickHouse query builders
(rows/count/chart/single) + row mappers that target the single `logs`
table keyed by `source`, reading fields from the `log_attributes` map
and aliasing columns to the leaf names the renderers expect.
- Parameterize `buildWhereClauses` / `genWhereStatement` in
`Logs.utils.ts` so the OTEL builders reuse the shared nested AND/OR
filter grouping. Defaults keep the BigQuery behavior unchanged.
- Gate `useLogsPreview` (rows, count, chart) and `useSingleLog` (detail)
on the new `otelLegacyLogs` flag. BigQuery stays the default when the
flag is off.
- Extract the OTEL timestamp parser into `parseOtelTimestamp`
(`otel-inspection.utils.ts`) and reuse it in
`unified-logs-infinite-query.ts` (replaces an inline copy of the same
logic; no behavior change).

## Dependencies

None. Standalone, safe to merge on its own. Behind `otelLegacyLogs` (off
by default), so no user-facing change.

Part of DEBUG-145 (split from #47087).

## How to test

- In staging, go to Legacy Logs. 
- All logs pages should work the same as before. 

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added OTEL-backed logs support for preview, count, chart, and
single-log details when enabled.
* **Bug Fixes**
* Improved timestamp parsing/normalization for OTEL data to ensure
correct display and pagination.
* Enhanced filtering behavior, including safer handling of unknown
filter keys and invalid values across OTEL queries.
* Improved single-log result shaping to preserve expected API/database
metadata in OTEL mode.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:31:22 +02:00
Chris Chinchilla bce47daeea docs: Only build reference docs if feature is enabled (#47273)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Reference documentation generation now only includes feature-enabled
SDK pages, so published docs better match what’s available.
* Legacy SDK reference pages are now shown selectively based on enabled
feature flags.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 13:16:38 +02:00
Gildas Garcia f34aa108d5 chore: dead code cleanup (#47294)
## Problem

There's still more unused code in the repository which slows down
everything:
- checkouts
- tooling
- probably builds (not sure how good turbopack is at handling this)

## Solution

- remove old unused code
- remove more recent code after checking git history to ensure it's not
unfinished/ongoing work

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Removed several unused interface, onboarding, and helper components
from the studio app.
* Cleaned up outdated branching, integrations, query performance,
support, and table/grid UI elements.
  * Removed a few unused utility hooks and key-mapping logic.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 12:34:19 +02:00
Joshen Lim afe405962e Joshen/fe 3698 observability overview links to logs should use unified logs (#47295)
## Context

Found some links pointing to the old logs pages. Should point to unified
logs if unified logs have been enabled

Also deprecates the old `ServiceStatus` file that's no longer used

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Updated observability “logs” links to open the correct view when
unified logs are enabled, including service-specific filtering.
  * Fixed navigation from log views to correctly preserve query strings.
* Refreshed project service status log links and health indicators to
stay consistent with the latest unified logs behavior.
* **Refactor**
* Consolidated service status UI and related logic into the project home
experience, replacing the prior shared implementation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 17:52:47 +08:00
pgburhan 8e2ce64c69 Add Burhan A to humans.txt (#47277)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES/NO

## What kind of change does this PR introduce?

Bug fix, feature, docs update, ...

## What is the current behavior?

Please link any relevant issues here.

## What is the new behavior?

Feel free to include screenshots if it includes visual changes.

## Additional context

Add any other context or screenshots.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Documentation**
* Updated the site’s credits/team listing to include an additional team
member name.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 10:56:22 +02:00
6946ec2b2d build(studio): Next-compat shims (stack 2/6, from #46424) (#47110)
**Stack 2/6** of the TanStack Start migration (#46424). Stacked on
**#47107** (S1) — review that first; this PR's diff is just the compat
shims.

> [!NOTE]
> Purely additive. Next never imports these files — under TanStack
they're wired in via Vite aliases (`next/*` → `@/compat/next/*`). No
routes consume them yet (that begins in stack 3).

## What's in this PR
`apps/studio/compat/next/*` — drop-in shims so the existing pages-router
code runs unchanged under TanStack Start:
- `link`, `router`, `navigation`, `head`, `image`, `legacy/image`,
`script`, `dynamic`, `server`, `_router-events` — React/runtime shims
over `@tanstack/react-router`.
- `api.ts` — `toWebHandler`, which adapts a pages-router API handler
`(req, res)` into a TanStack server-route Web `fetch` handler.

## Verification
On top of S1: `studio` typecheck ✓, lint (0 errors) ✓. Next build is
unaffected (nothing imports these under tsc).


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added broad Next.js compatibility support for routing, links, dynamic
imports, images, scripts, head metadata, navigation hooks, server
responses, and API handlers.
* Improved handling of redirects, pathname/search params, base paths,
and event callbacks for smoother app behavior.

* **Tests**
* Added coverage for URL resolution and dynamic route interpolation to
verify Next-style routing behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2026-06-25 16:52:34 +08:00