a love letter to tangled (android, iOS, and a search API)
19
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: readme rendering

+70 -6
+10 -5
apps/twisted/src/features/repo/RepoDetailPage.vue
··· 83 83 import { 84 84 useRepoRecord, 85 85 useDefaultBranch, 86 - useRepoBlob, 86 + useRepoReadme, 87 87 useRepoLanguages, 88 88 useRepoLog, 89 89 useRepoIssues, ··· 130 130 const branchQuery = useDefaultBranch(owner, repoName, { enabled: hasRecord }); 131 131 const defaultBranch = computed(() => branchQuery.data.value?.name ?? ""); 132 132 const hasBranch = computed(() => !!branchQuery.data.value?.name); 133 + const readmeQuery = useRepoReadme(owner, repoName, defaultBranch, { enabled: hasBranch }); 133 134 const markdownContext = computed<RepoAssetContext | undefined>(() => { 134 - if (!owner.value || !repoName.value || !defaultBranch.value) return undefined; 135 + if (!owner.value || !repoName.value || !defaultBranch.value || !readmeQuery.data.value?.path) return undefined; 135 136 136 - return { owner: owner.value, repo: repoName.value, branch: defaultBranch.value, sourcePath: "README.md" }; 137 + return { 138 + owner: owner.value, 139 + repo: repoName.value, 140 + branch: defaultBranch.value, 141 + sourcePath: readmeQuery.data.value.path, 142 + }; 137 143 }); 138 144 139 145 const languagesQuery = useRepoLanguages(owner, repoName, undefined, { enabled: hasBranch }); 140 - const readmeQuery = useRepoBlob(owner, repoName, defaultBranch, "README.md", { readme: true, enabled: hasBranch }); 141 146 const logQuery = useRepoLog(owner, repoName, defaultBranch, { limit: 20, enabled: hasBranch }); 142 147 143 148 const repo = computed((): RepoDetail | undefined => { ··· 148 153 stars: starCountQuery.data.value ?? rec.stars, 149 154 defaultBranch: defaultBranch.value || undefined, 150 155 languages: languagesQuery.data.value, 151 - readme: readmeQuery.data.value?.isBinary ? undefined : readmeQuery.data.value?.content, 156 + readme: readmeQuery.data.value?.content, 152 157 }; 153 158 }); 154 159
+15
apps/twisted/src/services/tangled/normalizers.ts
··· 48 48 return output.files.map((entry) => normalizeTreeEntry(entry, currentPath)); 49 49 } 50 50 51 + export type RepoReadme = { 52 + path: string; 53 + content: string; 54 + }; 55 + 56 + export function normalizeTreeReadme(output: ShTangledRepoTree.$output, currentPath = ""): RepoReadme | undefined { 57 + const readme = output.readme; 58 + if (!readme?.filename) return undefined; 59 + 60 + return { 61 + path: currentPath ? `${currentPath}/${readme.filename}` : readme.filename, 62 + content: readme.contents ?? "", 63 + }; 64 + } 65 + 51 66 export type BlobContent = { 52 67 path: string; 53 68 content: string;
+22 -1
apps/twisted/src/services/tangled/queries.ts
··· 34 34 } from "./endpoints.js"; 35 35 import { 36 36 normalizeTree, 37 + normalizeTreeReadme, 37 38 normalizeBlob, 38 39 normalizeDefaultBranch, 39 40 normalizeLanguages, ··· 53 54 normalizeStringRecord, 54 55 } from "./normalizers.js"; 55 56 56 - export type { CommitEntry, BranchEntry, BlobContent, DefaultBranchInfo } from "./normalizers.js"; 57 + export type { CommitEntry, BranchEntry, BlobContent, DefaultBranchInfo, RepoReadme } from "./normalizers.js"; 57 58 58 59 const MIN = 60_000; 59 60 ··· 104 105 enabled: computed(() => isEnabled(hasText(h) && hasText(r) && hasText(ref), options.enabled)), 105 106 staleTime: 2 * MIN, 106 107 gcTime: 10 * MIN, 108 + }); 109 + } 110 + 111 + /** README discovered by the repo tree endpoint for a ref root. */ 112 + export function useRepoReadme( 113 + handle: MaybeRef<string>, 114 + repo: MaybeRef<string>, 115 + ref: MaybeRef<string>, 116 + options: { enabled?: MaybeRef<boolean> } = {}, 117 + ) { 118 + const h = computed(() => toValue(handle).trim()); 119 + const r = computed(() => toValue(repo).trim()); 120 + 121 + return useQuery({ 122 + queryKey: computed(() => ["readme", h.value, r.value, toValue(ref)]), 123 + queryFn: () => 124 + fetchRepoTree(h.value, r.value, { repo: `${h.value}/${r.value}`, ref: toValue(ref) }).then(normalizeTreeReadme), 125 + enabled: computed(() => isEnabled(hasText(h) && hasText(r) && hasText(ref), options.enabled)), 126 + staleTime: 5 * MIN, 127 + gcTime: 30 * MIN, 107 128 }); 108 129 } 109 130
+23
apps/twisted/tests/unit/tangled-normalizers.spec.ts
··· 4 4 normalizeLogText, 5 5 normalizeRepoRecord, 6 6 normalizeTree, 7 + normalizeTreeReadme, 7 8 } from "@/services/tangled/normalizers.js"; 8 9 import { buildPublicRawUrl, resolveRepoRelativePath } from "@/services/tangled/repo-assets.js"; 9 10 import { getAtUriRkey, parseAtUri } from "@/services/tangled/uris.js"; ··· 84 85 ["README.md", "file"], 85 86 ["vendor/lib", "submodule"], 86 87 ]); 88 + }); 89 + 90 + it("preserves the README filename discovered by the tree endpoint", () => { 91 + const readme = normalizeTreeReadme({ 92 + files: [], 93 + lastCommit: { 94 + hash: "a", 95 + message: "docs", 96 + when: "2026-03-23T00:00:00Z", 97 + author: { name: "Test", email: "test@example.com", when: "" }, 98 + }, 99 + readme: { 100 + filename: "README", 101 + contents: "plain text readme", 102 + }, 103 + ref: "main", 104 + }); 105 + 106 + expect(readme).toEqual({ 107 + path: "README", 108 + content: "plain text readme", 109 + }); 87 110 }); 88 111 89 112 it("parses wrapped commit arrays from repo log payloads", () => {