···88## [Unreleased]
991010### Fixed
1111+- Slides: thumbnail element-count badge no longer visually merges with the slide-number, so two slides with single elements used to read as "11"/"22" etc. (v0.62.17, #727). The thumbnail previously rendered `<span class="slides-thumb-num">{N}</span>` followed by a bare `<span class="slides-thumb-count">{M}</span>` inside the preview — with no separator they appeared flush together. Prefixed the count with a `▦` glyph + space and added `aria-label="{M} element(s)"` so screen readers hear "N elements" rather than the number alone. (#727)
1212+- Slides: each click of "Add text" now offsets the new box by 20×N where N is the current element count on the slide, so repeated clicks don't stack boxes at the identical (100, 100) default position where the second would silently cover the first (v0.62.17, #728). (#728)
1313+- Forms: Preview and Responses modes hide the "+ Add Question" button and the editable form-description textarea, leaving only the mode-switch bar so the author can toggle back (v0.62.17, #715). Before: Preview mixed builder controls with respondent UI, making it unclear what a real respondent would see. (#715)
1414+- Docs: opening the formatting-toolbar kebab overflow menu while the cursor sits inside a table no longer has the table-context toolbar (cell-background color picker, row/col buttons) bleeding over "Code block" and other menu items (v0.62.17, #714). Both `.more-menu-panel` and `.table-toolbar` used `z-index: var(--z-overlay)` so DOM order decided who won. Raised the more-menu-panel to `var(--z-tooltip)` since the menu is always a modal-ish short-lived foreground overlay. (#714)
1115- Sheets: cross-sheet formula references (e.g. `='Sheet 1'!F11`) now resolve to actual values instead of returning `#REF!` (v0.62.16, #725). The formula engine in `src/sheets/formulas.ts` has always accepted a third `crossSheetResolver` argument and `tests/cross-sheet.test.ts` had 21 passing unit tests proving the parser works — but `evaluateFormula()` in `src/sheets/cell-computation.ts` (the one entry point every in-app cell flows through) never passed one. Every runtime cross-sheet formula hit the `#REF!` default branch. Built a CrossSheetResolver that walks `getYSheets()`, reads the named sheet's `cells` Y.Map, and recurses into formula cells with a sheet-scoped local resolver so nested references resolve in the correct sheet's context. 5 jsdom regression tests in `tests/sheets-cross-sheet-runtime.test.ts` build a real Y.Doc and pin: plain cross-ref, SUM over cross-sheet range, arithmetic, `#REF!` for missing sheet, and cross-sheet → formula-cell recursion. Caught while building a Homelab Budget workbook with Sheet 1 data + Sheet 2 summary. (#725)
1216- Docs: version-history preview now renders readable HTML instead of raw ProseMirror schema XML (v0.62.11, #719). Before: clicking a version entry dumped `<paragraph indent="0"><heading level="1">Title</heading><bulletList>…` into the preview pane — unreadable, and "Restore this version" was effectively blind. Added `renderYjsFragmentAsHtml()` in `src/version-panel.ts` that walks the decrypted Yjs XmlFragment and maps each node name to a sensible HTML tag (heading→h1..h6, paragraph→p, bulletList→ul, listItem→li, table/tableRow/tableCell/tableHeader→table/tr/td/th, codeBlock→pre/code, image→img, etc.), with HTML-escaped text and a `<div>` fallback for unknown node types. 11 regression tests in `tests/version-panel-render-preview.test.ts`. (#719)
1317- Sheets: Share button click now opens the in-app share dialog (v0.62.12, #720). The share-dialog markup shipped in `src/sheets/index.html` and the reusable `initShareDialog()` helper existed in `src/lib/share-dialog.ts`, but sheets' `main.ts` never called it — so clicking the Share button was a silent no-op. Threaded `keyString` through `session-bootstrap.ts`'s return value and added `initShareDialog({ docId, docType: 'sheets', keyString })` in sheets' init path. Share now opens a dialog with a live-generated link, mode selector, and expiry picker — same as docs. (#720)
···13201320 border: 1px solid var(--color-border);
13211321 border-radius: var(--radius-md);
13221322 box-shadow: var(--shadow-md);
13231323- z-index: var(--z-overlay);
13231323+ /* Raise above .table-toolbar (also z-overlay=100) so opening the kebab
13241324+ overflow while the cursor is inside a table doesn't leave the cell
13251325+ background picker bleeding over "Code block" etc. See #714. */
13261326+ z-index: var(--z-tooltip);
13241327 padding: var(--space-xs);
13251328 flex-direction: column;
13261329}
+13
src/forms/main.ts
···6060const previewPane = document.getElementById('form-preview')!;
6161const responsesPane = document.getElementById('form-responses-view')!;
6262const toolbar = document.getElementById('form-toolbar')!;
6363+const addQuestionBtn = document.getElementById('btn-add-question')!;
6464+const formHeader = document.getElementById('form-header')!;
63656466// --- Sync form to Yjs ---
6567function syncFormToYjs() {
···9092// --- Mode rendering ---
9193function renderBuilder() {
9294 toolbar.style.display = '';
9595+ // Builder mode shows + Add Question and the editable description textarea.
9696+ addQuestionBtn.style.display = '';
9797+ formHeader.style.display = '';
9398 questionsContainer.style.display = '';
9499 previewPane.style.display = 'none';
95100 responsesPane.style.display = 'none';
···102107103108function showPreview() {
104109 toolbar.style.display = '';
110110+ // Hide builder-only controls so the preview matches what a respondent will
111111+ // see. Keep Preview/Responses/Settings so the author can toggle back. See #715.
112112+ addQuestionBtn.style.display = 'none';
113113+ formHeader.style.display = 'none';
105114 questionsContainer.style.display = 'none';
106115 previewPane.style.display = '';
107116 responsesPane.style.display = 'none';
···115124116125function showResponses() {
117126 toolbar.style.display = '';
127127+ // Responses view is author-only but also shouldn't show the editable
128128+ // description textarea / Add Question button — those belong in Builder mode.
129129+ addQuestionBtn.style.display = 'none';
130130+ formHeader.style.display = 'none';
118131 questionsContainer.style.display = 'none';
119132 previewPane.style.display = 'none';
120133 responsesPane.style.display = '';
+8-1
src/slides/event-handlers.ts
···57575858 $('btn-add-text').addEventListener('click', () => {
5959 const s = actions.getState();
6060- actions.setState({ deck: addElement(s.deck, 'text', 100, 100, 300, 60, 'Click to edit') });
6060+ // Offset each new text box by 20px × (existing element count on this
6161+ // slide) so repeated clicks don't stack boxes at the identical default
6262+ // position where the second would silently cover the first. See #728.
6363+ const slide = s.deck.slides[s.deck.currentSlide];
6464+ const n = slide ? slide.elements.length : 0;
6565+ const x = 100 + (n * 20);
6666+ const y = 100 + (n * 20);
6767+ actions.setState({ deck: addElement(s.deck, 'text', x, y, 300, 60, 'Click to edit') });
6168 actions.syncDeckToYjs();
6269 actions.renderCanvas();
6370 });
+7-1
src/slides/rendering.ts
···3434 preview.style.background = slide.background;
3535 preview.style.aspectRatio = '16/9';
36363737+ // Element count as a corner badge — previously this rendered as bare
3838+ // `<span>{N}</span>` which rendered visually flush against the slide-
3939+ // number above ("slide 1 with 1 element" read as "11", slide 2 with 2
4040+ // elements as "22", etc). See #727. Prefix with the element glyph so
4141+ // the badge reads as "▦ N" and is clearly separate from the slide
4242+ // number.
3743 if (slide.elements.length > 0) {
3838- preview.innerHTML = `<span class="slides-thumb-count">${slide.elements.length}</span>`;
4444+ preview.innerHTML = `<span class="slides-thumb-count" aria-label="${slide.elements.length} element${slide.elements.length === 1 ? '' : 's'}">\u25a6 ${slide.elements.length}</span>`;
3945 }
40464147 thumb.appendChild(num);