Skip to content

Quilltap 4.8.2

Choose a tag to compare

@csebold csebold released this 13 Aug 20:31
· 2530 commits to main since this release

Quilltap 4.8.2 Release Notes

A patch release with ideas above its station.

Two rooms of the house were attended to. The first is the one you sit in — the composer, that unglamorous rectangle where the actual work happens — which has spent its entire life offering you the hundred-odd characters your keyboard could reach and a polite silence regarding the rest of Unicode. The second is the wardrobe, which turns out to have been an admirable filing system with no reading room attached.

The Typewriter Acquires Several Thousand Keys

Type a colon and at least two letters, and Quilltap will offer you emoji by name. Type a backslash and a symbol name, and it will offer you the rest: \to for →, \phi for φ, \Phi for Φ, or \right arrow if you cannot recall what the thing is called and would prefer to describe it. If you know the code point, \u2192 and \u{1D538} both work and resolve arithmetically, without consulting any table — which means every character in Unicode remains reachable, including the several thousand that curation quietly declined to list.

A menu appears; Enter or a click inserts. Complete the name and follow it with a space — \to — and the character arrives immediately, keeping the space, with no menu at all. Two buttons in the formatting toolbar open browsable pickers, one for emoji and one labeled Ω for everything else, each with a search field and a row of recents.

What is inserted is a plain Unicode character. Not a shortcode, not an image, not a bespoke node with opinions about serialization. Exports, Markdown round-trips, search, and the model all see ordinary text, because it is ordinary text. A single Cmd/Ctrl+Z removes the character and restores the literal :smi you typed.

The interesting engineering here is the restraint. The menu does not open after http://, nor inside 10:30, nor in a Windows path, nor on :). It stays shut inside fenced code blocks and inline code. Nothing fires inside a math span, so $$\phi$$ remains LaTeX and is not helpfully rewritten into a Greek letter by an assistant who has misjudged the moment. A $ followed by a digit or a space is read as currency, so a sentence containing both $5 and \to behaves. Markdown escapes are untouched, on the reasoning that an escape is a backslash plus punctuation while a symbol name must begin with a letter — so \*, \_, and \[ can never open a menu.

The datasets — 1,914 emoji, 3,282 characters across 26 Unicode blocks, about 93 KB gzipped between them — are served from public/ and fetched only on the first trigger or the first picker open. An instance that never uses either feature never downloads either file. The space bar never triggers the fetch. If a fetch fails, typing is unaffected and the menu simply declines to appear.

Both live behind toggles on the Chat settings tab, under Composer, and both default on. The toolbar buttons are not gated by the toggles; the flags govern the automatic triggers only.

Smart Typography, Split By How Confident The Rule Is

This one is two features that look like one, separated on a principle worth stating plainly: a rule you are certain about may change your text, and a rule you are merely confident about may only change how your text is displayed.

Quotation marks fall in the second category. Quilltap can now render curly quotes while storing and transmitting exactly the straight ones you typed — your text is unchanged, the model's input is unchanged, and only the display is prettier. Code, mathematics, and link addresses are skipped structurally rather than by pattern-matching, because they are separate node types in the markdown tree and not merely text that happens to look technical. A roleplay template that has claimed a quote character as one of its own delimiters suppresses the curling for that chat, as does dialogue-detection configuration naming a straight quote without its curly counterpart. It applies everywhere the message renderer runs — the Salon, streaming replies, thinking blocks, the help chat, the Brahma console — and it is off by default.

Dashes fall in the first. Typing -- gives an en dash, --- an em dash, ... an ellipsis, in the Salon composer and in Document Mode, and these write real characters into your text. On by default. One Backspace or one Cmd/Ctrl+Z reverts any substitution. Nothing fires in code blocks, inline code, source-mode editors, or during IME composition, and pasted text is left entirely alone.

Dashes are deliberately not applied at render time and will not be, so that --verbose written in ordinary prose survives to be read as --verbose.

The Wardrobe That Was Written To But Never Read

Since groups gained document stores, the wardrobe transfer dialog has offered every group as a destination for a garment. Moving one there worked. The file landed in the group store's Wardrobe/ folder, precisely as designed, and sat there in the dark.

Nothing ever read it back.

The wearable pool took the character's own vault, the chat's project stores, and Quilltap General — and not groups. So a garment moved into a group did not appear in the wardrobe dialog, was not listed by wardrobe_list, could not be resolved by wardrobe_read, wardrobe_wear, wardrobe_update, or wardrobe_archive, was never equipped as a default at chat start, and could not be moved back out, because the transfer dialog's source lookup did not scan groups either. The item was not lost. It was filed with perfect fidelity into a room with no door.

The group tier is now read everywhere the other shared tiers are read. A character's wearable pool is the union of their own vault, the Wardrobe/ folder of every store belonging to every group they belong to, the chat project's stores, and Quilltap General — with precedence matching the document-store tiers: character > group > project > general. A group's livery shadows a project's copy of the same item, and a character's personal copy shadows both, which is what makes the isDefault: false personal copy work as a way of declining a shared default.

Group wardrobes follow the character rather than the chat: a character sees their own groups' wardrobes and never a co-participant's, the same rule Knowledge already used. The single exception is the chat outfit-summary endpoint, which spans the whole cast and therefore reads the union of the participants' groups, so that every equipped item resolves to a title.

Underneath, the three separate copies of "read a mount's Wardrobe/ folder" collapsed into one module, and the tier options that used to travel as a bare array of project ids are now a single object resolved by a single helper — so that a call site can no longer thread one tier and silently drop another, which is exactly how this went unnoticed for as long as it did.

Bundles Now Come Apart In Your Hands

Putting on a composite outfit used to store the bundle's own id in every slot it covered, expanding to individual garments only at read time. The visible result was a single "Man in Black · bundle" card above four slot rows all reading Empty — the outfit was on, and nothing on the screen would tell you which shirt.

Wearing a bundle now dissolves it in the same gesture. Components are stored in the slots their own types cover, the bundle's id is never written to equipped state, and you can see each piece and remove one without disturbing the others. Expansion is complete, so a bundle nested inside a bundle comes apart too. This happens wherever an outfit is put on — the Live tab, the Outfit Builder, the chat-start composer, a character's default outfit, the cheap model's chat-start pick, and all three modes of wardrobe_wear.

The replace flag now clears the union of the slots a bundle designates and the slots its pieces actually land in, so a replacing outfit that includes boots removes the shoes already worn rather than layering over them. A "Naked" composite that designates all four slots still strips the character, as documented and as one would hope. Bundles whose components cannot be resolved — a shared outfit whose pieces live in a store the caller cannot read — are stored whole exactly as before. Outfits equipped before this change keep their composite id and their bundle card, Break apart remains available for them, and no migration is required.

Four Smaller Corrections

A wardrobe change made before the outfit finished loading was thrown away (bug 61). The dialog gets its item list from one request and the current outfit from a chain of three. In the window between, the Wear and Layer buttons were live with nothing to apply them to; a click was staged against an empty outfit, overwritten when the real one arrived, and the Done flush could not distinguish "this character has no recorded starting outfit" from "nothing changed" — so it sent no request, reported success, and closed. The change was gone, silently, and indistinguishable from a save that worked. Clicks in that window are now recorded and re-applied to the real outfit the moment it lands. If the outfit never loads at all, Done says so and asks before discarding the change.

Curly-quoted dialogue was never highlighted without a roleplay template (bug 62). The built-in fallback patterns claimed in a comment to handle straight and curly quotes alike. They did not: the character sets listed the straight quote twice, and the duplicate accomplished nothing. Since most model output is curly-quoted — as is anything pasted from Word, Pages, or Scrivener, or typed on a Mac with smart quotes on — this was the common case failing while the comment asserted otherwise. Both now match. Single quotes remain deliberately excluded, an apostrophe being a single quote far more often than a quotation mark is. The same mistake sat in the @quilltap/plugin-types documentation, where an author copying the DialogueDetection examples received a detector that never matched curly dialogue; fixed in 2.5.6, now spelled with explicit escapes so the characters cannot be misread.

Text replacements fired inside code blocks (bug 63). With any replacement rule defined, typing the trigger inside a fenced block or an inline code run rewrote it — a rule like fn → function silently editing code as you typed it. The plugin checked only that the cursor sat at the end of a text node, and CodeHighlightNode extends TextNode, so fenced tokens sailed through; nothing checked the format bit for inline runs. Both surfaces now bail, through a single helper shared with the new insertion plugins so the typing aids cannot drift apart again. The plugin previously had no tests. It has them now.

Chat export navigated the window instead of downloading, on Electron. The Salon sidebar set window.location.href to the export route — a download in a browser, and in the desktop shell a navigation of the app window to an API endpoint. It now uses the same helper the Markdown export directly beside it already used.

One Bug Filed And Not Fixed

Honesty compels a note about bug 64, which is documented in this release and repaired in none of it.

On a genuinely fresh instance, completing encryption-key setup closes the main SQLite connection out-of-band before converting the database files, while the backend and manager singletons keep the dead handle cached. Every repository call afterwards fails with "The database connection is not open" until the process is restarted. The conversion itself completes correctly and no data is lost — but a first-run user meets a wall of errors at the precise moment they have the least reason to trust the software. Restarting Quilltap clears it entirely.

The bug file documents the root cause and specifies the fix. It is not in 4.8.2.


What Changed

  • feat (Salon): Emoji insertion by name. Type : plus two or more letters to search and insert; a completed shortcode like :smile: inserts without the menu. A toolbar button opens a searchable picker with a browsable grid and recents. Works in the Salon composer and Document Mode. Inserts plain Unicode characters — no shortcodes, images, or custom nodes. The menu stays shut after http://, inside 10:30, in Windows paths, on :), and inside fenced or inline code. The 1,914-entry dataset (~53 KB gzipped) is served from public/ and fetched only on first use. Toggle under Chat → Composer ("Emoji shortcuts"), default on. Adds composerEmoji to chat_settings (migration add-composer-emoji-field-v1).
  • feat (Salon): Unicode insertion by LaTeX name (\to), description (\right arrow), or code point (\u2192, \u+2192, \u{1D538}). Aliases are case-sensitive, so \phi is φ and \Phi is Φ. A toolbar button labeled Ω opens a searchable picker. Nothing fires inside a math span, so $$\phi$$, $\phi$, \(\phi\), and \[\phi\] stay LaTeX; $ before a digit or space reads as currency. Markdown escapes are untouched, since a symbol name must start with a letter. 3,282 characters across 26 Unicode blocks (~40 KB gzipped), fetched on first \ query or picker open; the space bar never triggers the fetch. 165 characters already in the emoji index are excluded so the two triggers never offer the same character, and code-point entry resolves arithmetically so every character in Unicode stays reachable regardless. Toggle under Chat → Composer ("Symbol shortcuts"), default on. Adds composerUnicode to chat_settings (migration add-composer-unicode-field-v1).
  • feat (Calliope): Smart typography, in two parts. Quotes render curly while storing and sending exactly what you typed — text and model input are unchanged. Code, math, and link addresses are skipped structurally as distinct node types. A roleplay template claiming a quote character as a delimiter, or dialogue-detection config naming a straight quote without its curly counterpart, suppresses curling for that chat. Applies in the Salon, streaming replies, thinking blocks, the help chat, and the Brahma console; off by default. Dashes substitute at type time — -- → en dash, --- → em dash, ... → ellipsis — writing real characters, on by default, reverted by one Backspace or one undo, and inert in code, source-mode editors, IME composition, and pasted text. Dashes are deliberately never applied at render time, so --verbose in prose survives. Toggles on the Chat settings tab under Smart Typography. Adds smartTypographySettings to chat_settings.
  • fix (Aurora): Wardrobe items moved into a group are now readable. The group tier was a valid transfer destination but was read by nothing — such an item vanished from the wardrobe dialog, wardrobe_list, wardrobe_read/wear/update/archive, chat-start default selection, and the transfer dialog's own source lookup. A character's wearable pool is now the union of their vault, every group they belong to, the chat project's stores, and Quilltap General, with precedence character > group > project > general. Group stores follow the character, not the chat, so a character never sees a co-participant's group wardrobe; the chat outfit-summary endpoint is the one exception and reads the participants' union so every equipped item resolves to a title. Group items are wear-only, like project and General items. The three copies of "read a mount's Wardrobe/ folder" collapsed into lib/mount-index/shared-wardrobe.ts, and tier options are now one SharedWardrobeTiers object so a call site cannot thread one tier and drop another.
  • change (Aurora): Wearing a composite outfit now dissolves it into its component garments in the same gesture, storing each piece in the slot its own type covers and never writing the bundle's id to equipped state. Expansion is recursive, so a nested bundle comes apart too. Applies at every wear site — Live tab, Outfit Builder, chat-start composer, default outfits, the cheap model's pick, and wardrobe_wear's wear, replace, and add_to_slot modes. replace now clears the union of designated and actually-occupied slots, so a replacing outfit with boots removes worn shoes instead of layering. Unresolvable bundles are stored whole as before; previously-equipped outfits keep their composite id and Break apart button, and no migration is needed.
  • fix (Aurora): A wardrobe click made before the current outfit finished loading is no longer discarded. The item list and the equipped outfit load from separate requests, and in the window between them a Wear or Layer click was staged against an empty outfit, overwritten on arrival, and then flushed as a no-op that reported success and closed the dialog. Such clicks are now re-applied to the real outfit when it arrives, landing on top of what was already worn rather than replacing it. If the outfit never loads, Done warns and asks before discarding. (bug 61)
  • fix (Calliope): Curly-quoted dialogue is now highlighted in chats with no roleplay template, or whose template supplies no rendering patterns. The built-in fallback patterns listed the straight quote twice in character sets documented as covering "straight and curly", so the curly case — most model output, and anything from Word, Pages, Scrivener, or a Mac with smart quotes — was never matched, on both inline highlighting and whole-paragraph dialogue styling, mid-stream and settled. Single quotes remain excluded by design. The same error in the @quilltap/plugin-types DialogueDetection examples gave plugin authors a detector that never matched curly dialogue; corrected across six sites in 2.5.6 with explicit escapes. Documentation only there — no type or behavior change, and existing plugins are unaffected. (bug 62)
  • fix (Salon): Text replacements no longer fire inside fenced code blocks or inline code. The plugin checked only that the cursor sat at the end of a text node, and CodeHighlightNode extends TextNode, so fenced tokens passed; nothing checked the code format bit for inline runs. A rule like fn → function therefore rewrote code as it was typed. Both surfaces bail via a single $isInCodeContext helper shared with the new insertion plugins. Replacements in ordinary prose are unchanged, and the plugin now has tests. (bug 63)
  • fix (Salon): Chat export from the sidebar now downloads on Electron instead of navigating the app window to the API route, using the same triggerUrlDownload helper the adjacent Markdown export already used.
  • fix (Aurora): The "Aurora's Core whisper" card in the character editor now stacks its label and description above the dropdown instead of placing them in a side-by-side flex column, where the full-width dropdown squeezed the label into a sliver a few characters wide. The two checkbox cards above it are unchanged.
  • internal (Foundry): lib/emoji/ became lib/char-insert/, with emoji and Unicode as two dataset profiles over one implementation rather than two near-duplicate features. Search gained a NameAllWords bucket ranked below every existing bucket, so it can only add results that previously did not appear at all — which is why the emoji corpus passed through the refactor unedited, and is the regression proof. The dataset files did not change; public/emoji/emoji-index.v1.json is byte-identical.
  • internal (Foundry): The smart-typography rule engine (lib/smart-typography/) imports nothing outside the standard library, enforced by an ESLint rule, and is pinned by a fixture corpus so the v5 port can be verified against it rather than re-derived.
  • internal (Foundry): eslint-config-next 16.3 flags window.location.href for internal navigation. Two sites moved to router.push() — the legacy-shell handoff opening a standalone document in the workspace, and canceling the new-chat modal opened from a query parameter. Six keep the full page load deliberately and now carry comments explaining why: the three setup-wizard steps and the auto-lock redirect need client state rebuilt from scratch (for auto-lock that is the entire point, a client transition leaving decrypted data in memory), and the message-navigation helper is a plain module with no router in scope whose sessionStorage handoff depends on mount timing.
  • chore (Foundry): npm dependencies refreshed across the app, packages, and all 14 bundled plugins. Next.js 16.2 → 16.3, openai 7.2 → 7.4, katex 0.18.1 → 0.18.4, mammoth 1.12.0 → 1.12.1, ws 8.21.1 → 8.21.3, plus dev tooling. create-quilltap-theme 2.0.18, @quilltap/plugin-utils 2.2.20, @quilltap/theme-storybook 1.0.59. All 14 plugins patch-bumped and rebuilt against @quilltap/plugin-types 2.5.6 and @quilltap/plugin-utils 2.2.20. Every plugin's manifest.json version now matches its package.json version — nine had drifted by a patch or two from earlier bumps touching only one file, invisible because the plugin build does not sync them.
  • docs (Foundry): Bug 64 filed and not fixed. On a fresh instance, completing encryption-key setup closes the main SQLite connection out-of-band before converting the database files while the backend and manager singletons keep the dead handle cached, so every repository call fails with "The database connection is not open" until restart. No data is lost and the conversion completes correctly. docs/developer/bugs/bug-64-setup-stale-db-handle.md documents the root cause and specifies the fix: tear down through closeDatabase(), convert all three databases (the mount-index DB is currently skipped until the next restart), reinitialize before returning success, and give the auto-lock path the same treatment. Also noted: the LLM-logs client stays open on the pre-conversion file during setup, losing log writes.

Installation

Desktop App

Download from the quilltap-shell releases page:

macOS:

  1. Download the .dmg file and open it
  2. Drag Quilltap to your Applications folder
  3. Launch Quilltap from Applications

Windows:

  1. Download and run the .exe installer
  2. If SmartScreen warns about an unknown publisher, click "More info" → "Run anyway"
  3. Launch Quilltap from the Start Menu or desktop shortcut

Linux:

  1. Download the .AppImage file, make it executable (chmod +x), and run it
  2. Or install the .deb package: sudo dpkg -i quilltap_*.deb

Node.js (any platform)

npx quilltap

Or install globally:

npm install -g quilltap
quilltap

Open http://localhost:3000 in your browser. Requires Node.js 24+. First run downloads ~150–250 MB and caches locally.

Docker

docker pull foundry9/quilltap:4.8.2

Or use the startup scripts:

# Linux / macOS
curl -fsSL https://raw.githubusercontent.com/foundry-9/quilltap-server/refs/heads/main/scripts/start-quilltap.sh | bash

# Windows (PowerShell)
irm https://raw.githubusercontent.com/foundry-9/quilltap-server/refs/heads/main/scripts/start-quilltap.ps1 | iex

A patch release which spent most of its energy on the two places where intention and record had quietly parted company: the keyboard, which could not type what you meant, and the wardrobe, which recorded what you meant and then declined to mention it again.

Both now answer when spoken to. The composer will find you an em dash, a Greek letter, or a small drawing of a teacup, and will do so without disturbing your code, your mathematics, or your --verbose. The wardrobe will admit to owning the coat you put in it.

Installation

Desktop App (recommended)

The Quilltap desktop app (Electron) is available from
quilltap-shell 4.1.12.
Download the release for your platform (macOS, Windows, or Linux).

The quilltap-linux-arm64.tar.gz and quilltap-linux-amd64.tar.gz rootfs
tarballs attached to this release are used by the shell's Lima (macOS) and WSL2 (Windows) VM modes.

Node.js (any platform)

npm install -g quilltap
quilltap

On first run, the CLI downloads the application files (~150-250 MB)
and caches them locally. Subsequent launches start instantly.

Docker

docker pull foundry9/quilltap:4.8.2

See the README for setup instructions.