From 0c4672207737bc9a4d5eec5d351de96460114e5f Mon Sep 17 00:00:00 2001 From: oratis Date: Sat, 27 Jun 2026 16:26:31 +0800 Subject: [PATCH] =?UTF-8?q?docs(gtm):=20build-in-public=20article=20pipeli?= =?UTF-8?q?ne=20+=20first=20draft=20(=C2=A77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GTM-SCALE-PLAN.md §7 content flywheel. Adds the article pipeline (README) and the first full draft: "Reading a GitHub repo as a local vault — without a git library" (the materialize-then-local decision + computing git's blob SHA-1 by hand for edit detection). Tech claims verified against github_vault.rs (git_blob_sha) + docs/design/06-github-roundtrip.md. Drafts only — owner ratifies voice/depth before publishing; each post is its own launch point (space them to sustain the flywheel). Co-Authored-By: Claude Opus 4.8 --- docs/marketing/build-in-public/README.md | 28 +++++ .../build-in-public/github-roundtrip.md | 106 ++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 docs/marketing/build-in-public/README.md create mode 100644 docs/marketing/build-in-public/github-roundtrip.md diff --git a/docs/marketing/build-in-public/README.md b/docs/marketing/build-in-public/README.md new file mode 100644 index 0000000..f7a9b30 --- /dev/null +++ b/docs/marketing/build-in-public/README.md @@ -0,0 +1,28 @@ +# Build-in-public — technical article pipeline + +> GTM-SCALE-PLAN.md §7. Each post is a **launch point** of its own: it lands on +> HN / Lobsters / 掘金 / dev.to, pulls in developers, and earns stars — then the +> sustaining content flywheel that bridges 3K → 10K. +> +> **Drafts — owner ratifies voice + technical depth before publishing.** Tech +> claims are written from the actual code/design docs, but you know what's safe +> to reveal. Nothing is published. + +## Pipeline + +| Draft | Angle / hook | Source of truth | Status | +|---|---|---|---| +| `github-roundtrip.md` | "Materialize, then it's local" — reading a GitHub repo as a vault, and proposing edits as a PR, **without a git library** (git blob SHA-1 by hand) | `github_vault.rs`, `docs/design/06-github-roundtrip.md` | **draft ready** | +| *(todo)* `chinese-ime-tauri.md` | Fixing Chinese IME in a Tauri/WebView editor — a known pain point, and a core audience (GTM §4 中文) | editor code + WebView quirks | outline | +| *(todo)* `tantivy-1s-index.md` | Full-text search over thousands of Markdown files in ~1s with Tantivy | `index.rs`, `scanner.rs` | outline | +| *(todo)* `obsidian-canvas-reverse.md` | Reverse-engineering Obsidian's `.canvas` format for compatibility | Canvas code, `docs/V2_CANVAS.md` | outline | +| *(todo)* `tauri-sandbox-notes.md` | Tauri sandbox / capability gotchas shipping a real macOS app (signing, entitlements, MAS) | `release.yml`, `Entitlements.plist`, app-store docs | outline | + +## House rules + +- **One post = one launch.** Don't dump them all at once; space them to sustain + the flywheel (GTM §5/§7). +- **Accurate or omit.** Every technical claim must match the shipped code. When + unsure, cut it. +- **Link back** to the repo + a canonical copy (the product site once live). +- **Honest platform state** (macOS/iOS today; Win/Linux coming). diff --git a/docs/marketing/build-in-public/github-roundtrip.md b/docs/marketing/build-in-public/github-roundtrip.md new file mode 100644 index 0000000..a31d16e --- /dev/null +++ b/docs/marketing/build-in-public/github-roundtrip.md @@ -0,0 +1,106 @@ + + +# Reading a GitHub repo as a local vault — and proposing edits as a PR — without a git library + +One of [Markup](https://github.com/oratis/Markup)'s defining features is that it +opens **any GitHub repo as a vault**: browse the whole tree, full-text search, +follow wikilinks, read it offline — then fix a typo and send the change back as +a pull request. No `git` binary, no libgit2. Here's how it works, and the one +design decision that made the whole thing simple. + +## The trap: a "remote vault" mode + +The obvious design is a second code path — a "remote" vault that fetches files +from the GitHub API on demand, caches them, and renders them through some +special remote-aware pipeline. That means two of everything: two renderers, two +search indexes, two link resolvers. Every feature now ships twice and drifts. + +## The decision: materialize, then it's local + +We don't do remote vaults. We **download the repo to disk first**, and then the +*existing local pipeline runs unchanged*: + +1. Fetch the repo's **zipball** (one request, the whole tree). +2. **Extract it atomically** into the app cache. +3. Point the normal `open_vault` at the folder — the same Tantivy indexer, file + watcher, wikilink resolver, and backlink graph that a local folder uses. + +Reading a GitHub vault becomes **indistinguishable** from reading a local +folder: relative images resolve, links work, search is instant — because it +*is* a local folder. Zero new rendering paths. iOS proved this shape first; +the desktop inherited it wholesale. + +Two details that matter: + +- **GitHub zipballs wrap everything in a single `repo-/` directory**, so + extraction strips that prefix to land files at the vault root. +- **Extraction is atomic** — contents go into a partial directory and get + renamed into place only on success, so a half-downloaded repo never appears + as a corrupt vault. + +## Knowing what changed — git's blob SHA, computed by hand + +To send edits back, you have to know *which files the user changed* relative to +what they pulled. The temptation is to shell out to `git status`. We don't ship +git, so instead we record a tiny **manifest** when the vault is materialized: + +```jsonc +// .markup/manifest.json +{ + "commit_sha": "…", // the exact commit we pinned + "entries": [ + { "path": "README.md", "sha": "", "size": 1234 }, + … + ] +} +``` + +The `sha` is **git's blob SHA-1** — the same identifier GitHub reports for every +blob. The trick: git's blob id isn't a hash of the file bytes, it's + +``` +SHA1("blob " + + "\0" + ) +``` + +Compute that for a local file and you get **the exact id GitHub would give it** +— no git library required. So "did this file change?" is a one-line comparison: +hash the local file, check it against `entries[].sha`. + +Pulling the latest is the mirror image: fetch the new tree, then diff the new +blob SHAs against the manifest into `added` / `changed` / `removed`, apply, and +rewrite the manifest. The same hash powers both "what did I change locally?" and +"what changed upstream?". + +## Sending it back: a PR, not a git client + +Markup is a "fix this doc" button, not a git GUI. So write-back is the minimal +GitHub API dance: create a branch, commit the changed blobs, open a pull +request. When you *can't* push to the repo, it forks first and opens a +cross-repo PR — and the UI doesn't change at all; the "propose changes" dialog +just works either way. + +## Why this is the right shape + +- **One pipeline.** Read, search, render, and link-resolve have a single + implementation. A GitHub vault can't render differently from a local one + because there's only one renderer. +- **Offline by default.** Once materialized, the repo is just files — read it on + a plane. +- **No heavy dependencies.** A zip extractor and a SHA-1 function replace an + embedded git. The binary stays small (it's a [Tauri](https://tauri.app) + Rust + app — native, not Electron). + +The lesson generalizes: when you're tempted to add a "remote mode," check +whether you can **make the remote thing local first** and reuse everything you +already built. Here it collapsed two pipelines into one and made the killer +feature — *read any repo's docs natively, then fix them* — almost fall out for +free. + +--- + +*Markup is a free, open-source (MIT), native Markdown editor — reader-first, +with a vault, backlinks, graph, full-text search, and this GitHub round-trip. +macOS today; Windows & Linux coming. → [github.com/oratis/Markup](https://github.com/oratis/Markup)*