Docs

Business Themes & Reference-Style Mockups

Branch: theme/business in tranquil-client; new sibling repos tranquil-business-light and tranquil-business-dark; later work in tranquil-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 / -dark packages created, wired, and installed.
  • Phase B — Mockups & tab-bar controls: DONE. Mockups (main-view.html + properties.html) auto-open on startup from tranquil-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)LightDark
@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
@overlayrgba(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 app static/ base, not the package.
  • static/index.html’s CSP is default-src * atom://* with no font-src and no data: for fonts — so a data-URI @font-face is blocked, and * doesn’t cover data:.

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 entriesborder-bottom on .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-root itself 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::before bar 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 both dependencies and packageDependencies.
  • tranquil-dev.code-workspace: folder entries added.
  • Dev symlinks in ~/.tranquil/dev/packages/ (load as dev packages locally; from node_modules in production).
  • yarn install run; both compile clean via lessc and 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 per EditorTheme.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)

  1. Static: both packages compile via lessc, manifests valid, theme: "ui", linked in node_modules and ~/.tranquil/dev/packages. ✅
  2. 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)

  1. Static: node --check the 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. ✅
  2. Launch Tranquil (yarn start): with showBusinessMockups on, main-view.html opens in the center and properties.html in the right dock (proves the getAllowedLocations fix — pre-fix it fell back to center). ✅
  3. Both mockups render with Inter (UI/body) + IBM Plex Mono (code), the component showcase, themed scrollbars, and no browser url bar (hideURLBar). ✅
  4. 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. ✅
  5. 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).