* Themes: Glassy Neon and seasonal themes (Christmas, Halloween, Thanksgiving)
- Add themes/glassy-neon.theme.css: Tron-inspired cyberpunk glassmorphism with perspective neon floor grid, laser glow borders, and rotating identity disc.
- Add themes/christmas.theme.css: Festive evergreen pine with twinkling string lights, falling snow, candy-cane borders, and glowing Christmas tree (@section Seasonal).
- Add themes/halloween.theme.css: Eerie gothic midnight with drifting ghostly mist, flying witch moon, carved jack-o'-lantern, blinking black cat, and dripping toxic slime borders (@section Seasonal).
- Add themes/thanksgiving.theme.css: Warm autumn harvest with tumbling fall leaves, waddling turkey watermark, harvest garland, and striped borders (@section Seasonal).
- Parse optional @section and @category metadata tags in src/themeMetadata.js.
- Organize theme pickers and admin settings so seasonal themes appear in a dedicated 'Seasonal' group.
- Support prefers-reduced-motion in all four new themes.
- Document bundled additions in GUIDE.md.
* Themes: rebuild Glassy Neon and the seasonal themes as full scenes
Restyle every surface of the four themes (server rail, sidebars, header,
topic bar, message list, composer, buttons, inputs, scrollbars, modals,
theme popup and the login page) and give each a layered, animated scene
drawn with self-contained SVG data URIs.
- Glassy Neon: Haven on the Grid. Opaque holo-glass panels with lit cyan
and orange edge rails, circuit traces, data streams running down the
rails, light-cycle trails along the header and composer, a perspective
light floor that races toward the viewer, horizon ridges, a scan sweep,
counter-rotating identity disc rings and glitch hover accents.
- Christmas: a cozy cabin looking out on a moonlit pine forest. Plaid
walls, a fireplace with stockings and flickering flames, a decorated tree
with twinkling lights and presents, garland and string lights on the
header, snow caps, frosted window corners and three parallax snow layers.
- Halloween: a cartoony haunted night. Bats crossing a big moon, graveyard
fog, a floating ghost, flickering jack-o'-lanterns, a black cat with
glowing eyes and a swishing tail, a bubbling cauldron, cobwebs, a
dangling spider and green goo that drips from the header and composer.
- Thanksgiving: a harvest evening. Two parallax layers of falling leaves,
swaying maple trees, a farmstead sunset, a leaf garland, flannel and
barn-wood panels, hay bales, corn shocks, pumpkins, and two turkeys that
wobble, gobble and strut.
Decorations use pointer-events: none and sit behind content in negative
z-index layers, the message column keeps a dark veil so message text stays
above WCAG AA, animations only change transform and opacity, and
prefers-reduced-motion stops every animation. Pseudo-element rules raise
their specificity so the effects overlay system does not strip them.
Move the seasonal-theme check into HavenThemeCompat.isSeasonalTheme so the
theme bar, plugin loader and admin list share one implementation, and test
it.
People reach for a custom theme wanting a wallpaper, and the docs only
covered colour variables, so the answer kept being "it is possible" with
no instructions attached.
The guide and the theme template now carry the recipe: put the image in
themes/ beside the .theme.css and reference it with a relative path, which
works because the whole folder is served, and set it on .main, which is
the chat area. The message list, channel header and messages are all
transparent, so it shows through behind them.
The snippet includes a scrim gradient over the image, with a note on what
it is for, since text over a bright photo is unreadable without one. Also
states plainly what a theme cannot do: it is a file on the server, so
adding one needs server access and publishing it offers it to everyone.
There is no per-user wallpaper setting.
Verified against a running server: the theme file and a sibling image are
both served, a relative url() resolves to /themes/, and the theme's
background-image overrides the stylesheet's background shorthand on .main
while keeping the theme colour underneath.
Accent-filled controls (Join, Send, admin Save, active tabs, the Android
promo badge, and 25 more) hardcoded color:white on background:var(--accent).
That is fine on the default purple, but 14 themes use a light accent, so
the label was near-invisible: tron 1.39:1, ice 1.77:1, crt 1.83:1, nord
2.00:1, and the bundled braid theme worst of all at 1.33:1, which is the
mint Join button that got reported.
Added an --accent-text token, white by default so nothing changes for the
themes that were already fine, and overrode it on the 14 light-accent
themes with a dark drawn from each theme's own palette rather than flat
black. Those now land between 6.24:1 and 13.31:1. The 31 rules that
hardcoded white now read the token, so custom themes can set it too.
Left alone: matrix and fallout invert the button entirely (dark fill,
accent text, glowing border) as a deliberate terminal look and measure
13.49:1 and 12.62:1, and haven/chapel/midnightpurple/cloudy/cyberpunk sit
at 3.59:1 to 4.38:1 on saturated brand colours where white reads as
intended and dark would not look right.
A group leader indents its text by (row padding + avatar + gap). The messages
that follow it have no avatar, so they pad by that same total to line up. Both
numbers were written out by hand in five places, and two of them disagreed:
the spacious density set the leader side to 70px but never got a matching
compact rule, so follow-ups sat 14px to its left, and the Braid themes resized
the row and avatar with !important while leaving the compact side alone, for
8px. Two separate reports, one defect.
One side is now derived from the other through --msg-gutter, and density
tiers, breakpoints and themes set only the parts. The two cannot drift apart
again, and a theme that follows the same convention gets it right for free.
The ~480px breakpoint was 4px out for the same reason and is fixed too.
Measured against a live server: leader and follow-up left edges are identical
across cozy/compact/spacious at 1280/760/350px, and under both Braid themes.
* Fail loudly when the server port cannot be bound, and stop refusing Node 24
A failed bind (EADDRINUSE/EACCES/EADDRNOTAVAIL) surfaces as an async
'error' event on the HTTP server. With no listener there, it fell
through to the global uncaughtException keep-alive, which logged it to
crash.log and kept a process alive that never got its socket: no
banner, no exit code, and Start Haven.bat's netstat readiness probe
was satisfied by the stale port-holder — so launches died silently.
Reproduced on 3.39.0 by double-launching; %APPDATA%\Haven\crash.log
shows the same swallowed EADDRINUSE in the wild.
- server.js: bind failures on the main listener are now fatal and
explain themselves (which port, why, what to do) before exit 1.
Other server errors keep the existing keep-alive behaviour.
- server.js: the HTTP->HTTPS redirect listener (port+1) warns and
continues instead of feeding the same silent uncaught path.
- Start Haven.bat: the hard Node >=24 refusal is gone — Node 24 is
current LTS and better-sqlite3 12.10.0 ships prebuilds for it
(verified working). The honest gate is a functional
require('better-sqlite3') check after npm install; >=25 warns.
- package.json/lock: engines widened to >=18 <25 (lock version also
synced from stale 3.31.2).
* Add Braid themes (dark/light) and the Braid Layout plugin
Ports the Braid look from the Amni-Haven mobile app to the web and
desktop clients, as pure opt-ins riding the existing rails — no core
file changes:
- themes/braid.theme.css / braid-light.theme.css: the mobile BRAID /
BRAID_LIGHT palettes (mint on slate / deep green on white) with the
1.6x-rounder shape language. Unpublished by default; admins opt in
via Settings -> Custom Themes, exactly like any file theme.
- plugins/BraidLayout.plugin.js: the 'vastly simplified' two-edge
layout — folds the server rail (including manage/sync) into a strip
atop the sidebar, tucks header extras into a kebab menu, and uses
the full width for messages. Every stock feature stays reachable:
the kebab proxies search/pins/gallery/channel code/encryption/
settings and adds People, Soundboard, Status bar, and app-download
entries for the chrome it hides. A separate variable-driven shape
layer applies the Braid format (radii, blur, focus rings) on top of
ANY active theme, so all built-in themes reskin the Braid layout
with their own palette. start()/stop() fully applies and fully
restores (DOM, styles, listeners, localStorage); the
MutationObserver is childList-only and only mutates
styles/attributes, so it cannot observe its own writes.
All sizes are rem so the interface Zoom slider scales Braid chrome
together with the rest of the UI. prefers-reduced-motion disables the
motion layer outright.
* Braid Layout: mobile-parity chat cards and condensed chrome
Feedback round: the flat message list didn't carry the mobile Braid
shape language, and leftover strips still read as four-edge chrome.
- Merged run cards (desktop twin of Haven-Mobile's braidForm): a run of
same-author posts becomes one bordered card on .message-body, dashed
seams between posts, avatar and hover-timestamp in the gutter, own
messages tinted with the accent. Run position comes from O(n)
data-braid-run marking in the existing observer batch — NOT
:has(+ .message-compact), which makes Chromium re-run invalidation
per sibling insert and sent a 600-message channel load quadratic.
Channel list runs are carded the same way.
- Condensed chrome: the topic bar folds into the header band as a
quiet subtitle (no second border strip), header drops to 3rem,
sidebar footer and composer tighten, media buttons shrink to 2rem.
- All gutter math is rem (.625 pad + 2.25 avatar + .75 gap = 3.625rem)
so cards and compacts stay aligned under interface zoom.
* Braid Layout v1.2: Mod Mode interop, themed icons, modern settings, subtler seams
- Mod Mode (the layout editor) is now fully functional with Braid on:
the layer suspends itself while body.mod-mode-on is set — gates come
off and the server rail unfolds so every draggable section and panel
handle is real — and re-applies when editing ends. A saved custom
section order wins over Braid's default join/create-at-bottom
ordering (the order rules load only when no haven-layout is stored).
'Edit layout' joins the kebab menu.
- The bottom-left emoji buttons (theme/activities/members/settings/
donate/mute/deafen) become currentColor line icons so they follow the
palette like the rest of the chrome; originals restore on disable.
- Modern settings: nav rail becomes grouped pills with an accent-tinted
active state, each settings section becomes a rounded card (the
inline border-top hairlines between sections retire), header gets the
blur band + pill tabs treatment.
- The seam between same-author posts shrinks to a centered dotted
hairline (min(7rem,45%)) instead of a full-width dashed rule.
* Braid Layout v1.3: true author-run merging, hamburger menu, text sliders, hex loader, logo glow
- Message merging now chains on AUTHOR: consecutive posts by the same
user merge into one card even when the app rendered them as separate
full .message elements (persona changes, break_chain, and system
notices still split). Continuing messages hide their avatar/header
and surface their own timestamp in the gutter on hover
(data-time-short). The visible full-width divider is gone — bodies
inside a run no longer draw internal horizontal borders, so the only
separator is the small centered dotted seam.
- One animated hamburger (morphs to an X) replaces both the header
kebab and the bottom-left icon bar: grouped Channel / View / App menu
with staggered item animation. The menu mounts on <body> — the
blurred header is a containing block with overflow:hidden that clips
even position:fixed descendants. Every absorbed feature keeps its
door (themes, activities, members, settings, donate, apps, Mod Mode).
- Braid text size: two continuous sliders in Settings (chat text
80-160%, interface text 85-140%) layered over rem zoom, live-applied
via CSS vars and persisted per user.
- The brand hexagon gets an accent glow that breathes (and a slow spin
on hover); a hexagonal loading indicator appears on channel switches
and reskins the setup wizard's circular spinner. All animations
respect prefers-reduced-motion.
- Braid / Braid Light themes: type & color enforcement pass — the font
stack and palette land over style.css's per-element declarations
(fonts on all controls, selection/placeholder/link/timestamp colors),
and the own-message tint calms from 12% to 8% accent.
* Braid Layout v1.4: hamburger moves home to the bottom-left, settings nav gets real pills and themed icons
- The hamburger relocates to the bottom-left corner where the icon bar
it absorbed used to live — the popups it opens (themes, activities)
anchor down there, so the menu and its children now share a corner
instead of spawning across the screen from the button. It is styled
as a Haven tile (bordered, bg-tertiary, accent hover) with a Menu
label, and the grouped menu rises from the bottom-left with the same
morph-to-X and stagger.
- Settings nav highlights now encompass their rows: stock uses a
hanging-indent hack (padding-left 1.625rem + text-indent -16px) so
wrapped labels clear the leading emoji, which pulled the first line
left out of the highlight pill. Items are now full-width flex pills
with text-indent reset — and every leading emoji (nav rows and the
User/Admin tabs) becomes a currentColor line glyph keyed by
data-target, same treatment as the old bottom-left buttons, with a
Haven hexagon fallback for unmapped rows. Originals restore on
disable.
Admin can now promote .theme.css files into the main theme picker:
- Admin Settings → Appearance → Custom Themes: checkboxes for every
.theme.css file in themes/. Checked themes are stored server-wide
in the published_themes DB setting.
- /api/themes now returns a 'published' flag on each theme so clients
know which ones to show in the picker.
- plugin-loader.js injects published themes as real buttons in
#theme-selector. Clicking one applies it exclusively: any previous
file-theme link is removed, inline custom vars are cleared, and the
new .theme.css link is injected after all other stylesheets so its
:root{} rules win without fighting built-in [data-theme] selectors.
- theme.js: switching to a built-in theme strips injected file-theme
links. applyThemeFromServer handles 'file:' values.
- The Default Theme admin dropdown now auto-populates with published
file themes alongside built-in options.
- themes/custom.css.example: reference file documenting all CSS vars
and @icon metadata for custom theme authors.