Compare commits
5 Commits
2bc4a98164
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| cad3b9c57d | |||
| e551b15aca | |||
| 0b4459365d | |||
| 4aa74d535b | |||
| 48df4f2965 |
@@ -4,16 +4,39 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
|
- '.github/workflows/ci.yml'
|
||||||
|
# CHANGELOG.md is a build input: src/changelog.ts imports it ?raw for
|
||||||
|
# the What's New modal, and changelog.test.ts asserts on its content.
|
||||||
|
- 'CHANGELOG.md'
|
||||||
|
- 'index.html'
|
||||||
|
- 'package.json'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
|
- 'pnpm-workspace.yaml'
|
||||||
- 'src/**'
|
- 'src/**'
|
||||||
- 'src-tauri/**'
|
- 'src-tauri/**'
|
||||||
|
- 'tsconfig*.json'
|
||||||
|
- 'vite.config.ts'
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
|
- '.github/workflows/ci.yml'
|
||||||
|
# CHANGELOG.md is a build input: src/changelog.ts imports it ?raw for
|
||||||
|
# the What's New modal, and changelog.test.ts asserts on its content.
|
||||||
|
- 'CHANGELOG.md'
|
||||||
|
- 'index.html'
|
||||||
|
- 'package.json'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
|
- 'pnpm-workspace.yaml'
|
||||||
- 'src/**'
|
- 'src/**'
|
||||||
- 'src-tauri/**'
|
- 'src-tauri/**'
|
||||||
|
- 'tsconfig*.json'
|
||||||
|
- 'vite.config.ts'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ci-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -21,7 +44,9 @@ jobs:
|
|||||||
# windows-latest to match the release target — the ML crates (ort, candle)
|
# windows-latest to match the release target — the ML crates (ort, candle)
|
||||||
# and the NSIS bundle only ever ship from Windows.
|
# and the NSIS bundle only ever ship from Windows.
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 60
|
||||||
env:
|
env:
|
||||||
|
CARGO_INCREMENTAL: 0
|
||||||
GITEA_STATUS_TOKEN: ${{ secrets.GITEA_STATUS_TOKEN }}
|
GITEA_STATUS_TOKEN: ${{ secrets.GITEA_STATUS_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
- name: Report pending status to Gitea
|
- name: Report pending status to Gitea
|
||||||
@@ -58,10 +83,14 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: pnpm install --frozen-lockfile
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Unit tests
|
||||||
|
run: pnpm test:unit
|
||||||
|
|
||||||
# tsc + vite build; also produces dist/ which tauri's build script expects
|
# tsc + vite build; also produces dist/ which tauri's build script expects
|
||||||
- name: Type-check and build frontend
|
- name: Type-check and build frontend
|
||||||
run: pnpm build:vite
|
run: pnpm build:vite
|
||||||
@@ -85,6 +114,10 @@ jobs:
|
|||||||
working-directory: src-tauri
|
working-directory: src-tauri
|
||||||
run: cargo clippy --all-targets --locked --no-default-features -- -D warnings
|
run: cargo clippy --all-targets --locked --no-default-features -- -D warnings
|
||||||
|
|
||||||
|
- name: Rust tests
|
||||||
|
working-directory: src-tauri
|
||||||
|
run: cargo test --locked --no-default-features
|
||||||
|
|
||||||
- name: Report final status to Gitea
|
- name: Report final status to Gitea
|
||||||
if: always() && github.event_name != 'pull_request' && env.GITEA_STATUS_TOKEN != ''
|
if: always() && github.event_name != 'pull_request' && env.GITEA_STATUS_TOKEN != ''
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|||||||
@@ -8,15 +8,59 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Existing release tag to build, for example v0.1.1'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
timeout-minutes: 120
|
||||||
env:
|
env:
|
||||||
|
CARGO_INCREMENTAL: 0
|
||||||
GITEA_STATUS_TOKEN: ${{ secrets.GITEA_STATUS_TOKEN }}
|
GITEA_STATUS_TOKEN: ${{ secrets.GITEA_STATUS_TOKEN }}
|
||||||
|
RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }}
|
||||||
steps:
|
steps:
|
||||||
|
# refs/tags/ qualification: a branch with a tag-like name must never win
|
||||||
|
# ref resolution. Works for tag pushes too — RELEASE_TAG is the tag name.
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: refs/tags/${{ env.RELEASE_TAG }}
|
||||||
|
|
||||||
|
# On workflow_dispatch GITHUB_SHA is the dispatched branch head, not the
|
||||||
|
# tag's commit — resolve the real one for Gitea commit statuses.
|
||||||
|
- name: Resolve release commit
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
$sha = git rev-parse HEAD
|
||||||
|
"RELEASE_SHA=$sha" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||||
|
|
||||||
|
- name: Verify release version
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
if ($env:RELEASE_TAG -notmatch '^v\d+\.\d+\.\d+(-.+)?$') {
|
||||||
|
throw "Release tag '$env:RELEASE_TAG' must look like v0.1.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
$packageVersion = (Get-Content package.json -Raw | ConvertFrom-Json).version
|
||||||
|
$cargoVersion = (Select-String -Path src-tauri/Cargo.toml -Pattern '^version\s*=\s*"(.+)"').Matches[0].Groups[1].Value
|
||||||
|
|
||||||
|
if ($packageVersion -ne $cargoVersion) {
|
||||||
|
throw "package.json version ($packageVersion) does not match Cargo.toml version ($cargoVersion)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($env:RELEASE_TAG -ne "v$packageVersion") {
|
||||||
|
throw "Release tag '$env:RELEASE_TAG' does not match project version v$packageVersion"
|
||||||
|
}
|
||||||
|
|
||||||
- name: Report pending status to Gitea
|
- name: Report pending status to Gitea
|
||||||
if: env.GITEA_STATUS_TOKEN != ''
|
if: env.GITEA_STATUS_TOKEN != ''
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
@@ -36,13 +80,11 @@ jobs:
|
|||||||
} | ConvertTo-Json
|
} | ConvertTo-Json
|
||||||
Invoke-RestMethod `
|
Invoke-RestMethod `
|
||||||
-Method Post `
|
-Method Post `
|
||||||
-Uri "https://git.jezz.wtf/api/v1/repos/JezzWTF/phokus/statuses/$env:GITHUB_SHA" `
|
-Uri "https://git.jezz.wtf/api/v1/repos/JezzWTF/phokus/statuses/$env:RELEASE_SHA" `
|
||||||
-Headers $headers `
|
-Headers $headers `
|
||||||
-ContentType "application/json" `
|
-ContentType "application/json" `
|
||||||
-Body $body
|
-Body $body
|
||||||
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
with:
|
with:
|
||||||
version: 11
|
version: 11
|
||||||
@@ -51,6 +93,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
cache-dependency-path: pnpm-lock.yaml
|
||||||
|
|
||||||
- name: Install Rust stable
|
- name: Install Rust stable
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
@@ -71,7 +114,7 @@ jobs:
|
|||||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
with:
|
with:
|
||||||
tagName: ${{ github.ref_name }}
|
tagName: ${{ env.RELEASE_TAG }}
|
||||||
releaseName: 'Phokus v__VERSION__'
|
releaseName: 'Phokus v__VERSION__'
|
||||||
releaseBody: 'See the assets below to download and install this version.'
|
releaseBody: 'See the assets below to download and install this version.'
|
||||||
releaseDraft: true
|
releaseDraft: true
|
||||||
@@ -105,9 +148,11 @@ jobs:
|
|||||||
description = "GitHub Actions release finished: $env:JOB_STATUS"
|
description = "GitHub Actions release finished: $env:JOB_STATUS"
|
||||||
target_url = $env:GITHUB_RUN_URL
|
target_url = $env:GITHUB_RUN_URL
|
||||||
} | ConvertTo-Json
|
} | ConvertTo-Json
|
||||||
|
# RELEASE_SHA is unset if the job died before checkout; fall back.
|
||||||
|
$sha = if ($env:RELEASE_SHA) { $env:RELEASE_SHA } else { $env:GITHUB_SHA }
|
||||||
Invoke-RestMethod `
|
Invoke-RestMethod `
|
||||||
-Method Post `
|
-Method Post `
|
||||||
-Uri "https://git.jezz.wtf/api/v1/repos/JezzWTF/phokus/statuses/$env:GITHUB_SHA" `
|
-Uri "https://git.jezz.wtf/api/v1/repos/JezzWTF/phokus/statuses/$sha" `
|
||||||
-Headers $headers `
|
-Headers $headers `
|
||||||
-ContentType "application/json" `
|
-ContentType "application/json" `
|
||||||
-Body $body
|
-Body $body
|
||||||
|
|||||||
+2
-1
@@ -5,7 +5,7 @@ All notable changes to Phokus are documented here. The format is based on
|
|||||||
aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
||||||
(0.x: anything may change between minor versions).
|
(0.x: anything may change between minor versions).
|
||||||
|
|
||||||
## [Unreleased]
|
## [0.2.0] — 2026-07-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@@ -264,5 +264,6 @@ installer with a built-in updater.
|
|||||||
Settings, with live size/reclaimable stats.
|
Settings, with live size/reclaimable stats.
|
||||||
- **Window state** persistence and single-instance handling.
|
- **Window state** persistence and single-instance handling.
|
||||||
|
|
||||||
|
[0.2.0]: https://github.com/JezzWTF/phokus/releases/tag/v0.2.0
|
||||||
[0.1.1]: https://github.com/JezzWTF/phokus/releases/tag/v0.1.1
|
[0.1.1]: https://github.com/JezzWTF/phokus/releases/tag/v0.1.1
|
||||||
[0.1.0]: https://github.com/JezzWTF/phokus/releases/tag/v0.1.0
|
[0.1.0]: https://github.com/JezzWTF/phokus/releases/tag/v0.1.0
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Phokus v0.2.0
|
||||||
|
|
||||||
|
> Draft for the GitHub Release body. Fill in the checksums and trim as needed.
|
||||||
|
|
||||||
|
**Phokus is a local-first desktop media library for Windows** — point it at
|
||||||
|
your image and video folders and it builds a fast, searchable gallery with
|
||||||
|
thumbnails, semantic search, visual discovery, AI tagging, and duplicate
|
||||||
|
cleanup. Everything is processed on your machine; nothing is uploaded.
|
||||||
|
|
||||||
|
0.2.0 is the biggest update since launch: albums, gallery multi-select with
|
||||||
|
bulk actions, colour search, a second AI tagging model (JoyTag), a full tag
|
||||||
|
manager, camera EXIF details in the lightbox, a slideshow mode, and a large
|
||||||
|
batch of performance and theme fixes. Updating from 0.1.x? The built-in
|
||||||
|
updater will fetch this for you — and afterwards, a new in-app "What's New"
|
||||||
|
tour shows you around.
|
||||||
|
|
||||||
|
## Install / update
|
||||||
|
|
||||||
|
- **Updating from 0.1.x:** the in-app updater will offer 0.2.0 on launch — one
|
||||||
|
click downloads, installs, and relaunches.
|
||||||
|
- **Fresh install:** download `Phokus_0.2.0_x64-setup.exe` below and run it.
|
||||||
|
**Windows SmartScreen will warn** that the publisher is unrecognized — this
|
||||||
|
build is **not code-signed**. Click **More info → Run anyway**.
|
||||||
|
- Requires **Windows 10/11**. WebView2 is fetched automatically if missing.
|
||||||
|
|
||||||
|
NVIDIA users wanting GPU embedding/tagging speed can use the
|
||||||
|
`Phokus_0.2.0_x64-cuda-setup.exe` variant instead (larger download; bundles the
|
||||||
|
CUDA runtime DLLs). Settings → Updates now shows which build you are running.
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Albums** — cross-folder collections without moving files: their own
|
||||||
|
sidebar section with cover thumbnails, create/rename/reorder/manage, and
|
||||||
|
album-aware similar-image search.
|
||||||
|
- **Gallery multi-select** — hover a thumbnail's corner to start selecting,
|
||||||
|
then bulk tag, rate, favorite, add to an album, or delete from a floating
|
||||||
|
action bar. Works in similar-image, region, and album views too.
|
||||||
|
- **Colour search** — filter the Gallery, Timeline, or tag results by dominant
|
||||||
|
colour via toolbar swatches or a custom picker.
|
||||||
|
- **Choose your tagging model** — pick between the anime-focused WD tagger and
|
||||||
|
**JoyTag** (better for photo libraries), each with its own confidence
|
||||||
|
threshold, plus a **Reset AI tags** action that never touches manual tags.
|
||||||
|
- **Tag manager** — rename, merge, and delete tags across the library, with
|
||||||
|
live search and sorting; Explore's Tag Cloud also shows **related tags** on
|
||||||
|
hover.
|
||||||
|
- **Camera info in the lightbox** — EXIF camera, lens, aperture, shutter
|
||||||
|
speed, ISO, focal length, and a map link for geotagged photos.
|
||||||
|
- **Slideshow mode**, **What's New tour after updates**, **quick theme switch**
|
||||||
|
from the title bar, an editable path bar in the folder picker, persistent
|
||||||
|
per-folder worker pauses, and add-to-album from the right-click menu.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Settings reorganised** into General, Media, Updates & Setup, Storage, and
|
||||||
|
AI Workspace pages.
|
||||||
|
- **Menus unified** — all context menus and dropdowns share one style, stay on
|
||||||
|
screen, close on Escape, and support submenus.
|
||||||
|
- **Faster Explore** — quicker cluster revisits, much faster first-time
|
||||||
|
clustering on large libraries, and a calmer Tag Cloud during active tagging.
|
||||||
|
- **Faster CPU tagging** (multi-core with headroom) and smoother GPU tagging.
|
||||||
|
- **Safer deletion** — deleting media now asks for confirmation and says
|
||||||
|
clearly that files are removed from disk.
|
||||||
|
- Better narrow-window layouts, a real update download progress bar, and a
|
||||||
|
two-column lightbox info panel.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **No more self-indexing loops** — Phokus now skips its own thumbnail cache
|
||||||
|
when you add a broad folder like your user profile.
|
||||||
|
- **Ratings keep your search order** — rating an image mid-search no longer
|
||||||
|
reshuffles similar-image, region, semantic, tag, or album results.
|
||||||
|
- **AI tagging stays responsive** — big GPU tagging jobs no longer freeze the
|
||||||
|
UI, and noisy low-signal tags are filtered (older ones cleaned up on
|
||||||
|
startup).
|
||||||
|
- First launch now fits smaller screens, Explore no longer flashes the
|
||||||
|
previous folder, and a long list of Subtle Light readability fixes.
|
||||||
|
|
||||||
|
See the [changelog](https://github.com/JezzWTF/phokus/blob/main/CHANGELOG.md)
|
||||||
|
for the full list.
|
||||||
|
|
||||||
|
## Checksums
|
||||||
|
|
||||||
|
```
|
||||||
|
SHA-256 (Phokus_0.2.0_x64-setup.exe) = TBD
|
||||||
|
SHA-256 (Phokus_0.2.0_x64-cuda-setup.exe) = TBD
|
||||||
|
```
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "phokus",
|
"name": "phokus",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Generated
+1
-1
@@ -4595,7 +4595,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phokus"
|
name = "phokus"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"candle-core",
|
"candle-core",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "phokus"
|
name = "phokus"
|
||||||
version = "0.1.1"
|
version = "0.2.0"
|
||||||
description = "Local-first desktop media library"
|
description = "Local-first desktop media library"
|
||||||
authors = ["JezzWTF"]
|
authors = ["JezzWTF"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|||||||
+4
-1
@@ -1308,7 +1308,10 @@ fn media_kind_clause(include_videos: bool) -> &'static str {
|
|||||||
/// in a `LIKE` pattern (paired with `ESCAPE '\\'` in the query) without the
|
/// in a `LIKE` pattern (paired with `ESCAPE '\\'` in the query) without the
|
||||||
/// wildcards being interpreted literally.
|
/// wildcards being interpreted literally.
|
||||||
fn escape_like_pattern(value: &str) -> String {
|
fn escape_like_pattern(value: &str) -> String {
|
||||||
value.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_")
|
value
|
||||||
|
.replace('\\', "\\\\")
|
||||||
|
.replace('%', "\\%")
|
||||||
|
.replace('_', "\\_")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if any claimable (pending, non-excluded) jobs exist in `job_table`.
|
/// True if any claimable (pending, non-excluded) jobs exist in `job_table`.
|
||||||
|
|||||||
@@ -114,7 +114,10 @@ export function TitleBar() {
|
|||||||
up its focal point and the chip becomes a button that re-opens the prompt. */}
|
up its focal point and the chip becomes a button that re-opens the prompt. */}
|
||||||
<div className="flex items-center gap-2 pr-4 pl-3">
|
<div className="flex items-center gap-2 pr-4 pl-3">
|
||||||
{updatePending ? (
|
{updatePending ? (
|
||||||
<div style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}>
|
<div
|
||||||
|
className="flex h-5 w-5 items-center justify-center"
|
||||||
|
style={{ WebkitAppRegion: 'no-drag' } as React.CSSProperties}
|
||||||
|
>
|
||||||
{/* Instant tooltip (delay 0) — this affordance should read immediately. */}
|
{/* Instant tooltip (delay 0) — this affordance should read immediately. */}
|
||||||
<Tooltip label={`Click to update — v${updateVersion}`} delay={0} align="start">
|
<Tooltip label={`Click to update — v${updateVersion}`} delay={0} align="start">
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in New Issue
Block a user