incyCMS — Changelog
Branch & Deployment Pipeline — 10 June 2026
feat/cloudflare-migration→main— Consolidated the stable Cloudflare migration branch intomainfor normalized development.- Production deploys from
main—.github/workflows/cloudflare-deploy.ymlnow triggers onmainonly (removedfeat/**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
#importsforuseColorMode,useUserSession, anduseCsrfinAppHeader.vueto avoid stale.nuxttype generation errors afterclean.
Profile Menu & Session — 10 June 2026
- Account dropdown — Profile avatar opens a
UDropdownMenuwith 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(themeJsonin D1);useTheme()applies instantly withupdateAppConfig(). - Settings page (
/admin/settings) — Toggles for show color mode button and sticky header; mock extra user slots for quota testing ($2/user UI). - Themed headings —
CmsHeadingExtensionaddscms-h1…cms-h4classes; CSS usesvar(--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/loginand 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 page —
organizationNamefield on tenant with help text and mobile tooltip. - User quotas — Plan limits enforced via
getEffectiveMaxUsers()(base + mock extras); quota banner on users page. - UX polish —
cursor-pointeron interactive elements; expand-on-hover action buttons (AdminExpandActionButton).
Live CMS, Auto-Save & Header Nav — 8 June 2026
- Multi-page public site — Dynamic
[...slug].vueroutes; 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 viauseCmsEditorShell+provide/inject(fixes empty toolbar from earlyuseStateinitialization).- Logo controls in live edit — Replace/remove site logo from header hover overlay; persists to tenant theme.
- Nav refresh —
refreshNuxtData('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. - Scheduling —
publishAt/unpublishAtfields withAdminSchedulePickerand sharedpageVisibilityutilities. - Nav visibility —
showInNavcolumn 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), andauthroute middleware. - Users API —
GET/POST /api/users,PUT/DELETE /api/users/[id]with profile fields (firstName,lastName,profileImageUrl). - Tenant API —
GET/PATCH /api/tenantfor theme, organization name, and quota metadata. - Drizzle + D1 schema —
tenants,users,pagestables with 8 SQLite migrations (seed tenant, home page, schedule, sort order, show-in-nav, user profile, organization name). - CSRF —
nuxt-csurfintegrated; bootstrap auth routes exempt where needed; type augmentation for Nuxt 4 route rules. - Tenant bootstrap —
requireTenant()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 inbun run dev. - Deploy workflow — Replaced Vercel
deploy.ymlwithcloudflare-deploy.yml(Bun install, typecheck, build, D1 migrations apply, Wrangler deploy). - Manual bootstrap guide —
docs/manual-bootstrap.mdfor per-customer D1/R2/domain onboarding. - Environment — Updated
.env.examplefor 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/uploadvia 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
isEditingref andUSwitchinAppHeader(#actionsslot). - Preview as default — CMS opens in read-only preview; toggle switches to full editing.
- Single editor instance — One
UEditorwitheditor.setEditable(false)in preview (no separatev-htmlview). - Preview links — Links are clickable in preview with correct cursor; external links open in a new tab via
handlePreviewClickon a wrapperdivwith@click.capture.
Text Alignment - 7 June 2026
- TextAlign extension — Added
@tiptap/extension-text-alignfor 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: justifyinmain.cssso 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'inuseEditorDragHandle.tswith customonSelectwired 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 destructuringchainoff the editor (lostthisbinding). - 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/classattributes (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
kbdsto toolbar tooltips and dropdown items (undo/redo, marks, alignment, turn-into options, etc.). - OS-aware keys — Uses Nuxt UI’s
useKbdtokens (meta,alt,shift) so Mac shows ⌘/⌥/⇧ and Windows/Linux show Ctrl/Alt/Shift. - Subtle styling — Updated
app.config.tsandmain.cssto 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
**/*.cssfrom Biome (Tailwind v4@import/@themesyntax not supported by Biome’s CSS parser). - TypeScript — Fixed
onClickhandlers inuseEditorToolbar.tsthat implicitly returnedbooleanfrom 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-colorand@tiptap/extension-text-styleto enablesetColor/unsetColorcommands. - 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
UColorPickerthat 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 .ProseMirrorhaspointer-events: nonewhile the popover is open, preventing the color picker’swindow-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
plyrandhls.jspackages 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-loadedhls.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: trueso it can be repositioned via the drag handle. - Lazy Plyr loading —
import('plyr')is dynamic (insideonMounted) so the Plyr bundle is never loaded on the server or in preview mode. - HLS support — If the URL ends in
.m3u8,hls.jsis 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 amedia-blocknode below the hero image. - Plyr theme — Custom CSS variables in
main.csswire Plyr's color, fonts, and controls to Nuxt UI design tokens; audio player uses muted background and border. - Vite pre-bundling — Added
plyrandhls.jstonuxt.config.tsoptimizeDeps.includeto 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.ymlfor Vercel (vercel deploy --prod). (Replaced bycloudflare-deploy.yml.) - Environment variables — Updated
.env.examplefor 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
UEditortocontent-type="html". - Logo block — Replaced plain-text H1 with an embedded incyCMS logo via:
StylePreserveExtension.ts— preservesstyleandclasson headings/paragraphs/textStyleLogoBlockExtension.ts+LogoBlockNode.vue— custom TipTap node renderingIncyCmsLogo- Initial content uses
<div data-type="logo-block"></div>
- Editor logo dark mode —
LogoBlockNode.vuenow follows color mode viauseColorMode()(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 |