diff --git a/.claude/rules/docs.md b/.claude/rules/docs.md index dd84ad4..77cde8d 100644 --- a/.claude/rules/docs.md +++ b/.claude/rules/docs.md @@ -8,7 +8,7 @@ paths: When shipping a user-facing feature, update in the same commit: -- **`README.md`** — add a bullet under the relevant `## Features` subsection; if the feature adds a keyboard shortcut, add a row to the `## Keyboard Shortcuts` table +- **`README.md`** — add a bullet under the relevant `## Features` subsection - **`samples/README.md`** — showcase the feature where applicable (new markdown syntax gets a demo section; new shortcuts go in the shortcuts table). The `samples/` folder also doubles as a demo workspace, so add or update sibling files when introducing workspace-level features (wikilinks, backlinks, etc.) Treat these as part of the feature's definition of done, not a follow-up. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5290ee2..0635cfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -178,6 +178,24 @@ jobs: working-directory: src-tauri run: cargo clippy -- -D warnings + validate-packaging: + name: Validate packaging metadata + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Install validators + run: | + sudo apt-get update + sudo apt-get install -y appstream desktop-file-utils python3-yaml + - name: Validate Flatpak AppStream metainfo + run: appstreamcli validate --no-net flatpak/com.hamidfzm.glyph.metainfo.xml + - name: Validate Flatpak desktop entry + run: desktop-file-validate flatpak/com.hamidfzm.glyph.desktop + - name: Validate YAML manifests parse + run: | + python3 -c "import yaml,sys; yaml.safe_load(open('flatpak/com.hamidfzm.glyph.yml'))" + python3 -c "import yaml,sys; yaml.safe_load(open('snap/snapcraft.yaml'))" + build: name: Build (${{ matrix.platform }}) needs: [lint, typecheck, test-frontend, test-rust, clippy] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7aa18fc..03e5c18 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,6 +78,12 @@ jobs: ### Linux ```bash + # Flathub (Flatpak) + flatpak install flathub com.hamidfzm.glyph + + # Snap Store + sudo snap install glyph + # Arch (AUR) yay -S glyph-md-bin @@ -134,6 +140,12 @@ jobs: ### Linux ```bash + # Flathub (Flatpak) + flatpak install flathub com.hamidfzm.glyph + + # Snap Store + sudo snap install glyph + # Arch (AUR) yay -S glyph-md-bin @@ -625,3 +637,125 @@ jobs: git commit -m "chore: update packages to $VERSION" git push } + + publish-snap: + needs: build + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-22.04 + - arch: arm64 + runner: ubuntu-22.04-arm + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v6 + # The snap is built from the released .deb (see snap/snapcraft.yaml), so + # this job needs no toolchain. It is a no-op until the maintainer adds the + # SNAPCRAFT_STORE_CREDENTIALS secret (snapcraft export-login), so it never + # fails releases before the Snap Store account is set up. + - name: Check for store credentials + id: gate + env: + CREDS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} + run: | + if [ -n "$CREDS" ]; then + echo "enabled=true" >> "$GITHUB_OUTPUT" + else + echo "No SNAPCRAFT_STORE_CREDENTIALS secret; skipping snap publish." + echo "enabled=false" >> "$GITHUB_OUTPUT" + fi + # snapcraft.yaml discovers the latest release and pulls the .deb for the + # build architecture. This job runs after the release is published, so + # "latest" is the tag that triggered this run. (Host env vars are not + # forwarded into the snapcraft build instance, so the version can't be + # passed that way.) + - name: Build snap + if: steps.gate.outputs.enabled == 'true' + id: build + uses: snapcore/action-build@v1 + - name: Publish snap to stable + if: steps.gate.outputs.enabled == 'true' + uses: snapcore/action-publish@v1 + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} + with: + snap: ${{ steps.build.outputs.snap }} + release: stable + + publish-flathub: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + # The canonical Flatpak manifest lives in this repo (flatpak/). Flathub + # builds from its own app repo (flathub/com.hamidfzm.glyph), so each + # release we rewrite the version + per-arch sha256 in the manifest and + # push it (with the desktop + metainfo files) to that repo, whose + # buildbot then builds and publishes. No-op until FLATHUB_TOKEN (a PAT + # with push access to the Flathub app repo) is set, so it never fails a + # release before the Flathub submission is accepted. + - name: Check for Flathub token + id: gate + env: + TOKEN: ${{ secrets.FLATHUB_TOKEN }} + run: | + if [ -n "$TOKEN" ]; then + echo "enabled=true" >> "$GITHUB_OUTPUT" + else + echo "No FLATHUB_TOKEN secret; skipping Flathub publish." + echo "enabled=false" >> "$GITHUB_OUTPUT" + fi + - name: Get version + if: steps.gate.outputs.enabled == 'true' + id: version + run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + - name: Download debs and compute sha256 + if: steps.gate.outputs.enabled == 'true' + id: sha + env: + GH_TOKEN: ${{ github.token }} + run: | + VERSION="${{ steps.version.outputs.version }}" + gh release download "${{ github.ref_name }}" \ + --repo "${{ github.repository }}" \ + --pattern "Glyph_${VERSION}_amd64.deb" \ + --pattern "Glyph_${VERSION}_arm64.deb" + echo "amd64=$(sha256sum "Glyph_${VERSION}_amd64.deb" | awk '{print $1}')" >> "$GITHUB_OUTPUT" + echo "arm64=$(sha256sum "Glyph_${VERSION}_arm64.deb" | awk '{print $1}')" >> "$GITHUB_OUTPUT" + - name: Update manifest and push to the Flathub app repo + if: steps.gate.outputs.enabled == 'true' + env: + GH_TOKEN: ${{ secrets.FLATHUB_TOKEN }} + run: | + VERSION="${{ steps.version.outputs.version }}" + SHA_AMD64="${{ steps.sha.outputs.amd64 }}" + SHA_ARM64="${{ steps.sha.outputs.arm64 }}" + + # Rewrite the version in both download URLs and the per-arch sha256. + # Each `url:` line is immediately followed by its `sha256:` line, so + # tag the arch on the url and replace the next sha256 accordingly. + awk -v v="$VERSION" -v a="$SHA_AMD64" -v r="$SHA_ARM64" ' + /releases\/download\/.*_amd64\.deb/ { sub(/v[0-9][0-9.]*\/Glyph_[0-9][0-9.]*_/, "v" v "/Glyph_" v "_"); arch="amd64" } + /releases\/download\/.*_arm64\.deb/ { sub(/v[0-9][0-9.]*\/Glyph_[0-9][0-9.]*_/, "v" v "/Glyph_" v "_"); arch="arm64" } + /^[[:space:]]*sha256:/ { + if (arch == "amd64") { sub(/sha256:.*/, "sha256: " a); arch="" } + else if (arch == "arm64") { sub(/sha256:.*/, "sha256: " r); arch="" } + } + { print } + ' flatpak/com.hamidfzm.glyph.yml > /tmp/manifest.yml + + git clone "https://x-access-token:${GH_TOKEN}@github.com/flathub/com.hamidfzm.glyph.git" /tmp/flathub + cp /tmp/manifest.yml /tmp/flathub/com.hamidfzm.glyph.yml + cp flatpak/com.hamidfzm.glyph.desktop /tmp/flathub/ + cp flatpak/com.hamidfzm.glyph.metainfo.xml /tmp/flathub/ + + cd /tmp/flathub + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet || { + git commit -m "Update to $VERSION" + git push + } diff --git a/README.md b/README.md index 7267d70..4338d61 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,19 @@ scoop bucket add hamidfzm https://github.com/hamidfzm/scoop-bucket scoop install glyph ``` +### Linux (Flathub) + +```bash +flatpak install flathub com.hamidfzm.glyph +flatpak run com.hamidfzm.glyph +``` + +### Linux (Snap) + +```bash +sudo snap install glyph +``` + ### Arch Linux (AUR) ```bash @@ -192,26 +205,6 @@ pnpm check # Lint + format + organize imports cd src-tauri && cargo clippy # Lint Rust ``` -## Keyboard Shortcuts - -| Shortcut | Action | -|----------|--------| -| `Cmd+O` / `Ctrl+O` | Open file(s) | -| `Cmd+Shift+O` / `Ctrl+Shift+O` | Open folder | -| `Cmd+K` / `Ctrl+K` | Command palette (files, headings, app actions) | -| `Cmd+P` / `Ctrl+P` | Print / Export to PDF | -| `Cmd+F` / `Ctrl+F` | Find in document | -| `Cmd+=` / `Ctrl+=` | Zoom in | -| `Cmd+-` / `Ctrl+-` | Zoom out | -| `Cmd+0` / `Ctrl+0` | Reset zoom | -| `Cmd+Z` / `Ctrl+Z` | Undo last in-document edit (e.g. task checkbox toggle) | -| `Cmd+Shift+Z` / `Ctrl+Shift+Z` (also `Ctrl+Y` on Windows/Linux) | Redo | -| `Cmd+B` / `Ctrl+B` | Toggle files sidebar | -| `Cmd+\` / `Ctrl+\` | Toggle outline sidebar | -| `Cmd+,` / `Ctrl+,` | Settings | -| `Cmd+W` / `Ctrl+W` | Close tab | -| `Cmd+Shift+W` / `Ctrl+Shift+W` | Close window | - ## Comparison with Other Markdown Apps Glyph is built around speed, native feel, and offline-first usage. The tables below compare its current capabilities against widely used markdown apps. Items marked "planned" track to issues on the [roadmap](https://github.com/hamidfzm/glyph/issues). diff --git a/flatpak/com.hamidfzm.glyph.desktop b/flatpak/com.hamidfzm.glyph.desktop new file mode 100644 index 0000000..f6f0b97 --- /dev/null +++ b/flatpak/com.hamidfzm.glyph.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Name=Glyph +GenericName=Markdown Viewer +Comment=Cross-platform markdown viewer and editor +Exec=glyph %F +Icon=com.hamidfzm.glyph +Terminal=false +Categories=Office;Utility;TextEditor; +MimeType=text/markdown; +Keywords=markdown;md;viewer;editor;notes; +StartupNotify=true +StartupWMClass=Glyph diff --git a/flatpak/com.hamidfzm.glyph.metainfo.xml b/flatpak/com.hamidfzm.glyph.metainfo.xml new file mode 100644 index 0000000..72babaa --- /dev/null +++ b/flatpak/com.hamidfzm.glyph.metainfo.xml @@ -0,0 +1,76 @@ + + + + com.hamidfzm.glyph + + Glyph + Cross-platform markdown viewer and editor + + CC0-1.0 + MIT + + + hamidfzm + + + +

+ Glyph is a modern, cross-platform markdown viewer and editor with + platform-native styling. Built with Tauri v2, React 19, and TypeScript. +

+

Features:

+ +
+ + com.hamidfzm.glyph.desktop + + https://github.com/hamidfzm/glyph + https://github.com/hamidfzm/glyph/issues + https://github.com/hamidfzm/glyph + + + + https://raw.githubusercontent.com/hamidfzm/glyph/main/docs/assets/hero.png + Glyph rendering a markdown document with the outline sidebar + + + + + Office + Utility + TextEditor + + + + markdown + md + viewer + editor + notes + + + + glyph + + + + + + #f5f5f5 + #1e1e1e + + + + + https://github.com/hamidfzm/glyph/releases/tag/v0.10.0 + + +
diff --git a/flatpak/com.hamidfzm.glyph.yml b/flatpak/com.hamidfzm.glyph.yml new file mode 100644 index 0000000..7f67e08 --- /dev/null +++ b/flatpak/com.hamidfzm.glyph.yml @@ -0,0 +1,64 @@ +# Flatpak manifest for Glyph. +# +# This installs the released Debian package into the GNOME runtime rather than +# recompiling from source, matching the AUR/PPA pattern already used in +# `.github/workflows/release.yml`. flatpak-builder understands `.deb` archives +# and unpacks the embedded data tarball, so `usr/...` paths below come straight +# from the Tauri-generated package. +# +# The `url` versions and `sha256` checksums below are placeholders. They are +# filled in per release (the SHA256s are the same ones computed by the +# `publish-aur` / `publish-debian` jobs). Validate this manifest in a Linux +# environment with: +# flatpak-builder --user --install --force-clean build-dir flatpak/com.hamidfzm.glyph.yml +# +# See the Tauri Flatpak guide: https://v2.tauri.app/distribute/flatpak/ +id: com.hamidfzm.glyph +runtime: org.gnome.Platform +runtime-version: "50" +sdk: org.gnome.Sdk +command: glyph + +finish-args: + - --socket=wayland + - --socket=fallback-x11 + - --device=dri + - --share=ipc + # Network access is needed for the optional AI features (Claude/OpenAI/Ollama). + - --share=network + # Read/write access to the user's documents so markdown files can be opened. + - --filesystem=home + # Desktop notifications. + - --talk-name=org.freedesktop.Notifications + +modules: + - name: glyph + buildsystem: simple + build-commands: + - install -Dm755 usr/bin/glyph /app/bin/glyph + - install -Dm644 com.hamidfzm.glyph.desktop /app/share/applications/com.hamidfzm.glyph.desktop + - install -Dm644 com.hamidfzm.glyph.metainfo.xml /app/share/metainfo/com.hamidfzm.glyph.metainfo.xml + # Re-home every Tauri-generated PNG icon under the Flatpak app-id, + # preserving its hicolor size directory (the .deb ships 32x32, 128x128, + # and 256x256@2). Iterating over whatever exists avoids hardcoding the + # exact set of sizes the bundler emits. + - | + for src in $(find usr/share/icons -name glyph.png); do + dest="/app/${src#usr/}" + install -Dm644 "$src" "${dest%/glyph.png}/com.hamidfzm.glyph.png" + done + sources: + - type: archive + only-arches: + - x86_64 + url: https://github.com/hamidfzm/glyph/releases/download/v0.10.0/Glyph_0.10.0_amd64.deb + sha256: 93e5d40ba44ee78563bc27e717ee954c714bbe6e63337d361a801c6c8cb53e4d + - type: archive + only-arches: + - aarch64 + url: https://github.com/hamidfzm/glyph/releases/download/v0.10.0/Glyph_0.10.0_arm64.deb + sha256: 86dbc1e9b0f29e8c20464b7e2aaa33daead86f270d65a3b0f8ebed7f204c8dcb + - type: file + path: com.hamidfzm.glyph.desktop + - type: file + path: com.hamidfzm.glyph.metainfo.xml diff --git a/msstore/README.md b/msstore/README.md new file mode 100644 index 0000000..d5af2f1 --- /dev/null +++ b/msstore/README.md @@ -0,0 +1,37 @@ +# Microsoft Store packaging + +Tracks Glyph's Microsoft Store listing. See [issue #292](https://github.com/hamidfzm/glyph/issues/292). + +## Approach + +The Store now accepts traditional Win32 installers, so Glyph submits the same +WiX `.msi` that `release.yml` already builds (`Glyph__x64_en-US.msi`). +This avoids an MSIX repackage/resign step. If tighter integration (Store +auto-update, clean uninstall) is needed later, switch to MSIX. + +## One-time maintainer setup + +1. Register a [Microsoft Partner Center](https://partner.microsoft.com/dashboard) + developer account (one-time registration fee). +2. Reserve the app name **Glyph** and complete the Store listing: description, + screenshots, age rating questionnaire, and privacy policy URL. +3. Create an Azure AD application and link it in Partner Center to obtain the + Store submission API credentials. +4. Add the following GitHub repository secrets: + - `PARTNER_CENTER_TENANT_ID` + - `PARTNER_CENTER_CLIENT_ID` + - `PARTNER_CENTER_CLIENT_SECRET` + - `PARTNER_CENTER_SELLER_ID` + - `PARTNER_CENTER_PRODUCT_ID` + +## Automation (follow-up) + +Once the account exists and the packaging path (installer vs MSIX) is locked in, +add a `publish-msstore` job to +[`.github/workflows/release.yml`](../.github/workflows/release.yml) that +downloads the release artifact and submits it via the +[Microsoft Store submission API](https://learn.microsoft.com/partner-center/marketplace/submission-api-onboarding) +(e.g. the `microsoft/store-submission` action), gated on the `PARTNER_CENTER_*` +secrets so it stays a no-op until configured. This is intentionally deferred: +the submission inputs depend on the Partner Center product type, which is part +of the one-time setup above. diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 0000000..3b43847 --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,84 @@ +# Snapcraft recipe for Glyph. +# +# Like the AUR/PPA/Flatpak packaging, this reuses the released Debian package +# instead of recompiling. By default it discovers the latest GitHub release and +# pulls the .deb matching the build architecture, so it needs no inputs and +# works under any snapcraft provider (LXD, multipass, destructive mode) and in +# `snapcore/action-build` — none of which forward arbitrary host env vars into +# the build instance. To pin a specific version (e.g. for a local test build of +# an older release), export GLYPH_VERSION *and* build in --destructive-mode so +# the variable reaches the build environment. +# +# The `publish-snap` job in `.github/workflows/release.yml` runs after the +# release is published, so "latest" there is the tag that triggered the run. +# +# Validate locally with: +# snapcraft --build-for amd64 # builds the latest release for amd64 +# +# See the Tauri Snapcraft guide: https://v2.tauri.app/distribute/snapcraft/ +name: glyph +base: core22 +adopt-info: glyph +summary: Cross-platform markdown viewer and editor +description: | + Glyph is a modern, cross-platform markdown viewer and editor with + platform-native styling. Built with Tauri v2, React 19, and TypeScript. + + Features GitHub Flavored Markdown with syntax highlighting, KaTeX math, + Mermaid diagrams, multi-file tabs, a table-of-contents outline, live file + watching, wikilinks and backlinks, optional AI summarization + (Claude/OpenAI/Ollama), text-to-speech, and light/dark themes that follow + the system appearance. + +grade: stable +confinement: strict +license: MIT +website: https://github.com/hamidfzm/glyph +issues: https://github.com/hamidfzm/glyph/issues +source-code: https://github.com/hamidfzm/glyph +compression: lzo + +apps: + glyph: + command: usr/bin/glyph + desktop: usr/share/applications/Glyph.desktop + extensions: + - gnome + plugs: + - home + - removable-media + - network + - desktop + - desktop-legacy + - wayland + - x11 + +parts: + glyph: + plugin: dump + source: . + build-packages: + - curl + - dpkg + override-pull: | + craftctl default + case "$CRAFT_ARCH_BUILD_FOR" in + amd64) DEB_ARCH=amd64 ;; + arm64) DEB_ARCH=arm64 ;; + *) echo "unsupported arch: $CRAFT_ARCH_BUILD_FOR" >&2; exit 1 ;; + esac + REPO="hamidfzm/glyph" + if [ -n "${GLYPH_VERSION:-}" ]; then + VERSION="$GLYPH_VERSION" + URL="https://github.com/${REPO}/releases/download/v${VERSION}/Glyph_${VERSION}_${DEB_ARCH}.deb" + else + # Discover the .deb for this arch from the latest published release. + URL=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \ + | grep -oE "https://github.com/${REPO}/releases/download/[^\"]+_${DEB_ARCH}\.deb") + VERSION=$(printf '%s' "$URL" | sed -E "s#.*/Glyph_([^_]+)_${DEB_ARCH}\.deb#\1#") + fi + test -n "$URL" || { echo "could not resolve a .deb URL for $DEB_ARCH" >&2; exit 1; } + curl -fSL -o glyph.deb "$URL" + dpkg-deb -x glyph.deb . + rm glyph.deb + craftctl set version="$VERSION"