Home page: replace inline share-links panel with a proper modal #82

Open
opened 2026-04-27 14:14:28 +00:00 by AhmedHanafy725 · 3 comments
Member

Bug / Improvement

Clicking the share icon on a board card on the home page expands an inline panel inside the card containing two share URLs (View / Edit). The panel:

  • Has tiny click targets for copy (the URL itself is the click target via onclick="copyLink(...)").
  • Shrinks the card content and pushes other card elements around when it opens.
  • Has a custom-built close button (a free-floating × glyph) that doesn't match the rest of the page's modal patterns (rename / new board / new workspace / delete board).
  • Doesn't have explicit Copy buttons, so it's easy for the user to accidentally click somewhere else and dismiss the panel.
  • Opens up to two parallel share.create RPC calls per click; if the user toggles repeatedly the loading state isn't cleared cleanly.

A proper themed modal would match the rest of the page's modals, give each link a clear Copy button + a single Close button, and decouple the share UI from the card layout.

Reproduction

  1. Open the home page.
  2. Click the share icon on any board card.

Observed: an inline panel expands inside the card with two URLs styled as <code> and a &times; close glyph.

Expected: a centered, themed modal opens with the View and Edit URLs, each with a Copy button, and a single Close button.

Root cause / current implementation

crates/hero_whiteboard_ui/templates/web/home.html:

  • The trigger button is at line 267 (onclick="event.stopPropagation();toggleShareLinks(...)").
  • The inline container is rendered into each board card at line 263 (<div class="board-card-links" id="links-...">).
  • The implementation is toggleShareLinks(boardId) at lines 487-543, which:
    • Toggles the inline container's visibility.
    • On first open, calls share.list, then creates missing viewer / editor shares via share.create.
    • Renders the View / Edit URLs as clickable <code> elements.
    • The copyLink(url) helper at lines 545-554 writes to the clipboard and pops a small green toast.

Affected file

  • crates/hero_whiteboard_ui/templates/web/home.html -- replace the inline panel + toggleShareLinks with a modal-driven flow.

No server / SDK / openrpc / DB changes -- pure UI refactor; the same share.list and share.create RPCs are used.

Expected behavior

Clicking the share icon on a board card opens an in-page themed modal that:

  • Shows the board name in the header (e.g. Share "Sprint planning").
  • Lists the two share links (View / Edit) with each link displayed in a read-only input plus a Copy button.
  • Each Copy button copies the URL and gives the same brief green toast confirmation that exists today.
  • Has a single Close button at the bottom right.
  • Surfaces RPC errors inline (no alert).
  • Themed via var(--wb-...) CSS variables (matches rename / new-board / new-workspace / delete-board modals).
  • Escape closes the modal (no need for Enter to do anything since there's no submit).
  • The per-card <div class="board-card-links"> placeholder and the inline-render code are removed (the cards no longer expand on share).

Reference modals

The same file already has four modals to model after:

  • rename-modal (lines 108-128)
  • new-board-modal (lines 60-88)
  • new-workspace-modal (lines 11-29)
  • delete-board-modal (lines 90-106)

Follow the same conventions: centered card on var(--wb-surface) with padding:24px, border-radius:12px, inline error region using var(--wb-error), action buttons aligned right with display:flex;gap:8px;justify-content:flex-end.

Acceptance criteria

  • Clicking the share icon on a board card opens an in-page themed modal (no card expansion, no &times; glyph button).
  • The modal shows the board name in its header.
  • The View URL is shown in a read-only input + a Copy button.
  • The Edit URL is shown in a read-only input + a Copy button.
  • Copy buttons copy to the clipboard and show the existing brief green toast.
  • A single Close button at the bottom right closes the modal.
  • On RPC failure, the error is shown inline inside the modal (no alert).
  • Escape closes the modal.
  • The board card no longer renders the inline board-card-links placeholder.
  • The toggleShareLinks and inline-rendering code are removed; copyLink (and its toast) are kept.
  • Diff is contained to crates/hero_whiteboard_ui/templates/web/home.html.
  • No regression in rename / new-board / new-workspace / delete-board modals or in the rest of the home page.

Notes

  • The board name is already passed to other per-card actions (renameBoard, deleteBoard); pipe it through to the new share opener too so it can appear in the modal header.
  • Cache resolved share URLs per boardId in a small in-page object so re-opening the modal for the same board doesn't re-issue share.list / share.create calls (parity with the current data-loaded short-circuit).
  • Keep using share.list first and only fall back to share.create for the missing role(s) -- preserve the existing behavior so we don't create duplicate shares on every click.
## Bug / Improvement Clicking the share icon on a board card on the home page expands an inline panel inside the card containing two share URLs (View / Edit). The panel: - Has tiny click targets for copy (the URL itself is the click target via `onclick="copyLink(...)"`). - Shrinks the card content and pushes other card elements around when it opens. - Has a custom-built close button (a free-floating `&times;` glyph) that doesn't match the rest of the page's modal patterns (rename / new board / new workspace / delete board). - Doesn't have explicit Copy buttons, so it's easy for the user to accidentally click somewhere else and dismiss the panel. - Opens up to two parallel `share.create` RPC calls per click; if the user toggles repeatedly the loading state isn't cleared cleanly. A proper themed modal would match the rest of the page's modals, give each link a clear Copy button + a single Close button, and decouple the share UI from the card layout. ## Reproduction 1. Open the home page. 2. Click the share icon on any board card. Observed: an inline panel expands inside the card with two URLs styled as `<code>` and a `&times;` close glyph. Expected: a centered, themed modal opens with the View and Edit URLs, each with a Copy button, and a single Close button. ## Root cause / current implementation `crates/hero_whiteboard_ui/templates/web/home.html`: - The trigger button is at line 267 (`onclick="event.stopPropagation();toggleShareLinks(...)"`). - The inline container is rendered into each board card at line 263 (`<div class="board-card-links" id="links-...">`). - The implementation is `toggleShareLinks(boardId)` at lines 487-543, which: - Toggles the inline container's visibility. - On first open, calls `share.list`, then creates missing `viewer` / `editor` shares via `share.create`. - Renders the View / Edit URLs as clickable `<code>` elements. - The `copyLink(url)` helper at lines 545-554 writes to the clipboard and pops a small green toast. ## Affected file - `crates/hero_whiteboard_ui/templates/web/home.html` -- replace the inline panel + `toggleShareLinks` with a modal-driven flow. No server / SDK / openrpc / DB changes -- pure UI refactor; the same `share.list` and `share.create` RPCs are used. ## Expected behavior Clicking the share icon on a board card opens an in-page themed modal that: - Shows the board name in the header (e.g. `Share "Sprint planning"`). - Lists the two share links (View / Edit) with each link displayed in a read-only input plus a Copy button. - Each Copy button copies the URL and gives the same brief green toast confirmation that exists today. - Has a single Close button at the bottom right. - Surfaces RPC errors inline (no `alert`). - Themed via `var(--wb-...)` CSS variables (matches rename / new-board / new-workspace / delete-board modals). - Escape closes the modal (no need for Enter to do anything since there's no submit). - The per-card `<div class="board-card-links">` placeholder and the inline-render code are removed (the cards no longer expand on share). ## Reference modals The same file already has four modals to model after: - `rename-modal` (lines 108-128) - `new-board-modal` (lines 60-88) - `new-workspace-modal` (lines 11-29) - `delete-board-modal` (lines 90-106) Follow the same conventions: centered card on `var(--wb-surface)` with `padding:24px`, `border-radius:12px`, inline error region using `var(--wb-error)`, action buttons aligned right with `display:flex;gap:8px;justify-content:flex-end`. ## Acceptance criteria - [ ] Clicking the share icon on a board card opens an in-page themed modal (no card expansion, no `&times;` glyph button). - [ ] The modal shows the board name in its header. - [ ] The View URL is shown in a read-only input + a Copy button. - [ ] The Edit URL is shown in a read-only input + a Copy button. - [ ] Copy buttons copy to the clipboard and show the existing brief green toast. - [ ] A single Close button at the bottom right closes the modal. - [ ] On RPC failure, the error is shown inline inside the modal (no `alert`). - [ ] Escape closes the modal. - [ ] The board card no longer renders the inline `board-card-links` placeholder. - [ ] The `toggleShareLinks` and inline-rendering code are removed; `copyLink` (and its toast) are kept. - [ ] Diff is contained to `crates/hero_whiteboard_ui/templates/web/home.html`. - [ ] No regression in rename / new-board / new-workspace / delete-board modals or in the rest of the home page. ## Notes - The board name is already passed to other per-card actions (`renameBoard`, `deleteBoard`); pipe it through to the new share opener too so it can appear in the modal header. - Cache resolved share URLs per `boardId` in a small in-page object so re-opening the modal for the same board doesn't re-issue `share.list` / `share.create` calls (parity with the current `data-loaded` short-circuit). - Keep using `share.list` first and only fall back to `share.create` for the missing role(s) -- preserve the existing behavior so we don't create duplicate shares on every click.
Author
Member

Implementation Spec for Issue #82

Objective

Replace the inline share-links panel that expands inside each board card with a centered, themed modal that lists the View / Edit links with explicit Copy buttons and a single Close button — matching the rest of the home page's modal patterns.

Requirements

  • Clicking the share icon on a board card opens an in-page modal (no card expansion).
  • Modal header shows the board name (Share "<name>").
  • Modal body shows two read-only inputs (View URL and Edit URL), each followed by a Copy button.
  • Copy uses the existing copyLink(url) helper so the brief green toast confirmation is preserved.
  • A single Close button at the bottom right closes the modal.
  • RPC failures show inline (var(--wb-error)) inside the modal — no alert, modal stays open.
  • Escape closes the modal.
  • Modal is themed via var(--wb-...) CSS variables.
  • The board card no longer renders the inline board-card-links placeholder.
  • The toggleShareLinks function and the inline-render code are removed; copyLink (and its toast) stay.
  • A small boardId -> { viewUrl, editUrl } cache avoids re-issuing share.list / share.create calls when the same board's modal is opened more than once (parity with the current data-loaded short-circuit).
  • Diff stays inside crates/hero_whiteboard_ui/templates/web/home.html.

Files to Modify

  • crates/hero_whiteboard_ui/templates/web/home.html — add the modal markup, replace toggleShareLinks with an openShareModal(boardId, name) flow, drop the inline board-card-links placeholder, change the share button's onclick, and extend the Escape keydown handler.

No server / SDK / openrpc / DB changes — same share.list / share.create RPCs are used.

Implementation Plan

Files: crates/hero_whiteboard_ui/templates/web/home.html

  1. Modal markup. In the {% block content %} section (next to the existing modals), add:

    <!-- Share Board modal -->
    <div id="share-board-modal"
        style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;">
        <div
            style="background:var(--wb-surface);border:1px solid var(--wb-border);border-radius:12px;padding:24px;width:480px;max-width:90vw;">
            <h3 id="share-board-title" style="margin:0 0 16px;font-size:16px;">Share Board</h3>
            <div id="share-board-body">
                <div style="font-size:12px;color:var(--wb-text-muted);">Loading...</div>
            </div>
            <div id="share-board-error" style="margin-top:8px;color:var(--wb-error);font-size:12px;display:none;"></div>
            <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px;">
                <button class="btn btn-sm" onclick="closeShareModal()"
                    style="background:var(--wb-bg);border:1px solid var(--wb-border);">Close</button>
            </div>
        </div>
    </div>
    

    The body is populated dynamically by openShareModal — initially shows a Loading placeholder, then either the two View/Edit rows or the inline error.

  2. Module-scope cache. Add near the top of the script block alongside renamingBoardId / deletingBoardId:

    var shareUrlsCache = {}; // boardId (number) -> { viewUrl, editUrl }
    
  3. Replace the inline placeholder. Remove the <div class="board-card-links" id="links-..."> line from the per-card card.innerHTML template (currently around line 263). It's no longer needed.

  4. Change the share button's onclick (currently toggleShareLinks(...) around line 267) to:

    onclick="event.stopPropagation();openShareModal('+ board.id +','+ <board name as a JS string>+ ')"
    

    Pass the board name through escapeAttr so it survives the inline-string dance, the same way the rename and delete buttons do.

  5. Replace toggleShareLinks with the modal flow (delete lines ~487-543 and add):

    async function openShareModal(boardId, name) {
        boardId = parseInt(boardId, 10);
        var modal = document.getElementById('share-board-modal');
        var titleEl = document.getElementById('share-board-title');
        var body = document.getElementById('share-board-body');
        var err = document.getElementById('share-board-error');
        titleEl.textContent = 'Share "' + (name || 'this board') + '"';
        err.textContent = ''; err.style.display = 'none';
        modal.style.display = 'flex';
    
        function renderRows(viewUrl, editUrl) {
            // Two rows: each is a label + read-only input + Copy button.
            // Inline-style to match the rest of the modals on the page.
            body.innerHTML = '' +
                '<div>' +
                    '<label style="display:block;margin-bottom:4px;font-size:12px;color:var(--wb-text-muted);">View link (read-only)</label>' +
                    '<div style="display:flex;gap:8px;">' +
                        '<input type="text" readonly value="'+ escapeAttr(viewUrl) +'" ' +
                            'style="flex:1;display:block;box-sizing:border-box;width:100%;padding:8px 12px;border:1px solid var(--wb-border);border-radius:6px;background:var(--wb-bg);color:var(--wb-text);font-size:13px;" />' +
                        '<button class="btn btn-sm btn-primary" onclick="copyLink(\''+ escapeAttr(viewUrl) +'\')">Copy</button>' +
                    '</div>' +
                '</div>' +
                '<div style="margin-top:12px;">' +
                    '<label style="display:block;margin-bottom:4px;font-size:12px;color:var(--wb-text-muted);">Edit link</label>' +
                    '<div style="display:flex;gap:8px;">' +
                        '<input type="text" readonly value="'+ escapeAttr(editUrl) +'" ' +
                            'style="flex:1;display:block;box-sizing:border-box;width:100%;padding:8px 12px;border:1px solid var(--wb-border);border-radius:6px;background:var(--wb-bg);color:var(--wb-text);font-size:13px;" />' +
                        '<button class="btn btn-sm btn-primary" onclick="copyLink(\''+ escapeAttr(editUrl) +'\')">Copy</button>' +
                    '</div>' +
                '</div>';
        }
    
        // Cache hit: render immediately, no RPC.
        var cached = shareUrlsCache[boardId];
        if (cached) { renderRows(cached.viewUrl, cached.editUrl); return; }
    
        body.innerHTML = '<div style="font-size:12px;color:var(--wb-text-muted);">Loading...</div>';
        try {
            var shares = await rpcCall('share.list', { board_id: boardId });
            var viewerShare = null, editorShare = null;
            if (shares && shares.length) {
                for (var i = 0; i < shares.length; i++) {
                    if (shares[i].role === 'viewer' && !viewerShare) viewerShare = shares[i];
                    if (shares[i].role === 'editor' && !editorShare) editorShare = shares[i];
                }
            }
            if (!viewerShare) viewerShare = await rpcCall('share.create', { board_id: boardId, role: 'viewer' });
            if (!editorShare) editorShare = await rpcCall('share.create', { board_id: boardId, role: 'editor' });
            var viewUrl = window.location.origin + WB_BASE + '/s/' + (viewerShare.token || viewerShare.id);
            var editUrl = window.location.origin + WB_BASE + '/s/' + (editorShare.token || editorShare.id);
            shareUrlsCache[boardId] = { viewUrl: viewUrl, editUrl: editUrl };
            renderRows(viewUrl, editUrl);
        } catch (e) {
            body.innerHTML = '';
            err.textContent = 'Failed to load share links: ' + (e && e.message ? e.message : e);
            err.style.display = 'block';
        }
    }
    
    function closeShareModal() {
        document.getElementById('share-board-modal').style.display = 'none';
    }
    
  6. Extend the keydown handler. Add an Escape branch for the share modal alongside the others:

    var shModal = document.getElementById('share-board-modal');
    if (shModal && shModal.style.display === 'flex') {
        if (e.key === 'Escape') closeShareModal();
    }
    
  7. Keep copyLink unchanged.

Dependencies: none.

Acceptance Criteria

  • Clicking the share icon opens an in-page themed modal (no card expansion).
  • Modal header shows the board name (Share "<name>").
  • Modal lists two rows: View URL with Copy button, Edit URL with Copy button.
  • Each Copy button copies the URL and shows the existing brief green toast.
  • A single Close button at the bottom right closes the modal.
  • RPC failures show inline inside the modal (no window.alert); the modal stays open.
  • Escape closes the modal.
  • Modal is themed via var(--wb-...) (looks correct in dark + light mode).
  • Re-opening the modal for the same board does not re-issue share.list / share.create (cache hit).
  • Each board card no longer renders the inline board-card-links placeholder.
  • toggleShareLinks and its inline-rendering code are removed; copyLink (and its toast) stay.
  • No regression in rename / new-board / new-workspace / delete-board modals.
  • Diff is contained to crates/hero_whiteboard_ui/templates/web/home.html.

Notes

  • The board card row's Loading placeholder is now inside the modal, not inside the card — the card stays the same height when the user clicks share.
  • The cache key is the parsed board id (Number); the cache lives for the page lifetime, so a hard refresh re-fetches as before.
  • Use escapeAttr (already defined in the same script) when interpolating the URL and board name into HTML strings to avoid quote/special-character issues in the inline onclick and value attributes.
## Implementation Spec for Issue #82 ### Objective Replace the inline share-links panel that expands inside each board card with a centered, themed modal that lists the View / Edit links with explicit Copy buttons and a single Close button — matching the rest of the home page's modal patterns. ### Requirements - Clicking the share icon on a board card opens an in-page modal (no card expansion). - Modal header shows the board name (`Share "<name>"`). - Modal body shows two read-only inputs (View URL and Edit URL), each followed by a Copy button. - Copy uses the existing `copyLink(url)` helper so the brief green toast confirmation is preserved. - A single Close button at the bottom right closes the modal. - RPC failures show inline (`var(--wb-error)`) inside the modal — no `alert`, modal stays open. - Escape closes the modal. - Modal is themed via `var(--wb-...)` CSS variables. - The board card no longer renders the inline `board-card-links` placeholder. - The `toggleShareLinks` function and the inline-render code are removed; `copyLink` (and its toast) stay. - A small `boardId -> { viewUrl, editUrl }` cache avoids re-issuing `share.list` / `share.create` calls when the same board's modal is opened more than once (parity with the current `data-loaded` short-circuit). - Diff stays inside `crates/hero_whiteboard_ui/templates/web/home.html`. ### Files to Modify - `crates/hero_whiteboard_ui/templates/web/home.html` — add the modal markup, replace `toggleShareLinks` with an `openShareModal(boardId, name)` flow, drop the inline `board-card-links` placeholder, change the share button's `onclick`, and extend the Escape keydown handler. No server / SDK / openrpc / DB changes — same `share.list` / `share.create` RPCs are used. ### Implementation Plan #### Step 1: Add the share modal markup, replace toggleShareLinks, drop the inline panel, and extend the keydown handler Files: `crates/hero_whiteboard_ui/templates/web/home.html` 1. **Modal markup.** In the `{% block content %}` section (next to the existing modals), add: ```html <!-- Share Board modal --> <div id="share-board-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:9999;align-items:center;justify-content:center;"> <div style="background:var(--wb-surface);border:1px solid var(--wb-border);border-radius:12px;padding:24px;width:480px;max-width:90vw;"> <h3 id="share-board-title" style="margin:0 0 16px;font-size:16px;">Share Board</h3> <div id="share-board-body"> <div style="font-size:12px;color:var(--wb-text-muted);">Loading...</div> </div> <div id="share-board-error" style="margin-top:8px;color:var(--wb-error);font-size:12px;display:none;"></div> <div style="display:flex;gap:8px;justify-content:flex-end;margin-top:16px;"> <button class="btn btn-sm" onclick="closeShareModal()" style="background:var(--wb-bg);border:1px solid var(--wb-border);">Close</button> </div> </div> </div> ``` The body is populated dynamically by `openShareModal` — initially shows a Loading placeholder, then either the two View/Edit rows or the inline error. 2. **Module-scope cache.** Add near the top of the script block alongside `renamingBoardId` / `deletingBoardId`: ```js var shareUrlsCache = {}; // boardId (number) -> { viewUrl, editUrl } ``` 3. **Replace the inline placeholder.** Remove the `<div class="board-card-links" id="links-...">` line from the per-card `card.innerHTML` template (currently around line 263). It's no longer needed. 4. **Change the share button's onclick** (currently `toggleShareLinks(...)` around line 267) to: ```js onclick="event.stopPropagation();openShareModal('+ board.id +','+ <board name as a JS string>+ ')" ``` Pass the board name through `escapeAttr` so it survives the inline-string dance, the same way the rename and delete buttons do. 5. **Replace `toggleShareLinks` with the modal flow** (delete lines ~487-543 and add): ```js async function openShareModal(boardId, name) { boardId = parseInt(boardId, 10); var modal = document.getElementById('share-board-modal'); var titleEl = document.getElementById('share-board-title'); var body = document.getElementById('share-board-body'); var err = document.getElementById('share-board-error'); titleEl.textContent = 'Share "' + (name || 'this board') + '"'; err.textContent = ''; err.style.display = 'none'; modal.style.display = 'flex'; function renderRows(viewUrl, editUrl) { // Two rows: each is a label + read-only input + Copy button. // Inline-style to match the rest of the modals on the page. body.innerHTML = '' + '<div>' + '<label style="display:block;margin-bottom:4px;font-size:12px;color:var(--wb-text-muted);">View link (read-only)</label>' + '<div style="display:flex;gap:8px;">' + '<input type="text" readonly value="'+ escapeAttr(viewUrl) +'" ' + 'style="flex:1;display:block;box-sizing:border-box;width:100%;padding:8px 12px;border:1px solid var(--wb-border);border-radius:6px;background:var(--wb-bg);color:var(--wb-text);font-size:13px;" />' + '<button class="btn btn-sm btn-primary" onclick="copyLink(\''+ escapeAttr(viewUrl) +'\')">Copy</button>' + '</div>' + '</div>' + '<div style="margin-top:12px;">' + '<label style="display:block;margin-bottom:4px;font-size:12px;color:var(--wb-text-muted);">Edit link</label>' + '<div style="display:flex;gap:8px;">' + '<input type="text" readonly value="'+ escapeAttr(editUrl) +'" ' + 'style="flex:1;display:block;box-sizing:border-box;width:100%;padding:8px 12px;border:1px solid var(--wb-border);border-radius:6px;background:var(--wb-bg);color:var(--wb-text);font-size:13px;" />' + '<button class="btn btn-sm btn-primary" onclick="copyLink(\''+ escapeAttr(editUrl) +'\')">Copy</button>' + '</div>' + '</div>'; } // Cache hit: render immediately, no RPC. var cached = shareUrlsCache[boardId]; if (cached) { renderRows(cached.viewUrl, cached.editUrl); return; } body.innerHTML = '<div style="font-size:12px;color:var(--wb-text-muted);">Loading...</div>'; try { var shares = await rpcCall('share.list', { board_id: boardId }); var viewerShare = null, editorShare = null; if (shares && shares.length) { for (var i = 0; i < shares.length; i++) { if (shares[i].role === 'viewer' && !viewerShare) viewerShare = shares[i]; if (shares[i].role === 'editor' && !editorShare) editorShare = shares[i]; } } if (!viewerShare) viewerShare = await rpcCall('share.create', { board_id: boardId, role: 'viewer' }); if (!editorShare) editorShare = await rpcCall('share.create', { board_id: boardId, role: 'editor' }); var viewUrl = window.location.origin + WB_BASE + '/s/' + (viewerShare.token || viewerShare.id); var editUrl = window.location.origin + WB_BASE + '/s/' + (editorShare.token || editorShare.id); shareUrlsCache[boardId] = { viewUrl: viewUrl, editUrl: editUrl }; renderRows(viewUrl, editUrl); } catch (e) { body.innerHTML = ''; err.textContent = 'Failed to load share links: ' + (e && e.message ? e.message : e); err.style.display = 'block'; } } function closeShareModal() { document.getElementById('share-board-modal').style.display = 'none'; } ``` 6. **Extend the keydown handler.** Add an Escape branch for the share modal alongside the others: ```js var shModal = document.getElementById('share-board-modal'); if (shModal && shModal.style.display === 'flex') { if (e.key === 'Escape') closeShareModal(); } ``` 7. **Keep `copyLink` unchanged.** Dependencies: none. ### Acceptance Criteria - [ ] Clicking the share icon opens an in-page themed modal (no card expansion). - [ ] Modal header shows the board name (`Share "<name>"`). - [ ] Modal lists two rows: View URL with Copy button, Edit URL with Copy button. - [ ] Each Copy button copies the URL and shows the existing brief green toast. - [ ] A single Close button at the bottom right closes the modal. - [ ] RPC failures show inline inside the modal (no `window.alert`); the modal stays open. - [ ] Escape closes the modal. - [ ] Modal is themed via `var(--wb-...)` (looks correct in dark + light mode). - [ ] Re-opening the modal for the same board does not re-issue `share.list` / `share.create` (cache hit). - [ ] Each board card no longer renders the inline `board-card-links` placeholder. - [ ] `toggleShareLinks` and its inline-rendering code are removed; `copyLink` (and its toast) stay. - [ ] No regression in rename / new-board / new-workspace / delete-board modals. - [ ] Diff is contained to `crates/hero_whiteboard_ui/templates/web/home.html`. ### Notes - The board card row's Loading placeholder is now inside the modal, not inside the card — the card stays the same height when the user clicks share. - The cache key is the parsed board id (`Number`); the cache lives for the page lifetime, so a hard refresh re-fetches as before. - Use `escapeAttr` (already defined in the same script) when interpolating the URL and board name into HTML strings to avoid quote/special-character issues in the inline `onclick` and `value` attributes.
Author
Member

Test Results

  • cargo test --workspace --lib: 4 lib targets, 0 tests / 0 passed / 0 failed each.
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo check --workspace: clean (Askama compiled the template).

Template-only change. Manual verification recommended:

  1. Open the home page, click the share icon on a board card. Confirm a centered themed modal appears (no card expansion) with the board name in the header (Share "<name>").
  2. Confirm two read-only inputs are shown (View link + Edit link) each with a Copy button beside it.
  3. Click each Copy button — confirm the brief green toast appears and the URL is on the clipboard.
  4. Click Close (or press Escape) — confirm the modal closes.
  5. Re-open the same board's share modal — confirm it appears immediately with no Loading state (cache hit; no extra share.list / share.create RPC).
  6. Force an RPC failure (stop the server) and click share — confirm an inline error appears in the modal and the modal stays open.
  7. Repeat in dark + light themes.
## Test Results - `cargo test --workspace --lib`: 4 lib targets, 0 tests / 0 passed / 0 failed each. - `cargo clippy --workspace -- -D warnings`: clean. - `cargo check --workspace`: clean (Askama compiled the template). Template-only change. Manual verification recommended: 1. Open the home page, click the share icon on a board card. Confirm a centered themed modal appears (no card expansion) with the board name in the header (`Share "<name>"`). 2. Confirm two read-only inputs are shown (View link + Edit link) each with a Copy button beside it. 3. Click each Copy button — confirm the brief green toast appears and the URL is on the clipboard. 4. Click Close (or press Escape) — confirm the modal closes. 5. Re-open the same board's share modal — confirm it appears immediately with no Loading state (cache hit; no extra `share.list` / `share.create` RPC). 6. Force an RPC failure (stop the server) and click share — confirm an inline error appears in the modal and the modal stays open. 7. Repeat in dark + light themes.
Author
Member

Implementation Summary

Changes

  • crates/hero_whiteboard_ui/templates/web/home.html (+68 / -41)
    • Added a share-board-modal block in the content section, styled like the rest of the page's modals (480px-wide centered card on var(--wb-surface), padding:24px, inline error region, single Close button at bottom right).
    • Added var shareUrlsCache = {}; so re-opening a board's share modal doesn't re-issue share.list / share.create calls.
    • Removed the per-card <div class="board-card-links" id="links-..."> placeholder from the card template — share UI is no longer inline.
    • Updated the share button's onclick to call openShareModal(boardId, name); passes the board name through escapeAttr so the modal can show Share "<name>" in the header.
    • Replaced toggleShareLinks with openShareModal(boardId, name) and closeShareModal(). The new function:
      • Sets the modal title to Share "<board name>".
      • Cache-hit: renders the two URL rows immediately, no RPC.
      • Cache-miss: shows a small Loading placeholder, calls share.list, fills any missing role with share.create, caches the resulting URLs, and renders.
      • On RPC failure: shows an inline error inside the modal and keeps it open (no alert).
    • Each row is a label + read-only <input> + a Copy button that calls the existing copyLink(url) helper (preserves the brief green toast).
    • Extended the existing keydown handler to close the share modal on Escape.
    • copyLink and its toast are unchanged.

Verification

  • cargo test --workspace --lib: passes (4 lib targets, 0 tests).
  • cargo clippy --workspace -- -D warnings: clean.
  • cargo check --workspace: clean.

Notes / caveats

  • Template-only change; no Rust / SDK / openrpc / DB edits.
  • Same share.list and share.create RPCs are used.
  • The cache lives for the page lifetime; a hard refresh re-fetches.
  • The board card no longer expands when you click share, so the grid layout is more stable.
## Implementation Summary ### Changes - `crates/hero_whiteboard_ui/templates/web/home.html` (+68 / -41) - Added a `share-board-modal` block in the content section, styled like the rest of the page's modals (480px-wide centered card on `var(--wb-surface)`, `padding:24px`, inline error region, single Close button at bottom right). - Added `var shareUrlsCache = {};` so re-opening a board's share modal doesn't re-issue `share.list` / `share.create` calls. - Removed the per-card `<div class="board-card-links" id="links-...">` placeholder from the card template — share UI is no longer inline. - Updated the share button's `onclick` to call `openShareModal(boardId, name)`; passes the board name through `escapeAttr` so the modal can show `Share "<name>"` in the header. - Replaced `toggleShareLinks` with `openShareModal(boardId, name)` and `closeShareModal()`. The new function: - Sets the modal title to `Share "<board name>"`. - Cache-hit: renders the two URL rows immediately, no RPC. - Cache-miss: shows a small Loading placeholder, calls `share.list`, fills any missing role with `share.create`, caches the resulting URLs, and renders. - On RPC failure: shows an inline error inside the modal and keeps it open (no `alert`). - Each row is a label + read-only `<input>` + a `Copy` button that calls the existing `copyLink(url)` helper (preserves the brief green toast). - Extended the existing keydown handler to close the share modal on Escape. - `copyLink` and its toast are unchanged. ### Verification - `cargo test --workspace --lib`: passes (4 lib targets, 0 tests). - `cargo clippy --workspace -- -D warnings`: clean. - `cargo check --workspace`: clean. ### Notes / caveats - Template-only change; no Rust / SDK / openrpc / DB edits. - Same `share.list` and `share.create` RPCs are used. - The cache lives for the page lifetime; a hard refresh re-fetches. - The board card no longer expands when you click share, so the grid layout is more stable.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_whiteboard#82
No description provided.