···193193194194 onIonViewWillLeave(() => {
195195 client.disconnect();
196196- status.value = "connecting"; // Reset so next enter shows "connecting"
196196+ status.value = "connecting";
197197 });
198198199199 onUnmounted(() => {
···217217 client.disconnect();
218218 status.value = "connecting";
219219 client.connect();
220220- // Complete the refresher after a short delay
221220 await new Promise<void>((resolve) => setTimeout(resolve, 1000));
222221 (event.target as HTMLIonRefresherElement).complete();
223222 }
+1-1
apps/twisted/src/mocks/repos.ts
···120120121121const README_CONTENT = `# twisted
122122123123-A mobile companion reader for [Tangled](https://tangled.sh), built with Ionic Vue and Capacitor.
123123+A mobile companion reader for [Tangled](https://tangled.org), built with Ionic Vue and Capacitor.
124124125125## Features
126126
+3-1
apps/twisted/src/services/tangled/repo-assets.ts
···7979 const objectUrl = createObjectUrlFromBlobContent(blob);
8080 if (objectUrl) return { url: objectUrl, revoke: true };
8181 } catch {
8282- // Fall back to the public raw URL if the XRPC lookup fails.
8282+ console.warn(
8383+ `Failed to fetch blob for ${context.owner}/${context.repo} at ${repoPath}, falling back to public URL.`,
8484+ );
8385 }
84868587 return { url: buildPublicRawUrl(context, repoPath), revoke: false };
+4-4
docs/roadmap.md
···2020 - Follow AT URI (desertthunder.dev follows npmx): `at://did:plc:xg2vq45muivyy3xwatcehspu/sh.tangled.graph.follow/3mhofstanru22`
2121 - Star AT URI (desertthunder.dev stars microcosm-rs): `at://did:plc:lulmyldiq4sb2ikags5sfb25/sh.tangled.repo/3lvsxzinfz222`
2222- ~~Add `just` targets for smoke-test runs locally and against a remote base URL~~ directly invoking the scripts is fine.
2323-- [ ] Reuse the existing normalization and upsert path for on-demand indexing jobs
2424-- [ ] Trigger indexing jobs from repo, issue, PR, profile, and similar fetch handlers
2525-- [ ] Add dedupe, retries, and observability for indexing jobs
2323+- [x] Reuse the existing normalization and upsert path for on-demand indexing jobs
2424+- [x] Trigger indexing jobs from repo, issue, PR, profile, and similar fetch handlers
2525+- [x] Add dedupe, retries, and observability for indexing jobs
2626- [ ] Add a JetStream cache consumer with a persisted timestamp cursor
2727- [ ] Seed the JetStream cursor to `now - 24h` on first boot and rewind slightly on reconnect
2828- [ ] Store and serve bounded recent activity from the local cache
2929- [ ] Keep Tap as the authoritative indexing and bulk backfill path
3030-- [ ] Define a controlled backfill and repo-resync playbook for recovery
3030+- [ ] Define a controlled backfill and repo-resync playbook for recovery (`docs/references/resync.md`)
31313232## API: Constellation Integration
3333
+17-1
packages/api/README.md
···142142twister api # Start the HTTP API server
143143twister indexer # Start the Tap firehose consumer
144144twister backfill # Seed the index from upstream APIs
145145-twister reindex # Re-process existing documents
145145+twister reindex # Re-process existing documents (re-syncs FTS)
146146+twister enrich # Backfill RepoName, AuthorHandle, WebURL on existing documents
146147```
148148+149149+### enrich
150150+151151+Resolves missing `author_handle`, `repo_name`, and `web_url` fields on documents already
152152+in the database. Run this after deploying enrichment changes or when search results show
153153+documents with empty author handles.
154154+155155+```sh
156156+twister enrich --local # all documents
157157+twister enrich --local --collection sh.tangled.repo
158158+twister enrich --local --did did:plc:abc123
159159+twister enrich --local --dry-run # preview without writing
160160+```
161161+162162+Flags: `--collection`, `--did`, `--document`, `--dry-run`, `--concurrency` (default 5).
147163148164## Proxy endpoints
149165
+9-3
packages/api/internal/api/actors.go
···2222// issueEntry extends recordEntry with pre-joined issue state.
2323type issueEntry struct {
2424 recordEntry
2525- State string `json:"state"` // "open" or "closed"
2525+ // "open" or "closed"
2626+ State string `json:"state"`
2627}
27282829// pullEntry extends recordEntry with pre-joined pull status.
2930type pullEntry struct {
3031 recordEntry
3131- Status string `json:"status"` // "open", "merged", or "closed"
3232+ // "open", "merged", or "closed"
3333+ Status string `json:"status"`
3234}
33353436// actorContext holds resolved identity for a request.
3537type actorContext struct {
3638 DID string `json:"did"`
3739 Handle string `json:"handle"`
3838- PDS string `json:"pds"` // full URL, e.g. "https://bsky.social"
4040+ // full URL, e.g. "https://bsky.social"
4141+ PDS string `json:"pds"`
3942}
40434144// repoContext extends actorContext with the repo's knot host and AT URI.
···6467 identity, err := s.xrpc.ResolveIdentity(ctx, did)
6568 if err != nil {
6669 return nil, fmt.Errorf("resolve identity %q: %w", did, err)
7070+ }
7171+ if identity.PDS == "" {
7272+ return nil, fmt.Errorf("no atproto pds in did document for %q", did)
6773 }
68746975 return &actorContext{
···12121313// Options controls which documents are reindexed.
1414type Options struct {
1515- Collection string // reindex documents in this collection only
1616- DID string // reindex documents authored by this DID only
1717- DocumentID string // reindex a single document by stable ID
1818- DryRun bool // log intended work without writing
1515+ // reindex documents in this collection only
1616+ Collection string
1717+ // reindex documents authored by this DID only
1818+ DID string
1919+ // reindex a single document by stable ID
2020+ DocumentID string
2121+ // log intended work without writing
2222+ DryRun bool
1923}
20242125// Result summarises the outcome of a reindex run.
+9
packages/api/internal/store/sql_store.go
···460460 return n, nil
461461}
462462463463+func (s *SQLStore) CountPendingIndexingJobs(ctx context.Context) (int64, error) {
464464+ var n int64
465465+ err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM indexing_jobs WHERE status = 'pending'`).Scan(&n)
466466+ if err != nil {
467467+ return 0, fmt.Errorf("count pending indexing jobs: %w", err)
468468+ }
469469+ return n, nil
470470+}
471471+463472func (s *SQLStore) Ping(ctx context.Context) error {
464473 return s.db.PingContext(ctx)
465474}
···11{{define "title"}}API Docs — Twister{{end}}
22{{define "content"}}
33<h1>API Documentation</h1>
44-<p>Twister exposes a public JSON API for searching indexed <a href="https://tangled.sh" target="_blank" rel="noopener">Tangled</a> content. No authentication is required for read endpoints.</p>
44+<p>Twister exposes a public JSON API for searching indexed <a href="https://tangled.org" target="_blank" rel="noopener">Tangled</a> content. No authentication is required for read endpoints.</p>
5566<h2>Base URL</h2>
77<pre><code>https://<your-twister-domain></code></pre>