22 KiB
v0.1.0 Release Polish Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Prepare GUI Video Clipper for its initial tagged v0.1.0 release — versioning, branding, About dialog, LICENSE, README, history scrub, and push to remote.
Architecture: All changes are config/metadata/UI polish — no backend feature work. A VERSION file is the single source of truth, synced to manifests via a bump script. An AboutDialog.svelte component exposes version/author/license info. git-filter-repo scrubs leftover personal paths from history before the first push.
Tech Stack: Svelte 5, Tauri v2, Rust, shell scripting, git-filter-repo
Global Constraints
- Official title: "GUI Video Clipper"
- App identifier:
xyz.cottongin.gui-video-clipper - Author: cottongin
- License: MIT, copyright 2026
- Remote:
git@code.cottongin.xyz:cottongin/gui-video-clipper.git - Version:
0.1.0 - No new npm or crate dependencies
- The string
REDACTED_USERNAMEmust not appear anywhere in the final repo (files or git history)
Task 1: VERSION file and bump script
Files:
- Create:
VERSION - Create:
scripts/bump-version.sh
Interfaces:
-
Consumes: Nothing
-
Produces:
VERSIONfile containing0.1.0.scripts/bump-version.shthat readsVERSION(or accepts a version argument) and patchespackage.json,src-tauri/Cargo.toml, andsrc-tauri/tauri.conf.json. -
Step 1: Create the VERSION file
Create VERSION at the project root with exactly this content (no trailing newline):
0.1.0
- Step 2: Create the bump script
Create scripts/bump-version.sh:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
VERSION_FILE="$PROJECT_ROOT/VERSION"
if [ $# -ge 1 ]; then
echo "$1" > "$VERSION_FILE"
fi
if [ ! -f "$VERSION_FILE" ]; then
echo "ERROR: VERSION file not found at $VERSION_FILE" >&2
exit 1
fi
VERSION=$(cat "$VERSION_FILE")
# Sanitize: strip leading v/V, trim whitespace
VERSION=$(echo "$VERSION" | sed 's/^[vV]//' | tr -d '[:space:]')
# Validate semver (X.Y.Z)
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "ERROR: '$VERSION' is not valid semver (expected X.Y.Z)" >&2
exit 1
fi
echo "Bumping to version $VERSION"
# 1. package.json
cd "$PROJECT_ROOT"
npm pkg set "version=$VERSION" --json 2>/dev/null || npm pkg set "version=$VERSION"
echo " ✓ package.json"
# 2. src-tauri/Cargo.toml — update [package] version (first version = line)
sed -i '' "s/^version = \".*\"/version = \"$VERSION\"/" "$PROJECT_ROOT/src-tauri/Cargo.toml"
echo " ✓ src-tauri/Cargo.toml"
# 3. src-tauri/tauri.conf.json
node -e "
const fs = require('fs');
const path = '$PROJECT_ROOT/src-tauri/tauri.conf.json';
const conf = JSON.parse(fs.readFileSync(path, 'utf8'));
conf.version = '$VERSION';
fs.writeFileSync(path, JSON.stringify(conf, null, 2) + '\n');
"
echo " ✓ src-tauri/tauri.conf.json"
# 4. Sync Cargo.lock
cd "$PROJECT_ROOT/src-tauri"
cargo generate-lockfile 2>/dev/null
echo " ✓ src-tauri/Cargo.lock"
echo ""
echo "Done — all manifests set to $VERSION"
- Step 3: Make the script executable
Run: chmod +x scripts/bump-version.sh
- Step 4: Test the bump script
Run: ./scripts/bump-version.sh
Expected output:
Bumping to version 0.1.0
✓ package.json
✓ src-tauri/Cargo.toml
✓ src-tauri/tauri.conf.json
✓ src-tauri/Cargo.lock
Done — all manifests set to 0.1.0
Verify the files were updated:
Run: grep '"version"' package.json src-tauri/tauri.conf.json && grep '^version' src-tauri/Cargo.toml
Expected: all three show 0.1.0.
- Step 5: Test the sanitization (leading v, trailing newline)
Run: echo "v0.1.0 " > VERSION && ./scripts/bump-version.sh
Expected: script prints Bumping to version 0.1.0 (strips the v and whitespace) and completes successfully.
Reset: echo -n "0.1.0" > VERSION
- Step 6: Commit
git add VERSION scripts/bump-version.sh
git commit -m "chore: add VERSION file and bump-version.sh script"
Task 2: Metadata cleanup — config files and Rust crate rename
Files:
- Modify:
src-tauri/tauri.conf.json - Modify:
src-tauri/Cargo.toml - Modify:
src-tauri/src/main.rs - Modify:
index.html - Modify:
src/lib/components/SetupWizard.svelte
Interfaces:
-
Consumes: Nothing (standalone metadata changes)
-
Produces: All placeholder values replaced with real project metadata. The Rust lib crate is renamed from
tauri_app_libtogui_video_clipper_lib. -
Step 1: Update
src-tauri/tauri.conf.json
Replace the following values:
-
"productName": "Video Clipper"→"productName": "GUI Video Clipper" -
"identifier": "xyz.cottongin.gui-video-clipper"→"identifier": "xyz.cottongin.gui-video-clipper" -
"title": "Video Clipper"→"title": "GUI Video Clipper" -
Step 2: Update
src-tauri/Cargo.toml
Replace the following values in the [package] section:
name = "tauri-app"→name = "gui-video-clipper"description = "A Tauri App"→description = "A macOS GUI app for clipping online videos"authors = ["you"]→authors = ["cottongin"]
In the [lib] section:
-
name = "tauri_app_lib"→name = "gui_video_clipper_lib" -
Step 3: Update
src-tauri/src/main.rs
Replace tauri_app_lib::run() with gui_video_clipper_lib::run().
The full file should be:
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
gui_video_clipper_lib::run()
}
- Step 4: Update
index.html
Replace <title>Video Clipper</title> with <title>GUI Video Clipper</title>.
- Step 5: Update
src/lib/components/SetupWizard.svelte
Replace the string Video Clipper requires with GUI Video Clipper requires (line 82).
- Step 6: Sync Cargo.lock after crate rename
Run: cd src-tauri && cargo generate-lockfile
This updates the Cargo.lock entry from tauri-app to gui-video-clipper.
- Step 7: Verify the build compiles
Run: cd src-tauri && cargo check
Expected: compiles without errors. The lib name change is picked up via Cargo.toml and main.rs.
- Step 8: Commit
git add src-tauri/tauri.conf.json src-tauri/Cargo.toml src-tauri/Cargo.lock src-tauri/src/main.rs index.html src/lib/components/SetupWizard.svelte
git commit -m "chore: replace placeholder metadata with real project values"
Task 3: Path scrubbing in plan docs
Files:
- Modify:
docs/superpowers/plans/2026-09-21-video-clipper.md - Modify:
docs/superpowers/plans/2026-09-22-paged-teleprompter-subtitles.md - Modify:
docs/superpowers/plans/2026-09-22-extended-caption-styling.md
Interfaces:
-
Consumes: Nothing
-
Produces: All absolute paths containing the personal username are replaced with relative equivalents.
-
Step 1: Replace absolute paths in all three plan files
In each of the three files, replace all occurrences of `` (with trailing slash) with an empty string — this leaves just the command after cd ... && becomes just the command.
Also replace any remaining occurrences of `` (without trailing slash) with . (current directory).
The specific patterns to replace in order (longer match first):
→(empty — removes the cd entirely, leaving just the command)cd→cd .(standalone cd references)→(path prefixes before filenames)- `` →
.(any remaining)
- Step 2: Verify no personal paths remain
Run: grep -r "REDACTED_USERNAME" docs/superpowers/plans/
Expected: no output (no matches).
- Step 3: Commit
git add docs/superpowers/plans/
git commit -m "chore: replace absolute paths with relative equivalents in plan docs"
Task 4: LICENSE file
Files:
- Create:
LICENSE
Interfaces:
-
Consumes: Nothing
-
Produces: MIT license file at project root.
-
Step 1: Create the LICENSE file
Create LICENSE at the project root with this exact content:
MIT License
Copyright (c) 2026 cottongin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- Step 2: Commit
git add LICENSE
git commit -m "chore: add MIT license"
Task 5: README rewrite
Files:
- Modify:
README.md
Interfaces:
-
Consumes: Nothing
-
Produces: Full README replacing the stock Tauri template.
-
Step 1: Rewrite README.md
Replace the entire contents of README.md with:
# GUI Video Clipper
A macOS desktop app for clipping segments from online videos. Paste a URL, scrub the timeline, mark your clips, and export — without manually downloading the video first.
## Features
- **URL Input** — Paste a YouTube (or other supported) URL and start working immediately
- **Video Preview** — Scrub, seek, and frame-step through the video with full transport controls
- **Mark In/Out** — Set clip boundaries with keyboard shortcuts (I/O) or transport buttons
- **Timeline** — Visual timeline with waveform display and thumbnail strip
- **Clip Export** — Export clips in lossless (stream copy) or precise (re-encode) modes
- **Captions** — Download subtitles, preview with karaoke-style highlighting, burn into exports
- **Setup Wizard** — Automatically detects and installs required dependencies (ffmpeg, yt-dlp)
## Prerequisites
- **macOS** (primary target platform)
- **Node.js** (v18+)
- **Rust** toolchain ([rustup.rs](https://rustup.rs))
ffmpeg and yt-dlp are detected (and can be installed) automatically by the built-in setup wizard on first launch.
## Build & Run
```bash
# Install frontend dependencies
npm install
# Development (hot-reload)
npm run tauri dev
# Production build
npm run tauri build
Architecture
GUI Video Clipper is built with Tauri v2 (Rust backend) and Svelte 5 (TypeScript frontend).
Backend (Rust)
- Video Resolver — Resolves URLs via yt-dlp, extracts metadata and stream URLs
- Download Manager — Manages preview and high-quality downloads with progress tracking
- Media Server — Local HTTP server for streaming video to the frontend player
- Waveform Generator — Extracts audio waveform data at multiple zoom tiers via ffmpeg
- Thumbnail Extractor — Generates timeline thumbnail strips from keyframes
- Export Pipeline — Handles clip extraction with ffmpeg (lossless and precise modes)
- Subtitle Downloader — Fetches and converts subtitles for preview and burn-in
Frontend (Svelte 5 + TypeScript)
- Timeline — Canvas-based timeline with waveform, thumbnails, and clip region rendering
- Transport Controls — Play/pause, seek, frame-step, shuttle (J/K/L), keyboard shortcuts
- Video Player — HTML5 video element connected to the local media server
- Export Dialog — Configure and execute clip exports with progress tracking
- Caption Settings — Font, color, shadow, and positioning controls for subtitle burn-in
Contributing
- Fork the repository
- Create a feature branch (
git checkout -b feat/my-feature) - Make your changes
- Run checks:
npm run check - Commit with a descriptive message
- Open a pull request
License
- [ ] **Step 2: Commit**
```bash
git add README.md
git commit -m "docs: rewrite README with full project documentation"
Task 6: About dialog
Files:
- Create:
src/lib/components/AboutDialog.svelte - Modify:
src/App.svelte - Modify:
src/lib/components/StatusBar.svelte
Interfaces:
-
Consumes:
getVersion()from@tauri-apps/api/app. ExistingshowAboutDialogstate fromApp.svelte(added in this task).onClosecallback prop (same pattern asPreferencesPanel). -
Produces:
AboutDialog.sveltecomponent with{ onClose: () => void }props. Clickable version label inStatusBarthat callsonOpenAboutcallback. Toolbar info button and⌘/shortcut inApp.svelte. -
Step 1: Create
AboutDialog.svelte
Create src/lib/components/AboutDialog.svelte:
<script lang="ts">
import { getVersion } from '@tauri-apps/api/app';
let { onClose }: { onClose: () => void } = $props();
let version = $state('');
$effect(() => {
getVersion().then((v) => {
version = v;
});
});
</script>
<div class="overlay" role="presentation" onclick={onClose}>
<div class="dialog" role="dialog" onclick={(e) => e.stopPropagation()}>
<img class="app-icon" src="/favicon.svg" alt="GUI Video Clipper icon" />
<h2>GUI Video Clipper</h2>
{#if version}
<span class="version">v{version}</span>
{/if}
<p class="author">by cottongin</p>
<p class="license">MIT License</p>
<a
class="repo-link"
href="https://code.cottongin.xyz/cottongin/gui-video-clipper"
target="_blank"
rel="noopener noreferrer"
>
code.cottongin.xyz/cottongin/gui-video-clipper
</a>
<div class="actions">
<button class="primary" onclick={onClose}>Close</button>
</div>
</div>
</div>
<style>
.overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 900;
}
.dialog {
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: 12px;
padding: 32px;
max-width: 360px;
width: 100%;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.app-icon {
width: 64px;
height: 64px;
border-radius: 12px;
margin-bottom: 8px;
}
h2 {
margin: 0;
font-size: 18px;
}
.version {
color: var(--text-secondary);
font-size: 14px;
font-family: var(--font-mono);
}
.author {
color: var(--text-secondary);
font-size: 13px;
margin: 0;
}
.license {
color: var(--text-muted);
font-size: 12px;
margin: 0;
}
.repo-link {
color: var(--accent);
font-size: 12px;
text-decoration: none;
}
.repo-link:hover {
text-decoration: underline;
}
.actions {
margin-top: 12px;
}
.primary {
background: var(--accent);
color: var(--bg-primary);
font-weight: 600;
}
</style>
- Step 2: Add
showAboutDialogstate and import toApp.svelte
In src/App.svelte, add the import at the top of the <script> block (after the other component imports):
import AboutDialog from '$lib/components/AboutDialog.svelte';
Add a new state variable alongside the existing dialog states:
let showAboutDialog = $state(false);
- Step 3: Add the About dialog render block to
App.svelte
Add the following block after the existing {#if showExportDialog} block (before the ProcessingModal block):
{#if showAboutDialog}
<AboutDialog onClose={() => (showAboutDialog = false)} />
{/if}
- Step 4: Add the ⌘/ keyboard shortcut to
App.svelte
In the handleGlobalKeydown function's switch statement, add a new case before the default: case:
case '/':
if (e.metaKey) {
e.preventDefault();
showAboutDialog = true;
}
break;
- Step 5: Add the info button to the toolbar in
App.svelte
In the <header class="toolbar"> section, add an info button before the existing prefs button:
<button class="about-btn" onclick={() => (showAboutDialog = true)} title="About">ℹ</button>
<button class="prefs-btn" onclick={() => (showPreferences = true)} title="Preferences">⚙</button>
Add the corresponding style (in the <style> block, next to the existing .prefs-btn rule):
.about-btn {
font-size: 16px;
padding: 4px 8px;
}
- Step 6: Update
StatusBar.svelteto accept and useonOpenAboutcallback
In src/lib/components/StatusBar.svelte, update the script to accept a new prop and import getVersion:
Add imports at the top:
import { getVersion } from '@tauri-apps/api/app';
Add the prop and version state:
let { onOpenAbout }: { onOpenAbout?: () => void } = $props();
let appVersion = $state('');
$effect(() => {
getVersion().then((v) => {
appVersion = v;
});
});
At the end of the <div class="status-bar"> template (just before the closing </div>), add:
{#if appVersion}
<button class="version-btn" onclick={() => onOpenAbout?.()} title="About GUI Video Clipper">
v{appVersion}
</button>
{/if}
Add the style:
.version-btn {
margin-left: auto;
background: none;
border: none;
color: var(--text-muted);
font-size: 11px;
font-family: var(--font-mono);
padding: 2px 6px;
cursor: pointer;
border-radius: 3px;
}
.version-btn:hover {
background: var(--bg-secondary);
color: var(--text-secondary);
}
- Step 7: Pass the callback from
App.sveltetoStatusBar
In src/App.svelte, update the <StatusBar /> usage to:
<StatusBar onOpenAbout={() => (showAboutDialog = true)} />
- Step 8: Verify the frontend compiles
Run: npm run check
Expected: no errors.
- Step 9: Commit
git add src/lib/components/AboutDialog.svelte src/App.svelte src/lib/components/StatusBar.svelte
git commit -m "feat: add About dialog with version, author, and license info"
Task 7: Stage all untracked files and commit the release
Files:
- Modify:
docs/superpowers/specs/2026-09-22-v0.1-release-polish-design.md(already exists, just needs staging) - All other untracked files from git status
Interfaces:
-
Consumes: All changes from Tasks 1-6
-
Produces: A clean commit history with all project files tracked.
-
Step 1: Verify no
REDACTED_USERNAMEremains in tracked files
Run: grep -r "REDACTED_USERNAME" --include="*.json" --include="*.toml" --include="*.rs" --include="*.svelte" --include="*.html" --include="*.ts" --include="*.md" .
Expected: no matches in any source file. (If any remain, fix them before proceeding.)
- Step 2: Stage and commit the spec and plan docs
git add docs/superpowers/specs/2026-09-22-v0.1-release-polish-design.md
git add docs/superpowers/plans/2026-09-22-v0.1-release-polish.md
git commit -m "docs: add v0.1.0 release polish spec and implementation plan"
- Step 3: Final build verification
Run: npm run check && cd src-tauri && cargo check
Expected: both pass without errors.
Task 8: Git history scrub and push to remote
Files:
- Create (temporary):
replacements.txt - No permanent file changes — this task rewrites git history.
Interfaces:
-
Consumes: A fully committed repo with no dirty working tree.
-
Produces: Clean git history with no
REDACTED_USERNAMEreferences. Taggedv0.1.0release pushed to remote. -
Step 1: Create the replacements file
Create replacements.txt in the project root (this file will NOT be committed):
xyz.cottongin.gui-video-clipper==>xyz.cottongin.gui-video-clipper
==>
==>
==>
Each line uses ==> as the separator between old and new values. The path replacements have empty right-hand sides to delete the paths entirely. Longer matches come first to avoid partial replacements.
Note: This rewrite will also affect the spec and plan docs themselves (they contain the old values as documentation). The "before" columns in tables will be mangled. This is expected — a complete scrub is the goal.
- Step 2: Run git-filter-repo
Run: git filter-repo --replace-text replacements.txt --force
Expected: rewrites all commits. Output shows the number of commits processed.
- Step 3: Verify the scrub was successful
Run: git log --all -p --no-color | grep -i "REDACTED_USERNAME" | head -20
Expected: no output (no matches anywhere in the history).
Also verify the working tree is still clean:
Run: git status
Expected: clean working tree (filter-repo rewrites blobs in place).
- Step 4: Clean up the replacements file
Run: rm replacements.txt
- Step 5: Add the remote
Run: git remote add origin git@code.cottongin.xyz:cottongin/gui-video-clipper.git
(git-filter-repo removes remotes by default, so this is always needed.)
- Step 6: Tag the release
Run: git tag -a v0.1.0 -m "Initial release"
- Step 7: Push to remote
Run: git push -u origin main && git push origin v0.1.0
Expected: main branch and v0.1.0 tag pushed successfully.
- Step 8: Verify the remote
Run: git log --oneline -5 && git tag -l && git remote -v
Expected: shows recent commits with new hashes, v0.1.0 tag, and the origin remote URL.