Files
gui-video-clipper/docs/superpowers/plans/2026-09-22-v0.1-release-polish.md

22 KiB
Raw Permalink Blame History

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_USERNAME must 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: VERSION file containing 0.1.0. scripts/bump-version.sh that reads VERSION (or accepts a version argument) and patches package.json, src-tauri/Cargo.toml, and src-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_lib to gui_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):

  1. → (empty — removes the cd entirely, leaving just the command)
  2. cd → cd . (standalone cd references)
  3. → (path prefixes before filenames)
  4. `` → . (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

  1. Fork the repository
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Make your changes
  4. Run checks: npm run check
  5. Commit with a descriptive message
  6. Open a pull request

License

MIT


- [ ] **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. Existing showAboutDialog state from App.svelte (added in this task). onClose callback prop (same pattern as PreferencesPanel).

  • Produces: AboutDialog.svelte component with { onClose: () => void } props. Clickable version label in StatusBar that calls onOpenAbout callback. Toolbar info button and ⌘/ shortcut in App.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 showAboutDialog state and import to App.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.svelte to accept and use onOpenAbout callback

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.svelte to StatusBar

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_USERNAME remains 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_USERNAME references. Tagged v0.1.0 release 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.