From 06db8b959cdb1bdc34567fcf0711d79c7f7f4895 Mon Sep 17 00:00:00 2001 From: zai-bot Date: Sat, 7 Nov 2026 04:06:58 +0000 Subject: [PATCH] 830e3dce-c0ea-4998-9516-bbc5e8faac90 --- src/components/ava/directory-tab.tsx | 41 ++++++++++++-------- src/components/ava/script-tab.tsx | 56 +++++++++++++++++++++++++--- src/lib/release-notes.ts | 14 +++++++ src/lib/version.ts | 28 +++++++++++++- worklog.md | 29 ++++++++++++++ 5 files changed, 146 insertions(+), 22 deletions(-) diff --git a/src/components/ava/directory-tab.tsx b/src/components/ava/directory-tab.tsx index 3caaa36..7f6a419 100755 --- a/src/components/ava/directory-tab.tsx +++ b/src/components/ava/directory-tab.tsx @@ -8,6 +8,10 @@ // and "Get" any entry: the hash string is fetched, decoded back into the // exact script, and filed in this browser's preset tree under // "root/user created/OVE-Directory" -- from where it opens in the editor. +// v7.20: each entry card is compact -- the Get button sits at the card's +// top right with the date immediately to its left, so the rating row never +// wraps the button onto its own line (the card dropped from ~126 px to +// ~94 px at the sidebar width). 'use client' @@ -172,7 +176,12 @@ function DirRow({ return (
-
+ {/* v7.20 -- one header line: name + type on the left, the date and the + Get button clustered at the top right (date immediately left of the + button). The rating row below no longer carries the button, so it + can never wrap Get onto a line of its own -- every card stays two + lines tall. */} +

{entry.name}

@@ -182,20 +191,12 @@ function DirRow({ > {entry.type} - -
- -
- - - Average: {entry.average.toFixed(1)} - · {entry.votes} {entry.votes === 1 ? 'vote' : 'votes'} - - + + {getting ? ( - ) : ( @@ -203,7 +204,7 @@ function DirRow({ size="sm" variant="outline" onClick={onGet} - className="h-8 gap-1.5 border-zinc-700 text-xs" + className="h-7 gap-1 border-zinc-700 px-2.5 text-[11px]" title="Fetch this script: it's saved to this browser's presets and can open in the editor" > {got ? : } @@ -213,6 +214,14 @@ function DirRow({
+
+ + + Average: {entry.average.toFixed(1)} + · {entry.votes} {entry.votes === 1 ? 'vote' : 'votes'} + +
+ {got && (

@@ -458,7 +467,7 @@ export function DirectoryTab() { ) : loading ? (

{[0, 1, 2, 3].map((i) => ( -
+
))}
) : scripts.length === 0 ? ( diff --git a/src/components/ava/script-tab.tsx b/src/components/ava/script-tab.tsx index f31de10..dc7b2ec 100755 --- a/src/components/ava/script-tab.tsx +++ b/src/components/ava/script-tab.tsx @@ -5,11 +5,12 @@ // script as a new preset under "user created", auto-named from its name: // header), Save As…, Save as Default, Save to File, Load from File, // Script⇢Hash / Hash⇢Script (v7.7 — share the script as one short Unicode -// string and paste one back), Upload to OVE-Directory (v7.11 — publish the -// compiled script to the community directory), New, Help. Editor: line-number -// gutter + monospace textarea (synced scroll, Tab inserts two spaces), error -// lines flagged in the gutter. Status line under the editor shows compile -// status or runtime errors. +// string and paste one back; v7.20: pasting a share string straight into +// the editor decodes it in place, no button needed), Upload to OVE-Directory +// (v7.11 — publish the compiled script to the community directory), New, +// Help. Editor: line-number gutter + monospace textarea (synced scroll, Tab +// inserts two spaces), error lines flagged in the gutter. Status line under +// the editor shows compile status or runtime errors. 'use client' @@ -40,6 +41,7 @@ import { import { Input } from '@/components/ui/input' import { STARTER_TEMPLATE } from '@/lib/ava/presets' import { validateScript } from '@/lib/ava/script' +import { decodeScriptHash } from '@/lib/ava/share-codec' import { applyUserPresets, persistDefaultScript, @@ -393,6 +395,49 @@ export function ScriptTab() { }) } + /** + * v7.20 — pasting a share string straight into the editor decodes it in + * place: the hash never lands; the script it carries is inserted at the + * paste point instead, exactly like a paste (over the current selection, + * at the cursor when nothing is selected). This is Hash⇢Script's "Load into + * Editor" without the clicks -- the toolbar button stays for people who + * want the decode preview / error report first. Anything that does not + * decode (normal script text, a hash embedded in commentary, a truncated + * string) falls through to a perfectly ordinary paste. + */ + const onEditorPaste = (e: React.ClipboardEvent) => { + const pasted = e.clipboardData?.getData('text') ?? '' + if (!pasted) return // not a text paste (nothing we can decode) + const decoded = decodeScriptHash(pasted) + if (!decoded.ok) return // not a share string -- let the browser paste it + e.preventDefault() + const ta = e.currentTarget + const start = ta.selectionStart + const end = ta.selectionEnd + setScriptText(scriptText.slice(0, start) + decoded.script + scriptText.slice(end)) + requestAnimationFrame(() => { + ta.focus() + ta.selectionStart = ta.selectionEnd = start + decoded.script.length + }) + // the same import semantics the Hash⇢Script dialog applies (v7.11): the + // editor now holds FOREIGN content -- clear the save target (a later Save + // must not overwrite the previous preset with it), remember the display + // name for the one-click auto-named Save, and mark it as the visitor's + // own so the Demo showcase never clobbers it. + const v = validateScript(decoded.script) + const display = v.meta && v.meta.name !== 'Untitled' ? v.meta.name : decoded.name + setPendingName(display ?? null) + setEditTarget(null) + useAvaStore.getState().markManualChoice() + const lines = decoded.script.split('\n').length + toast({ + title: 'Share string decoded', + description: `"${display ?? 'Untitled'}" -- ${lines} lines loaded into the editor.${ + v.ok ? ' Hit Apply to run it, or Save to keep it as a preset.' : ' Fix the errors, then Apply.' + }`, + }) + } + const syncScroll = (e: React.UIEvent) => { if (gutterRef.current) gutterRef.current.scrollTop = e.currentTarget.scrollTop } @@ -582,6 +627,7 @@ export function ScriptTab() { ref={textareaRef} value={scriptText} onChange={(e) => setScriptText(e.target.value)} + onPaste={onEditorPaste} onKeyDown={onKeyDown} onScroll={syncScroll} spellCheck={false} diff --git a/src/lib/release-notes.ts b/src/lib/release-notes.ts index 6139534..5f92a92 100755 --- a/src/lib/release-notes.ts +++ b/src/lib/release-notes.ts @@ -39,6 +39,20 @@ export interface ReleaseNote { } export const RELEASE_NOTES: ReleaseNote[] = [ + { + version: '7.20.0', + name: 'Paste-to-decode + a compact Directory', + changes: [ + { + kind: 'added', + text: 'Pasting a share string straight into the Script editor now decodes it on the spot -- the hash never lands; the whole script it carries appears in the editor instead, right where the paste went, ready to Apply or Save. The Hash⇢Script button still works whenever I want the decode preview first.', + }, + { + kind: 'improved', + text: 'Directory entries are much more compact. The Get button moved to each card\'s top right with its date right beside it, so the rating row no longer wraps the button onto a line of its own -- every entry card dropped from 126 px to 94 px, a quarter shorter.', + }, + ], + }, { version: '7.19.0', name: 'Automatic true-black', diff --git a/src/lib/version.ts b/src/lib/version.ts index 8fa7de6..ad7445b 100755 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -12,6 +12,32 @@ // warns in the console when the two drift apart. // // History: +// 7.20 — Paste-to-decode + compact Directory cards (user request, three +// items). (1) Script editor: pasting a share string straight into +// the editor textarea now decodes it in place -- onPaste runs +// decodeScriptHash on the clipboard text, and when it decodes (the +// full `Name:[▼…]` / `[▼…]` form with valid check digits) the hash +// never lands; the script it carries is inserted at the paste +// point with true paste semantics (over the current selection, at +// the cursor when nothing is selected), followed by the exact +// import semantics of Hash⇢Script's "Load into Editor": pendingName +// from the script's name: header (else the share-string label), +// editTarget cleared (foreign content -- a later Save must not +// overwrite the previous preset with it), markManualChoice (the +// Demo showcase must never clobber it), toast "Share string +// decoded". Anything that does not decode -- normal script text, a +// hash embedded in commentary, a truncated string -- falls through +// to an ordinary paste, and the toolbar button remains for the +// preview-first flow. (2) Directory tab: each entry card moved its +// Get button from the rating row (where, at the sidebar width, +// stars + average + button did not fit and the ml-auto button +// wrapped onto a line of its own -- cards were 126 px) up to the +// header line: name + type on the left, then the date immediately +// left of the Get button at the top right (date used to sit alone +// at the right of the header). Button compacted to h-7 / 11 px so +// the header stays one line; rating row keeps only stars + average. +// Cards now ~94 px (measured) and the loading skeleton matched at +// h-24. (3) Version bump to 7.20.0 with the matching release note. // 7.19 — Automatic true-black (user request, "Texe-rings"): the v7.17 // trueblack tail is no longer opt-in. Root cause of the smear // the user saw: the persistence fade multiplies the framebuffer @@ -648,4 +674,4 @@ export const APP_NAME = 'Open Visualization Engine' export const APP_SHORT = 'OVE' -export const APP_VERSION = '7.19.0' +export const APP_VERSION = '7.20.0' diff --git a/worklog.md b/worklog.md index 4bd3b2d..7a31624 100755 --- a/worklog.md +++ b/worklog.md @@ -2749,3 +2749,32 @@ BROWSER E2E (agent-browser, ALL PASS): Stage Summary: - The Embedded branch is now exactly root/embedded/Beat Matrix: one hand-authored preset, zero subfolders, still read-only (server refuses all mutations; per-GET purge enforces the shape). The 95 retired presets and their folders are gone from the tree and the seed baseline; the "user created" branch is unchanged and verified working end-to-end. + +--- +Task ID: 54 +Agent: main (Z.ai Code) +Task: v7.20.0 (user request, three items): (1) pasting a valid share string into the Script editor auto-decodes it in place, no Hash⇢Script click needed; (2) Directory tab preset cards made compact -- Get button at each card's top right with the date to its left; (3) version bump to 7.20.0. + +(1) Paste-to-decode (script-tab.tsx): +- New onEditorPaste on the editor textarea: reads the clipboard text, runs decodeScriptHash; a successful decode (full Name:[▼…]/[▼…] form + valid check digits -- the codec's own gate, so a hash embedded in commentary or a truncated string never matches) preventDefaults the paste and inserts the decoded script with true paste semantics -- over the current selection, at the cursor when nothing is selected, cursor placed at the end of the insert, hash itself never lands. A failed decode returns early: the browser performs an ordinary paste. +- Follows with the exact import semantics of Hash⇢Script's "Load into Editor" (v7.11): pendingName set from the script's name: header (else the share-string label), editTarget cleared (editor holds foreign content -- a later Save must not overwrite the previous preset with it), markManualChoice (the Demo showcase must never clobber it). Toast "Share string decoded" mirrors the dialog's wording (name, line count, Apply/Save or fix-first hint by compile status). The toolbar Hash⇢Script button remains for the preview-first flow. Null-guard on clipboardData (?.) so malformed events can never throw. + +(2) Compact Directory cards (directory-tab.tsx): +- Measured the old layout live first (agent-browser): at the real sidebar width, stars + average + ml-auto Get did NOT fit one row -- the button wrapped onto a line of its own below the date on every card: 126 px per card. +- New DirRow header: name + type chip on the left; ONE right cluster (ml-auto, shrink-0, gap-2) holding the