830e3dce-c0ea-4998-9516-bbc5e8faac90

This commit is contained in:
zai-bot 2026-11-07 04:06:58 +00:00
parent 5f46fb73fc
commit 06db8b959c
5 changed files with 146 additions and 22 deletions

View file

@ -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 (
<article className="rounded-lg border border-zinc-800 bg-zinc-900/40 p-3">
<div className="flex items-baseline gap-2">
{/* 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. */}
<div className="flex items-center gap-2">
<h4 className="min-w-0 truncate text-sm font-medium text-zinc-100" title={entry.name}>
{entry.name}
</h4>
@ -182,20 +191,12 @@ function DirRow({
>
{entry.type}
</span>
<time className="ml-auto shrink-0 font-mono text-[11px] text-zinc-500" dateTime={entry.created_at}>
<span className="ml-auto flex shrink-0 items-center gap-2">
<time className="font-mono text-[11px] text-zinc-500" dateTime={entry.created_at}>
{formatDirDate(entry.created_at)}
</time>
</div>
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
<StarRow entry={entry} myVote={myVote} busy={voting} onVote={onVote} />
<span className="font-mono text-[11px] text-zinc-400" role="status">
Average: <span className="text-amber-300">{entry.average.toFixed(1)}</span>
<span className="text-zinc-600"> · {entry.votes} {entry.votes === 1 ? 'vote' : 'votes'}</span>
</span>
<span className="ml-auto">
{getting ? (
<Button size="sm" disabled className="h-8 gap-1.5 border-zinc-700 text-xs">
<Button size="sm" disabled className="h-7 gap-1 border-zinc-700 px-2.5 text-[11px]">
<Loader2 className="size-3.5 animate-spin" aria-hidden /> Getting…
</Button>
) : (
@ -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 ? <Check className="size-3.5 text-emerald-400" aria-hidden /> : <Download className="size-3.5" aria-hidden />}
@ -213,6 +214,14 @@ function DirRow({
</span>
</div>
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5">
<StarRow entry={entry} myVote={myVote} busy={voting} onVote={onVote} />
<span className="font-mono text-[11px] text-zinc-400" role="status">
Average: <span className="text-amber-300">{entry.average.toFixed(1)}</span>
<span className="text-zinc-600"> · {entry.votes} {entry.votes === 1 ? 'vote' : 'votes'}</span>
</span>
</div>
{got && (
<div className="mt-3 space-y-2 border-t border-zinc-800 pt-3">
<p className="text-[11px] leading-relaxed text-zinc-400">
@ -458,7 +467,7 @@ export function DirectoryTab() {
) : loading ? (
<div className="space-y-2" aria-busy>
{[0, 1, 2, 3].map((i) => (
<div key={i} className="h-20 animate-pulse rounded-lg border border-zinc-800 bg-zinc-900/40" />
<div key={i} className="h-24 animate-pulse rounded-lg border border-zinc-800 bg-zinc-900/40" />
))}
</div>
) : scripts.length === 0 ? (

View file

@ -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<HTMLTextAreaElement>) => {
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<HTMLTextAreaElement>) => {
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}

View file

@ -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',

View file

@ -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'

View file

@ -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 <time> date immediately left of the Get button at the card's top right. Get compacted h-8→h-7 / px-2.5 / 11 px so the header stays a single line; rating row below keeps only StarRow + average (never carries the button again, so it can never wrap it onto its own line). Loading skeleton matched to the new height (h-20→h-24).
- Measured after: 94 px per card at desktop (126→94, -25%); 117 px at 390 px mobile (rating text wraps under the stars, header still one line, Get never on its own line).
(3) Version 7.20.0:
- version.ts: APP_VERSION 7.19.0→7.20.0 + full 7.20 history entry. release-notes.ts: 7.20.0 "Paste-to-decode + a compact Directory" (added + improved, visitor voice, plain double minus). EMBEDDED_SEED_VERSION deliberately untouched at 7.19.0 -- the preset library did not change, no re-seed.
BROWSER E2E (agent-browser, ALL PASS):
- Paste-decode over a full selection: synthetic ClipboardEvent with a real Script⇢Hash string (Beat Matrix, 361 chars) -- event prevented (hash never lands), editor receives the decoded script verbatim, cursor at insert end (1143), save-target chip cleared, toast "Share string decoded | \"Beat Matrix\" -- 40 lines loaded into the editor. Hit Apply to run it, or Save to keep it as a preset." (proven with an in-eval 350 ms probe; the first check missed it only because of a wrong DOM selector -- no sonner is mounted, toasts are the shadcn Toaster -- plus CLI roundtrip latency; fresh re-dispatch confirmed).
- Paste at cursor (no selection): decoded script inserted at position 100, content 1143→2286, insertion verified at the cursor site.
- Fall-through guards: plain-text paste and a truncated hash (200-char cut, no "]") -- no decode, no toast, content untouched (native paste proceeds untouched for real user pastes).
- Toast plumbing cross-check: the pre-existing Apply toast ("Script applied") renders, so the mechanism itself is healthy.
- Directory: Get on "Aqua-Burst" -- got panel (hash + Open in editor) appears, header stays one line with date left of Get, toast "Saved to your presets as Aqua-Burst in root/user created/OVE-Directory."; Open in editor switches to the Script tab with the script loaded. Card heights: 12/12 rows at 94 px desktop / 117 px at 390 px; footer pushed naturally on long content (bottom 2340 > vh 844, no overlap).
- What's New dialog: v7.20.0 "Paste-to-decode + a compact Directory" CURRENT with both changes; release-notes/version drift warning gone after reload (the one seen mid-edit was a stale Fast Refresh buffer entry, confirmed clean on a fresh console).
- bun run lint clean; dev.log 200s only (presets + all three directory endpoints); no page errors.
Stage Summary:
- v7.20.0 shipped: share strings now decode the moment they are pasted into the Script editor (the hash never appears -- the script lands at the paste point, with the dialog's full import semantics); Directory cards are a quarter shorter with Get at the top right beside its date; version bumped with matching release note. The Hash⇢Script toolbar button remains for the preview-first flow.