chore: file cleanup
75142cc3
2 file(s) · +1 −181
| 27 | 27 | apps/og-go/og-go |
|
| 28 | 28 | apps/posts-go/posts-go |
|
| 29 | 29 | apps/shrink-go/shrink-go |
|
| 30 | - | apps/sipp-go/sipp-cli |
|
| 31 | - | apps/sipp-go/sipp-server |
|
| 30 | + | apps/sipp-go/sipp-go |
| 1 | - | # Plan: bring Go ports to parity with Rust originals |
|
| 2 | - | ||
| 3 | - | ## Context |
|
| 4 | - | ||
| 5 | - | Repo `/home/stevedylandev/Developer/andromeda` has paired Rust + Go versions of 10 apps. Audit (Phase 1) found gaps in 4 Go apps. User wants full parity. Deep-dive (Phase 2) corrected the initial gap list — some "missing" items in jotts-go are already implemented, and the posts EXIF strip referenced earlier does **not** exist in the Rust source either. |
|
| 6 | - | ||
| 7 | - | This plan covers only the real, confirmed gaps. |
|
| 8 | - | ||
| 9 | - | --- |
|
| 10 | - | ||
| 11 | - | ## Apps at parity (no work) |
|
| 12 | - | ||
| 13 | - | bookmarks, cellar, easel, feeds, library, og. |
|
| 14 | - | ||
| 15 | - | --- |
|
| 16 | - | ||
| 17 | - | ## Gap 1 — `posts-go`: R2/S3 storage backend |
|
| 18 | - | ||
| 19 | - | **Rust source to mirror:** |
|
| 20 | - | - `apps/posts/src/storage.rs` — `R2Config { bucket, creds, public_url, http }`, methods `from_env`, `put_object`, `delete_object`, `public_url_for`. Uses `rusty_s3 = "0.9"` + reqwest. |
|
| 21 | - | - `apps/posts/src/server/mod.rs:552` — init at startup, store `Option<R2Config>` in `AppState`. |
|
| 22 | - | - `apps/posts/src/server/handlers/admin.rs:439-522` — `admin_upload_file` routes via `if let Some(r2) = &state.r2`, sets `storage_backend` to `"r2"` or `"local"`, rolls back on failure. |
|
| 23 | - | - `apps/posts/src/server/handlers/public.rs:165-199` — `serve_uploaded_file` redirects to `r2.public_url_for(filename)` when backend is r2. |
|
| 24 | - | ||
| 25 | - | **EXIF strip is NOT in Rust** — drop from scope. |
|
| 26 | - | ||
| 27 | - | **Go changes (apps/posts-go/):** |
|
| 28 | - | - New package `storage/` with interface: |
|
| 29 | - | ```go |
|
| 30 | - | type Backend interface { |
|
| 31 | - | Put(ctx context.Context, key, contentType string, data []byte) error |
|
| 32 | - | Delete(ctx context.Context, key string) error |
|
| 33 | - | PublicURL(key string) string |
|
| 34 | - | Name() string // "local" | "r2" |
|
| 35 | - | } |
|
| 36 | - | ``` |
|
| 37 | - | - `storage/local.go` — wrap existing FS funcs from `storage.go`. |
|
| 38 | - | - `storage/r2.go` — use `github.com/aws/aws-sdk-go-v2/service/s3` with custom endpoint resolver for R2 (`https://<account>.r2.cloudflarestorage.com`). Or `github.com/minio/minio-go/v7` for less ceremony. |
|
| 39 | - | - `app.go` — add `Storage storage.Backend` field. |
|
| 40 | - | - `main.go` — `if os.Getenv("R2_BUCKET") != "" { storage.NewR2(...) } else { storage.NewLocal(uploadsDir) }`. |
|
| 41 | - | - `handlers_admin.go` (~line 301 `adminUploadFile`) — call `a.Storage.Put(...)`, capture `a.Storage.Name()` for DB insert, delete on rollback. |
|
| 42 | - | - `handlers_public.go` (~line 156 `serveUploadedFile`) — if `f.StorageBackend == "r2"`, `http.Redirect(... StatusTemporaryRedirect)`. |
|
| 43 | - | - `db.go` (~line 395 `createFile`) — pass backend string; column already exists. |
|
| 44 | - | - `.env.example` — add `R2_ACCOUNT_ID`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `R2_BUCKET`, `R2_PUBLIC_URL`. |
|
| 45 | - | ||
| 46 | - | --- |
|
| 47 | - | ||
| 48 | - | ## Gap 2 — `shrink-go`: EXIF preserve + GPS strip |
|
| 49 | - | ||
| 50 | - | **Rust source to mirror:** `apps/shrink/src/server.rs:103-207` (`strip_gps_from_exif`). Crates: `img-parts = "0.3"` + `image = "0.25"`. Algorithm: |
|
| 51 | - | 1. `Jpeg::from_bytes(orig)` → `j.exif().to_vec()` (raw APP1 payload). |
|
| 52 | - | 2. Re-encode resized JPEG (loses EXIF). |
|
| 53 | - | 3. Parse raw EXIF: TIFF header (II/MM), read IFD0 offset, walk entries (12 bytes each) for tag `0x8825` (GPS IFD pointer), zero the GPS IFD's entry count (2 bytes at the pointed offset). |
|
| 54 | - | 4. `out_jpeg.set_exif(Some(exif.into()))`, write final bytes. |
|
| 55 | - | ||
| 56 | - | **Go changes (apps/shrink-go/):** |
|
| 57 | - | - Add deps: `github.com/dsoprea/go-exif/v3` and `github.com/dsoprea/go-jpeg-image-structure/v2`. |
|
| 58 | - | - New `exif.go`: |
|
| 59 | - | - `extractExif(orig []byte) []byte` — use go-jpeg-image-structure segment list, return APP1 bytes (skip first 6 `Exif\0\0` prefix). |
|
| 60 | - | - `stripGPS(exif []byte) []byte` — mirror Rust byte-level walk (don't use go-exif builder; cheaper to keep parity). |
|
| 61 | - | - `injectExif(jpeg, exif []byte) []byte` — splice modified APP1 after SOI marker. |
|
| 62 | - | - `image.go` / `handlers.go` — wrap compress flow: extract before resize, strip GPS, inject after re-encode. |
|
| 63 | - | ||
| 64 | - | --- |
|
| 65 | - | ||
| 66 | - | ## Gap 3 — `sipp-go`: content wrap toggle (TUI theme is out of scope) |
|
| 67 | - | ||
| 68 | - | **Already done in Go** (verified via `apps/sipp-go/tui/`): syntect→chroma highlight, clipboard auto-copy on select (`update.go:244-261`), line numbers (`ShowLineNumbers = true`), vim keybindings (`keys.go`). |
|
| 69 | - | ||
| 70 | - | **Remaining:** Ctrl+W content wrap toggle (Rust `wrap_content: bool`). |
|
| 71 | - | ||
| 72 | - | **Go changes (apps/sipp-go/tui/):** |
|
| 73 | - | - `model.go` — add `wrapContent bool` field. |
|
| 74 | - | - `keys.go` — add `WrapToggle` binding (`ctrl+w`). |
|
| 75 | - | - `update.go` — when focus is content view/edit and `WrapToggle` matches, flip flag, reset scroll, emit status message. |
|
| 76 | - | - `view.go` — when rendering content viewport/textarea, branch on `wrapContent`. |
|
| 77 | - | ||
| 78 | - | **Custom .tmTheme loading:** Chroma has no native TextMate XML loader. Defer — README already documents the trade-off. Re-open as a follow-up if user wants it; will require either a tmTheme→chroma converter or hand-porting the two themes to chroma `chroma.MustNewStyle`. |
|
| 79 | - | ||
| 80 | - | --- |
|
| 81 | - | ||
| 82 | - | ## Gap 4 — `jotts-go`: complete the TUI editor |
|
| 83 | - | ||
| 84 | - | **Already done in Go** (verified): |
|
| 85 | - | - `cmd_auth.go` — interactive auth + `~/.config/jotts/config.toml` (BurntSushi/toml). |
|
| 86 | - | - `cmd_upload.go` — file → note + clipboard. |
|
| 87 | - | - `cmd_server.go:29` — startup `sessions.PruneExpired()` (in `crates-go/auth/auth.go`). |
|
| 88 | - | ||
| 89 | - | **Remaining:** interactive TUI editor. Mirror `apps/jotts/src/tui/{app,events,render}.rs` + `apps/jotts/src/tui.rs`. |
|
| 90 | - | ||
| 91 | - | **Go changes (apps/jotts-go/tui/):** |
|
| 92 | - | - `model.go` — `Focus` enum: List, Content, CreateTitle, CreateContent, EditTitle, EditContent, Search. |
|
| 93 | - | - `keys.go` — vim bindings: `hjkl`, `y` (copy content), `Y` (copy share link), `d` (delete with confirm), `c` (new), `e` (edit), `E` (external editor), `o` (open in browser), `/` (search), `?` (help), `q`/`esc` (quit/back). |
|
| 94 | - | - `update.go` — mode FSM driving Focus transitions; copy triggers via `atotto/clipboard.WriteAll`; status-line messages. |
|
| 95 | - | - `editor.go` (new) — external editor: read `$EDITOR`, write content to `os.CreateTemp`, `exec.Command(editor, path).Run()` with stdio attached to current TTY, re-read file on exit. |
|
| 96 | - | - `browser.go` (new) — open `<remote_url>/notes/<short_id>` via `github.com/pkg/browser` (cross-platform). |
|
| 97 | - | - `view.go` — two-pane layout (lipgloss `JoinHorizontal`, 30/70), borders/title styles, help line at bottom. |
|
| 98 | - | - Markdown render: keep existing `glamour` (acceptable substitute for syntect — agent confirmed parity in behavior). |
|
| 99 | - | - `backend.go` — already abstracts local/remote; reuse. |
|
| 100 | - | ||
| 101 | - | **Deps to add:** `github.com/pkg/browser` (everything else already in go.mod). |
|
| 102 | - | ||
| 103 | - | --- |
|
| 104 | - | ||
| 105 | - | ## Critical files (modified or created) |
|
| 106 | - | ||
| 107 | - | | App | Path | Action | |
|
| 108 | - | |-----|------|--------| |
|
| 109 | - | | posts-go | `storage/{interface,local,r2}.go` | new | |
|
| 110 | - | | posts-go | `app.go`, `main.go`, `handlers_admin.go`, `handlers_public.go`, `db.go`, `.env.example` | edit | |
|
| 111 | - | | shrink-go | `exif.go` | new | |
|
| 112 | - | | shrink-go | `image.go`, `handlers.go`, `go.mod` | edit | |
|
| 113 | - | | sipp-go | `tui/{model,keys,update,view}.go` | edit | |
|
| 114 | - | | jotts-go | `tui/{editor,browser}.go` | new | |
|
| 115 | - | | jotts-go | `tui/{model,keys,update,view}.go`, `go.mod` | edit | |
|
| 116 | - | ||
| 117 | - | --- |
|
| 118 | - | ||
| 119 | - | ## Reused existing utilities |
|
| 120 | - | ||
| 121 | - | - `crates-go/auth/auth.go` — sessions, bearer/session middleware (no changes). |
|
| 122 | - | - `crates-go/web` — embedded handler, render helpers (no changes). |
|
| 123 | - | - `crates-go/sqlite`, `crates-go/config`, `crates-go/darkmatter` — reused as-is. |
|
| 124 | - | - posts-go local FS funcs already in `storage.go` — wrap into `LocalStorage`. |
|
| 125 | - | - sipp-go clipboard + chroma flow already wired — only add wrap toggle. |
|
| 126 | - | - jotts-go `tui/backend.go` (local + remote impls) — reused. |
|
| 127 | - | ||
| 128 | - | --- |
|
| 129 | - | ||
| 130 | - | ## Execution order (suggested) |
|
| 131 | - | ||
| 132 | - | 1. **shrink-go EXIF** — smallest, self-contained, no schema changes. |
|
| 133 | - | 2. **sipp-go wrap toggle** — ~20 LoC, fast win. |
|
| 134 | - | 3. **posts-go R2** — biggest data-path change; needs R2 creds for end-to-end test. |
|
| 135 | - | 4. **jotts-go TUI** — largest, mostly UI iteration; do last so other parity work isn't blocked. |
|
| 136 | - | ||
| 137 | - | --- |
|
| 138 | - | ||
| 139 | - | ## Verification |
|
| 140 | - | ||
| 141 | - | Per app: |
|
| 142 | - | ||
| 143 | - | **shrink-go** |
|
| 144 | - | ```bash |
|
| 145 | - | cd apps/shrink-go && go build ./... && go run . |
|
| 146 | - | # upload a JPEG with GPS via the form, download result, run: |
|
| 147 | - | exiftool result.jpg | rg -i 'gps|orientation|make|model' |
|
| 148 | - | # expect: GPS fields absent, other EXIF present |
|
| 149 | - | ``` |
|
| 150 | - | ||
| 151 | - | **sipp-go** |
|
| 152 | - | ```bash |
|
| 153 | - | cd apps/sipp-go && go run ./cmd/server |
|
| 154 | - | # open snippet, press Ctrl+W, confirm wrap behavior toggles, scroll resets |
|
| 155 | - | ``` |
|
| 156 | - | ||
| 157 | - | **posts-go** |
|
| 158 | - | ```bash |
|
| 159 | - | cd apps/posts-go && go build ./... && go test ./... |
|
| 160 | - | # unset R2 vars: upload via admin → file lands in uploads/, GET /uploads/<f> returns 200 |
|
| 161 | - | # set R2 vars (test bucket): upload → check bucket contains object, GET returns 307 to R2 public URL |
|
| 162 | - | sqlite3 posts.sqlite "select storage_backend from files order by id desc limit 5;" |
|
| 163 | - | ``` |
|
| 164 | - | ||
| 165 | - | **jotts-go** |
|
| 166 | - | ```bash |
|
| 167 | - | cd apps/jotts-go && go run . server & |
|
| 168 | - | go run . tui --remote http://localhost:3000 --api-key $KEY |
|
| 169 | - | # walk: create note, edit, y copies content, Y copies link, E opens $EDITOR, o opens browser, d deletes |
|
| 170 | - | ``` |
|
| 171 | - | ||
| 172 | - | Cross-cutting: `go vet ./...` and `go build ./...` from repo root after each app's changes. |
|
| 173 | - | ||
| 174 | - | --- |
|
| 175 | - | ||
| 176 | - | ## Out of scope (recorded for follow-up) |
|
| 177 | - | ||
| 178 | - | - sipp-go custom `.tmTheme` loading — needs chroma converter or hand-port; defer per README's existing call-out. |
|
| 179 | - | - posts-go EXIF strip on upload — not in Rust either; only do if user asks separately. |