docs: add v0.1.0 release polish spec and implementation plan
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
763
docs/superpowers/plans/2026-09-22-v0.1-release-polish.md
Normal file
763
docs/superpowers/plans/2026-09-22-v0.1-release-polish.md
Normal file
@@ -0,0 +1,763 @@
|
||||
# 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`:
|
||||
|
||||
```bash
|
||||
#!/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**
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```rust
|
||||
// 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**
|
||||
|
||||
```bash
|
||||
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**
|
||||
|
||||
```bash
|
||||
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**
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```markdown
|
||||
# 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](https://v2.tauri.app/) (Rust backend) and [Svelte 5](https://svelte.dev/) (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](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`. 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`:
|
||||
|
||||
```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):
|
||||
|
||||
```typescript
|
||||
import AboutDialog from '$lib/components/AboutDialog.svelte';
|
||||
```
|
||||
|
||||
Add a new state variable alongside the existing dialog states:
|
||||
|
||||
```typescript
|
||||
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):
|
||||
|
||||
```svelte
|
||||
{#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:
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```svelte
|
||||
<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):
|
||||
|
||||
```css
|
||||
.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:
|
||||
|
||||
```typescript
|
||||
import { getVersion } from '@tauri-apps/api/app';
|
||||
```
|
||||
|
||||
Add the prop and version state:
|
||||
|
||||
```typescript
|
||||
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:
|
||||
|
||||
```svelte
|
||||
{#if appVersion}
|
||||
<button class="version-btn" onclick={() => onOpenAbout?.()} title="About GUI Video Clipper">
|
||||
v{appVersion}
|
||||
</button>
|
||||
{/if}
|
||||
```
|
||||
|
||||
Add the style:
|
||||
|
||||
```css
|
||||
.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:
|
||||
|
||||
```svelte
|
||||
<StatusBar onOpenAbout={() => (showAboutDialog = true)} />
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Verify the frontend compiles**
|
||||
|
||||
Run: `npm run check`
|
||||
|
||||
Expected: no errors.
|
||||
|
||||
- [ ] **Step 9: Commit**
|
||||
|
||||
```bash
|
||||
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**
|
||||
|
||||
```bash
|
||||
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.
|
||||
225
docs/superpowers/specs/2026-09-22-v0.1-release-polish-design.md
Normal file
225
docs/superpowers/specs/2026-09-22-v0.1-release-polish-design.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# GUI Video Clipper — v0.1.0 Release Polish Design Spec
|
||||
|
||||
## Overview
|
||||
|
||||
Prepare the GUI Video Clipper project for its initial `v0.1.0` tagged release. This covers versioning infrastructure, metadata cleanup, branding, an About dialog, licensing, a proper README, and pushing to the remote repository.
|
||||
|
||||
**Official title:** GUI Video Clipper
|
||||
**Short title:** gui-vc
|
||||
**Author:** cottongin
|
||||
**Remote:** `git@code.cottongin.xyz:cottongin/gui-video-clipper.git`
|
||||
**License:** MIT (2026)
|
||||
**App identifier:** `xyz.cottongin.gui-video-clipper`
|
||||
|
||||
---
|
||||
|
||||
## 1. Versioning Infrastructure
|
||||
|
||||
### VERSION File
|
||||
|
||||
A plain-text `VERSION` file at the project root. Contains the semantic version with no prefix or trailing whitespace:
|
||||
|
||||
```
|
||||
0.1.0
|
||||
```
|
||||
|
||||
### Bump Script
|
||||
|
||||
`scripts/bump-version.sh` — a shell script that synchronizes the version across all manifest files.
|
||||
|
||||
**Behavior:**
|
||||
1. If given an argument (`./scripts/bump-version.sh 0.2.0`), write that version to `VERSION`. Otherwise, read the current `VERSION`.
|
||||
2. Sanitize the value: strip leading `v` or `V`, trim all trailing whitespace/newlines.
|
||||
3. Validate the result matches semver (`X.Y.Z`). Exit with an error if not.
|
||||
4. Patch `package.json` via `npm pkg set version=$VERSION`.
|
||||
5. Patch `src-tauri/Cargo.toml`: update the `version = "..."` line under `[package]`.
|
||||
6. Patch `src-tauri/tauri.conf.json`: update the `"version": "..."` field.
|
||||
7. Run `cd src-tauri && cargo generate-lockfile` to sync `Cargo.lock`.
|
||||
8. Print a summary of all updated files and the new version.
|
||||
|
||||
### Frontend Version Access
|
||||
|
||||
The frontend reads the version at runtime via `getVersion()` from `@tauri-apps/api/app`. This reads from the built `tauri.conf.json` — no custom Tauri command needed.
|
||||
|
||||
---
|
||||
|
||||
## 2. Metadata Cleanup
|
||||
|
||||
Replace all placeholder/template values:
|
||||
|
||||
| File | Field | Current | New |
|
||||
|------|-------|---------|-----|
|
||||
| `src-tauri/tauri.conf.json` | `productName` | `"Video Clipper"` | `"GUI Video Clipper"` |
|
||||
| `src-tauri/tauri.conf.json` | `identifier` | `"xyz.cottongin.gui-video-clipper"` | `"xyz.cottongin.gui-video-clipper"` |
|
||||
| `src-tauri/tauri.conf.json` | `windows[0].title` | `"Video Clipper"` | `"GUI Video Clipper"` |
|
||||
| `src-tauri/Cargo.toml` | `name` | `"tauri-app"` | `"gui-video-clipper"` |
|
||||
| `src-tauri/Cargo.toml` | `description` | `"A Tauri App"` | `"A macOS GUI app for clipping online videos"` |
|
||||
| `src-tauri/Cargo.toml` | `authors` | `["you"]` | `["cottongin"]` |
|
||||
| `src-tauri/Cargo.toml` | `lib.name` | `"tauri_app_lib"` | `"gui_video_clipper_lib"` |
|
||||
| `src-tauri/src/main.rs` | `run()` call | `tauri_app_lib::run()` | `gui_video_clipper_lib::run()` |
|
||||
| `index.html` | `<title>` | `"Video Clipper"` | `"GUI Video Clipper"` |
|
||||
| `src/lib/components/SetupWizard.svelte` | body text | `"Video Clipper requires…"` | `"GUI Video Clipper requires…"` |
|
||||
|
||||
The `Cargo.lock` entry for `tauri-app` will auto-update when `cargo generate-lockfile` runs after the crate rename.
|
||||
|
||||
### Path Scrubbing
|
||||
|
||||
Three files under `docs/superpowers/plans/` contain absolute paths with a username that should not appear in the public repo. Replace all occurrences of the absolute project path with relative equivalents (e.g., `cd /Users/.../gui-video-clipper &&` becomes just the command itself, or a relative path).
|
||||
|
||||
Affected files:
|
||||
- `docs/superpowers/plans/2026-09-21-video-clipper.md`
|
||||
- `docs/superpowers/plans/2026-09-22-paged-teleprompter-subtitles.md`
|
||||
- `docs/superpowers/plans/2026-09-22-extended-caption-styling.md`
|
||||
|
||||
---
|
||||
|
||||
## 3. About Dialog
|
||||
|
||||
### Component: `AboutDialog.svelte`
|
||||
|
||||
A modal overlay following the existing pattern used by `PreferencesPanel` and `ExportDialog`.
|
||||
|
||||
**Contents (top to bottom):**
|
||||
- App icon (the scissors/film strip SVG, rendered as an `<img>` or inline SVG, ~64px)
|
||||
- "GUI Video Clipper" as the title
|
||||
- Version string (e.g., "v0.1.0") read via `getVersion()` from `@tauri-apps/api/app`
|
||||
- "by cottongin"
|
||||
- "MIT License"
|
||||
- Link to repo: `https://code.cottongin.xyz/cottongin/gui-video-clipper`
|
||||
- "Close" button
|
||||
|
||||
**Styling:** Matches the existing dark theme (Catppuccin Mocha palette). Centered modal, rounded corners, consistent with `PreferencesPanel`.
|
||||
|
||||
### Triggers
|
||||
|
||||
Three ways to open the About dialog:
|
||||
|
||||
1. **Toolbar button:** An `(i)` / info button in the header toolbar, next to the existing `⚙` preferences button.
|
||||
2. **Keyboard shortcut:** `⌘/` — added to the global `handleGlobalKeydown` in `App.svelte`.
|
||||
3. **Status bar version:** A clickable `v0.1.0` label in `StatusBar.svelte`.
|
||||
|
||||
All three toggle the same `showAboutDialog` state in `App.svelte`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Window Title
|
||||
|
||||
The window title is set statically in `tauri.conf.json` as `"GUI Video Clipper"`. No runtime version suffix — the version is available in the About dialog.
|
||||
|
||||
---
|
||||
|
||||
## 5. LICENSE & README
|
||||
|
||||
### LICENSE
|
||||
|
||||
A standard MIT license file at project root (`LICENSE`).
|
||||
|
||||
```
|
||||
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.
|
||||
```
|
||||
|
||||
### README.md
|
||||
|
||||
Full rewrite replacing the stock Tauri template. Sections:
|
||||
|
||||
1. **Title & Description** — "GUI Video Clipper" with a one-line description: a macOS desktop app for clipping segments from online videos.
|
||||
2. **Features** — URL input (YouTube, etc.), video preview with scrubbing, mark in/out points, timeline with waveform and thumbnails, clip export (lossless and precise modes), caption/subtitle support with burn-in, automatic dependency setup wizard.
|
||||
3. **Prerequisites** — macOS, Node.js, Rust toolchain. ffmpeg and yt-dlp are installed automatically by the setup wizard.
|
||||
4. **Build & Run** — `npm install`, `npm run tauri dev` (development), `npm run tauri build` (production).
|
||||
5. **Architecture** — Tauri v2 backend (Rust) + Svelte 5 frontend (TypeScript). Brief description of the major components: video resolver, media server, waveform generator, thumbnail extractor, export pipeline.
|
||||
6. **Contributing** — Fork, branch, PR workflow. Run `npm run check` before submitting.
|
||||
7. **License** — MIT, with a link to the LICENSE file.
|
||||
|
||||
No screenshots.
|
||||
|
||||
---
|
||||
|
||||
## 6. Icons
|
||||
|
||||
The custom icon set (scissors cutting a film strip, dark background) is already in place at `src-tauri/icons/` with all required sizes:
|
||||
- `icon.svg` (source) + color variants in `variants/`
|
||||
- `icon.png` (512x512), `32x32.png`, `128x128.png`, `128x128@2x.png`
|
||||
- `icon.icns` (macOS), `icon.ico` (Windows)
|
||||
- Windows Store logos (`Square*.png`, `StoreLogo.png`)
|
||||
- iOS and Android directories
|
||||
|
||||
The `tauri.conf.json` bundle icon paths already reference the correct files. **No changes needed.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Git History Scrub
|
||||
|
||||
The committed history contains `REDACTED_USERNAME` in blob content (not in author/committer fields — those are already `cottongin`). Specifically:
|
||||
|
||||
- `src-tauri/tauri.conf.json`: the old `"xyz.cottongin.gui-video-clipper"` identifier
|
||||
- `docs/superpowers/plans/*.md`: absolute paths like ``
|
||||
|
||||
### Approach: `git-filter-repo --replace-text`
|
||||
|
||||
After all code/config changes are committed, use `git-filter-repo` to rewrite history:
|
||||
|
||||
1. Create a replacements file (e.g., `replacements.txt`) with literal string replacements:
|
||||
```
|
||||
xyz.cottongin.gui-video-clipper==>xyz.cottongin.gui-video-clipper
|
||||
==>
|
||||
==>
|
||||
```
|
||||
Each line is `OLD==>NEW`. The path replacements remove the absolute prefix, leaving just the command (or a relative path). Order matters — longer matches first.
|
||||
|
||||
2. Run: `git filter-repo --replace-text replacements.txt --force`
|
||||
|
||||
3. Verify: `git log --all -p | grep -i REDACTED_USERNAME` should return nothing.
|
||||
|
||||
4. Clean up: remove `replacements.txt` (it's a one-time tool, not committed).
|
||||
|
||||
**Important:** `git-filter-repo` rewrites all commit hashes. Since this repo has never been pushed to a remote, that's fine — there's no shared history to disrupt. The remote must be re-added after `filter-repo` runs (it strips remotes by default).
|
||||
|
||||
---
|
||||
|
||||
## 8. Commit, Remote & Tag
|
||||
|
||||
### Commit Strategy
|
||||
|
||||
1. Make all code/config changes (sections 1-5 above).
|
||||
2. Stage and commit everything — including all currently untracked files (`chat-summaries/`, `docs/superpowers/`, new source files under `src-tauri/src/services/`, `src/lib/components/ProcessingModal.svelte`, `src/lib/utils/vttParser.ts`).
|
||||
3. Use a descriptive commit message: `chore: v0.1.0 release polish — versioning, branding, about dialog, license, readme`.
|
||||
|
||||
### History Scrub
|
||||
|
||||
4. Run the `git-filter-repo --replace-text` pass (Section 7).
|
||||
5. Verify no `REDACTED_USERNAME` remains in any blob.
|
||||
|
||||
### Remote & Tag
|
||||
|
||||
6. Re-add the remote (filter-repo removes it): `git remote add origin git@code.cottongin.xyz:cottongin/gui-video-clipper.git`
|
||||
7. Tag: `git tag -a v0.1.0 -m "Initial release"`
|
||||
8. Push: `git push -u origin main && git push origin v0.1.0`
|
||||
|
||||
---
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- No CI/CD pipeline setup (future work).
|
||||
- No `.dmg` / installer packaging (future work).
|
||||
- No changelog generation (future work).
|
||||
- No automated version bumping via git hooks.
|
||||
Reference in New Issue
Block a user