make preview context #11

Open
opened 2026-04-22 05:21:30 +00:00 by despiegk · 4 comments
Owner

image

do right mouse to preview a context, use same modal

do preview at:

image

when context open, then do modal, where we see full context

![image](/attachments/fd1fd37f-cdb5-4b31-9ed6-bf86039347d0) do right mouse to preview a context, use same modal do preview at: ![image](/attachments/e4cd4103-7103-4d73-80e2-d2ff67e2d6f7) when context open, then do modal, where we see full context
Owner

Implementation Spec for Issue #11 — "Make Preview Context"

Objective

Add a "Preview" action to the right-click context menu on each context item in the left-hand context list panel. When triggered, the action calls the existing context.render RPC method and displays the returned markdown text inside a Bootstrap modal.

Architecture Notes

The UI is a server-side-rendered Askama/HTML + vanilla JavaScript application served by an Axum web server. No Rust/server-side changes are needed — context.render RPC already exists and returns { text, file_count, byte_count }.

Requirements

  • Right-clicking a context list item shows a context menu with: Rename | separator | Preview | separator | Delete
  • "Preview" calls context.render({ id }) and displays the result in a modal
  • The modal shows the context name as title, a file/byte summary badge, and the rendered text in a scrollable body
  • The modal uses the same Bootstrap .modal.fade pattern already present in the app
  • A loading state is shown while the RPC call is in flight
  • On error, a danger toast is shown and the modal is closed
  • A Copy button in the modal header lets users copy the rendered text

Files to Modify

File Change
crates/hero_code_ui/templates/index.html Add the context-preview Bootstrap modal HTML element
crates/hero_code_ui/static/js/contexts.js Add "Preview" to right-click menu; add ctxPreviewContext(id, name) function

Implementation Plan

Step 1 — Add modal HTML to index.html

File: crates/hero_code_ui/templates/index.html

Insert immediately after the <!-- IDE OUTPUT MODAL --> block (around line 969), before the Bootstrap bundle script tag.

<!-- CONTEXT PREVIEW MODAL -->
<div class="modal fade" id="ctxPreviewModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-xl modal-dialog-scrollable" style="max-width:88vw;height:88vh;margin:3vh auto">
    <div class="modal-content" style="height:100%">
      <div class="modal-header py-2 px-3">
        <i class="bi bi-folder2-open me-2" style="color:var(--accent-blue)"></i>
        <span id="ctx-preview-title" class="fw-semibold" style="font-size:0.9rem">Context Preview</span>
        <span id="ctx-preview-badge" class="badge bg-secondary ms-2" style="font-size:0.72rem"></span>
        <div class="ms-auto d-flex align-items-center gap-2">
          <button type="button" class="btn btn-sm btn-outline-secondary" id="ctx-preview-copy-btn" style="font-size:0.75rem;display:none">
            <i class="bi bi-clipboard"></i> Copy
          </button>
          <button type="button" class="btn-close" data-bs-dismiss="modal" style="font-size:0.7rem"></button>
        </div>
      </div>
      <div class="modal-body p-0" style="overflow:hidden;display:flex;flex-direction:column">
        <div id="ctx-preview-body" style="flex:1;overflow-y:auto;padding:0.8rem 1rem;font-family:Menlo,Monaco,'Courier New',monospace;font-size:0.82rem;line-height:1.5;white-space:pre-wrap;word-break:break-word"></div>
      </div>
    </div>
  </div>
</div>

Step 2 — Add ctxPreviewContext function to contexts.js

Append after the last function in crates/hero_code_ui/static/js/contexts.js:

async function ctxPreviewContext(id, name) {
    var modalEl = document.getElementById("ctxPreviewModal");
    if (!modalEl || typeof bootstrap === "undefined") return;

    var titleEl = document.getElementById("ctx-preview-title");
    var badgeEl = document.getElementById("ctx-preview-badge");
    var bodyEl  = document.getElementById("ctx-preview-body");
    var copyBtn = document.getElementById("ctx-preview-copy-btn");

    if (titleEl) titleEl.textContent = name || "Context Preview";
    if (badgeEl) { badgeEl.textContent = "Loading…"; badgeEl.className = "badge bg-secondary ms-2"; }
    if (bodyEl)  bodyEl.textContent = "";
    if (copyBtn) copyBtn.style.display = "none";

    var modal = bootstrap.Modal.getOrCreateInstance(modalEl);
    modal.show();

    try {
        var result = await rpcCall("context.render", { id: id });
        var text = result && result.text ? result.text : "(empty)";
        var fileCount = result && result.file_count != null ? result.file_count : 0;
        var byteCount = result && result.byte_count != null ? result.byte_count : 0;

        if (bodyEl) bodyEl.textContent = text;
        if (badgeEl) {
            badgeEl.textContent = fileCount + " file" + (fileCount !== 1 ? "s" : "") +
                " · " + (byteCount >= 1024
                    ? Math.round(byteCount / 1024) + " KB"
                    : byteCount + " B");
            badgeEl.className = "badge bg-primary ms-2";
        }
        if (copyBtn) {
            copyBtn.style.display = "";
            var newBtn = copyBtn.cloneNode(true);
            copyBtn.parentNode.replaceChild(newBtn, copyBtn);
            newBtn.addEventListener("click", function () {
                if (typeof copyToClipboard === "function") {
                    copyToClipboard(text, "Context copied to clipboard");
                }
            });
        }
    } catch (e) {
        console.error("ctxPreviewContext error", e);
        if (typeof toast === "function") toast("Failed to preview: " + e.message, "danger");
        modal.hide();
    }
}

Step 3 — Add "Preview" to the right-click context menu in contexts.js

Inside ctxRenderList(), in the contextmenu event listener, replace the items array:

Before:

[
    { icon: "bi-pencil", label: "Rename", action: function() { ctxRenameContext(id, name); } },
    { separator: true },
    { icon: "bi-trash3", label: "Delete", danger: true, action: function() { ctxDeleteById(id, name); } },
]

After:

[
    { icon: "bi-pencil", label: "Rename", action: function() { ctxRenameContext(id, name); } },
    { separator: true },
    { icon: "bi-eye", label: "Preview", action: function() { ctxPreviewContext(id, name); } },
    { separator: true },
    { icon: "bi-trash3", label: "Delete", danger: true, action: function() { ctxDeleteById(id, name); } },
]

Acceptance Criteria

  • Right-clicking any context list item shows a context menu with a "Preview" entry between Rename and Delete
  • Clicking "Preview" opens the Bootstrap modal immediately, showing context name and "Loading…"
  • After RPC resolves, modal body shows the full rendered text and badge shows file/byte count
  • Copy button appears after load; clicking it copies text to clipboard with a success toast
  • Escape / backdrop / × closes the modal (Bootstrap default behavior)
  • On RPC failure, a danger toast is shown and the modal is closed
  • No conflict with existing ideOutputModal or jobDetailModal

Notes

  • context.render RPC already exists server-side — no backend changes needed
  • white-space:pre-wrap on the body div renders markdown/code-fence text legibly without XSS risk (textContent not innerHTML)
  • The cloneNode copy-button pattern prevents duplicate event listeners on repeated modal opens
## Implementation Spec for Issue #11 — "Make Preview Context" ### Objective Add a "Preview" action to the right-click context menu on each context item in the left-hand context list panel. When triggered, the action calls the existing `context.render` RPC method and displays the returned markdown text inside a Bootstrap modal. ### Architecture Notes The UI is a server-side-rendered Askama/HTML + vanilla JavaScript application served by an Axum web server. No Rust/server-side changes are needed — `context.render` RPC already exists and returns `{ text, file_count, byte_count }`. ### Requirements - Right-clicking a context list item shows a context menu with: Rename | separator | **Preview** | separator | Delete - "Preview" calls `context.render({ id })` and displays the result in a modal - The modal shows the context name as title, a file/byte summary badge, and the rendered text in a scrollable body - The modal uses the same Bootstrap `.modal.fade` pattern already present in the app - A loading state is shown while the RPC call is in flight - On error, a danger toast is shown and the modal is closed - A Copy button in the modal header lets users copy the rendered text ### Files to Modify | File | Change | |---|---| | `crates/hero_code_ui/templates/index.html` | Add the context-preview Bootstrap modal HTML element | | `crates/hero_code_ui/static/js/contexts.js` | Add "Preview" to right-click menu; add `ctxPreviewContext(id, name)` function | ### Implementation Plan #### Step 1 — Add modal HTML to `index.html` **File:** `crates/hero_code_ui/templates/index.html` Insert immediately after the `<!-- IDE OUTPUT MODAL -->` block (around line 969), before the Bootstrap bundle script tag. ```html <!-- CONTEXT PREVIEW MODAL --> <div class="modal fade" id="ctxPreviewModal" tabindex="-1" aria-hidden="true"> <div class="modal-dialog modal-xl modal-dialog-scrollable" style="max-width:88vw;height:88vh;margin:3vh auto"> <div class="modal-content" style="height:100%"> <div class="modal-header py-2 px-3"> <i class="bi bi-folder2-open me-2" style="color:var(--accent-blue)"></i> <span id="ctx-preview-title" class="fw-semibold" style="font-size:0.9rem">Context Preview</span> <span id="ctx-preview-badge" class="badge bg-secondary ms-2" style="font-size:0.72rem"></span> <div class="ms-auto d-flex align-items-center gap-2"> <button type="button" class="btn btn-sm btn-outline-secondary" id="ctx-preview-copy-btn" style="font-size:0.75rem;display:none"> <i class="bi bi-clipboard"></i> Copy </button> <button type="button" class="btn-close" data-bs-dismiss="modal" style="font-size:0.7rem"></button> </div> </div> <div class="modal-body p-0" style="overflow:hidden;display:flex;flex-direction:column"> <div id="ctx-preview-body" style="flex:1;overflow-y:auto;padding:0.8rem 1rem;font-family:Menlo,Monaco,'Courier New',monospace;font-size:0.82rem;line-height:1.5;white-space:pre-wrap;word-break:break-word"></div> </div> </div> </div> </div> ``` #### Step 2 — Add `ctxPreviewContext` function to `contexts.js` Append after the last function in `crates/hero_code_ui/static/js/contexts.js`: ```javascript async function ctxPreviewContext(id, name) { var modalEl = document.getElementById("ctxPreviewModal"); if (!modalEl || typeof bootstrap === "undefined") return; var titleEl = document.getElementById("ctx-preview-title"); var badgeEl = document.getElementById("ctx-preview-badge"); var bodyEl = document.getElementById("ctx-preview-body"); var copyBtn = document.getElementById("ctx-preview-copy-btn"); if (titleEl) titleEl.textContent = name || "Context Preview"; if (badgeEl) { badgeEl.textContent = "Loading…"; badgeEl.className = "badge bg-secondary ms-2"; } if (bodyEl) bodyEl.textContent = ""; if (copyBtn) copyBtn.style.display = "none"; var modal = bootstrap.Modal.getOrCreateInstance(modalEl); modal.show(); try { var result = await rpcCall("context.render", { id: id }); var text = result && result.text ? result.text : "(empty)"; var fileCount = result && result.file_count != null ? result.file_count : 0; var byteCount = result && result.byte_count != null ? result.byte_count : 0; if (bodyEl) bodyEl.textContent = text; if (badgeEl) { badgeEl.textContent = fileCount + " file" + (fileCount !== 1 ? "s" : "") + " · " + (byteCount >= 1024 ? Math.round(byteCount / 1024) + " KB" : byteCount + " B"); badgeEl.className = "badge bg-primary ms-2"; } if (copyBtn) { copyBtn.style.display = ""; var newBtn = copyBtn.cloneNode(true); copyBtn.parentNode.replaceChild(newBtn, copyBtn); newBtn.addEventListener("click", function () { if (typeof copyToClipboard === "function") { copyToClipboard(text, "Context copied to clipboard"); } }); } } catch (e) { console.error("ctxPreviewContext error", e); if (typeof toast === "function") toast("Failed to preview: " + e.message, "danger"); modal.hide(); } } ``` #### Step 3 — Add "Preview" to the right-click context menu in `contexts.js` Inside `ctxRenderList()`, in the `contextmenu` event listener, replace the items array: **Before:** ```javascript [ { icon: "bi-pencil", label: "Rename", action: function() { ctxRenameContext(id, name); } }, { separator: true }, { icon: "bi-trash3", label: "Delete", danger: true, action: function() { ctxDeleteById(id, name); } }, ] ``` **After:** ```javascript [ { icon: "bi-pencil", label: "Rename", action: function() { ctxRenameContext(id, name); } }, { separator: true }, { icon: "bi-eye", label: "Preview", action: function() { ctxPreviewContext(id, name); } }, { separator: true }, { icon: "bi-trash3", label: "Delete", danger: true, action: function() { ctxDeleteById(id, name); } }, ] ``` ### Acceptance Criteria - [ ] Right-clicking any context list item shows a context menu with a "Preview" entry between Rename and Delete - [ ] Clicking "Preview" opens the Bootstrap modal immediately, showing context name and "Loading…" - [ ] After RPC resolves, modal body shows the full rendered text and badge shows file/byte count - [ ] Copy button appears after load; clicking it copies text to clipboard with a success toast - [ ] Escape / backdrop / × closes the modal (Bootstrap default behavior) - [ ] On RPC failure, a danger toast is shown and the modal is closed - [ ] No conflict with existing `ideOutputModal` or `jobDetailModal` ### Notes - `context.render` RPC already exists server-side — no backend changes needed - `white-space:pre-wrap` on the body div renders markdown/code-fence text legibly without XSS risk (`textContent` not `innerHTML`) - The `cloneNode` copy-button pattern prevents duplicate event listeners on repeated modal opens
Owner

Test Results

  • Total: 107
  • Passed: 107
  • Failed: 0

All tests passed. Build completed with 2 warnings (unused fields in hero_code_ui) and 1 warning (unused function in hero_runner_rhai), but no errors.

Breakdown by crate/suite

Suite Tests
hero_code_lib (unit) 48
hero_code_sdk (unit) 5
hero_code_server (unit) 7
hero_code_tests / job_lifecycle 9
hero_code_tests / nu_execution 3
hero_code_tests / rhai_execution 6
hero_code_tests / rpc_dispatch 27
Doc-tests (hero_runner_nu, hero_runner_rhai, hero_runner_rust) 3

No failures. 3 doc-tests ignored (expected).

## Test Results - Total: 107 - Passed: 107 - Failed: 0 All tests passed. Build completed with 2 warnings (unused fields in hero_code_ui) and 1 warning (unused function in hero_runner_rhai), but no errors. ### Breakdown by crate/suite | Suite | Tests | |---|---| | hero_code_lib (unit) | 48 | | hero_code_sdk (unit) | 5 | | hero_code_server (unit) | 7 | | hero_code_tests / job_lifecycle | 9 | | hero_code_tests / nu_execution | 3 | | hero_code_tests / rhai_execution | 6 | | hero_code_tests / rpc_dispatch | 27 | | Doc-tests (hero_runner_nu, hero_runner_rhai, hero_runner_rust) | 3 | No failures. 3 doc-tests ignored (expected).
Owner

Implementation Complete

Changes Made

crates/hero_code_ui/templates/index.html

  • Added ctxPreviewModal — a Bootstrap .modal.fade element with a title, file/byte-count badge, monospace scrollable body, and Copy button

crates/hero_code_ui/static/js/contexts.js

  • Added ctxPreviewContext(id, name) async function: opens the modal immediately in a loading state, calls context.render RPC, then populates the modal body with the rendered text and updates the badge with file/byte counts. On error, shows a danger toast and closes the modal.
  • Updated the right-click context menu in ctxRenderList() to include a "Preview" entry (bi-eye icon) between Rename and Delete

How it works

Right-clicking any context in the context list now shows: Rename | Preview | Delete.

Clicking "Preview" opens a large Bootstrap modal with the context name as the title. While the context.render RPC is in flight a "Loading..." badge is shown. Once loaded, the full rendered context text appears in a monospace scrollable pane, and a Copy button appears to copy the content to clipboard.

No server-side changes were needed — context.render was already implemented.

Test Results

All 107 tests passed (0 failures).

## Implementation Complete ### Changes Made **`crates/hero_code_ui/templates/index.html`** - Added `ctxPreviewModal` — a Bootstrap `.modal.fade` element with a title, file/byte-count badge, monospace scrollable body, and Copy button **`crates/hero_code_ui/static/js/contexts.js`** - Added `ctxPreviewContext(id, name)` async function: opens the modal immediately in a loading state, calls `context.render` RPC, then populates the modal body with the rendered text and updates the badge with file/byte counts. On error, shows a danger toast and closes the modal. - Updated the right-click context menu in `ctxRenderList()` to include a "Preview" entry (bi-eye icon) between Rename and Delete ### How it works Right-clicking any context in the context list now shows: Rename | Preview | Delete. Clicking "Preview" opens a large Bootstrap modal with the context name as the title. While the `context.render` RPC is in flight a "Loading..." badge is shown. Once loaded, the full rendered context text appears in a monospace scrollable pane, and a Copy button appears to copy the content to clipboard. No server-side changes were needed — `context.render` was already implemented. ### Test Results All 107 tests passed (0 failures).
Owner

Implementation Complete

Changes Made

crates/hero_code_ui/templates/index.html

  • Added ctxPreviewModal — a Bootstrap .modal.fade element with a title, file/byte-count badge, monospace scrollable body, and Copy button

crates/hero_code_ui/static/js/contexts.js

  • Added ctxPreviewContext(id, name) async function: opens the modal immediately in a loading state, calls context.render RPC, then populates the modal body with the rendered text and updates the badge with file/byte counts. On error, shows a danger toast and closes the modal.
  • Updated the right-click context menu in ctxRenderList() to include a "Preview" entry (bi-eye icon) between Rename and Delete

How it works

Right-clicking any context in the context list now shows: Rename | Preview | Delete.

Clicking "Preview" opens a large Bootstrap modal with the context name as the title. While the context.render RPC is in flight a "Loading..." badge is shown. Once loaded, the full rendered context text appears in a monospace scrollable pane, and a Copy button appears to copy the content to clipboard.

No server-side changes were needed — context.render was already implemented.

Test Results

All 107 tests passed (0 failures).

## Implementation Complete ### Changes Made **`crates/hero_code_ui/templates/index.html`** - Added `ctxPreviewModal` — a Bootstrap `.modal.fade` element with a title, file/byte-count badge, monospace scrollable body, and Copy button **`crates/hero_code_ui/static/js/contexts.js`** - Added `ctxPreviewContext(id, name)` async function: opens the modal immediately in a loading state, calls `context.render` RPC, then populates the modal body with the rendered text and updates the badge with file/byte counts. On error, shows a danger toast and closes the modal. - Updated the right-click context menu in `ctxRenderList()` to include a "Preview" entry (bi-eye icon) between Rename and Delete ### How it works Right-clicking any context in the context list now shows: Rename | Preview | Delete. Clicking "Preview" opens a large Bootstrap modal with the context name as the title. While the `context.render` RPC is in flight a "Loading..." badge is shown. Once loaded, the full rendered context text appears in a monospace scrollable pane, and a Copy button appears to copy the content to clipboard. No server-side changes were needed — `context.render` was already implemented. ### Test Results All 107 tests passed (0 failures).
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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_code#11
No description provided.