diff --git a/.gitignore b/.gitignore index e15caba..7d2a880 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ yarn-error.log* # temporary placeholder assets (deployed to wisp.place, not used in app) public/Under construction.json + +# synced from web_themes (build-time pull, do not commit) +/src/styles/theme diff --git a/AGENTS.md b/AGENTS.md index 3536770..36cb0ef 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,5 @@ # AGENTS.md — altifier +Conventions: AGENTS.md is the primary agent instructions file; .hermes.md (if present) is secondary. + For any frontend/UI design, redesign, critique, audit, polish, or visual work, load the `impeccable` skill first (skill_view name='impeccable') and follow it. diff --git a/agent/PATCH-altifier.md b/agent/PATCH-altifier.md new file mode 100644 index 0000000..030fc70 --- /dev/null +++ b/agent/PATCH-altifier.md @@ -0,0 +1,86 @@ +# PATCH-altifier.md +Repo: tangled.org/psingletary.com/altifier +Role: you are a code-modification agent. Apply the patches below to the +existing codebase. Do not redesign, do not add features beyond this +file, do not touch files not named here. Work top to bottom. When all +patches are applied and `npm run build` passes, print "DONE". + +## P1 — Replace delete-and-recreate with in-place putRecord [CRITICAL] +Where: the "apply alt text" mutation path (search for calls to +`deleteRecord` followed by `createRecord`, or `post.delete`). +Change: +- Fetch the current record with `com.atproto.repo.getRecord` + ({ repo: did, collection: 'app.bsky.feed.post', rkey }) and keep the + returned `cid`. +- Mutate ONLY the `alt` fields inside `record.embed.images[]` (and + `record.embed.media.images[]` for recordWithMedia; `record.embed.alt` + for video — see P3). Do not touch any other field, including createdAt. +- Write back with `com.atproto.repo.putRecord` + ({ repo, collection, rkey, record, swapRecord: cid }). +- On swap failure (record changed concurrently): re-fetch once, re-apply + the alt edits, retry once; if it fails again surface an error toast. +- Delete all delete+recreate code. Update any UI copy claiming the post + is "reposted"/"recreated": it is now edited in place; URI, likes and + replies are preserved. + +## P2 — Scan the PDS, not the AppView [HIGH] +Where: the scanning routine that enumerates the user's posts. +Change: +- Enumerate via `com.atproto.repo.listRecords` + ({ repo: did, collection: 'app.bsky.feed.post', limit: 100, cursor }) + against the user's own PDS endpoint (from their DID doc / session), + looping until cursor is absent. +- Detection operates on the raw record value, which is also exactly the + object P1 writes back. +- Remove AppView feed-pagination scanning (`getAuthorFeed` or similar). + +## P3 — Widen embed coverage [HIGH] +Where: the "has image without alt" predicate. +Change: flag a record when ANY of: +- `embed.$type == 'app.bsky.embed.images'` and any `images[i].alt` is + empty/missing; +- `embed.$type == 'app.bsky.embed.recordWithMedia'` and its `media` is + an images embed with any empty alt; +- `embed.$type == 'app.bsky.embed.video'` and `embed.alt` is + empty/missing (render one textarea for the video; thumbnail may be + omitted). +The editor UI must render/edit alt for all three shapes. + +## P4 — Make AI strictly BYO-key [HIGH] +Where: Gemini integration. +Change: +- Remove any bundled/committed API key and any build-time env key. +- Add a settings field "Gemini API key (optional)" stored ONLY in + localStorage under `altifier.geminiKey`, with a note that it never + leaves the browser except in direct calls to Google. +- If no key is set: hide all "Generate" buttons; manual textareas remain + fully functional. No other behavior change. + +## P5 — Single source of truth for OAuth client metadata [MEDIUM] +Where: `public/client-metadata.json` and the in-code `clientMetadata` +object. +Change: delete the in-code object; at startup `fetch('/client-metadata.json')` +and pass the parsed JSON to the OAuth client. One file to edit on deploy. + +## P6 — Resumable scans [MEDIUM] +Where: the P2 scan loop. +Change: after each page, persist `{ cursor, hits }` to localStorage key +`altifier.scan.`. On page load, if present, offer "Resume scan +(N found so far)" vs "Restart". Clear the key when the scan completes. + +## P7 — Pre-write diff [LOW] +Where: the apply flow from P1. +Change: before putRecord, show a modal with old vs new `alt` values per +image ("(empty)" → new text) and Confirm/Cancel. Apply proceeds only on +confirm. + +## Out of scope +Do NOT migrate off React in this patch. Do NOT change OAuth scopes, +routing, or styling beyond what the above requires. + +## Acceptance +- Applying alt text preserves the post's at:// URI and rkey (verify: + getRecord before/after shows same uri, new cid). +- Scan output includes recordWithMedia and video posts. +- Build contains no Gemini key (grep for "AIza" must be empty). +- Only one client-metadata definition exists in the source tree. diff --git a/agent/PROMPT-ui-changes.md b/agent/PROMPT-ui-changes.md new file mode 100644 index 0000000..208e254 --- /dev/null +++ b/agent/PROMPT-ui-changes.md @@ -0,0 +1,113 @@ +# PROMPT — Apply Altifier-style UI changes (embed + login flow) + +Use this prompt to direct the agent of another website (an app that lets a user +log in with Bluesky and displays their posts) to make the same UI changes +applied to Altifier. There are two independent parts: (A) replace a plain +"View on Bluesky" external link with a live inline post embed via Bluesky's +official oEmbed endpoint and display the post's at:// URI, and (B) clean up the +login/navigation flow so the user reaches the tool directly with correct +branding and return URLs. + +Apply both parts unless told otherwise. Do not redesign beyond what is +described. Follow the site's existing component/style conventions and keep all +existing functionality. + +--- + +## Part A — oEmbed post embed + at:// URI + +For each post the app displays, do the following instead of (or in addition to) +a plain "View on Bluesky" outbound link: + +1. **Embed the post inline** using Bluesky's official oEmbed endpoint + (`https://embed.bsky.app/oembed`), rendering the live post widget in place. +2. **Show the post's at:// URI** in a small, muted, monospace line so the + post's immutable identity is visible. + +### How the embed works (required implementation detail) + +- Endpoint: `GET https://embed.bsky.app/oembed?url=&format=json` + - `url` must be a post URL in the form + `https://bsky.app/profile//post/` (or an at:// URI). + - CORS is open (`Access-Control-Allow-Origin: *`), so fetch it directly from + the browser — no server proxy needed. +- The JSON response contains an `html` string of roughly: + ```html +
+

post text

+

author

+
+ + ``` +- **Critical:** `