incyCMS — Changelog

Branch & Deployment Pipeline — 10 June 2026

  • feat/cloudflare-migrationmain — Consolidated the stable Cloudflare migration branch into main for normalized development.
  • Production deploys from main.github/workflows/cloudflare-deploy.yml now triggers on main only (removed feat/** branch deploys).
  • CI pipeline — GitHub Actions runs typecheck, build, Wrangler dry-run, remote D1 migrations, and Cloudflare deploy on each push to main.
  • TypeScript fix — Explicit #imports for useColorMode, useUserSession, and useCsrf in AppHeader.vue to avoid stale .nuxt type generation errors after clean.

Profile Menu & Session — 10 June 2026

  • Account dropdown — Profile avatar opens a UDropdownMenu with Pages, Users, Organization, Settings, and Log out (replacing the old hover-only logout and direct link to /admin).
  • Logout redirect — Signing out clears the session and returns to / (public home), not /admin/login.

Theme Editor & Site Settings — 9–10 June 2026

  • CmsThemeSwitcher — Popover in the header (when logged in on admin or live-edit routes) with named presets (Default, Ocean, Forest, Sunset, Royal, Monochrome) plus primary/neutral color grids.
  • Tenant-persisted theme — Theme choices saved via PATCH /api/tenant (themeJson in D1); useTheme() applies instantly with updateAppConfig().
  • Settings page (/admin/settings) — Toggles for show color mode button and sticky header; mock extra user slots for quota testing ($2/user UI).
  • Themed headingsCmsHeadingExtension adds cms-h1cms-h4 classes; CSS uses var(--ui-primary) so headings follow the active theme.

Admin Dashboard — 9–10 June 2026

  • Dedicated admin routes/admin (pages), /admin/organization, /admin/users, /admin/settings, plus /admin/login and first-run /admin/setup.
  • AdminNav — Icon-first nav with tooltips matching admin sections.
  • AdminPageRow — Inline name/slug editing, publish & show-in-nav switches, schedule picker, edit link, and delete.
  • AdminUserRow — User create/edit with profile photo upload, first/last name, and email.
  • AdminSchedulePicker — Publish/unpublish datetime scheduling per page.
  • AdminMockUserLimit — Adjust mock extra users above plan limit in Settings.
  • Organization pageorganizationName field on tenant with help text and mobile tooltip.
  • User quotas — Plan limits enforced via getEffectiveMaxUsers() (base + mock extras); quota banner on users page.
  • UX polishcursor-pointer on interactive elements; expand-on-hover action buttons (AdminExpandActionButton).

Live CMS, Auto-Save & Header Nav — 8 June 2026

  • Multi-page public site — Dynamic [...slug].vue routes; pages served from D1 via /api/pages/view/[slug] and /api/pages/public/[slug].
  • Live editing — Logged-in users edit in place on public pages (CmsLivePage, CmsEditor, CmsPage); admin routes use the same header without inline editing.
  • Auto-save — Debounced content saves (~1.2s) with header save status (Saving… / Saved / Save failed).
  • Header navigation management — Drag-to-reorder nav items, unpublish (×) sets draft + hides from nav, inline New Page, double-click nav item opens page palette.
  • CmsPagePalette — Popover for page-level settings; primary toolbar (undo/redo/add) wired via useCmsEditorShell + provide/inject (fixes empty toolbar from early useState initialization).
  • Logo controls in live edit — Replace/remove site logo from header hover overlay; persists to tenant theme.
  • Nav refreshrefreshNuxtData('cms-nav') after nav-affecting changes.

Page Management, Scheduling & Visibility — 8 June 2026

  • Pages API — Full CRUD at /api/pages, reorder at /api/pages/reorder, nav list at /api/pages/nav.
  • SchedulingpublishAt / unpublishAt fields with AdminSchedulePicker and shared pageVisibility utilities.
  • Nav visibilityshowInNav column and switch; unpublished pages can be hidden from header nav.
  • Sort order — Drag reorder in admin and header; persisted via sortOrder.
  • Admin pages list — Create, delete, drag-reorder, and inline row editing on /admin.

Auth, Users & Database — 7–8 June 2026

  • Authentication — Email/password login, logout, first-user setup (/admin/setup), and auth route middleware.
  • Users APIGET/POST /api/users, PUT/DELETE /api/users/[id] with profile fields (firstName, lastName, profileImageUrl).
  • Tenant APIGET/PATCH /api/tenant for theme, organization name, and quota metadata.
  • Drizzle + D1 schematenants, users, pages tables with 8 SQLite migrations (seed tenant, home page, schedule, sort order, show-in-nav, user profile, organization name).
  • CSRFnuxt-csurf integrated; bootstrap auth routes exempt where needed; type augmentation for Nuxt 4 route rules.
  • Tenant bootstraprequireTenant() ensures default tenant exists on first request.

Cloudflare / NuxtHub Platform — 7–8 June 2026

  • Full Cloudflare migration — Rebuilt CMS on NuxtHub with D1 (database), R2 (media blob), and KV (cache) bindings via wrangler.jsonc.
  • Local dev — File-backed libsql (.data/db/sqlite.db), FS blob, and FS-lite KV when not building for Cloudflare; avoids missing-binding errors in bun run dev.
  • Deploy workflow — Replaced Vercel deploy.yml with cloudflare-deploy.yml (Bun install, typecheck, build, D1 migrations apply, Wrangler deploy).
  • Manual bootstrap guidedocs/manual-bootstrap.md for per-customer D1/R2/domain onboarding.
  • Environment — Updated .env.example for Cloudflare (NUXT_HUB_CLOUDFLARE_DATABASE_ID, session password, tenant slug/name, etc.).

Note: The earlier 7 June “Pivot to Vercel” entry below was superseded by this Cloudflare/NuxtHub deployment path.


Uploads, Blocks & Media — 7–8 June 2026

  • Blob uploads/api/upload via NuxtHub blob binding; used for profile photos, logo, and editor image blocks.
  • Block registry — Editor blocks moved to app/components/blocks/ (image, logo, media) with preview variants for read-only rendering.
  • Media in preview — Plyr/HLS player works in preview mode (lazy-loaded client-side).
  • Live data — Public pages pull published content from D1 instead of hardcoded demo HTML.

Editor & Content Fixes — 7 June 2026

  • Upload wiring — Image upload block connected to blob storage API.
  • Text formatting — Expanded bubble toolbar formatting options (marks, blocks, turn-into).
  • Preview mode guard — Removed ability to add new text while in preview/read-only mode.
  • Meta & OG — Updated site meta and Open Graph tags.
  • Preview default retained — CMS still opens in preview; toggle switches to edit mode (see section below).

Preview vs Edit Mode - 7 June 2026

  • Edit toggle — Added isEditing ref and USwitch in AppHeader (#actions slot).
  • Preview as default — CMS opens in read-only preview; toggle switches to full editing.
  • Single editor instance — One UEditor with editor.setEditable(false) in preview (no separate v-html view).
  • Preview links — Links are clickable in preview with correct cursor; external links open in a new tab via handlePreviewClick on a wrapper div with @click.capture.

Text Alignment - 7 June 2026

  • TextAlign extension — Added @tiptap/extension-text-align for headings and paragraphs.
  • Bubble toolbar controls — Left, center, right, and justify buttons with active states via getBubbleToolbarItems(editor).
  • Justify on single lines — Added text-align-last: justify in main.css so justify is visible on one-line blocks.
  • Stable bubble position — Added getAnchoredVirtualElement() so the bubble toolbar stays horizontally centered over the content column when alignment changes (only moves vertically with selection).

Clear / Reset Formatting - 7 June 2026

  • Link-aware dialog — When clearing formatting on content that contains links, shows a modal: “Would you like to keep or remove links?” with Keep, Remove, and Cancel.
  • Immediate clear when no links — Skips the dialog if the selection has no <a> marks.
  • Drag handle integration — Replaced built-in kind: 'clearFormatting' in useEditorDragHandle.ts with custom onSelect wired to the same logic.
  • Bubble toolbar button — Added clear-formatting button (i-lucide-remove-formatting) to the bubble toolbar.
  • “Keep links” bug fix — Fixed broken chain() call caused by destructuring chain off the editor (lost this binding).
  • Selection-scoped clearing — New applyClearFormatting() clears formatting on the current selection only, including:
    • Inline marks (all, or all except link)
    • Text alignment (unsetTextAlign)
    • Preserved style / class attributes (resetAttributes)
    • Block type normalization (clearNodes)
  • Simplified dialog UI — Stripped the modal down to the question and action buttons only: removed the title heading, header close button (:close="false"), and footer slot; buttons (Cancel, Keep, Remove) live inline in the body.

Toolbar UX — Keyboard Shortcuts - 7 June 2026

  • Shortcut hints in tooltips — Added kbds to toolbar tooltips and dropdown items (undo/redo, marks, alignment, turn-into options, etc.).
  • OS-aware keys — Uses Nuxt UI’s useKbd tokens (meta, alt, shift) so Mac shows ⌘/⌥/⇧ and Windows/Linux show Ctrl/Alt/Shift.
  • Subtle styling — Updated app.config.ts and main.css to remove kbd boxes, use dimmed text, tighten spacing, and bump size slightly.
  • Removed divider dot — Removed the · between tooltip label and shortcuts while keeping equivalent spacing.

Tooling & Fixes - 7 June 2026

  • Biome CSS errors — Excluded **/*.css from Biome (Tailwind v4 @import / @theme syntax not supported by Biome’s CSS parser).
  • TypeScript — Fixed onClick handlers in useEditorToolbar.ts that implicitly returned boolean from TipTap’s .run() (wrapped in block bodies).

Brand Color Picker - 7 June 2026

  • New feature — Text color selector added to the bubble toolbar (appears after the AI group when text is selected).
  • Extensions — Installed @tiptap/extension-color and @tiptap/extension-text-style to enable setColor / unsetColor commands.
  • Quick swatches — First 5 brand colors shown directly in the toolbar for one-click application; active color highlighted with a ring.
  • No-color button — Slash-circle button strips color from the selection.
  • Manage popover — Palette icon opens an advanced panel (opens above the toolbar so it never covers the selected text) with:
    • All saved brand colors with per-swatch remove (×) on hover
    • Full UColorPicker that applies color live to the selection as you drag
    • Hex input field for manual entry
    • Bookmark button to save the current picker color to brand swatches
    • Reset to defaults and Done buttons
  • Persistence — Brand colors stored in localStorage (incy-cms:brand-colors); default palette is black, red, orange, blue, violet (up to 10 swatches).
  • Selection preservation — Selection is saved on palette-button click so colors are applied to exactly the right text even after the editor loses focus.
  • Frozen bubble position — When the picker is open, the bubble toolbar’s anchor rect is frozen so window-blur events, .focus() calls, and editor state changes cannot cause it to jump or reposition.
  • Pointer isolation.color-picker-open .ProseMirror has pointer-events: none while the popover is open, preventing the color picker’s window-level drag events from bleeding through and starting a ProseMirror text selection.
  • No spurious focus — Color commands omit .focus() while a saved selection exists, preventing the focus-event cycle that previously repositioned the bubble on every picker interaction.

Media Player — Plyr Integration - 7 June 2026

  • Plyr installed — Added plyr and hls.js packages for rich audio/video playback.
  • MediaBlock TipTap node — New custom node (MediaBlockExtension.ts + MediaBlockNode.vue) supporting:
    • YouTube & Vimeo embeds (auto-detected from URL)
    • Native <video> and <audio> with HLS adaptive streaming via lazy-loaded hls.js
    • Optional title and poster attributes
  • In-editor UI — URL input with media type toggle; Plyr player initialises after URL is confirmed; "×" button clears back to input.
  • Drag & drop — Node is atom: true, draggable: true so it can be repositioned via the drag handle.
  • Lazy Plyr loadingimport('plyr') is dynamic (inside onMounted) so the Plyr bundle is never loaded on the server or in preview mode.
  • HLS support — If the URL ends in .m3u8, hls.js is lazy-loaded and attached to the media element before Plyr initialises.
  • Default demo video — Initial page content includes the incyCMS YouTube demo video (https://www.youtube.com/watch?v=ab_C_NQGgQI&t=14s) as a media-block node below the hero image.
  • Plyr theme — Custom CSS variables in main.css wire Plyr's color, fonts, and controls to Nuxt UI design tokens; audio player uses muted background and border.
  • Vite pre-bundling — Added plyr and hls.js to nuxt.config.ts optimizeDeps.include to avoid dev-server cold-start delays.

Deployment & Infrastructure - 7 June 2026 (superseded — see Cloudflare / NuxtHub above)

  • Cloudflare deployment guidance — Documented NuxtHub, Workers, R2, and Wrangler setup.
  • R2 billing blocker — Cloudflare R2 could not be enabled due to unknown errors; recommended contacting Cloudflare support or switching providers.
  • Pivot to Vercel — Updated deployment path to use Vercel Blob instead of Cloudflare R2 to get it up and running. (Later reversed: production now deploys to Cloudflare via NuxtHub.)
  • GitHub Actions — Added/updated .github/workflows/deploy.yml for Vercel (vercel deploy --prod). (Replaced by cloudflare-deploy.yml.)
  • Environment variables — Updated .env.example for Vercel (BLOB_READ_WRITE_TOKEN, AI_GATEWAY_API_KEY, NUXT_PUBLIC_PARTYKIT_HOST, etc.). (Now Cloudflare-focused — see .env.example.)

Editor Content Model - 7 June 2026

  • HTML instead of Markdown — Switched default content and UEditor to content-type="html".
  • Logo block — Replaced plain-text H1 with an embedded incyCMS logo via:
    • StylePreserveExtension.ts — preserves style and class on headings/paragraphs/textStyle
    • LogoBlockExtension.ts + LogoBlockNode.vue — custom TipTap node rendering IncyCmsLogo
    • Initial content uses <div data-type="logo-block"></div>
  • Editor logo dark modeLogoBlockNode.vue now follows color mode via useColorMode() (white “incy” in dark mode, dark text in light mode), using the horizontal variant without the bordered box used in the header’s compact logo.

Files Touched (Summary) - 7–10 June 2026

Area Key paths
Platform nuxt.config.ts, wrangler.jsonc, .github/workflows/cloudflare-deploy.yml, server/db/schema.ts, server/db/migrations/sqlite/*, docs/manual-bootstrap.md
API server/api/pages/*, server/api/users/*, server/api/tenant.*, server/api/auth/*, server/api/upload.post.ts
Admin UI app/pages/admin/*, app/components/admin/*
Live CMS app/components/cms/*, app/composables/useCmsLivePage.ts, app/composables/useCmsHeader.ts, app/pages/[...slug].vue
Theme app/composables/useTheme.ts, app/composables/useThemes.ts, app/components/cms/CmsThemeSwitcher.vue, app/utils/uiThemes.ts
Header app/components/AppHeader.vue
Blocks app/components/blocks/*
Auth types shared/types/auth.d.ts, app/middleware/auth.ts

Files Touched (Summary) - 7 June 2026

File Changes
app/pages/index.vue HTML content, preview/edit mode, link handling, bubble anchoring, clear-formatting dialog & logic, simplified clear-formatting modal UI
app/composables/useEditorToolbar.ts Alignment buttons, clear formatting, keyboard shortcuts
app/composables/useEditorDragHandle.ts Custom reset formatting, keyboard shortcuts on turn-into items
app/components/AppHeader.vue #actions slot for edit toggle
app/components/editor/StylePreserveExtension.ts New — preserve style/class attributes
app/components/editor/LogoBlockExtension.ts New — logo TipTap node
app/components/editor/LogoBlockNode.vue New — logo node view; color-mode-aware dark/light rendering
app/components/editor/MediaBlockExtension.ts New — media TipTap node (YouTube, Vimeo, video, audio, HLS)
app/components/editor/MediaBlockNode.vue New — Plyr-powered media player node view with URL input UI
app/assets/css/main.css Preview cursors, justify, kbd shortcut styling, Plyr theme tokens
app/app.config.ts Subtle kbd variant override
biome.json Ignore CSS files
.env.example Vercel deployment vars
.github/workflows/deploy.yml Vercel deploy workflow
nuxt.config.ts TextAlign extension, Plyr + hls.js optimizeDeps