Business Themes & Reference-Style Mockups
Branch:
theme/businessintranquil-client; new sibling repostranquil-business-lightandtranquil-business-dark; later work intranquil-automations.
An effort to evolve Tranquil’s overall look and feel toward a clean, warm, editorial
aesthetic (target: the reference layout — nav / note-list / editor / properties). It adds new, additive UI themes plus in-app HTML mockups of the target layout. The existing tranquil-theme-light/dark are left untouched — these business themes are siblings, not
replacements. (The default themes were already given a reference-inspired refresh; see theme-refresh.md, which is why the palettes align.)
Phase status
- Phase A — Themes: DONE.
tranquil-business-light/-darkpackages created, wired, and installed. - Phase B — Mockups & tab-bar controls: DONE. Mockups (
main-view.html+properties.html) auto-open on startup fromtranquil-automations; custom tab-bar controls injected into center + right panes. - Phase C — Docs polish: DONE. This doc updated with the Phase B implementation notes below.
Source of truth for the design: the reference app at ../reference (src/index.css tokens, src/theme.json type scale, src/components/EditorTheme.css component styles). Fonts: Inter (UI/body), IBM Plex Mono (code).
Phase A — the two theme packages (done)
Two new UI-theme packages next to the existing themes. Each mirrors the tranquil-theme-* structure. A semantic color vocabulary lives in styles/colors.less; every partial is written against those names and scoped under an
interpolated @{theme-scope} selector, so the two packages share byte-identical
partials and differ only in colors.less (plus package.json / README).
Color tokens
| Role (LESS var) | Light | Dark |
|---|---|---|
@app-bg (content) | #FFFFFF | #1F1E1B |
@sidebar-bg (workspace/docks/tab-bar) | #F7F6F3 | #191814 |
@panel-bg (panels/active tab/cards) | #FFFFFF | #23221F |
@surface-active (inputs/active rows/badges) | #EBEBEA | #34322D |
@dialog-bg (modal/palette) | #FFFFFF | #272622 |
@overlay | rgba(0,0,0,.30) | rgba(8,8,7,.58) |
@text-primary | #37352F | #E6E1D8 |
@text-secondary | #787774 | #B8B1A6 |
@text-muted | #B4B4B4 | #7F776D |
@text-heading | #37352F | #F1ECE3 |
@border | #E9E9E7 | #34322D |
@border-strong | #D9D9D6 | #46433B |
@hover / @hover-subtle | #EBEBEA / #F0F0EF | #2D2B27 / #262520 |
@selected | #E8F4FE | #1E344C |
@accent / @accent-hover | #155DFF / #0D4AD6 | #78A4FF / #9BBEFF |
accents @green/@orange/@red/@purple/@yellow/@teal | #38A169 / #D9730D / #E53E3E / #805AD5 / #D69E2E / #319795 | #79D89D / #F3A15B / #FF8A86 / #B69CFF / #F2C86B / #64D1C8 |
Typography: Inter UI chrome at 13px; editor body 15px/1.5; H1 32/700 (−0.5 tracking), H2 27/600, H3–H4 20/600; code IBM Plex Mono ~14px.
Bundled Inter font (important)
The app has no system/bundled Inter and no @font-face for it — every theme (existing
and new) sets font-family: Inter, … but it silently fell back to -apple-system (SF).
That’s why the default themes never actually looked like Inter.
Loading it from a theme is non-trivial because of two constraints:
- Theme stylesheets are injected into the app’s main document, so a relative
url()resolves against the appstatic/base, not the package. static/index.html’s CSP isdefault-src * atom://*with nofont-srcand nodata:for fonts — so a data-URI@font-faceis blocked, and*doesn’t coverdata:.
The CSP does allow atom://*, and Pulsar’s atom-protocol handler
(src/main-process/atom-protocol-handler.js) resolves atom://<pkg>/<path> from ~/.tranquil/dev/packages (dev) and RESOURCE_PATH/node_modules (production). So the
business themes bundle the Inter variable latin woff2 at resources/fonts/Inter-Variable-latin.woff2 (~48KB, font-weight: 100 900 covers all
weights) and load it in styles/fonts.less (imported first) via url("atom://tranquil-business-<light|dark>/resources/fonts/Inter-Variable-latin.woff2").
CSP-compliant, no core edit, works in dev and production. (Phase B mockups are webviews
whose document base is the mockup folder, so they can use a plain relative @font-face.)
base.less also sets -webkit-font-smoothing: auto (not antialiased): grayscale
smoothing renders Inter thin/narrow on non-retina (1×) displays; auto gives subpixel
antialiasing (fuller strokes) at 1× and still resolves to grayscale at 2×, so it looks
right on both.
Style partials
base.less (workspace/docks/panes/buttons/modal-overlay/scrollbars), tree-view.less, tabs.less, status-bar.less, editor.less, settings.less, model.less, welcome.less, image-viewer.less, find-and-replace.less, packages.less, tranquil-browser.less, terminal.less (see the terminal note above), popovers.less (tooltips/hovers + autocomplete & select-list popovers → @popover-bg surface, border, @shadow-popover; tooltip rules are global + body-anchored, not @{theme-scope} — tooltips attach to <body> (outside atom-workspace where the theme
class lives), so a scoped rule never matches them and one-dark-ui’s blue wins), progress.less, notifications.less (neutral card +
per-type palette accent stripe, replacing the default saturated blocks), syntax-variables.less (empty — UI theme only). Scoped under .theme-tranquil-business-light / -dark via @{theme-scope}; a few intentionally
global rules (* { color }, .workspace, .item-views) mirror the existing themes and
only load when the theme is active.
Tree-view restyle (explicit deliverable)
Restyled so the left nav reads as sectioned navigation:
- Section dividers between top-level entries —
border-bottomon.tree-view .project-root > .entries > .entry(the first-level folders/files under each root) plus between multiple project roots. Nested entries are not bordered (fixes the existing themes over-drawing lines on nested folders). Note: bordering.project-rootitself yields no visible line in a single-project workspace — the sections are the top-level entries. - Rows match the reference nav item: Inter 13px / font-weight 500, padding
6px 12px, radius 4px, icon-to-label gap 8px. - Root headers as section labels — 10px/600, uppercase, 0.5px tracking,
@text-muted(mirrors the reference’s group headers). - Selected entry as an accent-tinted pill —
fade(@accent, 10%)background with the label in@accent(matches the reference active state), radius 4px; the core.list-tree .selected::beforebar is neutralized. - Hover
@hover; keeps the accent inset-ring drag-over affordance.
Icon colors (owned tranquil-theme-icons)
The icon theme applies per-file-type colors scoped to the active UI theme class (.theme-tranquil-theme-dark { .icon-colors(…) }, etc.). So the business themes needed
their own blocks or icons render monochrome. Added additive .theme-tranquil-business-dark / -light .icon-colors(…) invocations to tranquil-theme-icons/styles/tranquil-theme-icons.less, reusing the dark/light palettes
respectively. Existing themes are unaffected. (A third owned repo touched — additive only.)
Terminal & scrollbars (driven from the UI theme — deliberate)
The built-in terminal (pulsar-edit/terminal, third-party — not modified) uses its default “Stylesheet” color mode, which reads --terminal-* custom properties from document.documentElement. By default those are emitted from the active syntax theme, so
a light UI theme still showed a dark terminal. styles/terminal.less overrides the --terminal-* vars at :root from the business palette (bg/fg/selection/cursor + the 16 ANSI
slots; grayscale anchors @ansi-* live in colors.less). The terminal re-applies on atom.themes.onDidChangeActiveThemes / atom.styles.onDidAddStyleElement, so open terminals
flip light↔dark live.
Scrollbars (tree-view, terminal viewport, settings .config-menu/.panels) share a .themed-scrollbar() mixin in colors.less, applied per scroll-container under @{theme-scope} — never a universal * rule (that would disturb the editor’s own .vertical-scrollbar elements), mirroring how the stock tranquil-theme-* do it.
Why the UI theme and not a syntax theme, and no !important: in Pulsar, terminal/scrollbar
colors conventionally follow the syntax theme (terminal-variables.less maps @terminal-* → @syntax-*). We drive them from the UI theme on purpose, because the
business themes are UI-only and we want the terminal to track the light/dark UI toggle — the
“more idiomatic” alternative would be a companion syntax theme, which is larger scope and would
also recolor editor tokens. No !important is needed: theme stylesheets load at a higher
priority than package stylesheets (ThemePackage.getStyleSheetPriority() → 1 vs Package → 0, inserted later in the document), and @{theme-scope} adds a class, so our rules
win on the cascade. Do not “fix” this by moving it to a syntax theme or adding !important.
Wiring
tranquil-client/package.json:link:../tranquil-business-{light,dark}added to bothdependenciesandpackageDependencies.tranquil-dev.code-workspace: folder entries added.- Dev symlinks in
~/.tranquil/dev/packages/(load as dev packages locally; fromnode_modulesin production). yarn installrun; both compile clean vialesscand are discoverable.
Activate via Settings ▸ Themes (writes core.themes). No default change was made.
Phase B — mockups & tab-bar controls (done)
Self-contained static HTML (one file each) + shared CSS + bundled Inter / IBM Plex Mono .woff2, no JS framework, housed in tranquil-automations/mockups/ and opened on
startup from activate() (gated behind the tranquil-automations.showBusinessMockups config flag, default on) via the tranquil-browser file opener.
Files: mockups/mockup.css (shared), mockups/main-view.html, mockups/properties.html, mockups/fonts/{Inter-Variable-latin,IBMPlexMono-Regular,IBMPlexMono-Medium}.woff2. Wiring
lives in tranquil-automations/lib/mockups.js (open) and lib/pane-controls.js (tab-bar
controls), called from lib/tranquil-automations.js activate().
main-view.html→ center. Two columns: left note-list pane + main editor pane. Content source: the real reference demo vault (../reference/demo-vault-v2), not a screenshot (the original screenshot was not recoverable). The open note is the Sponsorships responsibility so the editor and the right-dock properties stay coherent; the Inbox lists the real demo notes (Responsibility / Procedure / Measure / Person / Event / Area / Note) driving the type pills. Below the note, a component showcase — one example of every editor element (headings, inline styles, bullet/numbered/check lists, blockquote, inline + fenced code, table, hr, math, mermaid, tldraw placeholder, image) styled perEditorTheme.css/theme.json.properties.html→ right dock. Properties panel for Sponsorships (Type / Status / Aliases / Notion id / Created), then relationship groups (belongs_to,has_measures,has_procedures) with wikilink chips + + Add affordances, and a Children section.
Fonts (relative @font-face, unlike the theme)
A webview’s document base is the mockup folder, so the mockups load fonts with plain
relative @font-face url("fonts/…") — no atom:// and no CSP problem (that constraint
only applies to theme stylesheets injected into the app’s main document; see Phase A). Inter
is copied from the business-light package’s resources/fonts/; IBM Plex Mono latin woff2 is
fetched from fontsource. mockup.css falls back to ui-monospace if the Plex files are
absent.
Opening into the right dock (the key gotcha)
The browser item (HTMLEditor) defaulted to getAllowedLocations() → ['center'], and workspace.open silently falls back to center when the requested location isn’t allowed
(src/workspace.js: location = allowedLocations.includes(location) ? location : allowedLocations[0]). So a right-dock open needs the item to allow the dock. Fix: tranquil-browser-model.js now reads per-instance allowedLocations / defaultLocation from opt (defaults unchanged → still center-only for normal browser tabs, preserving the
drag-into-dock guard) and persists them (plus hideURLBar) in serialize()/copy() so a
dock-placed, chrome-less mockup survives window reload. openMockups() passes { location:'right', allowedLocations:['center','right'], defaultLocation:'right', hideURLBar:true }; the right dock auto-shows when its pane activates.
Also fixed: the opener’s “avoid multiple tab open” guard was time-only
(Date.now()-prevId < 50), which swallowed the second of two mockups opened back-to-back.
Made it url-aware (window.prevUrl === url) so different urls both open. The hideURLBar: true opt (already supported by the browser view via .hideURLBar {display:none})
removes the url bar; duplicate-open is guarded by isOpen() keyed on getURL.
Panel top alignment
The tabs package inserts a uniform .tab-bar above every pane in every location, so the
columns share a content origin. Each mockup gets a fixed 52px header (breadcrumb-bar
height) so the center/right headers line up. mockup.css also themes the webview scrollbars
(::-webkit-scrollbar, --border-strong thumb) and supports light + dark (default light,
plus prefers-color-scheme / data-theme="dark").
Custom pane controls in the tab-bar
A right-aligned .tranquil-pane-controls cluster is injected into each center / right
pane’s existing .tab-bar from lib/pane-controls.js (getCenter() + getRightDock() observePanes → filter by getContainer().getLocation() → append into .tab-bar, margin-left:auto + high flex order so real tabs never displace it). Because the tabs
package only ever inserts/removes individual tab <li>s (never rebuilds the <ul>), the
injected node persists; a MutationObserver covers the case where our observer fires before
the tab-bar exists. Visual mimics only; the tabs package is never modified.
Phase C — docs polish (done)
This doc’s phase status and Phase B section were updated with the as-built specifics above
(demo-vault content source, per-instance dock locations + the workspace.open fallback, the
url-aware open guard, relative mockup fonts, the config flag). See Verification (Phase B).
Verification (Phase A)
- Static: both packages compile via
lessc, manifests valid,theme: "ui", linked innode_modulesand~/.tranquil/dev/packages. ✅ - Visual (user review): launch Tranquil, Settings ▸ Themes → activate
tranquil-business-light/-dark; confirm tree-view (sectioned dividers, selected pill), tabs, status bar, editor, settings, and browser toolbar adopt the palette.
Verification (Phase B)
- Static:
node --checkthe changed JS (tranquil-automations/lib/{mockups,pane-controls}.js,tranquil-browser/lib/tranquil-browser-{model,browser}.js);mockup.css+ font assets resolve; the tab-bar action styles compile. ✅ - Launch Tranquil (
yarn start): withshowBusinessMockupson,main-view.htmlopens in the center andproperties.htmlin the right dock (proves thegetAllowedLocationsfix — pre-fix it fell back to center). ✅ - Both mockups render with Inter (UI/body) + IBM Plex Mono (code), the component showcase,
themed scrollbars, and no browser url bar (
hideURLBar). ✅ - Right-aligned tab-bar control cluster appears in center + right panes, stays pinned right as tabs open/close, and is absent from the left dock. ✅
- Reload keeps properties in the right dock and doesn’t double-open (guard). Toggling the config flag off + reload removes the mockups and controls.
Tip: to inspect the running app, connect Puppeteer via the WebSocket endpoint from curl -s http://localhost:9222/json/version (the local Node was 16 without global fetch,
so browserURL fails — use browserWSEndpoint).