fix: issue #22 blogmarks not creating drafts

This commit is contained in:
hugo committed 2026-07-13 10:45:22 +02:00
1 parent 89859684eb
commit 6ec61610f1
11 files changed
+67461 -608736

No files matched your search

+28
View File
@@ -124,6 +124,34 @@ This document provides context and best practices for GitHub Copilot when workin
--- ---
## ⚠️ MANDATORY: macOS Bundle Build Order and Blogmark Verification
**`mix release` copies `priv/static` as-is — it never rebuilds assets.** Always build in this order, or the bundle ships stale JS while the server code is current (this silently broke blogmark deep links in July 2026: the server waited for the client's `shell_ready` event that the stale `app.js` never sent, so every deep link queued forever with no error, no toast, no log):
```
mix assets.deploy
MIX_ENV=prod mix release bds --overwrite
mix bds.bundle.macos
```
**After building a bundle, verify blogmark deep links end-to-end with:**
```
scripts/verify_blogmark_macos.sh [path/to/BDS2.app] # defaults to dist/macos/BDS2.app
```
The script launches the bundle via LaunchServices with an isolated database (`launchctl setenv BDS_DATABASE_PATH ...`), fires `bds2://new-post` links with `open -a` exactly like a browser bookmarklet, and asserts draft posts appear in the database — both for cold start (link launches the app, queued until the shell attaches) and for an already-running app. It refuses to run while any BDS2 instance is up; quit the app first.
Things learned the hard way, encoded in the script — do not "simplify" them away:
- Deep links only reach an app **launched through LaunchServices** (`open -a`). A process started by exec'ing `Contents/MacOS/bds2` from a shell never receives Apple Events, so deep links vanish without a trace. For the same reason `open` does not inherit the shell environment — inject env vars with `launchctl setenv` (and unset them afterwards).
- To introspect a running bundle, launch it with `RELEASE_DISTRIBUTION=name` and `RELEASE_NODE=bds@127.0.0.1` set (via `launchctl setenv`), then use `.../Contents/Resources/rel/bin/bds rpc '<elixir>'` with the same env. The default `-sname` node is unreachable when the hostname resolves to LAN addresses.
- The wx → `Desktop.Env` → `BDS.Desktop.DeepLink` → shell pipeline downstream of the OS can be exercised without LaunchServices via `send(Process.whereis(BDS.Desktop.DeepLink), {:open_url, ~c"bds2://new-post?..."})` over rpc.
> **Assets before release. Verify blogmarks after bundling. No exceptions.**
---
## ⚠️ MANDATORY: Proper I18N for UI and Rendering Text ## ⚠️ MANDATORY: Proper I18N for UI and Rendering Text
**All user-facing text MUST follow proper i18n patterns.** **All user-facing text MUST follow proper i18n patterns.**
File diff suppressed because it is too large. Load diff
+15 -10674
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+367 -239538
View File
File diff suppressed because it is too large. Load diff
+93 -54269
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+376 -30359
View File
File diff suppressed because it is too large. Load diff
+58 -22096
View File
File diff suppressed because it is too large. Load diff
+66416 -224638
View File
File diff suppressed because it is too large. Load diff
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
# End-to-end blogmark verification against the bundled macOS app.
#
# Launches BDS2.app through LaunchServices with an isolated database, fires
# bds2://new-post deep links the same way a browser bookmarklet does, and
# asserts draft posts show up in the database. Covers both the cold-start
# path (link launches the app, gets queued, drains on shell attach) and the
# already-running path.
#
# The app MUST be launched via LaunchServices (`open -a`): a process started
# by exec'ing Contents/MacOS/bds2 from a shell never receives Apple Events,
# so deep links silently go nowhere. Env vars are injected with
# `launchctl setenv` because `open` does not pass the shell environment.
#
# Usage: scripts/verify_blogmark_macos.sh [path/to/BDS2.app]
set -euo pipefail
APP="$(cd "${1:-dist/macos/BDS2.app}" 2>/dev/null && pwd)" || { echo "FAIL: app bundle not found" >&2; exit 1; }
fail() { echo "FAIL: $*" >&2; exit 1; }
[ -x "$APP/Contents/MacOS/bds2" ] \
|| fail "$APP is not a built bundle. Build with: mix assets.deploy && MIX_ENV=prod mix release bds --overwrite && mix bds.bundle.macos"
# Guard against the regression that broke blogmarks in July 2026: a bundle
# whose server waits for the client's shell_ready event but whose stale
# app.js never sends it. Deep links then queue forever with no error anywhere.
grep -q shell_ready "$APP"/Contents/Resources/rel/lib/bds-*/priv/static/assets/app.js \
|| fail "bundle ships stale app.js (no shell_ready) — run mix assets.deploy BEFORE mix release"
# epmd survives app quit and lives under the .app path, so match the VM only.
# Any running BDS2 (installed or dist) makes LaunchServices routing ambiguous.
pgrep -f "BDS2.app.*beam" >/dev/null && fail "a BDS2.app instance is already running; quit it first"
WORK="$(mktemp -d /tmp/bds2-blogmark-verify.XXXXXX)"
DB="$WORK/bds.db"
STAMP="$(date +%s)"
cleanup() {
# The launcher execs a script that runs beam as a child, so kill by path.
pkill -f "$APP.*beam" 2>/dev/null || true
launchctl unsetenv BDS_DATABASE_PATH
rm -rf "$WORK"
}
trap cleanup EXIT
sql() { sqlite3 -cmd '.timeout 2000' "$DB" "$1" 2>/dev/null || true; }
wait_for_draft() { # $1 = title, $2 = timeout seconds
local i row
for i in $(seq 1 "$2"); do
row="$(sql "select status || '|' || content from posts where title = '$1';")"
if [ -n "$row" ]; then
case "$row" in
draft\|*example.com*) return 0 ;;
*) fail "post '$1' found but wrong shape: $row" ;;
esac
fi
sleep 1
done
return 1
}
launchctl setenv BDS_DATABASE_PATH "$DB"
# Cold start: the deep link itself launches the app. The link must survive
# boot (queued in BDS.Desktop.DeepLink) and import once the shell attaches.
COLD="Blogmark cold $STAMP"
echo "Cold start: launching $APP via deep link ..."
open -a "$APP" "bds2://new-post?title=${COLD// /%20}&url=https%3A%2F%2Fexample.com%2Fcold"
wait_for_draft "$COLD" 120 || fail "cold-start deep link produced no draft within 120s"
echo " ok: cold-start draft created"
# Already running: a second link must import immediately.
WARM="Blogmark warm $STAMP"
open -a "$APP" "bds2://new-post?title=${WARM// /%20}&url=https%3A%2F%2Fexample.com%2Fwarm"
wait_for_draft "$WARM" 30 || fail "deep link to the running app produced no draft within 30s"
echo " ok: already-running draft created"
echo "PASS: blogmark deep links verified against $APP"