v7.20.1 — Directory Get save-on-demand: explicit Save button (left of Open in editor) files fetched scripts into root/user created/OVE-Directory; no more auto-save on Get

This commit is contained in:
zai-bot 2026-11-07 04:54:54 +00:00
parent 65d4f91be6
commit 1afece5a9b
4 changed files with 155 additions and 29 deletions

View file

@ -5,13 +5,14 @@
// type, its date, and its star rating. Visitors rate entries 1–5 (one vote // type, its date, and its star rating. Visitors rate entries 1–5 (one vote
// per script per visitor, enforced upstream), see the live average to the // per script per visitor, enforced upstream), see the live average to the
// right of the stars, sort the whole list by Name / Type / Date / Rating, // right of the stars, sort the whole list by Name / Type / Date / Rating,
// and "Get" any entry: the hash string is fetched, decoded back into the // and "Get" any entry: the hash string is fetched and decoded back into the
// exact script, and filed in this browser's preset tree under // exact script. v7.20: each entry card is compact -- the Get button sits at
// "root/user created/OVE-Directory" -- from where it opens in the editor. // the card's top right with the date immediately to its left, so the rating
// v7.20: each entry card is compact -- the Get button sits at the card's // row never wraps the button onto its own line (the card dropped from
// top right with the date immediately to its left, so the rating row never // ~126 px to ~94 px at the sidebar width). v7.20.1: Get no longer files the
// wraps the button onto its own line (the card dropped from ~126 px to // script into the preset tree automatically -- the panel carries an
// ~94 px at the sidebar width). // explicit Save button (left of Open in editor) that places the preset
// under "root/user created/OVE-Directory" on demand, deduped by content.
'use client' 'use client'
@ -24,6 +25,7 @@ import {
FileCode2, FileCode2,
Loader2, Loader2,
RefreshCw, RefreshCw,
Save,
Star, Star,
TriangleAlert, TriangleAlert,
} from 'lucide-react' } from 'lucide-react'
@ -78,8 +80,10 @@ interface GotPanel {
hash: string hash: string
script: string script: string
compiles: boolean compiles: boolean
savedAs: string /** Name the preset was saved under; null until the Save button is hit. */
alreadyHad: boolean savedAs: string | null
/** Whether a content-identical preset was already in the folder; null unsaved. */
alreadyHad: boolean | null
} }
// ── one directory row ───────────────────────────────────────────────────── // ── one directory row ─────────────────────────────────────────────────────
@ -148,6 +152,7 @@ function DirRow({
got, got,
onVote, onVote,
onGet, onGet,
onSave,
}: { }: {
entry: DirMeta entry: DirMeta
myVote: number myVote: number
@ -156,6 +161,7 @@ function DirRow({
got: GotPanel | null got: GotPanel | null
onVote: (stars: number) => void onVote: (stars: number) => void
onGet: () => void onGet: () => void
onSave: () => void
}) { }) {
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
@ -205,7 +211,7 @@ function DirRow({
variant="outline" variant="outline"
onClick={onGet} onClick={onGet}
className="h-7 gap-1 border-zinc-700 px-2.5 text-[11px]" 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" title="Fetch this script -- then Save it to your presets or open it in the editor"
> >
{got ? <Check className="size-3.5 text-emerald-400" aria-hidden /> : <Download className="size-3.5" aria-hidden />} {got ? <Check className="size-3.5 text-emerald-400" aria-hidden /> : <Download className="size-3.5" aria-hidden />}
{got ? 'Get again' : 'Get'} {got ? 'Get again' : 'Get'}
@ -213,6 +219,8 @@ function DirRow({
)} )}
</span> </span>
</div> </div>
{/* v7.20.1 -- the whole card body below stays identical; only the
got-panel footer grows the Save button. */}
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1.5"> <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} /> <StarRow entry={entry} myVote={myVote} busy={voting} onVote={onVote} />
@ -225,13 +233,20 @@ function DirRow({
{got && ( {got && (
<div className="mt-3 space-y-2 border-t border-zinc-800 pt-3"> <div className="mt-3 space-y-2 border-t border-zinc-800 pt-3">
<p className="text-[11px] leading-relaxed text-zinc-400"> <p className="text-[11px] leading-relaxed text-zinc-400">
{got.alreadyHad ? ( {got.savedAs ? (
got.alreadyHad ? (
<>You already had this exact script -- no duplicate was made.</> <>You already had this exact script -- no duplicate was made.</>
) : ( ) : (
<> <>
Saved to your presets as <span className="text-emerald-300">{got.savedAs}</span> in{' '} Saved to your presets as <span className="text-emerald-300">{got.savedAs}</span> in{' '}
<span className="font-mono">{DOWNLOAD_FOLDER}</span>. <span className="font-mono">{DOWNLOAD_FOLDER}</span>.
</> </>
)
) : (
<>
Fetched -- not saved yet. Hit <span className="text-emerald-300">Save</span> to keep it in{' '}
<span className="font-mono">{DOWNLOAD_FOLDER}</span>, or open it in the editor.
</>
)} )}
</p> </p>
<div className="space-y-1"> <div className="space-y-1">
@ -262,6 +277,31 @@ function DirRow({
<FileCode2 className="size-3.5 shrink-0" aria-hidden /> <FileCode2 className="size-3.5 shrink-0" aria-hidden />
{got.compiles ? 'Decodes and compiles OK' : "Decodes, but doesn't compile (yet)"} {got.compiles ? 'Decodes and compiles OK' : "Decodes, but doesn't compile (yet)"}
</p> </p>
{/* v7.20.1 -- Save (left) places the preset into the user's
"user created/OVE-Directory" folder; Open in editor (right)
just loads the script text into the editor. After saving, the
button turns into a static Saved badge (the content-twin check
makes repeat saves a no-op anyway). */}
<span className="flex shrink-0 items-center gap-2">
{got.savedAs ? (
<span
role="status"
title={`This preset is in ${DOWNLOAD_FOLDER}`}
className="flex h-8 items-center gap-1.5 rounded-md border border-emerald-400/30 bg-emerald-400/10 px-3 text-xs font-semibold text-emerald-300"
>
<Check className="size-3.5" aria-hidden /> Saved
</span>
) : (
<Button
size="sm"
variant="outline"
onClick={onSave}
className="h-8 gap-1.5 border-emerald-400/40 bg-emerald-400/10 px-3 text-xs font-semibold text-emerald-300 hover:bg-emerald-400/20 hover:text-emerald-200"
title={`Save this preset into ${DOWNLOAD_FOLDER}`}
>
<Save className="size-3.5" aria-hidden /> Save
</Button>
)}
<Button <Button
size="sm" size="sm"
onClick={openInEditor} onClick={openInEditor}
@ -270,6 +310,7 @@ function DirRow({
> >
<FileCode2 className="size-3.5" aria-hidden /> Open in editor <FileCode2 className="size-3.5" aria-hidden /> Open in editor
</Button> </Button>
</span>
</div> </div>
</div> </div>
)} )}
@ -348,6 +389,8 @@ export function DirectoryTab() {
[voting, toast], [voting, toast],
) )
// v7.20.1 -- Get only fetches + decodes + previews. The Save button (per
// row, in the got panel) does the filing into the preset tree on demand.
const get = useCallback( const get = useCallback(
async (entry: DirMeta) => { async (entry: DirMeta) => {
if (getting !== null) return if (getting !== null) return
@ -357,21 +400,19 @@ export function DirectoryTab() {
const decoded = decodeScriptHash(full.hash) const decoded = decodeScriptHash(full.hash)
if (!decoded.ok) throw new Error('The stored share string did not decode -- it may be corrupted.') if (!decoded.ok) throw new Error('The stored share string did not decode -- it may be corrupted.')
const v = validateScript(decoded.script) const v = validateScript(decoded.script)
const save = saveDownloadedPreset(decoded.script, entry.name)
if (!save.ok) throw new Error(save.error ?? 'Could not save to this browser.')
setGot((prev) => ({ setGot((prev) => ({
...prev, ...prev,
[entry.id]: { [entry.id]: {
hash: full.hash, hash: full.hash,
script: decoded.script, script: decoded.script,
compiles: v.ok, compiles: v.ok,
savedAs: save.preset?.name ?? entry.name, savedAs: null,
alreadyHad: Boolean(save.alreadyHad), alreadyHad: null,
}, },
})) }))
toast({ toast({
title: save.alreadyHad ? 'Already in your presets' : 'Saved to your presets', title: 'Fetched from the directory',
description: `"${save.preset?.name ?? entry.name}" -- open it in the editor from the panel below, or hit Apply after switching to the Script tab.`, description: `"${entry.name}" -- Save it to your presets or open it in the editor.`,
}) })
} catch (e) { } catch (e) {
toast({ toast({
@ -386,6 +427,36 @@ export function DirectoryTab() {
[getting, toast], [getting, toast],
) )
// v7.20.1 -- the explicit Save: files the fetched script into this
// browser's preset tree under root/user created/OVE-Directory, deduped
// by exact content (a twin already in the folder is reported, not copied).
// Synchronous (localStorage), so no busy state to track.
const save = useCallback(
(entry: DirMeta) => {
const panel = got[entry.id]
if (!panel || panel.savedAs) return
const res = saveDownloadedPreset(panel.script, entry.name)
if (!res.ok) {
toast({
title: 'Save failed',
description: res.error ?? 'Could not save to this browser.',
variant: 'destructive',
})
return
}
const savedAs = res.preset?.name ?? entry.name
setGot((prev) => ({
...prev,
[entry.id]: { ...panel, savedAs, alreadyHad: Boolean(res.alreadyHad) },
}))
toast({
title: res.alreadyHad ? 'Already in your presets' : 'Saved to your presets',
description: `"${savedAs}" in ${DOWNLOAD_FOLDER}.`,
})
},
[got, toast],
)
const online = status?.status === 'ok' const online = status?.status === 'ok'
return ( return (
@ -492,6 +563,7 @@ export function DirectoryTab() {
got={got[entry.id] ?? null} got={got[entry.id] ?? null}
onVote={(stars) => void vote(entry, stars)} onVote={(stars) => void vote(entry, stars)}
onGet={() => void get(entry)} onGet={() => void get(entry)}
onSave={() => save(entry)}
/> />
))} ))}
<p className="flex items-center justify-center gap-1.5 pt-1 text-center font-mono text-[11px] text-zinc-600"> <p className="flex items-center justify-center gap-1.5 pt-1 text-center font-mono text-[11px] text-zinc-600">

View file

@ -39,6 +39,16 @@ export interface ReleaseNote {
} }
export const RELEASE_NOTES: ReleaseNote[] = [ export const RELEASE_NOTES: ReleaseNote[] = [
{
version: '7.20.1',
name: 'Directory Gets save on demand',
changes: [
{
kind: 'improved',
text: 'Getting a script from the Directory no longer drops it into the preset tree on its own. The Get panel now has a Save button right beside Open in editor -- I fetch first, look over the share string and the compile status, and only when I hit Save does the preset land in root/user created/OVE-Directory. Saving twice never makes a duplicate, and the button settles into a quiet Saved badge once it\'s filed.',
},
],
},
{ {
version: '7.20.0', version: '7.20.0',
name: 'Paste-to-decode + a compact Directory', name: 'Paste-to-decode + a compact Directory',

View file

@ -12,6 +12,22 @@
// warns in the console when the two drift apart. // warns in the console when the two drift apart.
// //
// History: // History:
// 7.20.1 — Directory Get save-on-demand (user request): clicking Get on a
// directory entry no longer files the script into the preset tree
// automatically -- the got panel now carries an explicit Save
// button, placed left of "Open in editor" in the panel footer,
// that places the preset under root/user created/OVE-Directory on
// demand (saveDownloadedPreset, deduped by exact content so repeat
// saves never make a duplicate; an identical twin already in the
// folder is reported as "Already in your presets", no copy).
// After saving, the button becomes a static "Saved" badge. Get
// itself now only fetches + decodes + previews (toast "Fetched
// from the directory"); Open in editor stays exactly as it was
// (loads the script text into the editor, saves nothing). GotPanel's
// savedAs/alreadyHad became string|null / boolean|null (null until
// the Save button is hit). Version bump with matching release note;
// EMBEDDED_SEED_VERSION untouched (7.19.0 -- the library did not
// change).
// 7.20 — Paste-to-decode + compact Directory cards (user request, three // 7.20 — Paste-to-decode + compact Directory cards (user request, three
// items). (1) Script editor: pasting a share string straight into // items). (1) Script editor: pasting a share string straight into
// the editor textarea now decodes it in place -- onPaste runs // the editor textarea now decodes it in place -- onPaste runs
@ -674,4 +690,4 @@
export const APP_NAME = 'Open Visualization Engine' export const APP_NAME = 'Open Visualization Engine'
export const APP_SHORT = 'OVE' export const APP_SHORT = 'OVE'
export const APP_VERSION = '7.20.0' export const APP_VERSION = '7.20.1'

View file

@ -2849,3 +2849,31 @@ The persistence chain is now end-to-end: every future sandbox reprovision restor
Stage Summary: Stage Summary:
- PUSH DELIVERED: origin/zai-dev now at 631642d+ (v7.18.0 + v7.19.0 x2 + v7.20.0 + worklogs), authenticated with the operator-supplied scoped token, which was used once as a throwaway HTTP header and never stored anywhere. origin/main remains protected at bbbee3c -- merging zai-dev into main (PR or local merge + push from an authorized lane) is the one remaining operator-side step if they want main current. - PUSH DELIVERED: origin/zai-dev now at 631642d+ (v7.18.0 + v7.19.0 x2 + v7.20.0 + worklogs), authenticated with the operator-supplied scoped token, which was used once as a throwaway HTTP header and never stored anywhere. origin/main remains protected at bbbee3c -- merging zai-dev into main (PR or local merge + push from an authorized lane) is the one remaining operator-side step if they want main current.
---
Task ID: 58
Agent: main (Z.ai Code)
Task: v7.20.1 (user request): "On the Directory tab, when the user clicks the 'Get' button, there needs to be a 'Save' button to the left of the 'Open in Editor' button that will place the preset into the users 'user created' 'OVE-Directory' path. With this, let's bump the version to 7.20.1."
Behavior change (directory-tab.tsx):
- Get is now fetch + decode + preview ONLY: the got panel opens with "Fetched -- not saved yet. Hit Save to keep it in root/user created/OVE-Directory, or open it in the editor." and the toast "Fetched from the directory". saveDownloadedPreset moved out of the get callback entirely.
- New explicit Save button in the panel footer, LEFT of "Open in editor" (outline + emerald accent + lucide Save icon, title "Save this preset into root/user created/OVE-Directory"). It calls a new save callback: saveDownloadedPreset(script, entry.name) -- the existing v7.11 helper that files the preset under root/user created/OVE-Directory with unique-name numbering, deduped by exact content. Toast "Saved to your presets" / "Already in your presets"; the panel intro flips to the saved wording; the button becomes a static "Saved ✓" badge (role=status) so it cannot double-fire. "Open in editor" is unchanged: loads the script text into the editor, saves nothing.
- GotPanel.savedAs/alreadyHad became string|null / boolean|null (null until Save is hit). Get-again resets them to null (Save button returns; the content-twin dedup keeps re-saves no-ops).
- Mid-task bug caught by E2E: first edit round added onSave to the props TYPE but not the destructuring -> ReferenceError "onSave is not defined" crashed the DirectoryTab render the moment a got panel appeared (articles vanished; a stale-Fast-Refresh red herring initially pointed elsewhere). One-line fix; lint clean after.
Version: version.ts APP_VERSION 7.20.0 -> 7.20.1 + full history entry; release-notes.ts 7.20.1 "Directory Gets save on demand" (improved, first person, plain double minus). EMBEDDED_SEED_VERSION untouched at 7.19.0 (library unchanged, no re-seed).
BROWSER E2E (agent-browser, ALL PASS after the fix):
- Baseline: OVE-Directory folder empty in localStorage; title v7.20.1.
- Get on Aqua-Burst: panel intro "Fetched -- not saved yet...", buttons in order [Copy, Save, Open in editor] (Save LEFT of Open in editor verified by index), localStorage still empty -- no auto-save.
- Save: localStorage gains "Aqua-Burst" under root/user created/OVE-Directory; panel intro "Saved to your presets as Aqua-Burst in root/user created/OVE-Directory."; Save button replaced by the "Saved" badge; Open in editor still present.
- Get again: panel resets to unsaved with the Save button back; Save again -> "You already had this exact script -- no duplicate was made.", folder still exactly 1 preset (content-twin dedup held).
- Toasts confirmed live: "Fetched from the directory | 'Aqua-Burst' -- Save it to your presets or open it in the editor." and "Already in your presets | 'Aqua-Burst' in root/user created/OVE-Directory."
- Open in editor: switches to the Script tab with the decoded script loaded (2,663 chars, name + [frame] sections).
- Presets tab: root -> user created -> OVE-Directory -> Aqua-Burst ("Your preset, stored in this browser") -- the exact path requested.
- What's New: 7.20.1 "Directory Gets save on demand" CURRENT, 7.20.0 below it. No console errors; footer flush (docH 577 = innerH 577, footer bottom = docH), no horizontal scroll; bun run lint clean; dev.log all 200s (list + status + two script fetches).
Delivery: committed and pushed to origin/zai-dev with the operator's token from Task 57 (same one-off http.extraheader pattern, never stored).
Stage Summary:
- v7.20.1 shipped: Directory Gets are save-on-demand. The Get panel's new Save button (left of Open in editor) places the fetched script into root/user created/OVE-Directory on click, deduped by content, with a Saved badge confirming it; Get itself no longer auto-files anything. Verified end-to-end in the browser and pushed to the remote.