a very good jj gui
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: publishing + release workflow

## RN:
- Add GitHub Actions publishing on every push to main (macOS-only for now)
- Generate GitHub prerelease notes from commit bodies under ## RN: with stable RN-ID dedupe
- Add MIT license and document publishing workflow

RN-ID: publishing-workflow

+403
+102
.claude/skills/publishing/SKILL.md
··· 1 + --- 2 + name: publishing 3 + description: Tatami publishing workflow (GitHub Actions + tauri-action), and the exact rules for building GitHub release notes from commit bodies (jj-friendly). 4 + --- 5 + 6 + # Tatami Publishing (GitHub Actions) 7 + 8 + Source of truth: `.github/workflows/release.yml`. 9 + 10 + ## Publishing: what happens (1–3) 11 + 12 + 1) **Push triggers workflow** 13 + - Any push to `main` triggers the workflow (also runnable via `workflow_dispatch`). 14 + - Concurrency is enabled with `cancel-in-progress: true`, so a newer push to `main` cancels any in-flight run for `main`. 15 + 16 + 2) **Prepare release metadata** 17 + - Computes the tag and name: 18 + - Tag: `nightly-<github.run_number>` 19 + - Name: `Nightly <github.run_number>` 20 + - Generates `releaseBody` by parsing commit bodies since the last `nightly-*` tag (details below). 21 + - If the last `nightly-*` tag is not an ancestor of `HEAD` (e.g. `main` rewritten), it falls back to commits since that tag’s committer timestamp. 22 + 23 + 3) **Build + publish (macOS-only)** 24 + - Runs on `macos-latest` only. 25 + - Uses `tauri-apps/tauri-action` to build `apps/desktop` and publish/attach artifacts to a GitHub Release. 26 + - Releases are marked as prereleases. 27 + 28 + ## Release notes: exactly what gets included 29 + 30 + Release notes are generated from commit **bodies** only (never from commit subjects). 31 + 32 + ### Opt-in section 33 + 34 + Only content under a commit-body heading line matching exactly: 35 + 36 + - `## RN:` 37 + - `## RN` 38 + 39 + is included. 40 + 41 + The RN section starts *after* that heading line. 42 + 43 + ### Where the RN section ends 44 + 45 + The RN section ends at the first subsequent markdown heading line that matches: 46 + 47 + - `## <something>` 48 + 49 + (i.e. the next H2 heading). 50 + 51 + ### Trailers are excluded 52 + 53 + Before parsing the RN section, the workflow strips commit trailers at the end of the commit body. 54 + 55 + A “trailer” is any line matching: 56 + 57 + - `Key: value` 58 + 59 + and any blank lines immediately above trailers. 60 + 61 + ### Dedupe (jj-friendly) 62 + 63 + Because `jj` history may be rewritten and hashes are not stable, dedupe is not hash-based. 64 + 65 + - If the RN section contains a line `RN-ID: <stable-id>`, that id is used as the dedupe key. 66 + - The `RN-ID:` line itself is removed from the published notes. 67 + - Otherwise, the entire RN markdown content is used as the dedupe key. 68 + 69 + ### Empty notes behavior 70 + 71 + If no commits in the selected range contain an RN section, the release body includes: 72 + 73 + - `_No release notes in this push. Add a ## RN: section to commit bodies._` 74 + 75 + ## Example commit message 76 + 77 + ```text 78 + feat: improve diff viewer 79 + 80 + Details that won’t be included. 81 + 82 + ## RN: 83 + - Add syntax highlighting for unified diffs 84 + - Fix scroll position when switching files 85 + RN-ID: diff-viewer-v1 86 + 87 + Co-authored-by: Someone <x@y.z> 88 + ``` 89 + 90 + Included in release notes: 91 + 92 + - The two bullet points under `## RN:` (without the `RN-ID:` line) 93 + 94 + Excluded: 95 + 96 + - Everything outside `## RN:` 97 + - Trailers like `Co-authored-by:` 98 + 99 + ## Files to edit 100 + 101 + - Workflow: `.github/workflows/release.yml` 102 + - Docs: `README.md` (publishing + RN convention)
+198
.github/workflows/release.yml
··· 1 + name: Release 2 + 3 + on: 4 + push: 5 + branches: [main] 6 + workflow_dispatch: 7 + 8 + permissions: 9 + contents: write 10 + 11 + concurrency: 12 + group: release-${{ github.ref }} 13 + cancel-in-progress: true 14 + 15 + jobs: 16 + prepare: 17 + name: Prepare release metadata 18 + runs-on: ubuntu-22.04 19 + outputs: 20 + tag: ${{ steps.meta.outputs.tag }} 21 + name: ${{ steps.meta.outputs.name }} 22 + body: ${{ steps.notes.outputs.body }} 23 + steps: 24 + - name: Checkout 25 + uses: actions/checkout@v4 26 + with: 27 + fetch-depth: 0 28 + 29 + - name: Compute tag/name 30 + id: meta 31 + run: | 32 + echo "tag=nightly-${{ github.run_number }}" >> "$GITHUB_OUTPUT" 33 + echo "name=Nightly ${{ github.run_number }}" >> "$GITHUB_OUTPUT" 34 + 35 + - name: Generate release notes 36 + id: notes 37 + shell: bash 38 + run: | 39 + set -euo pipefail 40 + 41 + git fetch --tags --force 42 + 43 + LAST_TAG="$(git tag --list 'nightly-*' --sort=-creatordate | head -n 1 || true)" 44 + if [ -n "${LAST_TAG}" ] && git merge-base --is-ancestor "${LAST_TAG}" HEAD; then 45 + RANGE="${LAST_TAG}..HEAD" 46 + LOG_ARGS=("${RANGE}") 47 + elif [ -n "${LAST_TAG}" ]; then 48 + # If main was force-pushed / rewritten, the last tag might not be an ancestor. 49 + # Fall back to commits since the tag's committer timestamp. 50 + SINCE="$(git log -1 --format=%cI "${LAST_TAG}")" 51 + LOG_ARGS=("--since=${SINCE}" "HEAD") 52 + else 53 + LOG_ARGS=("HEAD") 54 + fi 55 + 56 + # Collect markdown under a "## RN" / "## RN:" heading from commit bodies. 57 + # Trailers (Key: value) at the end of commit messages are stripped. 58 + NOTES="$( 59 + git log "${LOG_ARGS[@]}" --format=%B%x00 \ 60 + | awk -v RS='\0' ' 61 + function rtrim(s){ sub(/[[:space:]]+$/, "", s); return s } 62 + function ltrim(s){ sub(/^[[:space:]]+/, "", s); return s } 63 + function trim(s){ return rtrim(ltrim(s)) } 64 + function is_trailer(line){ return line ~ /^[A-Za-z0-9-]+: .+/ } 65 + 66 + function strip_trailers(n, i){ 67 + i = n 68 + while (i >= 1) { 69 + if (lines[i] ~ /^[[:space:]]*$/) { i--; continue } 70 + if (is_trailer(lines[i])) { i--; continue } 71 + break 72 + } 73 + return i 74 + } 75 + 76 + function emit_entry(entry, lines_n, j, line, id, cleaned, key){ 77 + # Optional stable key for dedupe across rewritten commits: 78 + # RN-ID: some-stable-id 79 + lines_n = split(entry, entry_lines, "\n") 80 + id = "" 81 + cleaned = "" 82 + for (j = 1; j <= lines_n; j++) { 83 + line = entry_lines[j] 84 + if (line ~ /^RN-ID:[[:space:]]*/) { 85 + id = line 86 + sub(/^RN-ID:[[:space:]]*/, "", id) 87 + continue 88 + } 89 + cleaned = cleaned line "\n" 90 + } 91 + cleaned = rtrim(cleaned) 92 + 93 + key = (id != "" ? "id:" id : cleaned) 94 + gsub(/[[:space:]]+$/, "", key) 95 + gsub(/\n{3,}/, "\n\n", key) 96 + 97 + if (!(key in seen) && length(trim(cleaned)) > 0) { 98 + seen[key] = 1 99 + if (out != "") out = out "\n\n" 100 + out = out cleaned 101 + } 102 + } 103 + 104 + { 105 + if ($0 == "") next 106 + 107 + n = split($0, lines, "\n") 108 + n = strip_trailers(n) 109 + 110 + in_rn = 0 111 + entry = "" 112 + for (i = 1; i <= n; i++) { 113 + line = lines[i] 114 + 115 + if (line ~ /^##[[:space:]]*RN:?[[:space:]]*$/) { 116 + in_rn = 1 117 + continue 118 + } 119 + 120 + if (in_rn && line ~ /^##[[:space:]]+[^#]/) { 121 + break 122 + } 123 + 124 + if (in_rn) { 125 + entry = entry line "\n" 126 + } 127 + } 128 + 129 + entry = rtrim(entry) 130 + # Trim leading blank lines 131 + while (entry ~ /^[[:space:]]*\n/) sub(/^[[:space:]]*\n/, "", entry) 132 + # Trim trailing blank lines 133 + while (entry ~ /\n[[:space:]]*$/) sub(/\n[[:space:]]*$/, "", entry) 134 + 135 + if (in_rn) emit_entry(entry) 136 + } 137 + 138 + END { 139 + print out 140 + } 141 + ' 142 + )" 143 + 144 + if [ -z "$(echo "$NOTES" | tr -d '[:space:]')" ]; then 145 + NOTES="_No release notes in this push. Add a ## RN: section to commit bodies._" 146 + fi 147 + 148 + BODY=$(cat <<EOF 149 + ## RN 150 + 151 + ${NOTES} 152 + 153 + --- 154 + 155 + Built from \`${{ github.sha }}\`. 156 + EOF 157 + ) 158 + 159 + { 160 + echo "body<<'EOF'" 161 + echo "$BODY" 162 + echo "EOF" 163 + } >> "$GITHUB_OUTPUT" 164 + 165 + build: 166 + name: Build and publish (${{ matrix.platform }}) 167 + needs: prepare 168 + strategy: 169 + fail-fast: false 170 + matrix: 171 + platform: [macos-latest] 172 + 173 + runs-on: ${{ matrix.platform }} 174 + 175 + steps: 176 + - name: Checkout 177 + uses: actions/checkout@v4 178 + with: 179 + fetch-depth: 0 180 + 181 + - name: Set up Bun 182 + uses: oven-sh/setup-bun@v2 183 + with: 184 + bun-version: latest 185 + 186 + - name: Install JS dependencies 187 + run: bun install --frozen-lockfile 188 + 189 + - name: Build and release 190 + uses: tauri-apps/tauri-action@v0 191 + env: 192 + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 193 + with: 194 + projectPath: apps/desktop 195 + tagName: ${{ needs.prepare.outputs.tag }} 196 + releaseName: ${{ needs.prepare.outputs.name }} 197 + releaseBody: ${{ needs.prepare.outputs.body }} 198 + prerelease: true
+21
LICENSE
··· 1 + MIT License 2 + 3 + Copyright (c) 2026 Tatami contributors 4 + 5 + Permission is hereby granted, free of charge, to any person obtaining a copy 6 + of this software and associated documentation files (the "Software"), to deal 7 + in the Software without restriction, including without limitation the rights 8 + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 + copies of the Software, and to permit persons to whom the Software is 10 + furnished to do so, subject to the following conditions: 11 + 12 + The above copyright notice and this permission notice shall be included in all 13 + copies or substantial portions of the Software. 14 + 15 + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 + SOFTWARE.
+82
README.md
··· 1 + # Tatami 2 + 3 + Tatami is a desktop GUI client for [Jujutsu](https://github.com/martinvonz/jj) (`jj`). It’s built with Tauri v2 + React, with a Rust backend that integrates `jj-lib`. 4 + 5 + ![Tatami screenshot](assets/screenshot.png) 6 + 7 + ## What it does 8 + 9 + - Revset-driven revision graph (lane assignment, trunk detection, related-commit dimming) 10 + - File diff viewer (hunks/lines, syntax highlighting, line numbers) 11 + - Keyboard-first navigation (vim-ish movement + command palette) 12 + - Live updates via filesystem watching 13 + - Persists projects + UI layout state in SQLite (Tauri app data dir) 14 + 15 + ## Repo layout 16 + 17 + - `apps/desktop/` — Tauri + React desktop app 18 + - `apps/desktop/src/` — frontend (TanStack Router/Query/DB, effect-atom, shadcn/ui) 19 + - `apps/desktop/src-tauri/` — Rust backend + Tauri commands 20 + - `assets/` — screenshots and other repo assets 21 + 22 + ## Quickstart 23 + 24 + Prereqs: `bun`, Rust toolchain, Tauri system deps, and `jj`. 25 + 26 + ```bash 27 + bun install 28 + bun run tauri dev 29 + ``` 30 + 31 + Browser-only UI development is supported via mocks in `apps/desktop/src/mocks/`. 32 + 33 + ## Contributing 34 + 35 + Keep dev commands short and prefer putting release notes in commit bodies. 36 + 37 + - Release notes: add a `## RN:` section to commit bodies (optional `RN-ID:` line for stable dedupe) 38 + 39 + ```bash 40 + # Frontend-only (Vite) 41 + bun run dev 42 + 43 + # Frontend checks (from apps/desktop/) 44 + cd apps/desktop 45 + bun run typecheck 46 + bun run lint 47 + bun run format 48 + ``` 49 + 50 + ## Publishing 51 + 52 + Publishing is automated via GitHub Actions in `.github/workflows/release.yml`. 53 + 54 + - Trigger: every push to `main` (plus `workflow_dispatch`) 55 + - Artifacts: macOS-only build for now, published to a GitHub prerelease (`nightly-<run_number>` tags) 56 + - Concurrency: new pushes cancel in-progress builds 57 + - Release notes: collected from commit bodies under a `## RN:` heading; trailers are ignored; use `RN-ID:` for stable dedupe when `jj` history is rewritten 58 + 59 + ## How it’s wired 60 + 61 + - Frontend calls Tauri commands via `apps/desktop/src/tauri-commands.ts` 62 + - Tauri commands live in `apps/desktop/src-tauri/src/lib.rs` 63 + - Backend uses `jj-lib` for repo access and emits repo change events for live refresh 64 + 65 + ## Issues 66 + 67 + This repo uses Fiberplane’s `fp` CLI for local-first issue tracking (configured in `.fp/config.toml`, prefix `TAT`). 68 + 69 + ```bash 70 + fp issue list 71 + fp tree 72 + fp context TAT-sfsb 73 + ``` 74 + 75 + ## More docs 76 + 77 + - `CLAUDE.md` — architecture notes + useful dev commands 78 + - `apps/desktop/README.md` — app-specific notes 79 + 80 + ## License 81 + 82 + MIT. See `LICENSE`.