764 lines
22 KiB
Markdown
764 lines
22 KiB
Markdown
# 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.
|