From 672ad5c4d72fd9901e5080ebfd34e64d4221ce9c Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 16:03:39 +0000 Subject: [PATCH 1/2] Explain how scripts, templates and presets get to each machine A first-time tester couldn't tell how to get Reaper scripts and track templates working across machines: linking, Bring over, Keep Files in Sync and Choose what syncs were spread over four screens with nothing saying which comes first. A compact "Scripts, templates and presets" Notice line now sits in Get Up To Speed, Set up, This machine and Settings > Sync. It names the next step (link, then turn on syncing) with its button, or turns green once both are done. "How it works" opens the ways in order, each ticked when done here, plus what is never copied. Buttons reach the Link dialog and other tabs through a small ShellContext. Bring over's "link first" line gets a Link a machine button; Set up's intro and tour step 5 say to link first. Decision #122; docs/what-syncs.md gains a "Getting it working" section. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013Se1kixnHqBR6qsjjYcSA9 --- app/src/App.tsx | 33 ++++++- app/src/Backup.tsx | 6 +- app/src/BringOver.tsx | 11 ++- app/src/FilesGuide.tsx | 177 +++++++++++++++++++++++++++++++++++ app/src/Tour.tsx | 4 +- app/src/screens/Compare.tsx | 2 + app/src/screens/Rig.tsx | 2 + app/src/screens/Settings.tsx | 2 + app/src/screens/Setup.tsx | 7 +- app/src/shell.ts | 28 ++++++ docs/decisions.md | 1 + docs/what-syncs.md | 17 ++++ 12 files changed, 280 insertions(+), 10 deletions(-) create mode 100644 app/src/FilesGuide.tsx create mode 100644 app/src/shell.ts diff --git a/app/src/App.tsx b/app/src/App.tsx index 579db9b..6ed5af9 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { listen } from "@tauri-apps/api/event"; import { getCurrentWebview } from "@tauri-apps/api/webview"; import { @@ -30,6 +30,7 @@ import { Vault } from "./screens/Vault"; import { Scanning } from "./screens/Scanning"; import { Settings } from "./screens/Settings"; import { Setup, SetupOffer } from "./screens/Setup"; +import { ShellContext, type Shell } from "./shell"; import { Welcome } from "./screens/Welcome"; import { resetTour, Tour, tourPending } from "./Tour"; import { Banner, Button, Loading, Mark, NavTab } from "./ui"; @@ -181,7 +182,36 @@ export default function App() { const canCompare = !!view && view.golden_version !== null && view.machines.length > 0; + // For parts deep in a screen (the files guide): the Link dialog, tabs, and + // whether this is the Primary Rig's machine. + const isPrimary = !!view?.machines.some((m) => m.golden_source && m.is_this_machine); + const linked = !!sync?.linked; + const thisId = overview?.machine?.id ?? null; + const ownPlan = canCompare && !!thisId && !isPrimary; + const shell = useMemo( + () => ({ + linked, + isPrimary, + link: () => setLinking(true), + setUpSync: () => { + setTab("rig"); + setSyncSetupAt(Date.now()); + }, + settings: () => setTab("settings"), + upToSpeed: + ownPlan && thisId + ? () => { + setFocus({ id: thisId, plan: true, at: Date.now() }); + setCompareId(thisId); + setTab("compare"); + } + : null, + }), + [linked, isPrimary, ownPlan, thisId], + ); + return ( +
{overview?.machine && !scanning && !updateScan && !replay && (
+
); } diff --git a/app/src/Backup.tsx b/app/src/Backup.tsx index 085bd88..d285b15 100644 --- a/app/src/Backup.tsx +++ b/app/src/Backup.tsx @@ -637,8 +637,8 @@ export function SyncHint() { return ( <> Which cloud service do you use? Pick the same one on each machine. New and changed templates, scripts and presets - go through a Patch Pilot Backup folder there and land on your other machines - on their own. + go through a Patch Pilot Backup folder there and land on your linked + machines on their own. ); } @@ -993,7 +993,7 @@ function restoreSummary(r: RestoreReport): string { } /** "Dropbox", or the folder's name. */ -function placeName(view: BackupView): string { +export function placeName(view: BackupView): string { if (view.github) return "GitHub"; if (view.lan) return "your home network"; const cloud = view.clouds.find((c) => c.path === view.settings.folder); diff --git a/app/src/BringOver.tsx b/app/src/BringOver.tsx index b78a0d5..fd4f41d 100644 --- a/app/src/BringOver.tsx +++ b/app/src/BringOver.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react import { bringFiles, fileKey, filesToBring, ignoreItems, type BringResult, type BringView, type FileToBring } from "./api"; import { bringSummary, isReaperScript, isTemplate, Needs } from "./bring"; import { IgnoreButton, IgnoredToggle, pathRules } from "./Ignore"; +import { useShell } from "./shell"; import { Badge, Button, buildTree, Card, Folder, FolderTree, isConflictCopy, Tile, treeItems, useFolders } from "./ui"; /** @@ -19,6 +20,7 @@ export function BringOver({ onError }: { onError: (e: string) => void }) { const [busy, setBusy] = useState(false); const [result, setResult] = useState(null); const [leftOut, setLeftOut] = useState(0); + const shell = useShell(); const files = useMemo(() => view?.files ?? [], [view]); const needs = useMemo(() => new Needs(files), [files]); @@ -162,8 +164,13 @@ export function BringOver({ onError }: { onError: (e: string) => void }) { } > {!view.linked && files.length > 0 && ( -

- Link this machine with the Primary one (Link a machine, top bar) to bring these over. +

+ Link this machine with the Primary one to bring these over. + {shell && ( + + )}

)} diff --git a/app/src/FilesGuide.tsx b/app/src/FilesGuide.tsx new file mode 100644 index 0000000..81864dd --- /dev/null +++ b/app/src/FilesGuide.tsx @@ -0,0 +1,177 @@ +import { useEffect, useState, type ReactNode } from "react"; +import { backupView, onBackupChanged, onSyncChanged, type BackupView } from "./api"; +import { placeName } from "./Backup"; +import { useShell } from "./shell"; +import { Button, Notice, Tile, useFolders, type GlyphName, type Tone } from "./ui"; + +/** The screen the guide is on: a button to that same screen is left out. */ +type Where = "plan" | "setup" | "rig" | "settings"; + +/** + * How scripts, templates and presets get to a machine (decision #122). A + * first-time user found the ways spread over This machine, Get Up To Speed + * and Settings with nothing tying them together, so this one line names the + * next thing to set up and opens to the ways in order: link the machines, + * Bring over what's missing, Keep Files in Sync (a cloud folder, GitHub or + * the home network), Choose what syncs, and what never travels. Each says + * whether it's done here and has its button. + * + * On a checklist it starts open until syncing is on; elsewhere it starts + * folded. Once toggled, that's remembered. On This machine it shows only + * while something is left to set up, since Sync & backup says the rest. + */ +export function FilesGuide({ where }: { where: Where }) { + const shell = useShell(); + const [view, setView] = useState(null); + useEffect(() => { + const load = () => backupView().then(setView, () => {}); + load(); + const offBackup = onBackupChanged(load); + const offSync = onSyncChanged(load); + return () => { + offBackup.then((f) => f()); + offSync.then((f) => f()); + }; + }, []); + const syncing = !!view?.settings.keep_in_sync; + const checklist = where === "plan" || where === "setup"; + const folders = useFolders("files-guide", () => checklist && !syncing); + if (!shell || !view) return null; + + const { linked, isPrimary } = shell; + if (where === "rig" && linked && syncing) return null; + const place = placeName(view); + const open = folders.isOpen("guide"); + + const linkButton = ( + + ); + const syncButton = where !== "settings" && ( + + ); + + let tone: Tone = "accent"; + let text: string; + let next: ReactNode = null; + if (!linked) { + text = syncing + ? `Syncing through ${place}. Link your other machines so they can share it.` + : "Link your machines so these can move between them."; + next = linkButton; + } else if (!syncing) { + text = isPrimary + ? "Turn on Keep Files in Sync so new ones reach your other machines." + : "Bring over copies them once; Keep Files in Sync keeps them matching."; + next = syncButton; + } else if (!view.dir) { + tone = "warn"; + text = `Keep Files in Sync is on, but ${place} isn't there now (signed out, or not connected).`; + } else { + tone = "ok"; + text = `Kept in sync with your other machines through ${place}.`; + } + + const bringText = isPrimary + ? "This machine is the Primary Rig, so your other machines bring over its templates, scripts, themes and presets from their Get Up To Speed." + : `${checklist ? "The Bring over step" : "Get Up To Speed's Bring over step"} copies the Primary Rig's templates, scripts, themes and presets this machine lacks, with the files each needs. Anything replaced is saved first.`; + + return ( + + Scripts, templates and presets · {text} + + } + actions={!open && next} + open={open} + onToggle={() => folders.toggle("guide")} + toggle={["How it works", "Hide"]} + > +
    + + Files only move between linked machines. Show a code on one and type it on the other. + + + Open Up To Speed + + ) + } + > + {bringText} + + + Pick the same cloud folder (Dropbox, Google Drive, OneDrive, iCloud), private GitHub repo or home network + on each. New, changed and renamed files then travel both ways on their own; History can undo each. + + + Choose… + + ) + } + > + {where === "settings" ? "Under What syncs below" : "In Settings"}, untick a kind to keep it this machine's + own. DAW settings such as shortcuts and menus change only while that DAW is closed. + +
+

+ Never copied: plug-ins, Reaper extensions (SWS, ReaPack, + ReaImGui) and sample libraries need their own installers, so{" "} + {checklist ? "they're steps in this checklist" : "Get Up To Speed lists them"}. Reaper's folder settings and + ReaPack repositories are set from {checklist ? "the" : "Get Up To Speed's"} Reaper setup card. reaper.ini and + audio and MIDI settings stay with each machine. +

+
+ ); +} + +/** One way files travel: its icon (a green tick once done here), what it is, and its button. */ +function Way({ + glyph, + title, + done = false, + action, + children, +}: { + glyph: GlyphName; + title: string; + /** What's done here, shown in green beside the title. */ + done?: string | false; + action?: ReactNode; + children: ReactNode; +}) { + return ( +
  • + +
    +

    + {title} + {done && {done}} +

    +

    {children}

    +
    + {action} +
  • + ); +} diff --git a/app/src/Tour.tsx b/app/src/Tour.tsx index a3c7af9..fc8e3b6 100644 --- a/app/src/Tour.tsx +++ b/app/src/Tour.tsx @@ -180,7 +180,7 @@ function SyncStep() { } const intro = - "Templates, scripts, presets and DAW settings reach your other machines on their own. Choose what syncs on each machine; Reaper shortcuts move between PC and Mac keys."; + "Link your machines, then pick the same cloud folder on each. Templates, scripts, presets and DAW settings then reach the others on their own; Get Up To Speed brings over what a machine still lacks. Choose what syncs per machine; Reaper shortcuts move between PC and Mac keys."; if (view?.github && view.settings.keep_in_sync) { return ( <> @@ -211,7 +211,7 @@ function SyncStep() { missing={false} preferred={view.settings.folder ?? view.transfer_folder} onChoose={choose} - hint="Which cloud service do you use? Pick the same one on each machine, or GitHub later on This machine." + hint="Which cloud service do you use? Pick the same one on each machine, or GitHub or your home network later on This machine." /> )} diff --git a/app/src/screens/Compare.tsx b/app/src/screens/Compare.tsx index 562e001..87a1b7f 100644 --- a/app/src/screens/Compare.tsx +++ b/app/src/screens/Compare.tsx @@ -50,6 +50,7 @@ import { TemplateLine } from "../Templates"; import { DiskList, DiskSummary, LowDiskNotice } from "../Disks"; import { ReaperExtensionsNotice } from "../ReaperExtensions"; import { ReaperStartupNotice } from "../ReaperStartup"; +import { FilesGuide } from "../FilesGuide"; import { Badge, Button, @@ -1802,6 +1803,7 @@ function PlanView({ )} {progress && } + {here && } {here && progress && } diff --git a/app/src/screens/Rig.tsx b/app/src/screens/Rig.tsx index ece6daf..11f0ac8 100644 --- a/app/src/screens/Rig.tsx +++ b/app/src/screens/Rig.tsx @@ -1,4 +1,5 @@ import { BackupLine } from "../Backup"; +import { FilesGuide } from "../FilesGuide"; import { BigFiles } from "../BigFiles"; import { useEffect, useMemo, useState, type ReactNode } from "react"; import { @@ -138,6 +139,7 @@ export function Rig({ + {setupOffer} {/* Errors here surface where the files are listed, on Up To Speed. */} {}} compact /> diff --git a/app/src/screens/Settings.tsx b/app/src/screens/Settings.tsx index 6c4917b..f9af03e 100644 --- a/app/src/screens/Settings.tsx +++ b/app/src/screens/Settings.tsx @@ -27,6 +27,7 @@ import { type Theme, } from "../api"; import { BackupLine, SyncChoices } from "../Backup"; +import { FilesGuide } from "../FilesGuide"; import { IgnoredToggle, useIgnores } from "../Ignore"; import { Button, Glyph, Label, Loading, Panel, type GlyphName } from "../ui"; @@ -100,6 +101,7 @@ export function Settings({
    +
    diff --git a/app/src/screens/Setup.tsx b/app/src/screens/Setup.tsx index 681395d..c195040 100644 --- a/app/src/screens/Setup.tsx +++ b/app/src/screens/Setup.tsx @@ -23,6 +23,7 @@ import { import { RestoreFromBackup } from "../Backup"; import { BigFiles } from "../BigFiles"; import { BringOver } from "../BringOver"; +import { FilesGuide } from "../FilesGuide"; import { ReaperExtras } from "../ReaperExtras"; import { IgnoreButton, IgnoredNotice, productRules, useIgnores } from "../Ignore"; import { AppButton } from "./Compare"; @@ -137,8 +138,9 @@ function Start({

    Set up this machine

    Patch Pilot lists what this machine needs from your Primary Rig, in the order to do it: licences and - vendor apps first, then plug-ins, then your DAW files. It never installs or copies plug-ins for you. As - you install things, it ticks them off after each rescan, and you get a report at the end. + vendor apps first, then plug-ins, then your DAW files. It never installs or copies plug-ins for you, but + once your machines are linked it can bring over your templates, scripts and presets. As you install things, + it ticks them off after each rescan, and you get a report at the end.

    @@ -300,6 +302,7 @@ function Progress({ )} + void; + /** This machine, with its Sync & backup line open. */ + setUpSync: () => void; + /** The Settings tab. */ + settings: () => void; + /** This machine's Get Up To Speed, or null when it has none (no Primary Rig, or this is its machine). */ + upToSpeed: (() => void) | null; +} + +export const ShellContext = createContext(null); + +/** The main window's {@link Shell}; null in the tray popover. */ +export function useShell(): Shell | null { + return useContext(ShellContext); +} diff --git a/docs/decisions.md b/docs/decisions.md index 30fc4a1..675c300 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -125,3 +125,4 @@ Claude: never undo an Accepted decision without adding a new line that supersede | 118 | 2026-10-31 | **Backups are signed with the rig key** (Joshua chose it after the security audit). Each backup writes `Backups/.sig` beside its index: `ppsig1:` + a BLAKE3 keyed hash of the index's exact bytes under a key derived from the rig key (`RigKey::sign_backup`), written just before the index; an unchanged backup is signed in place. Keep files in sync (`autosync::sync_in_with`) takes a linked machine's newest backup that checks; a machine's unsigned backups (before 0.4.55) are taken only until it has sent a signed one (`SyncState::signed`), and one signed under another key never. A newer backup someone else drops in, even stamped years ahead, can't hide the machine's signed ones. Home network and GitHub pushes send the `.sig` before the index. Sync with and Restore stay manual and unchanged. Sync & backup says when a machine's latest backup was left alone. | The backup folder (a cloud folder or GitHub repo) was the one place sync trusted plain text: a forged backup naming a linked machine could place Reaper scripts and the SWS startup action on every machine with no click. | Accepted | | 119 | 2026-11-01 | **The tray popover opens in the taskbar corner on Windows** (amends #35; Joshua: clicking Patch Pilot in the tray opens it in an awkward spot). Centring it above the icon left it floating mid-screen when the icon sat in the hidden-icons overflow, which opens above the taskbar. On Windows it now sits in the corner of the icon's screen's work area on the taskbar's side (found from where the work area stops short of the screen; an auto-hidden taskbar goes by the icon's half of the screen), right-aligned, 12 px from the taskbar and the edge like Windows 11's flyouts; top, left and right taskbars get the matching corner. macOS is unchanged: centred under the menu bar icon, inside the screen. Positions are worked out at the target screen's scale and allow for the invisible resize border Windows counts in a window's position (`tray::place`, unit-tested). | Accepted | | 120 | 2026-11-03 | **Releases also show on the main repo** (Joshua made `PatchPilotSync/patch-pilot` public and asked for releases on its page; amends #32). After the Release workflow publishes to `patch-pilot-releases`, a new job calls `.github/workflows/mirror-release.yml`, which copies the release to the main repo with the same tag, title and notes, attaching only the installers people download (`*_aarch64.dmg`, `*_x64.dmg`, `*_x64-setup.exe`). The updater endpoint, update packages, `latest.json` and `catalog.json` stay on `patch-pilot-releases`. The copy is made as a draft, then published once every installer is attached; it is marked Latest only when its tag is the releases repo's latest, so backfilling an older tag (run the workflow from the Actions tab) never displaces the newest. It uses only the built-in token (the releases repo is public). The README's badge and download link now point to the main repo's releases. | Installed apps already read `patch-pilot-releases/releases/latest/download/latest.json`, so moving the updater would strand every existing install; copying keeps them working while the public repo's page shows each release. Installers only keeps the page clean. | Accepted | +| 122 | 2026-11-06 | **One "Scripts, templates and presets" guide on every screen that moves them** (Joshua: a first-time tester couldn't tell how to get Reaper scripts and templates working across machines). A `FilesGuide` Notice line (app/src/FilesGuide.tsx) sits in Get Up To Speed (this machine), Set up, This machine and Settings > Sync. Its line names the next thing to do (link the machines, then Keep Files in Sync) with that button, or turns green once both are done; How it works opens the ways in order, each ticked when done here: link your machines, Bring over what's missing (from the Primary Rig), Keep Files in Sync on every machine (same cloud folder, GitHub repo or home network), Choose what syncs, and what's never copied (plug-ins, Reaper extensions, sample libraries; reaper.ini and audio/MIDI settings stay per machine). On a checklist it starts open until syncing is on; elsewhere folded; the toggle is remembered. On This machine it hides once linked and syncing, since Sync & backup says the rest. Buttons reach the Link dialog and other tabs through a small `ShellContext` (app/src/shell.ts) instead of props through every screen. Bring over's "link first" line gets a Link a machine button, Set up's intro and tour step 5 say to link first. | The ways existed but were spread over four places with nothing saying which comes first or that linking is required; one line per screen keeps it compact (decision #68) while answering "how do my scripts get there?" where people look. | Accepted | diff --git a/docs/what-syncs.md b/docs/what-syncs.md index 30f8bda..b1e0a52 100644 --- a/docs/what-syncs.md +++ b/docs/what-syncs.md @@ -1,5 +1,22 @@ # What keeps in sync between machines +## Getting it working + +1. **Link your machines** (Link a machine, top bar). Files only move between + linked machines. +2. **Bring over what's missing.** On a machine that's behind, Get Up To Speed's + Bring over step copies the Primary Rig's templates, scripts, themes and + presets it lacks, each with the files it needs. +3. **Turn on Keep Files in Sync on every machine** (Sync & backup on This + machine, or Settings): pick the same cloud folder, GitHub repo or home + network on each. From then on the files below follow on their own. +4. **Choose what syncs** per machine in Settings, if a machine should keep some + kind of file its own. + +The "Scripts, templates and presets" line in Get Up To Speed, Set up, This +machine and Settings walks through the same steps and shows which are done +(decision #122). + With **Keep Files in Sync** on, each machine backs up after every change and places what's new, changed or renamed on its linked machines (decisions #70, #74, #77). Every placement is a restore point that History can undo. From ab2f3f562d6130ad63468d181396cbdf78ef6116 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 6 Nov 2026 16:40:11 +0000 Subject: [PATCH 2/2] Release 0.4.58: how scripts, templates and presets sync Version bump and CHANGELOG entry for the files guide (#214, decision #122). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013Se1kixnHqBR6qsjjYcSA9 --- app/CHANGELOG.md | 6 ++++++ app/package-lock.json | 4 ++-- app/package.json | 2 +- app/src-tauri/Cargo.lock | 2 +- app/src-tauri/Cargo.toml | 2 +- app/src-tauri/tauri.conf.json | 2 +- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/app/CHANGELOG.md b/app/CHANGELOG.md index 585a82c..9ce213d 100644 --- a/app/CHANGELOG.md +++ b/app/CHANGELOG.md @@ -4,6 +4,12 @@ What changed in each version of the Patch Pilot app. The section for a version becomes its release notes, which the app shows when it offers that update (see `docs/releasing.md`). Write for users, not developers. +## 0.4.58 - 2026-11-06 + +### New + +- Get Up To Speed, Set up, This machine and Settings now say how your scripts, templates and presets get to your other machines. One line shows the next thing to do (link your machines, then turn on Keep Files in Sync) with a button for it, and turns green once both are done. "How it works" walks through the steps in order, ticks the ones already done, and says what's never copied: plug-ins, Reaper extensions like SWS and ReaPack, and sample libraries, which need their own installers. + ## 0.4.57 - 2026-11-06 ### New diff --git a/app/package-lock.json b/app/package-lock.json index 91eefef..cfe4215 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "patchpilot-app", - "version": "0.4.57", + "version": "0.4.58", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "patchpilot-app", - "version": "0.4.57", + "version": "0.4.58", "dependencies": { "@tauri-apps/api": "2.11.1", "@tauri-apps/plugin-dialog": "2.7.3", diff --git a/app/package.json b/app/package.json index dc24a71..308f851 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,7 @@ { "name": "patchpilot-app", "private": true, - "version": "0.4.57", + "version": "0.4.58", "type": "module", "scripts": { "dev": "vite", diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index 81f7675..219d242 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -3015,7 +3015,7 @@ dependencies = [ [[package]] name = "patchpilot-app" -version = "0.4.57" +version = "0.4.58" dependencies = [ "base64 0.22.1", "chrono", diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index 714cf57..3a80aa9 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "patchpilot-app" -version = "0.4.57" +version = "0.4.58" description = "Patch Pilot desktop app" edition = "2024" rust-version = "1.88" diff --git a/app/src-tauri/tauri.conf.json b/app/src-tauri/tauri.conf.json index 20247c4..e6b8f82 100644 --- a/app/src-tauri/tauri.conf.json +++ b/app/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Patch Pilot", - "version": "0.4.57", + "version": "0.4.58", "identifier": "com.patchpilot.app", "build": { "beforeDevCommand": "npm run dev",