diff --git a/docs/architecture/ribbon.md b/docs/architecture/ribbon.md index b0f6713..987f5ba 100644 --- a/docs/architecture/ribbon.md +++ b/docs/architecture/ribbon.md @@ -6,16 +6,16 @@ compared. Everything the ribbon drives is the same editor, through the same comm ## The mode switch -`chromeMode: 'classic' | 'ribbon'` lives in `storage/theme.ts` beside the other UI-chrome prefs +`chromeMode: 'modern' | 'ribbon'` lives in `storage/theme.ts` beside the other UI-chrome prefs (`edentext-chrome`, default `'ribbon'`). `App.svelte` forks on it: the ribbon docks as a plain flex child, the island keeps its absolute overlay. Reachable from both sides — the theme dropdown -in classic, the strip's appearance menu in the ribbon. +in the modern chrome, the strip's appearance menu in the ribbon. -`--toolbar-overlay-h` is written **only** in classic mode. `editor.css` reads it as +`--toolbar-overlay-h` is written **only** in modern mode. `editor.css` reads it as `var(--toolbar-overlay-h, 0px)`, so the ribbon's docked layout needs no second rule. -**Classic is the baseline the ribbon is measured against, so it stays untouched.** Where a shared -helper would improve both, the ribbon takes it and classic keeps its copy; where a dialog is +**The modern chrome is the baseline the ribbon is measured against, so it stays untouched.** Where a shared +helper would improve both, the ribbon takes it and the modern chrome keeps its copy; where a dialog is mounted inside `ToolbarExpanded`, the ribbon mounts its own rather than hoisting it. Only one chrome is mounted at a time, so the two sets never coexist. @@ -29,12 +29,12 @@ active underline is 2.5px **inset 13px from each side**, not a full-width border The logo's other colour, `--brand-clay`, marks the **contextual tabs** — the ones that exist only for the selected object — as their 2px bar, over a `--w-contextual` label darkened to stay readable (the clay itself is 3.3:1 on white). Both brand tokens are defined once, from -`favicon.svg`; the classic chrome's island hairline already gradients between the same pair. +`favicon.svg`; the modern chrome's island hairline already gradients between the same pair. `.ribbon` remaps the app's own tokens onto that palette (`--color-surface`, `--color-primary`, `--toolbar-btn-size`, …). Custom properties inherit, so every reused picker — `ColorPicker`, `HistoryButton`, `TablePicker`, `TableStylePicker`, the border pickers — adopts the ribbon's look -with no edit of its own, while the classic toolbar keeps the themed set. The **status bar** takes +with no edit of its own, while the modern toolbar keeps the themed set. The **status bar** takes the same remap while the ribbon is mounted (`.statusbar.w-chrome`, `App.svelte`) — it closes the frame the chrome opens, and the app's dark neutrals are bluer than Word's. @@ -127,7 +127,7 @@ margins, Save As. ## Dialogs `ParagraphDialog` and `TabsDialog` open from Word's ↘ launcher in a group's corner. They are -chrome-agnostic, so classic could gain launchers of its own at no cost. +chrome-agnostic, so the modern chrome could gain launchers of its own at no cost. There is **no Font dialog**: the Home tab already carries change case and the underline and strikethrough line styles, and the two things left over — `letterSpacingPt` and `kerning` — live @@ -135,7 +135,7 @@ on a named style, not on a run. Setting them as direct formatting would mean a n and export work on both formats, which is more than a chrome rebuild. The style manager is where they belong. -## What retiring classic would free +## What retiring the modern chrome would free Recorded so the option stays visible, not as a plan: `Toolbar.svelte` and `ToolbarExpanded.svelte` (~3200 lines), the overlay island and its hand-rolled horizontal diff --git a/src/App.svelte b/src/App.svelte index 29c986a..9102764 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1038,7 +1038,7 @@ } -
{#snippet toolbarScrollbar()} diff --git a/src/lib/components/CLAUDE.md b/src/lib/components/CLAUDE.md index cae8902..ef6ddd9 100644 --- a/src/lib/components/CLAUDE.md +++ b/src/lib/components/CLAUDE.md @@ -150,7 +150,7 @@ reject in place through `acceptRevision`/`rejectRevision`. Opened from the Revie ## AutoText (`AutoTextDialog.svelte`, `editor/extensions/autoText.ts`) LibreOffice's Tools ▸ AutoText, reachable where Word puts it too (the ribbon's Insert ▸ -Text group, the classic chrome's Tools menu): the library, a click to insert, and "new +Text group, the modern chrome's Tools menu): the library, a click to insert, and "new from selection" — the selected slice serialized to HTML. `hasSelection` is derived off `open` because the dialog stays mounted and a modal freezes the selection it opened on. @@ -187,7 +187,7 @@ the code keep the technical name. LibreOffice's Navigator and Word's Navigation pane: the outline, click to jump, and the four chapter operations on hover. Docked right beside `CommentsPane`, not left where both -reference products put it — the classic chrome's floating "Tools" chip lives there. The +reference products put it — the modern chrome's floating "Tools" chip lives there. The toolbar island overlays the row's top, so the pane takes a `margin-top` of `--toolbar-overlay-h` (`App.svelte` sets it; 0 for the ribbon, which is in flow). diff --git a/src/lib/components/ColorPicker.svelte b/src/lib/components/ColorPicker.svelte index 436bf53..e435d5c 100644 --- a/src/lib/components/ColorPicker.svelte +++ b/src/lib/components/ColorPicker.svelte @@ -94,7 +94,7 @@ const anchor = split?.getBoundingClientRect(); if (!anchor) return; // A ribbon panel drops below the whole band, or it would cover the controls - // under it. The classic toolbar has no band, so this finds nothing there. + // under it. The modern toolbar has no band, so this finds nothing there. const band = split?.closest('.ribbon-body')?.getBoundingClientRect(); pos = { top: Math.max(anchor.bottom, band?.bottom ?? 0) + 3, left: anchor.left }; await tick(); diff --git a/src/lib/components/NavigatorPane.svelte b/src/lib/components/NavigatorPane.svelte index 326e97a..77b51eb 100644 --- a/src/lib/components/NavigatorPane.svelte +++ b/src/lib/components/NavigatorPane.svelte @@ -110,7 +110,7 @@ background: var(--color-surface); font-family: var(--font-sans); font-size: 0.8rem; - /* The classic chrome's toolbar island overlays the top of the row; a margin, not + /* The modern chrome's toolbar island overlays the top of the row; a margin, not padding, or the sticky header would scroll up under it. The ribbon sets the variable to 0, being in flow. */ margin-top: var(--toolbar-overlay-h, 0px); diff --git a/src/lib/components/ribbon/Ribbon.svelte b/src/lib/components/ribbon/Ribbon.svelte index 85869ee..1dd70df 100644 --- a/src/lib/components/ribbon/Ribbon.svelte +++ b/src/lib/components/ribbon/Ribbon.svelte @@ -312,8 +312,8 @@ -
{t().appearance.title}
@@ -373,7 +373,7 @@ /* Word's palette reaches the reused pickers (ColorPicker, HistoryButton, the table pickers …) by remapping the app's own tokens here: custom properties inherit, so those components adopt the ribbon's look with no edits, and the - classic toolbar keeps the themed set. */ + modern toolbar keeps the themed set. */ .ribbon { --color-surface: var(--w-surface); --color-toolbar-bg: var(--w-chrome); @@ -400,7 +400,7 @@ .rb-collapse :global(svg) { transition: transform 0.12s ease; } .rb-collapse-up :global(svg) { transform: rotate(180deg); } - /* Those pickers frame their trigger for the classic toolbar's tinted island. A + /* Those pickers frame their trigger for the modern toolbar's tinted island. A ribbon row is borderless icon buttons, so the frame comes off and the hover tint takes over — same 30px box as .rb-icon, so a row reads as one strip. */ .ribbon :global(.color-split) { diff --git a/src/lib/i18n/locales/de.ts b/src/lib/i18n/locales/de.ts index 585545f..f16658d 100644 --- a/src/lib/i18n/locales/de.ts +++ b/src/lib/i18n/locales/de.ts @@ -852,14 +852,12 @@ const de: Messages = { cellMargins: 'Zellenränder', altText: 'Alternativtext', altTextHint: 'Das Bild beschreiben', - // Keyed by the mode id, which names the shape: the ribbon is the look users - // know as classic, the floating island the modern one. chrome: { title: 'Symbolleisten-Stil', ribbon: 'Klassisch', ribbonHint: 'Mit Registerkarten, wie in Word', - classic: 'Modern (beta)', - classicHint: 'Eine schwebende Symbolleiste', + modern: 'Modern (beta)', + modernHint: 'Eine schwebende Symbolleiste', }, }, app: { diff --git a/src/lib/i18n/locales/en.ts b/src/lib/i18n/locales/en.ts index c903fb4..0055718 100644 --- a/src/lib/i18n/locales/en.ts +++ b/src/lib/i18n/locales/en.ts @@ -858,14 +858,12 @@ const en = { cellMargins: 'Cell margins', altText: 'Alt text', altTextHint: 'Describe the image', - // Keyed by the mode id, which names the shape: the ribbon is the look users - // know as classic, the floating island the modern one. chrome: { title: 'Toolbar style', ribbon: 'Classic', ribbonHint: 'Tabbed, like Word', - classic: 'Modern (beta)', - classicHint: 'One floating toolbar', + modern: 'Modern (beta)', + modernHint: 'One floating toolbar', }, }, app: { diff --git a/src/lib/storage/CLAUDE.md b/src/lib/storage/CLAUDE.md index 5976af5..4362d95 100644 --- a/src/lib/storage/CLAUDE.md +++ b/src/lib/storage/CLAUDE.md @@ -31,7 +31,7 @@ All keys share the `edentext-` prefix — a namespace, not a description: on Git - **Recent files:** `edentext-recent-files` — `{id, name, at}` per entry, newest first, eight kept (`recentFiles.ts`). Only the label lives here: the `FileSystemFileHandle` that reopens one is structured-cloneable but not JSON, so it sits in the `edentext-recent` IndexedDB store under the same id, and each write sweeps the handles no entry names. Two handles compare only through `isSameEntry`, which is async — hence the walk when recording a file already in the list. Reopening re-asks for permission (the API drops it on reload); a refusal, a moved file or a browser with no picker leaves the entry unopenable, and it is dropped with a message instead of failing again. - **Theme:** `edentext-theme` — `'light' | 'dark' | 'allBlack' | 'auto'`. - **Toolbar expanded:** `edentext-toolbar-expanded` — boolean string. -- **Chrome:** `edentext-chrome` — `'classic' | 'ribbon'`; absent = ribbon. +- **Chrome:** `edentext-chrome` — `'modern' | 'ribbon'` ('classic' read as the modern island's legacy stored name); absent = ribbon. - **Ribbon collapsed:** `edentext-ribbon-collapsed` — boolean string; absent = expanded. - **Formatting marks:** `edentext-formatting-marks` — boolean string. - **Ruler:** `edentext-ruler` — boolean string; absent = on. diff --git a/src/lib/storage/theme.ts b/src/lib/storage/theme.ts index f619fef..b63f933 100644 --- a/src/lib/storage/theme.ts +++ b/src/lib/storage/theme.ts @@ -2,7 +2,7 @@ export type ThemeMode = 'light' | 'dark' | 'allBlack' | 'auto'; // Which chrome mounts above the document: the floating command island, or the // Word-style ribbon. Both drive the same editor; only one is mounted at a time. -export type ChromeMode = 'classic' | 'ribbon'; +export type ChromeMode = 'modern' | 'ribbon'; const THEME_KEY = 'edentext-theme'; const TOOLBAR_KEY = 'edentext-toolbar-expanded'; @@ -36,7 +36,9 @@ export function saveToolbarExpanded(expanded: boolean): void { } export function loadChromeMode(): ChromeMode { - return localStorage.getItem(CHROME_KEY) === 'classic' ? 'classic' : 'ribbon'; + const stored = localStorage.getItem(CHROME_KEY); + // 'classic' is the floating island's legacy stored name, kept readable. + return stored === 'modern' || stored === 'classic' ? 'modern' : 'ribbon'; } export function saveChromeMode(mode: ChromeMode): void { diff --git a/src/styles/global.css b/src/styles/global.css index 35d79fa..313d014 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -346,7 +346,7 @@ contents from a caller's snippet, which Svelte scopes to that caller. */ /* `anchored` (menu.svelte.ts) switches this to position: fixed and pins it under the trigger — the ribbon band clips its own overflow, so a panel has to leave - the flow to be seen. z-index clears the classic island (200) and the editor's + the flow to be seen. z-index clears the modern island (200) and the editor's own overlays. */ .ribbon-menu { position: absolute; @@ -432,7 +432,7 @@ margin: 0 4px; } -/* The reused pickers style their trigger for the classic toolbar's tinted island: +/* The reused pickers style their trigger for the modern toolbar's tinted island: a frame on hover, the accent as the open state. Beside a row of ribbon buttons that reads as a different control, so both become the ribbon's own tints. */ .ribbon .table-trigger:hover, diff --git a/tests/unit/chrome-mode.test.ts b/tests/unit/chrome-mode.test.ts new file mode 100644 index 0000000..d1933ef --- /dev/null +++ b/tests/unit/chrome-mode.test.ts @@ -0,0 +1,19 @@ +// The chrome default is the ribbon; a stored choice wins, and the floating +// island's legacy stored name 'classic' still selects it. +import { describe, it, expect, beforeEach } from 'vitest'; +import { loadChromeMode, saveChromeMode } from '../../src/lib/storage/theme'; + +describe('chrome mode', () => { + beforeEach(() => localStorage.clear()); + + it('defaults to the ribbon', () => { + expect(loadChromeMode()).toBe('ribbon'); + }); + + it('keeps a stored choice, the legacy name included', () => { + saveChromeMode('modern'); + expect(loadChromeMode()).toBe('modern'); + localStorage.setItem('edentext-chrome', 'classic'); + expect(loadChromeMode()).toBe('modern'); + }); +});