From 14a7d8c81e989ec96f1a1ef01f71ee3cdd6c2a07 Mon Sep 17 00:00:00 2001 From: Owais Jamil Date: Sun, 22 Mar 2026 14:20:20 -0500 Subject: [PATCH] feat: issue and pull request handling --- .gitignore | 1 + docs/tasks/phase-2.md | 12 ++-- package.json | 5 +- pnpm-lock.yaml | 23 +++++++ src/App.vue | 2 +- src/core/query/client.ts | 4 +- src/core/query/persister.ts | 12 ++++ src/features/repo/RepoDetailPage.vue | 14 ++++- src/features/repo/RepoFiles.vue | 13 +--- src/features/repo/RepoIssues.vue | 90 +++++++++++++++------------ src/features/repo/RepoPRs.vue | 92 ++++++++++++++++------------ src/main.ts | 5 ++ src/services/tangled/endpoints.ts | 66 ++++++++++++++++++++ src/services/tangled/normalizers.ts | 33 ++++++++++ src/services/tangled/queries.ts | 84 +++++++++++++++++++++++++ src/views/HomePage.vue | 15 +++-- src/views/TabsPage.vue | 12 +--- tsconfig.node.json | 1 + 18 files changed, 367 insertions(+), 117 deletions(-) create mode 100644 src/core/query/persister.ts diff --git a/.gitignore b/.gitignore index 2fc2afd..cbab434 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ $RECYCLE.BIN/ *.log log.txt npm-debug.log* +*.tsbuildinfo /.idea /.ionic diff --git a/docs/tasks/phase-2.md b/docs/tasks/phase-2.md index 391179f..167b700 100644 --- a/docs/tasks/phase-2.md +++ b/docs/tasks/phase-2.md @@ -41,22 +41,22 @@ ## Issues (read-only) -- [ ] Fetch issues for a repo from PDS records -- [ ] Display issue list with state filter (open/closed) +- [x] Fetch issues for a repo from PDS records (`listIssueRecords` + `listIssueStateRecords` from owner's PDS) +- [x] Display issue list with state filter (open/closed) - [ ] Issue detail view: title, body, author, state - [ ] Issue comments: fetch `sh.tangled.repo.issue.comment` records, render threaded ## Pull Requests (read-only) -- [ ] Fetch PRs for a repo from PDS records -- [ ] Display PR list with status filter (open/closed/merged) +- [x] Fetch PRs for a repo from PDS records (`listPullRecords` + `listPullStatusRecords` from owner's PDS) +- [x] Display PR list with status filter (open/closed/merged) - [ ] PR detail view: title, body, author, source/target branches - [ ] PR comments: fetch `sh.tangled.repo.pull.comment` records ## Caching -- [ ] Configure TanStack Query stale/gc times per data type (see spec) -- [ ] Set up IndexedDB query persister for offline reads +- [x] Configure TanStack Query stale/gc times per data type (see spec) +- [x] Set up IndexedDB query persister for offline reads - [ ] Verify stale-while-revalidate behavior: cached data shows immediately, refreshes in background ## Quality diff --git a/package.json b/package.json index 0a66a94..5b0833d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "preview": "vite preview", "test:e2e": "cypress run", "test:unit": "vitest", - "lint": "eslint ." + "lint": "eslint .", + "check": "vue-tsc --noEmit" }, "dependencies": { "@atcute/bluesky": "^3.3.0", @@ -24,7 +25,9 @@ "@capacitor/status-bar": "8.0.1", "@ionic/vue": "^8.0.0", "@ionic/vue-router": "^8.0.0", + "@tanstack/query-persist-client-core": "^5.95.0", "@tanstack/vue-query": "^5.94.5", + "idb-keyval": "^6.2.2", "ionicons": "^7.0.0", "pinia": "^3.0.4", "vue": "^3.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 43fa501..019643d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,9 +44,15 @@ importers: '@ionic/vue-router': specifier: ^8.0.0 version: 8.8.1(@stencil/core@4.43.3)(vue-router@4.6.4(vue@3.5.30(typescript@5.9.3)))(vue@3.5.30(typescript@5.9.3)) + '@tanstack/query-persist-client-core': + specifier: ^5.95.0 + version: 5.95.0 '@tanstack/vue-query': specifier: ^5.94.5 version: 5.94.5(vue@3.5.30(typescript@5.9.3)) + idb-keyval: + specifier: ^6.2.2 + version: 6.2.2 ionicons: specifier: ^7.0.0 version: 7.4.0 @@ -1177,6 +1183,12 @@ packages: '@tanstack/query-core@5.94.5': resolution: {integrity: sha512-Vx1JJiBURW/wdNGP45afjrqn0LfxYwL7K/bSrQvNRtyLGF1bxQPgUXCpzscG29e+UeFOh9hz1KOVala0N+bZiA==} + '@tanstack/query-core@5.95.0': + resolution: {integrity: sha512-H1/CWCe8tGL3YIVeo770Z6kPbt0B3M1d/iQXIIK1qlFiFt6G2neYdkHgLapOC8uMYNt9DmHjmGukEKgdMk1P+A==} + + '@tanstack/query-persist-client-core@5.95.0': + resolution: {integrity: sha512-83qvMi11P+8kPqgnvgB8qz37UDUnw7htrBkoc8r2SMso+ZfxwUIi0rIrJljrQuS8rW99QweoP3ACfG5SeBORkw==} + '@tanstack/vue-query@5.94.5': resolution: {integrity: sha512-xmnOj1fP0JvUqGrkHmdIY/3FyO4L0IjJBCqOFxnnMIJjrsvCvlpjp/XpI1Zv4eLuV0e8l1LIOOuEvN40ckVuOA==} peerDependencies: @@ -2132,6 +2144,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + idb-keyval@6.2.2: + resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -4456,6 +4471,12 @@ snapshots: '@tanstack/query-core@5.94.5': {} + '@tanstack/query-core@5.95.0': {} + + '@tanstack/query-persist-client-core@5.95.0': + dependencies: + '@tanstack/query-core': 5.95.0 + '@tanstack/vue-query@5.94.5(vue@3.5.30(typescript@5.9.3))': dependencies: '@tanstack/match-sorter-utils': 8.19.4 @@ -5570,6 +5591,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + idb-keyval@6.2.2: {} + ieee754@1.2.1: {} ignore@5.3.2: {} diff --git a/src/App.vue b/src/App.vue index 30dd406..c9462b9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -5,5 +5,5 @@ diff --git a/src/core/query/client.ts b/src/core/query/client.ts index 474e361..5a4162a 100644 --- a/src/core/query/client.ts +++ b/src/core/query/client.ts @@ -1,10 +1,10 @@ -import { QueryClient } from '@tanstack/vue-query'; +import { QueryClient } from "@tanstack/vue-query"; export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 minutes - gcTime: 10 * 60 * 1000, // 10 minutes + gcTime: 10 * 60 * 1000, // 10 minutes retry: 2, }, }, diff --git a/src/core/query/persister.ts b/src/core/query/persister.ts new file mode 100644 index 0000000..61ff022 --- /dev/null +++ b/src/core/query/persister.ts @@ -0,0 +1,12 @@ +import { get, set, del } from "idb-keyval"; +import type { Persister } from "@tanstack/query-persist-client-core"; + +const CACHE_KEY = "twisted-query-cache"; + +export function createIdbPersister(): Persister { + return { + persistClient: (client) => set(CACHE_KEY, client), + restoreClient: () => get(CACHE_KEY), + removeClient: () => del(CACHE_KEY), + }; +} diff --git a/src/features/repo/RepoDetailPage.vue b/src/features/repo/RepoDetailPage.vue index ff14382..1b9a91a 100644 --- a/src/features/repo/RepoDetailPage.vue +++ b/src/features/repo/RepoDetailPage.vue @@ -45,8 +45,8 @@ :knot-host="knotHost" :knot-repo="knotRepo" :branch="defaultBranch" /> - - + + @@ -81,6 +81,8 @@ import { useRepoBlob, useRepoLanguages, useRepoLog, + useRepoIssues, + useRepoPRs, } from "@/services/tangled/queries.js"; import type { RepoDetail } from "@/domain/models/repo.js"; @@ -120,8 +122,16 @@ const repo = computed((): RepoDetail | undefined => { }; }); +const repoAtUri = computed(() => recordQuery.data.value?.atUri ?? ""); +const hasAtUri = computed(() => !!repoAtUri.value); + +const issuesQuery = useRepoIssues(pds, did, owner, repoAtUri, { enabled: hasAtUri }); +const prsQuery = useRepoPRs(pds, did, owner, repoAtUri, { enabled: hasAtUri }); + const files = computed(() => treeQuery.data.value ?? []); const commits = computed(() => logQuery.data.value ?? []); +const issues = computed(() => issuesQuery.data.value ?? []); +const prs = computed(() => prsQuery.data.value ?? []); const isLoading = computed(() => identity.isPending.value || recordQuery.isPending.value); const isError = computed(() => identity.isError.value || recordQuery.isError.value); diff --git a/src/features/repo/RepoFiles.vue b/src/features/repo/RepoFiles.vue index 97ee251..7fc09ae 100644 --- a/src/features/repo/RepoFiles.vue +++ b/src/features/repo/RepoFiles.vue @@ -38,11 +38,7 @@