Renameable app display name
Lets a user rename what the application calls itself in its own title bar and About screen, without touching its install identity.
Behavior
A settings field (Customise everything → Identity → Display name) lets a user set a custom display name, persisted across restarts through the durable-storage settings snapshot and resettable to the shipped name in one action (the "Restore the shipped name" switch beside it).
The chosen name reaches every surface the console uses to introduce itself to its own user:
- The in-app title bar — the custom drag-region title bar the frameless window renders.
- The native OS window title — taskbar and Alt+Tab, pushed to the main process over IPC (
window:set-title) whenever the name changes, and once on startup after the durable-storage snapshot loads. - The About screen's heading — reads "About \<name\>" instead of a bare "About".
- Its own notifications — a toast confirms "Renamed to \<name\>" when a rename is set, and "Name restored to \<shipped name\>" when it is reset.
Configuration
Renaming changes display only. display-name.ts keeps two deliberately separate exports with no path between them: IDENTITY (a frozen constant: the shipped product name, the application-data directory, the packaging identifier, and the credential-vault service key) and displayName() (the user setting). Nothing derives an identity value from the chosen name.
Diagnostics, crash logs, issue reports, the update feed, and the installer always use IDENTITY.productName, never the chosen name (nameFor()'s SHIPPED_NAME_SURFACES) — the rename control's own explanatory copy states this plainly, in text that itself always names the shipped product, regardless of what the console is currently renamed to.
Implementation notes
The compiled title bar text is a design-reference literal with no bound value (h("span", {...}, "Material Asterisk")), and the renderer is compiled from the design reference and must never be hand-edited. Rather than edit the design reference for one label — which would need a matching, independently re-audited change to its pinned binding and expression counts — title-bar-name.ts rewrites the already-built element tree on its way out of App.render(), the same way text-boundary.ts already applies language and personal-vocabulary substitution to the compiled tree without touching a generated file. It finds the title bar by its data-window-drag marker (which compile-design.mjs guarantees is unique at compile time) and, within it, the one row carrying the leading Material Symbols icon — never by matching the shipped-name string itself, so an already vocabulary-substituted name is still found.
Failure modes
A rename that accidentally altered the application's data-directory path rather than only its display label is the specific failure this feature is designed to prevent by deriving the two from separate constants — exercised directly by display-name.test.tsx's identity tests. An invalid name (empty, over 60 characters, or containing a control character) is refused before it reaches storage, and a name a hand-edited settings file or an older version wrote that the app would no longer accept falls back to the shipped name rather than rendering.
Accessibility and localization
Follows the product's standing accessibility contract: the rename field and reset switch are ordinary keyboard-reachable, correctly-named text and switch controls with the compiled design's own focus and contrast handling. Copy is currently fixed English; the disclosure and confirmation strings are not yet routed through the language-mode/funny-level boundary.
On the documentation site
The published site carries the same feature, built separately because it is a different
program: no Electron, no durable-storage snapshot, no window title to push over IPC. It
lives in site/settings.html (an IDENTITY card holding a bounded text field, a
Reset to shipped name button and a live status line) and site/app.js
(applyDisplayName(), called from applyState()).
Two surfaces are renamed and are named rather than swept: every .brand-name element —
the brand line in the header and footer of all six pages — and document.title, which is
the browser tab and the closest thing a web page has to a title bar. The title is
recomposed each time from SHIPPED_TITLE, captured once at load, so a second rename
composes against the shipped title instead of against the first rename's output; a title
that never carried the shipped name is left exactly as it was rather than guessed at.
The boundary is the point of the feature, so it is stated as a list of things a rename
must not reach:
og:metadata and the page description — what somebody else's chat window and
somebody else's search result read. app.js never writes them.
- The product prose on the home and product pages, which describes the real software
by its real name.
- Every identity constant:
STORAGE_KEY,HISTORY_KEY, the vocabulary and logo cache
keys, and the ding-pbx-page-settings.json export filename. All are literal constants
that nothing derives from the chosen name, so a rename cannot orphan a visitor's stored
settings — the same separation IDENTITY and displayName() keep in the console.
Typing applies the rename live and persists on every keystroke, so it survives a reload
even if the field is never left; one local-history entry (display-name-changed) and one
notification are written per committed rename rather than per keystroke. Reset settings
clears the chosen name along with everything else and says so in its own confirmation
text. The card's description carries four English and four Cantonese variants at the two
funny levels, and every one of the eight names the shipped product, so the boundary is
stated at every level rather than at some of them.
One ordering detail carries the whole feature and is easy to reverse by accident:
applyDisplayName() runs before applyVocabulary() in applyState(). The vocabulary
walker caches the first text it sees for each node and thereafter rewrites from that
cache, so reversing the two would put the shipped name straight back after every rename,
with nothing failing to say so. site/tests/contracts/app-display-name.test.mjs pins the
order for that reason.
Verification
tests/ui/display-name.test.tsx exercises the display-name.ts module directly: identity isolation, validation, persistence, reset, and shipped-name-only surfaces. tests/ui/display-name-wired.test.tsx proves the wiring itself against the real App — the title bar and About heading actually rendering the chosen name (and the shipped name when unrenamed), the notification toast, the native window-title IPC push, and (by source-scan, since it is Electron-only code this suite cannot import and run directly) main.ts and preload.cjs. tests/ui/title-bar-name.test.tsx covers the tree-rewrite's precision in isolation, including the negative case that would let it silently overwrite the connection-status pill instead.
The site's own half is covered by site/tests/contracts/app-display-name.test.mjs, which
extracts the real applyDisplayName/setDisplayName/commitDisplayName source out of
site/app.js and runs it against a recording page rather than asserting patterns over it
— "the value is stored" and "the setting persists" are both true of a rename that never
reaches a single pixel, and a source-pattern test cannot tell those apart from a working
one. The recorder keeps every selector asked for and every element id requested, so the
boundary is proved by what the code reached for rather than by what its comments claim:
exactly one selector (.brand-name) and exactly two ids (its own field and status line).
No capture of the site has been taken yet, so the pages-site inventory row remains
unverified.
Suggested articles
App logo customization, About and policy, Platform feature index.