# Klottle changelog
All notable changes to Klottle. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versioned per [SemVer](https://semver.org/).
## [Unreleased]
### Added
- Google indexing prep: sitemap.xml served dynamically (lists public Klotts), JSON-LD structured data (WebSite + Organization + per-doc Article), canonical URLs, noindex for gated docs, GSC verification config slot. See docs/google-indexing-setup.md for the post-deploy steps.
- First-visit onboarding tour. Walks new users through the left panel (browse/TOC), the Settings/Share/Versions tabs, and the slash menu. Skip available on every step.
### Fixed
- Tooltips and popovers now stay within the viewport. Replaced hand-rolled positioning in the onboarding tour and other floating UI with @floating-ui/dom's shift + flip middleware.
- Critical: switching to another Klott while edits were pending could silently drop them. Saves now flush before slug change with a slug captured at the call site, the flush triggers on a `dirtyRef` flag (not just on an armed debounce timer) so saves that fired moments before the swap can't lose follow-up keystrokes, and `useDoc.save` warns + refuses to mutate state when the active slug has drifted from the explicit POST target. Added a `beforeunload` "Leave site?" prompt as a defense-in-depth backstop for unsaved changes, and an explicit Cmd+S "Saved" toast.
- "HTTP 502" error toast that surfaced on transient PHP-FPM hiccups. `useDoc.ts` now retries 502/503/504/408 once with a 600ms backoff before bubbling the error up, and on autosave the post-retry failure is silenced entirely (the next debounce will write through) instead of popping a sticky toast. On the initial load path the error is also swallowed when we already painted from cache.
- Toast border width is now uniform on all four sides. Replaced the inset 3px left accent with a plain 1px border on every edge; severity is conveyed via the existing WarningCircle bullet on the left.
- Data table now responds to the 6-dot global drag handle. Added `data-type="dataTable"` to the wrapper and registered it under `tiptap-extension-global-drag-handle`'s `customNodes` array so the handle activates when hovering a table row.
- Code block content now has 16px padding on the box edges. Both the plain `.ProseMirror pre` and the Shiki-flavored `.klottle-code-block` overlay / editable layers were reduced from 28px / 32px to a uniform 16px.
### Changed
- Block math equations now render full-width with a 12px-radius 1px outline and 24px inner padding. The equation stays horizontally centred inside the box.
- Doodle moved under the Media slash submenu (was Advanced). The Advanced submenu still hosts the gated executable-code block but auto-collapses when that flag is off.
- Symbol and Emoji slash commands merged into a single tabbed picker. The slash menu entry is now "Symbol & emoji"; the body has two tabs (Emoji + Symbol) and reuses the existing EmojiPicker / SymbolPalette components verbatim.
- Custom-mood emoji picker moved directly under the Mood grid (was buried below Typography). The picked emoji also styles `
` bullet markers via the new `--klott-bullet-emoji` CSS variable, so the same character drives both the typing-effect particle and the list bullets.
- Internal rename: `package.json` `name` is now `klott` (was `klottle`). CSS class names that are persisted in user-saved HTML (`klottle-title`, `klottle-math-block`, `klottle-themed-bullets`, etc.) were intentionally **not** renamed in this batch — renaming them would break existing documents whose stored HTML still references the old class names. A future patch will introduce a parser shim that accepts both prefixes before the CSS-class rename lands.
### Added
- Iframe (embed) resize handles. A bottom-right corner grip appears on hover and updates the wrapper's `pxWidth` + `height` attributes; minimum 200×120. Dimensions persist via the existing node attrs and survive copy/paste.
- Version-tab preview-on-tap. Tapping a version row paints that version's content into the editor in read-only mode and surfaces a fixed banner above the editor with Restore / Cancel. Restore commits the previewed content as the live doc; Cancel rolls back to the snapshotted working copy. Autosave is gated off while a preview is active so nothing reaches the server until the user opts in.
### Removed
- References features (Cite / Bibliography / Cross-reference / Figure) hidden behind a feature flag (`SHOW_REFERENCES` in `src/lib/blockCommands.tsx`). Slash submenu, recursive search results, and the Settings tab References accordion are all gated. Flip the flag to `true` to re-enable without code changes.
### Added
- Klott logo as favicon set (`favicon.svg`, multi-resolution `favicon.ico` at 16/32/48, `apple-touch-icon.png` 180×180, `icon-192.png`, `icon-512.png`) + new static OG image rebuilt around the wordmark and the tagline "Shareable docs that feel alive."
- Per-document OG images (GitHub-style) — title + author + logo rendered server-side via GD in `api/og.php` from the bundled Inter TTFs; crawlers fetching `?p=` get a doc-specific image via a `mod_rewrite` shim (`og-doc.php`) that patches `og:title`, `og:description`, `og:image`, `og:url`, and the `twitter:*` mirrors into the served `index.html`. PNG renders are cached under `api/lib/og-cache/` and invalidated by `content_.json` mtime.
- Archive section in the Klotts list is now collapsible (collapsed by default). Header pulses briefly when a Klott is moved to archive so you can see where it went.
- Google Analytics on klott.cc (production only). Loader is gated to the production hostname so staging.klott.cc and localhost traffic don't pollute the metrics.
- Notifications dropdown in the profile popover, with a bell icon and unread count badge. Nudges from collaborators now appear in-app (poll on app boot for the active account, and again whenever the popover opens) — not just by email.
### Changed
- DOCX export now renders math as native Office Math (OMML) equations instead of monospace TeX. TeX is converted to MathML via KaTeX and then to OMML via `mathml2omml`, spliced into the paragraph as raw XML so Word opens the doc with editable, properly-typeset equations. Falls back to the previous monospace-TeX rendering if conversion fails (HAG-19).
- Slash command menu reorganized. Heavy / recent block types (math, charts, references, advanced) now nest under categorized submenus so the top-level list stays scannable. Typing keeps recursive search across all items.
- Performance + accessibility improvements. Route-split heavy editor libs out of marketing/guide routes — OptionsPanel, IdentityPrompt, GlobalDoodle, ActiveEditorDialog, KeyboardShortcutsDialog now `next/dynamic` so they ship after first paint instead of in the initial DocPage bundle. Dropped legacy JS polyfills via `browserslist`. Fixed input labels (aria-label on the TipTap contenteditable root), color contrast (`#525252` → `#8a8a8a/#9a9a9a` for prompt + placeholder text, `#737373` → `#a3a3a3` for IdentityPrompt faint labels in dark mode, homepage faint paragraphs/eyebrows/footer), and tap-target sizes (empty-block-prompt-btn now ≥ 26×26). Desktop Lighthouse perf 83 → 90, a11y 90 → 100. (HAG-37, HAG-39)
- Nudge / poke emails rewritten with warmer, playful copy and plain-markdown styling that respects email-client light/dark mode. Four subject + body variants chosen at random per send, so back-to-back nudges don't read identical. HTML alt + plain text via multipart/alternative; no background colors or wrappers that break in dark-mode mail clients.
### Fixed
- Page no longer freezes when selecting text in the doc title. The touch-only handle-reposition `selectionUpdate` listener was firing a synthetic `mousemove` mid-drag-selection on touch-capable laptops, which re-entered ProseMirror's DOM observer and looped the selection pipeline. Now skipped while a mouse drag is in progress and while the selection is a range (only fires for caret moves).
- OTP "first code fails, resend works" sign-in bug. Root cause: `verifyCode` was being invoked from inside a `setDigits(prev => …)` updater, which under React-strict-mode replay (and under certain change-then-blur sequencing) could fire the verify POST twice. The first POST succeeded and burned the server-side code record; the second POST read no record and came back as `expired`, clobbering the success state. Fix: hoist the verify call out of the state updater (the updater is now a pure transform), de-dupe in-flight verifies for the same code via a ref, reset the `autoSubmitFiredRef` + in-flight guard on every resend, and harden the server-side `klott_auth_write_json` with `LOCK_EX` + explicit `fflush` so the OTP record is durable before the response returns.
### Changed
- Renamed product from Klottle to Klott in all user-visible copy.
- Profile emoji picker: tapping the avatar circle now opens the picker directly. The separate "Change emoji" button is gone, and the per-emoji size slider has been removed — avatars render at a fixed size.
- Removed the "About Klott" link from the bottom of the right panel.
- Migrated to klott.cc domain. Production at klott.cc, staging at staging.klott.cc. Default basePath is now empty (webroot) instead of `/apps/klottle`. OTP and notification emails now sent from hello@klott.cc (was noreply@hagerling.se). PHP endpoints derive scheme/host from the request so the same code runs on both hosts. CORS allow-list updated to klott.cc + staging.klott.cc. Share URLs, release manifest URL, sitemap, robots, and og metadata all point at klott.cc.
### Added
- Multi-account sign-in. Add multiple verified accounts and switch between them in the profile dropdown. Existing single-account users migrate seamlessly.
- Custom typing effect: in Custom mood, pick any emoji or upload a custom image to use as the typing-effect particle.
- Inline invite-collaborator input with action button inside (matches Share Link pattern).
- Toggle to invite collaborators without a password.
- Email OTP verification for sign-in via Loopia SMTP + PHPMailer. New users type their email, receive a 6-digit code, and verify on this device. The email also contains a tappable verify link that opens Klott with the code already entered and auto-submits. Verified state persists in localStorage and never expires; existing `klottle.userEmail` identities are grandfathered as verified — no forced re-verification (HAG-103). After deploy, place `public/api/smtp-config.php` (copy from `smtp-config.example.php`) and create `public/api/data/` (0700) on the server.
- Getting started guide at `/guide` — in-app docs covering the menu, slash commands, markdown shortcuts, math, code, diagrams, citations, tags, sharing, exports, moods, self-host, and a keyboard cheat sheet. Linked from the Settings tab and the identity prompt; greyscale palette, scroll-fade sections, smooth-scroll TOC on desktop (HAG-80).
- Rename a tag across every Klott via a pencil affordance in the Tags popover (HAG-33).
- Browse search can now look inside each Klott's content; toggle via the small text icon in the search input. Matching rows show a snippet preview with the match highlighted (HAG-38).
- "Experimental features" subsection in Settings exposes the Executable Python (Pyodide) flag without needing DevTools. Toggling fires `klottle:feature-flag-change` so the slash menu refreshes immediately (HAG-30).
- Mermaid + Vega-Lite spec toolbars now expose "Copy SVG" and "Download PNG" buttons. Copy writes the rendered SVG markup to the clipboard; download rasterises it to a 2x PNG with a themed background (HAG-36).
- Editable bibliography heading — the References block now renders its title as an inline-editable `` so users can rename it per-doc (e.g. "References" → "Bibliography", "Sources"). Persisted on the node as `data-heading` and survives static export (HAG-20).
- Drag-and-drop + file picker for `.csl` files in the References panel — drop one or more CSL XML files (or pick via the file dialog) to install them as custom styles alongside the existing paste textarea (HAG-31).
- Multi-language CSL locale support — pick the bibliography language from English, Swedish, German, French, Spanish, or Italian. Locale XML is fetched on demand from the CSL Project's CDN and cached in localStorage; the active locale is persisted per-doc as `citationLocale` (HAG-32).
### Changed
- `/homepage` is now a true server component — dropped `'use client'`, removed framer-motion in favour of scoped CSS keyframes, and the marketing route no longer downloads the animation runtime. Editor stack stays lazy at `/` via the existing `dynamic(..., { ssr: false })` import, so marketing visitors don't pay for TipTap/ProseMirror (HAG-23).
- Pane handles sit flush (no rounded corners) on mobile (HAG-87).
- Side panels cover ~2/3 of mobile viewport instead of nearly full width (HAG-83).
- EdgeStrip uses `role="button"` (was `role="separator"`) — the strip's primary affordance is "open/close panel", which a button conveys correctly to assistive tech. `aria-pressed` and `aria-label` retained (HAG-28).
- EquationEditor adapts to viewports below 640px — palette grid drops from 8 to 4 columns, footer toggles stack vertically, and the modal allows vertical scroll so the footer stays reachable on short screens (HAG-29).
- Lazy-load EmojiPicker and ImageView (HAG-24)
- 4-item grids in the right pane (Font preset, Font size, Line height, Klott width, Privacy type) wrap 2×2 on narrow viewports instead of 3+1 — cleaner two-row layout when the panel runs out of room for a single row (HAG-85).
### Removed
- Duplicate Contents tab from the right pane. The headings outline is already available in the left pane's Content tab, reachable on every viewport via the left edge strip — the mobile-only right-pane copy was redundant (HAG-84).
### Fixed
- Title editing no longer freezes the page (HAG-88). Cmd/Ctrl+A inside the title now scopes the selection to the title's inline content instead of the whole document, so typing replaces the title text as expected (previously TitleLock rejected every keystroke because the doc-spanning replace would have stripped the title node).
- Drag handle follows the cursor to new rows on mobile (HAG-86).
- Right pane content wraps and scales on mobile (HAG-82).
- Refreshing on a freshly created blank Klott no longer mints a new one (HAG-81).
- MermaidView export handlers (Copy SVG / Download PNG) referenced `sanitizedSvg` before its declaration after the HAG-36 changes landed; hoisted the `useMemo` above the export callbacks so the file type-checks again (HAG-36).
- PDF export now hydrates KaTeX, Mermaid, Vega, Shiki, doodle, and citation NodeViews with their rendered output instead of empty shells. OptionsPanel forwards the live `.ProseMirror` element to `buildPrintHtml` via `PdfExportDialog`'s `editorDom` prop (HAG-16).
- Clicking an inline citation chip scrolls the matching `.csl-entry` in the bibliography into view and briefly highlights it (~1s background fade) (HAG-17).
- BibTeX import preserves brace-protected casing. `{NASA}` in titles is now emitted as `NASA` in the CSL-JSON output so citeproc respects case-protection across all styles (HAG-18).
- Tooltips now open on touch via a 500ms long-press (no significant movement). Touchend/cancel/large movement dismisses. Hover/focus behaviour on pointer devices is unchanged (HAG-27).
- ImageView tracks every captured pointer ID in a ref and releases them all on unmount, so a node replaced mid-pan or mid-crop no longer leaks pointer capture to a detached element (HAG-35).
## [1.0.0] - 2026-06-01
### Added
- Initial public release.
- LaTeX math (KaTeX) — inline + block, equation editor with palette and autocomplete.
- Code blocks with Shiki syntax highlighting (12 languages).
- Mermaid diagrams + Vega-Lite charts.
- Citations via citeproc-js (APA 7, MLA, Chicago, IEEE, Vancouver) + BibTeX import.
- Smart symbols + units (67 input rules + 5-tab palette).
- Data tables with formulas (SUM, AVG, MIN, MAX, COUNT, arithmetic).
- Exports: PDF, LaTeX (.tex + .bib), Markdown, DOCX.
- Pyodide executable Python (behind feature flag).
- Inline doodles + global doodle overlay with collab sync.
- Shared sections (Insert Klott).
- Tags replacing folders.
- Live presence indicators.
### Changed
- Mood system bundles font + size + line-height + page-width + emoji + animation.