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.

feat: home screen

+1225 -858
+5 -5
docs/specs/README.md
··· 12 12 13 13 ## What Twisted Does 14 14 15 - **Reader and social companion** for Tangled. Focused on discovery, browsing, and lightweight interactions. 15 + **Reader and social companion** for Tangled. Focused on browsing, known-handle lookup, and lightweight interactions. 16 16 17 17 - Browse repos, files, READMEs, issues, PRs 18 - - Discover trending/recent repos and users 19 - - Activity feed (global and personalized) 18 + - Jump to profiles and repos from a known AT Protocol handle 19 + - Explore and Activity placeholders until search/feed work lands 20 20 - Sign in via AT Protocol OAuth 21 21 - Star repos, follow users, react to content 22 22 - Offline-capable with cached data ··· 63 63 | ----- | ------------------------------------------------------------------------ | ------------------------------------ | ------------------------------------ | 64 64 | 1 | Project shell, tabs, mock data, design system | [specs/phase-1.md](specs/phase-1.md) | [tasks/phase-1.md](tasks/phase-1.md) | 65 65 | 2 | Public browsing — repos, files, profiles, issues, PRs | [specs/phase-2.md](specs/phase-2.md) | [tasks/phase-2.md](tasks/phase-2.md) | 66 - | 3 | Search, discovery, activity feed | [specs/phase-3.md](specs/phase-3.md) | [tasks/phase-3.md](tasks/phase-3.md) | 66 + | 3 | Deferred search/feed placeholders and Home-first public browsing | [specs/phase-3.md](specs/phase-3.md) | [tasks/phase-3.md](tasks/phase-3.md) | 67 67 | 4 | OAuth sign-in, star, follow, react, personalized feed | [specs/phase-4.md](specs/phase-4.md) | [tasks/phase-4.md](tasks/phase-4.md) | 68 68 | 5 | Offline persistence, performance, bundle optimization | [specs/phase-5.md](specs/phase-5.md) | [tasks/phase-5.md](tasks/phase-5.md) | 69 69 | 6 | Write features (issues, comments, profile edit), BFF, push notifications | [specs/phase-6.md](specs/phase-6.md) | [tasks/phase-6.md](tasks/phase-6.md) | ··· 75 75 2. **Tangled lexicon handling in one module boundary** (`src/services/tangled/`) — don't scatter `sh.tangled.*` awareness across pages. 76 76 3. **Read-first** — the primary product is a fast reader. Social mutations are a controlled second layer. 77 77 4. **Thin BFF when needed** (Phase 6+) for search indexing, personalized feeds, push notifications, and unstable procedure wrapping. 78 - 5. **Mobile-first, not desktop-forge-first** — prioritize discovery, readability, feed-driven interactions, small focused actions. 78 + 5. **Mobile-first, not desktop-forge-first** — prioritize readability, direct browsing, and small focused actions before broader discovery surfaces.
+9 -4
docs/specs/phase-2.md
··· 2 2 3 3 ## Goal 4 4 5 - Replace mock data with live Tangled API calls. Users can browse repos, profiles, file trees, README content, issues, and pull requests without signing in. 5 + Replace mock data on the shippable public-browsing surface with live Tangled API calls. Users can browse repos, profiles, file trees, README content, issues, and pull requests without signing in. Public entry points are intentionally scoped down for now: Home is a known-handle jump surface, while Explore and Activity remain clearly labeled placeholders until their dedicated work lands. 6 6 7 7 ## Protocol Stack 8 8 ··· 58 58 | Appview | `tangled.org` | HTTP (HTML, HTMX) | Profile pages, repo listings, timeline, search | 59 59 | Knots | `us-west.tangled.sh`, etc. | XRPC (`/xrpc/sh.tangled.*`) | Git data — trees, blobs, commits, branches, diffs | 60 60 61 - For Phase 2, git data comes from knots via XRPC. Profile and repo metadata comes from the appview. The service layer must route requests to the correct host based on the operation. 62 - 63 - > **Open question**: The appview serves HTML, not JSON API responses. We may need to scrape, use AT Protocol PDS queries (`com.atproto.repo.getRecord`), or discover if the appview exposes a JSON API. This must be validated early in Phase 2. 61 + For Phase 2, git data comes from knots via XRPC. Profile and repo metadata come from PDS records queried through `com.atproto.repo.getRecord` and `com.atproto.repo.listRecords`, not from the HTML appview. The service layer must route requests to the correct host based on the operation. 64 62 65 63 ## Features 66 64 ··· 79 77 - View user profile: avatar, bio, links, pronouns, location, pinned repos 80 78 - Profile data comes from `sh.tangled.actor.profile` record (key: `self`) on the user's PDS 81 79 - List user's repos 80 + 81 + ### Public Discovery (scoped down) 82 + 83 + - Home acts as the temporary public entry point: enter a known AT Protocol handle, then jump to profile or browse that handle's repos 84 + - Explore remains visible as a placeholder for future search work, but should not pretend global search already exists 85 + - Activity remains visible as a placeholder for future feed work, but should not pretend a public timeline already exists 86 + - Unsupported global search/trending behavior should be omitted or clearly labeled as future work, never filled with silent mock data 82 87 83 88 ### Pull Requests (read-only) 84 89
+42 -51
docs/specs/phase-3.md
··· 1 - # Phase 3 — Search & Activity Feed 1 + # Phase 3 — Deferred Search and Activity 2 2 3 3 ## Goal 4 4 5 - Add repository/user search and a public activity feed so unauthenticated users can discover content and follow what's happening across Tangled. 5 + Preserve honest product boundaries before search is implemented as a separate project. Public browsing continues through known AT Protocol handles on Home, while Explore and Activity stay visible as clearly labeled in-progress placeholders. 6 6 7 - ## Search 7 + ## Current Product Shape 8 8 9 - ### Discovery Problem 9 + ### Home 10 10 11 - Tangled's appview serves HTML — there is no documented public JSON search API. Search implementation must be validated against one of these strategies: 11 + Home is the temporary public entry point for unauthenticated browsing: 12 12 13 - 1. **Appview JSON endpoint** — check if `tangled.org` exposes a search query endpoint (undocumented but possible) 14 - 2. **AT Protocol relay/firehose indexing** — build a lightweight search index from ingested records (requires backend) 15 - 3. **Client-side PDS enumeration** — impractical at scale 16 - 4. **Scrape appview HTML** — fragile, last resort 13 + - Enter a known AT Protocol handle 14 + - Open that user's profile directly 15 + - Resolve the handle to DID + PDS via AT Protocol identity 16 + - List that user's public Tangled repos inline and open one directly 17 17 18 - **Recommended approach**: Start with strategy 1 (probe for JSON endpoints). If unavailable, implement curated discovery (trending, recent) from cached data and defer full search to Phase 6 with a backend. 18 + This keeps public browsing fully real without implying that global discovery already exists. 19 19 20 - ### Search UI 20 + ### Explore 21 21 22 - - Search bar at top of Explore tab 23 - - Segmented results: Repos | Users 24 - - Recent searches (persisted locally) 25 - - Debounced input (300ms) 26 - - Empty state with suggested queries 22 + Explore remains a tab-level placeholder: 27 23 28 - ### Discovery Sections (fallback if search API unavailable) 24 + - No global repo search 25 + - No global user search 26 + - No curated fallback discovery pretending to be search 27 + - Empty state should explicitly say search is in progress 29 28 30 - - Trending repos (most stars in recent window) 31 - - Recently created repos 32 - - Active repos (recent commits) 33 - - Suggested users 29 + ### Activity 34 30 35 - ## Activity Feed 31 + Activity also remains a tab-level placeholder: 36 32 37 - ### Data Source 33 + - No public timeline yet 34 + - No curated public feed fallback 35 + - Empty state should explicitly say activity is in progress 38 36 39 - Activity is derived from AT Protocol records created by users. The appview's `/timeline` page shows this data. Options for the mobile client: 37 + ## Identity and Routing 40 38 41 - 1. **Appview timeline endpoint** — check if there's a JSON variant 42 - 2. **Jetstream subscription** — `@atcute/jetstream` can subscribe to the AT Protocol event stream and filter for `sh.tangled.*` record types 43 - 3. **PDS record queries** — poll known users' PDS for recent records 39 + The Home handle flow continues to use the existing AT Protocol resolution path: 44 40 45 - **Recommended approach**: Try option 1 first. Fall back to option 2 (Jetstream) for a real-time feed. Option 3 is too slow for a general feed. 41 + 1. Resolve `handle -> DID` via `com.atproto.identity.resolveHandle` 42 + 2. Fetch the DID document and extract the PDS endpoint 43 + 3. Query the user's PDS for `sh.tangled.repo` records via `com.atproto.repo.listRecords` 44 + 4. Route to existing profile and repo detail screens 46 45 47 - ### Feed Item Types 46 + No backend search index, feed service, or additional dependency is introduced in this phase. 48 47 49 - Map these AT Protocol record creations to activity cards: 48 + ## UI Expectations 50 49 51 - | Record Type | Activity Kind | Display | 52 - | -------------------------------------- | ------------- | -------------------------------- | 53 - | `sh.tangled.repo` created | repo_created | "{actor} created {repo}" | 54 - | `sh.tangled.feed.star` created | repo_starred | "{actor} starred {repo}" | 55 - | `sh.tangled.graph.follow` created | user_followed | "{actor} followed {target}" | 56 - | `sh.tangled.repo.pull` created | pr_opened | "{actor} opened PR on {repo}" | 57 - | `sh.tangled.repo.pull.status` → merged | pr_merged | "{actor} merged PR on {repo}" | 58 - | `sh.tangled.repo.issue` created | issue_opened | "{actor} opened issue on {repo}" | 59 - | `sh.tangled.repo.issue.state` → closed | issue_closed | "{actor} closed issue on {repo}" | 60 - | `sh.tangled.feed.reaction` created | reaction | "{actor} reacted to {target}" | 50 + - Home shows one handle input plus explicit actions for profile jump and repo browsing 51 + - Home shows loading, invalid-handle, no-repos, and resolved-repo-list states 52 + - Explore shows a static in-progress empty state 53 + - Activity shows a static in-progress empty state 54 + - Profile remains unchanged 61 55 62 - ### Feed UI 56 + ## Deferred Work 63 57 64 - - Filter chips: All, Repos, PRs, Issues, Social 65 - - Infinite scroll with cursor-based pagination 66 - - Pull-to-refresh 67 - - Activity cards: actor avatar + verb + target + relative timestamp 68 - - Tap card → navigate to repo/profile/PR/issue detail 58 + The following work is intentionally deferred out of this phase: 69 59 70 - ### Feed Caching 60 + - Search indexing and ranking 61 + - Search result UI and recent searches 62 + - Trending or suggested discovery sections 63 + - Public activity feed ingestion, pagination, and caching 64 + - Jetstream or appview timeline investigation 71 65 72 - - Cache last 100 feed items in IndexedDB 73 - - Show cached feed immediately, refresh in background (stale-while-revalidate) 74 - - Stale time: 1 min 75 - - Persist across app restarts 66 + These capabilities will be revisited when search and feed work are scheduled independently.
+2 -2
docs/tasks/phase-2.md
··· 57 57 58 58 - [x] Configure TanStack Query stale/gc times per data type (see spec) 59 59 - [x] Set up IndexedDB query persister for offline reads 60 - - [ ] Verify stale-while-revalidate behavior: cached data shows immediately, refreshes in background 61 60 62 61 ## Quality 63 62 64 - - [ ] Replace all mock data usage with live queries (remove or gate mocks behind a flag) 63 + - [x] Replace default mock-backed Home/Explore/Activity surfaces with scoped-down curated live discovery/activity 64 + - [ ] Verify stale-while-revalidate behavior: cached data shows immediately, refreshes in background 65 65 - [ ] Test with real Tangled repos (e.g., `tangled.org/core`) 66 66 - [ ] Verify error states render correctly: 404, network failure, empty repos 67 67 - [ ] Test on slow network (throttled devtools) — verify skeleton → content transition
+6 -3
package.json
··· 35 35 }, 36 36 "devDependencies": { 37 37 "@capacitor/cli": "8.2.0", 38 + "@eslint/js": "10.0.1", 38 39 "@vitejs/plugin-legacy": "^5.0.0", 39 40 "@vitejs/plugin-vue": "^4.0.0", 40 - "@vue/eslint-config-typescript": "^12.0.0", 41 41 "@vue/test-utils": "^2.3.0", 42 42 "cypress": "^13.5.0", 43 - "eslint": "^8.35.0", 44 - "eslint-plugin-vue": "^9.9.0", 43 + "eslint": "^10.1.0", 44 + "eslint-plugin-vue": "^10.8.0", 45 + "globals": "17.4.0", 45 46 "jsdom": "^22.1.0", 46 47 "prettier": "^3.8.1", 47 48 "terser": "^5.4.0", 48 49 "typescript": "~5.9.0", 50 + "typescript-eslint": "^8.57.1", 49 51 "vite": "^5.0.0", 50 52 "vitest": "^0.34.6", 53 + "vue-eslint-parser": "10.4.0", 51 54 "vue-tsc": "^2.1.10" 52 55 }, 53 56 "description": "An Ionic project"
+325 -551
pnpm-lock.yaml
··· 69 69 '@capacitor/cli': 70 70 specifier: 8.2.0 71 71 version: 8.2.0 72 + '@eslint/js': 73 + specifier: 10.0.1 74 + version: 10.0.1(eslint@10.1.0) 72 75 '@vitejs/plugin-legacy': 73 76 specifier: ^5.0.0 74 77 version: 5.4.3(terser@5.46.1)(vite@5.4.21(@types/node@25.5.0)(terser@5.46.1)) 75 78 '@vitejs/plugin-vue': 76 79 specifier: ^4.0.0 77 80 version: 4.6.2(vite@5.4.21(@types/node@25.5.0)(terser@5.46.1))(vue@3.5.30(typescript@5.9.3)) 78 - '@vue/eslint-config-typescript': 79 - specifier: ^12.0.0 80 - version: 12.0.0(eslint-plugin-vue@9.33.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.9.3) 81 81 '@vue/test-utils': 82 82 specifier: ^2.3.0 83 83 version: 2.4.6 ··· 85 85 specifier: ^13.5.0 86 86 version: 13.17.0 87 87 eslint: 88 - specifier: ^8.35.0 89 - version: 8.57.1 88 + specifier: ^10.1.0 89 + version: 10.1.0 90 90 eslint-plugin-vue: 91 - specifier: ^9.9.0 92 - version: 9.33.0(eslint@8.57.1) 91 + specifier: ^10.8.0 92 + version: 10.8.0(@typescript-eslint/parser@8.57.1(eslint@10.1.0)(typescript@5.9.3))(eslint@10.1.0)(vue-eslint-parser@10.4.0(eslint@10.1.0)) 93 + globals: 94 + specifier: 17.4.0 95 + version: 17.4.0 93 96 jsdom: 94 97 specifier: ^22.1.0 95 98 version: 22.1.0 ··· 102 105 typescript: 103 106 specifier: ~5.9.0 104 107 version: 5.9.3 108 + typescript-eslint: 109 + specifier: ^8.57.1 110 + version: 8.57.1(eslint@10.1.0)(typescript@5.9.3) 105 111 vite: 106 112 specifier: ^5.0.0 107 113 version: 5.4.21(@types/node@25.5.0)(terser@5.46.1) 108 114 vitest: 109 115 specifier: ^0.34.6 110 116 version: 0.34.6(jsdom@22.1.0)(terser@5.46.1) 117 + vue-eslint-parser: 118 + specifier: 10.4.0 119 + version: 10.4.0(eslint@10.1.0) 111 120 vue-tsc: 112 121 specifier: ^2.1.10 113 122 version: 2.2.12(typescript@5.9.3) ··· 830 839 resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} 831 840 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 832 841 833 - '@eslint/eslintrc@2.1.4': 834 - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} 835 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 842 + '@eslint/config-array@0.23.3': 843 + resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==} 844 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 836 845 837 - '@eslint/js@8.57.1': 838 - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} 839 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 846 + '@eslint/config-helpers@0.5.3': 847 + resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==} 848 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 840 849 841 - '@humanwhocodes/config-array@0.13.0': 842 - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} 843 - engines: {node: '>=10.10.0'} 844 - deprecated: Use @eslint/config-array instead 850 + '@eslint/core@1.1.1': 851 + resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==} 852 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 853 + 854 + '@eslint/js@10.0.1': 855 + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} 856 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 857 + peerDependencies: 858 + eslint: ^10.0.0 859 + peerDependenciesMeta: 860 + eslint: 861 + optional: true 862 + 863 + '@eslint/object-schema@3.0.3': 864 + resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==} 865 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 866 + 867 + '@eslint/plugin-kit@0.6.1': 868 + resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==} 869 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 870 + 871 + '@humanfs/core@0.19.1': 872 + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} 873 + engines: {node: '>=18.18.0'} 874 + 875 + '@humanfs/node@0.16.7': 876 + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} 877 + engines: {node: '>=18.18.0'} 845 878 846 879 '@humanwhocodes/module-importer@1.0.1': 847 880 resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 848 881 engines: {node: '>=12.22'} 849 882 850 - '@humanwhocodes/object-schema@2.0.3': 851 - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} 852 - deprecated: Use @eslint/object-schema instead 883 + '@humanwhocodes/retry@0.4.3': 884 + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} 885 + engines: {node: '>=18.18'} 853 886 854 887 '@ionic/cli-framework-output@2.2.8': 855 888 resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==} ··· 923 956 924 957 '@jridgewell/trace-mapping@0.3.31': 925 958 resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} 926 - 927 - '@nodelib/fs.scandir@2.1.5': 928 - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 929 - engines: {node: '>= 8'} 930 - 931 - '@nodelib/fs.stat@2.0.5': 932 - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 933 - engines: {node: '>= 8'} 934 - 935 - '@nodelib/fs.walk@1.2.8': 936 - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 937 - engines: {node: '>= 8'} 938 959 939 960 '@one-ini/wasm@0.1.1': 940 961 resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} ··· 1210 1231 '@types/chai@4.3.20': 1211 1232 resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==} 1212 1233 1234 + '@types/esrecurse@4.3.1': 1235 + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} 1236 + 1213 1237 '@types/estree@1.0.8': 1214 1238 resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 1215 1239 ··· 1222 1246 '@types/node@25.5.0': 1223 1247 resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} 1224 1248 1225 - '@types/semver@7.7.1': 1226 - resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} 1227 - 1228 1249 '@types/sinonjs__fake-timers@8.1.1': 1229 1250 resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} 1230 1251 ··· 1237 1258 '@types/yauzl@2.10.3': 1238 1259 resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} 1239 1260 1240 - '@typescript-eslint/eslint-plugin@6.21.0': 1241 - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} 1242 - engines: {node: ^16.0.0 || >=18.0.0} 1261 + '@typescript-eslint/eslint-plugin@8.57.1': 1262 + resolution: {integrity: sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==} 1263 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1264 + peerDependencies: 1265 + '@typescript-eslint/parser': ^8.57.1 1266 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 1267 + typescript: '>=4.8.4 <6.0.0' 1268 + 1269 + '@typescript-eslint/parser@8.57.1': 1270 + resolution: {integrity: sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==} 1271 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1243 1272 peerDependencies: 1244 - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha 1245 - eslint: ^7.0.0 || ^8.0.0 1246 - typescript: '*' 1247 - peerDependenciesMeta: 1248 - typescript: 1249 - optional: true 1273 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 1274 + typescript: '>=4.8.4 <6.0.0' 1250 1275 1251 - '@typescript-eslint/parser@6.21.0': 1252 - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} 1253 - engines: {node: ^16.0.0 || >=18.0.0} 1276 + '@typescript-eslint/project-service@8.57.1': 1277 + resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==} 1278 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1254 1279 peerDependencies: 1255 - eslint: ^7.0.0 || ^8.0.0 1256 - typescript: '*' 1257 - peerDependenciesMeta: 1258 - typescript: 1259 - optional: true 1280 + typescript: '>=4.8.4 <6.0.0' 1260 1281 1261 - '@typescript-eslint/scope-manager@6.21.0': 1262 - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} 1263 - engines: {node: ^16.0.0 || >=18.0.0} 1282 + '@typescript-eslint/scope-manager@8.57.1': 1283 + resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==} 1284 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1264 1285 1265 - '@typescript-eslint/type-utils@6.21.0': 1266 - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} 1267 - engines: {node: ^16.0.0 || >=18.0.0} 1286 + '@typescript-eslint/tsconfig-utils@8.57.1': 1287 + resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==} 1288 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1289 + peerDependencies: 1290 + typescript: '>=4.8.4 <6.0.0' 1291 + 1292 + '@typescript-eslint/type-utils@8.57.1': 1293 + resolution: {integrity: sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==} 1294 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1268 1295 peerDependencies: 1269 - eslint: ^7.0.0 || ^8.0.0 1270 - typescript: '*' 1271 - peerDependenciesMeta: 1272 - typescript: 1273 - optional: true 1296 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 1297 + typescript: '>=4.8.4 <6.0.0' 1274 1298 1275 - '@typescript-eslint/types@6.21.0': 1276 - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} 1277 - engines: {node: ^16.0.0 || >=18.0.0} 1299 + '@typescript-eslint/types@8.57.1': 1300 + resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==} 1301 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1278 1302 1279 - '@typescript-eslint/typescript-estree@6.21.0': 1280 - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} 1281 - engines: {node: ^16.0.0 || >=18.0.0} 1303 + '@typescript-eslint/typescript-estree@8.57.1': 1304 + resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==} 1305 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1282 1306 peerDependencies: 1283 - typescript: '*' 1284 - peerDependenciesMeta: 1285 - typescript: 1286 - optional: true 1307 + typescript: '>=4.8.4 <6.0.0' 1287 1308 1288 - '@typescript-eslint/utils@6.21.0': 1289 - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} 1290 - engines: {node: ^16.0.0 || >=18.0.0} 1309 + '@typescript-eslint/utils@8.57.1': 1310 + resolution: {integrity: sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==} 1311 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1291 1312 peerDependencies: 1292 - eslint: ^7.0.0 || ^8.0.0 1293 - 1294 - '@typescript-eslint/visitor-keys@6.21.0': 1295 - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} 1296 - engines: {node: ^16.0.0 || >=18.0.0} 1313 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 1314 + typescript: '>=4.8.4 <6.0.0' 1297 1315 1298 - '@ungap/structured-clone@1.3.0': 1299 - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} 1316 + '@typescript-eslint/visitor-keys@8.57.1': 1317 + resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==} 1318 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1300 1319 1301 1320 '@vitejs/plugin-legacy@5.4.3': 1302 1321 resolution: {integrity: sha512-wsyXK9mascyplcqvww1gA1xYiy29iRHfyciw+a0t7qRNdzX6PdfSWmOoCi74epr87DujM+5J+rnnSv+4PazqVg==} ··· 1363 1382 '@vue/devtools-shared@7.7.9': 1364 1383 resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} 1365 1384 1366 - '@vue/eslint-config-typescript@12.0.0': 1367 - resolution: {integrity: sha512-StxLFet2Qe97T8+7L8pGlhYBBr8Eg05LPuTDVopQV6il+SK6qqom59BA/rcFipUef2jD8P2X44Vd8tMFytfvlg==} 1368 - engines: {node: ^14.17.0 || >=16.0.0} 1369 - peerDependencies: 1370 - eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 1371 - eslint-plugin-vue: ^9.0.0 1372 - typescript: '*' 1373 - peerDependenciesMeta: 1374 - typescript: 1375 - optional: true 1376 - 1377 1385 '@vue/language-core@2.2.12': 1378 1386 resolution: {integrity: sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==} 1379 1387 peerDependencies: ··· 1473 1481 arch@2.2.0: 1474 1482 resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} 1475 1483 1476 - argparse@2.0.1: 1477 - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 1478 - 1479 - array-union@2.1.0: 1480 - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 1481 - engines: {node: '>=8'} 1482 - 1483 1484 asn1@0.2.6: 1484 1485 resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} 1485 1486 ··· 1562 1563 bplist-parser@0.3.2: 1563 1564 resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} 1564 1565 engines: {node: '>= 5.10.0'} 1565 - 1566 - brace-expansion@1.1.12: 1567 - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} 1568 1566 1569 1567 brace-expansion@2.0.2: 1570 1568 resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} ··· 1573 1571 resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} 1574 1572 engines: {node: 18 || 20 || >=22} 1575 1573 1576 - braces@3.0.3: 1577 - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} 1578 - engines: {node: '>=8'} 1579 - 1580 1574 browserslist-to-esbuild@2.1.1: 1581 1575 resolution: {integrity: sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw==} 1582 1576 engines: {node: '>=18'} ··· 1614 1608 resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} 1615 1609 engines: {node: '>= 0.4'} 1616 1610 1617 - callsites@3.1.0: 1618 - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} 1619 - engines: {node: '>=6'} 1620 - 1621 1611 caniuse-lite@1.0.30001780: 1622 1612 resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==} 1623 1613 ··· 1695 1685 common-tags@1.8.2: 1696 1686 resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} 1697 1687 engines: {node: '>=4.0.0'} 1698 - 1699 - concat-map@0.0.1: 1700 - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 1701 1688 1702 1689 confbox@0.1.8: 1703 1690 resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} ··· 1795 1782 resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} 1796 1783 engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 1797 1784 1798 - dir-glob@3.0.1: 1799 - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 1800 - engines: {node: '>=8'} 1801 - 1802 - doctrine@3.0.0: 1803 - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 1804 - engines: {node: '>=6.0.0'} 1805 - 1806 1785 domexception@4.0.0: 1807 1786 resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} 1808 1787 engines: {node: '>=12'} ··· 1888 1867 resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 1889 1868 engines: {node: '>=10'} 1890 1869 1891 - eslint-plugin-vue@9.33.0: 1892 - resolution: {integrity: sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==} 1893 - engines: {node: ^14.17.0 || >=16.0.0} 1870 + eslint-plugin-vue@10.8.0: 1871 + resolution: {integrity: sha512-f1J/tcbnrpgC8suPN5AtdJ5MQjuXbSU9pGRSSYAuF3SHoiYCOdEX6O22pLaRyLHXvDcOe+O5ENgc1owQ587agA==} 1872 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1894 1873 peerDependencies: 1895 - eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 1874 + '@stylistic/eslint-plugin': ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 1875 + '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 1876 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 1877 + vue-eslint-parser: ^10.0.0 1878 + peerDependenciesMeta: 1879 + '@stylistic/eslint-plugin': 1880 + optional: true 1881 + '@typescript-eslint/parser': 1882 + optional: true 1896 1883 1897 - eslint-scope@7.2.2: 1898 - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 1899 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1884 + eslint-scope@9.1.2: 1885 + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} 1886 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 1900 1887 1901 1888 eslint-visitor-keys@3.4.3: 1902 1889 resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 1903 1890 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1904 1891 1905 - eslint@8.57.1: 1906 - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} 1907 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1908 - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. 1892 + eslint-visitor-keys@5.0.1: 1893 + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} 1894 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 1895 + 1896 + eslint@10.1.0: 1897 + resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==} 1898 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 1909 1899 hasBin: true 1900 + peerDependencies: 1901 + jiti: '*' 1902 + peerDependenciesMeta: 1903 + jiti: 1904 + optional: true 1910 1905 1911 1906 esm-env@1.2.2: 1912 1907 resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 1913 1908 1914 - espree@9.6.1: 1915 - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 1916 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 1909 + espree@11.2.0: 1910 + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} 1911 + engines: {node: ^20.19.0 || ^22.13.0 || >=24} 1917 1912 1918 1913 esquery@1.7.0: 1919 1914 resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} ··· 1960 1955 fast-deep-equal@3.1.3: 1961 1956 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} 1962 1957 1963 - fast-glob@3.3.3: 1964 - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} 1965 - engines: {node: '>=8.6.0'} 1966 - 1967 1958 fast-json-stable-stringify@2.1.0: 1968 1959 resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} 1969 1960 1970 1961 fast-levenshtein@2.0.6: 1971 1962 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 1972 1963 1973 - fastq@1.20.1: 1974 - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} 1975 - 1976 1964 fd-slicer@1.1.0: 1977 1965 resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} 1978 1966 1967 + fdir@6.5.0: 1968 + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 1969 + engines: {node: '>=12.0.0'} 1970 + peerDependencies: 1971 + picomatch: ^3 || ^4 1972 + peerDependenciesMeta: 1973 + picomatch: 1974 + optional: true 1975 + 1979 1976 figures@3.2.0: 1980 1977 resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} 1981 1978 engines: {node: '>=8'} 1982 1979 1983 - file-entry-cache@6.0.1: 1984 - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 1985 - engines: {node: ^10.12.0 || >=12.0.0} 1986 - 1987 - fill-range@7.1.1: 1988 - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} 1989 - engines: {node: '>=8'} 1980 + file-entry-cache@8.0.0: 1981 + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} 1982 + engines: {node: '>=16.0.0'} 1990 1983 1991 1984 find-up@5.0.0: 1992 1985 resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 1993 1986 engines: {node: '>=10'} 1994 1987 1995 - flat-cache@3.2.0: 1996 - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 1997 - engines: {node: ^10.12.0 || >=12.0.0} 1988 + flat-cache@4.0.1: 1989 + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} 1990 + engines: {node: '>=16'} 1998 1991 1999 1992 flatted@3.4.2: 2000 1993 resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} ··· 2018 2011 resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} 2019 2012 engines: {node: '>=10'} 2020 2013 2021 - fs.realpath@1.0.0: 2022 - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 2023 - 2024 2014 fsevents@2.3.3: 2025 2015 resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 2026 2016 engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} ··· 2054 2044 getpass@0.1.7: 2055 2045 resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} 2056 2046 2057 - glob-parent@5.1.2: 2058 - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 2059 - engines: {node: '>= 6'} 2060 - 2061 2047 glob-parent@6.0.2: 2062 2048 resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 2063 2049 engines: {node: '>=10.13.0'} ··· 2071 2057 resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} 2072 2058 engines: {node: 18 || 20 || >=22} 2073 2059 2074 - glob@7.2.3: 2075 - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 2076 - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me 2077 - 2078 2060 global-dirs@3.0.1: 2079 2061 resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} 2080 2062 engines: {node: '>=10'} 2081 2063 2082 - globals@13.24.0: 2083 - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} 2084 - engines: {node: '>=8'} 2085 - 2086 - globby@11.1.0: 2087 - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 2088 - engines: {node: '>=10'} 2064 + globals@17.4.0: 2065 + resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} 2066 + engines: {node: '>=18'} 2089 2067 2090 2068 gopd@1.2.0: 2091 2069 resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} ··· 2093 2071 2094 2072 graceful-fs@4.2.11: 2095 2073 resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} 2096 - 2097 - graphemer@1.4.0: 2098 - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 2099 2074 2100 2075 has-flag@4.0.0: 2101 2076 resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} ··· 2154 2129 resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 2155 2130 engines: {node: '>= 4'} 2156 2131 2157 - import-fresh@3.3.1: 2158 - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} 2159 - engines: {node: '>=6'} 2132 + ignore@7.0.5: 2133 + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} 2134 + engines: {node: '>= 4'} 2160 2135 2161 2136 imurmurhash@0.1.4: 2162 2137 resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} ··· 2165 2140 indent-string@4.0.0: 2166 2141 resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} 2167 2142 engines: {node: '>=8'} 2168 - 2169 - inflight@1.0.6: 2170 - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 2171 - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. 2172 2143 2173 2144 inherits@2.0.4: 2174 2145 resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} ··· 2215 2186 resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} 2216 2187 engines: {node: '>=10'} 2217 2188 2218 - is-number@7.0.0: 2219 - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 2220 - engines: {node: '>=0.12.0'} 2221 - 2222 2189 is-path-inside@3.0.3: 2223 2190 resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 2224 2191 engines: {node: '>=8'} ··· 2265 2232 2266 2233 js-tokens@4.0.0: 2267 2234 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 2268 - 2269 - js-yaml@4.1.1: 2270 - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} 2271 - hasBin: true 2272 2235 2273 2236 jsbn@0.1.1: 2274 2237 resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} ··· 2353 2316 lodash.debounce@4.0.8: 2354 2317 resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} 2355 2318 2356 - lodash.merge@4.6.2: 2357 - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} 2358 - 2359 2319 lodash.once@4.1.1: 2360 2320 resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} 2361 2321 ··· 2397 2357 merge-stream@2.0.0: 2398 2358 resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 2399 2359 2400 - merge2@1.4.1: 2401 - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 2402 - engines: {node: '>= 8'} 2403 - 2404 - micromatch@4.0.8: 2405 - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} 2406 - engines: {node: '>=8.6'} 2407 - 2408 2360 mime-db@1.52.0: 2409 2361 resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 2410 2362 engines: {node: '>= 0.6'} ··· 2421 2373 resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} 2422 2374 engines: {node: 18 || 20 || >=22} 2423 2375 2424 - minimatch@3.1.5: 2425 - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} 2426 - 2427 - minimatch@9.0.3: 2428 - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} 2429 - engines: {node: '>=16 || 14 >=14.17'} 2430 - 2431 2376 minimatch@9.0.9: 2432 2377 resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} 2433 2378 engines: {node: '>=16 || 14 >=14.17'} ··· 2527 2472 package-json-from-dist@1.0.1: 2528 2473 resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 2529 2474 2530 - parent-module@1.0.1: 2531 - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 2532 - engines: {node: '>=6'} 2533 - 2534 2475 parse5@7.3.0: 2535 2476 resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} 2536 2477 ··· 2541 2482 resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 2542 2483 engines: {node: '>=8'} 2543 2484 2544 - path-is-absolute@1.0.1: 2545 - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 2546 - engines: {node: '>=0.10.0'} 2547 - 2548 2485 path-key@3.1.1: 2549 2486 resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 2550 2487 engines: {node: '>=8'} ··· 2560 2497 resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} 2561 2498 engines: {node: 18 || 20 || >=22} 2562 2499 2563 - path-type@4.0.0: 2564 - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 2565 - engines: {node: '>=8'} 2566 - 2567 2500 pathe@1.1.2: 2568 2501 resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} 2569 2502 ··· 2585 2518 picocolors@1.1.1: 2586 2519 resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 2587 2520 2588 - picomatch@2.3.1: 2589 - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 2590 - engines: {node: '>=8.6'} 2521 + picomatch@4.0.3: 2522 + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 2523 + engines: {node: '>=12'} 2591 2524 2592 2525 pify@2.3.0: 2593 2526 resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} ··· 2609 2542 resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} 2610 2543 engines: {node: '>=10.4.0'} 2611 2544 2612 - postcss-selector-parser@6.1.2: 2613 - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} 2545 + postcss-selector-parser@7.1.1: 2546 + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} 2614 2547 engines: {node: '>=4'} 2615 2548 2616 2549 postcss@8.5.8: ··· 2665 2598 querystringify@2.2.0: 2666 2599 resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} 2667 2600 2668 - queue-microtask@1.2.3: 2669 - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 2670 - 2671 2601 react-is@18.3.1: 2672 2602 resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} 2673 2603 ··· 2705 2635 requires-port@1.0.0: 2706 2636 resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} 2707 2637 2708 - resolve-from@4.0.0: 2709 - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} 2710 - engines: {node: '>=4'} 2711 - 2712 2638 resolve@1.22.11: 2713 2639 resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} 2714 2640 engines: {node: '>= 0.4'} ··· 2718 2644 resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} 2719 2645 engines: {node: '>=8'} 2720 2646 2721 - reusify@1.1.0: 2722 - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} 2723 - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 2724 - 2725 2647 rfdc@1.4.1: 2726 2648 resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} 2727 2649 2728 - rimraf@3.0.2: 2729 - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 2730 - deprecated: Rimraf versions prior to v4 are no longer supported 2731 - hasBin: true 2732 - 2733 2650 rimraf@6.1.3: 2734 2651 resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} 2735 2652 engines: {node: 20 || >=22} ··· 2742 2659 2743 2660 rrweb-cssom@0.6.0: 2744 2661 resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} 2745 - 2746 - run-parallel@1.2.0: 2747 - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 2748 2662 2749 2663 rxjs@7.8.2: 2750 2664 resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} ··· 2812 2726 sisteransi@1.0.5: 2813 2727 resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} 2814 2728 2815 - slash@3.0.0: 2816 - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 2817 - engines: {node: '>=8'} 2818 - 2819 2729 slice-ansi@3.0.0: 2820 2730 resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} 2821 2731 engines: {node: '>=8'} ··· 2877 2787 resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 2878 2788 engines: {node: '>=6'} 2879 2789 2880 - strip-json-comments@3.1.1: 2881 - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} 2882 - engines: {node: '>=8'} 2883 - 2884 2790 strip-literal@1.3.0: 2885 2791 resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} 2886 2792 ··· 2915 2821 engines: {node: '>=10'} 2916 2822 hasBin: true 2917 2823 2918 - text-table@0.2.0: 2919 - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 2920 - 2921 2824 throttleit@1.0.1: 2922 2825 resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} 2923 2826 ··· 2929 2832 2930 2833 tinybench@2.9.0: 2931 2834 resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 2835 + 2836 + tinyglobby@0.2.15: 2837 + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 2838 + engines: {node: '>=12.0.0'} 2932 2839 2933 2840 tinypool@0.7.0: 2934 2841 resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} ··· 2949 2856 resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} 2950 2857 engines: {node: '>=14.14'} 2951 2858 2952 - to-regex-range@5.0.1: 2953 - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 2954 - engines: {node: '>=8.0'} 2955 - 2956 2859 tough-cookie@4.1.4: 2957 2860 resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} 2958 2861 engines: {node: '>=6'} ··· 2969 2872 resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 2970 2873 hasBin: true 2971 2874 2972 - ts-api-utils@1.4.3: 2973 - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} 2974 - engines: {node: '>=16'} 2875 + ts-api-utils@2.5.0: 2876 + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} 2877 + engines: {node: '>=18.12'} 2975 2878 peerDependencies: 2976 - typescript: '>=4.2.0' 2879 + typescript: '>=4.8.4' 2977 2880 2978 2881 tslib@2.8.1: 2979 2882 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} ··· 2992 2895 resolution: {integrity: sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==} 2993 2896 engines: {node: '>=4'} 2994 2897 2995 - type-fest@0.20.2: 2996 - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 2997 - engines: {node: '>=10'} 2998 - 2999 2898 type-fest@0.21.3: 3000 2899 resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} 3001 2900 engines: {node: '>=10'} 3002 2901 2902 + typescript-eslint@8.57.1: 2903 + resolution: {integrity: sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==} 2904 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 2905 + peerDependencies: 2906 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 2907 + typescript: '>=4.8.4 <6.0.0' 2908 + 3003 2909 typescript@5.9.3: 3004 2910 resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} 3005 2911 engines: {node: '>=14.17'} ··· 3149 3055 '@vue/composition-api': 3150 3056 optional: true 3151 3057 3152 - vue-eslint-parser@9.4.3: 3153 - resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==} 3154 - engines: {node: ^14.17.0 || >=16.0.0} 3058 + vue-eslint-parser@10.4.0: 3059 + resolution: {integrity: sha512-Vxi9pJdbN3ZnVGLODVtZ7y4Y2kzAAE2Cm0CZ3ZDRvydVYxZ6VrnBhLikBsRS+dpwj4Jv4UCv21PTEwF5rQ9WXg==} 3060 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 3155 3061 peerDependencies: 3156 - eslint: '>=6.0.0' 3062 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 3157 3063 3158 3064 vue-router@4.6.4: 3159 3065 resolution: {integrity: sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==} ··· 4116 4022 '@esbuild/win32-x64@0.21.5': 4117 4023 optional: true 4118 4024 4119 - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': 4025 + '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0)': 4120 4026 dependencies: 4121 - eslint: 8.57.1 4027 + eslint: 10.1.0 4122 4028 eslint-visitor-keys: 3.4.3 4123 4029 4124 4030 '@eslint-community/regexpp@4.12.2': {} 4125 4031 4126 - '@eslint/eslintrc@2.1.4': 4032 + '@eslint/config-array@0.23.3': 4127 4033 dependencies: 4128 - ajv: 6.14.0 4034 + '@eslint/object-schema': 3.0.3 4129 4035 debug: 4.4.3(supports-color@8.1.1) 4130 - espree: 9.6.1 4131 - globals: 13.24.0 4132 - ignore: 5.3.2 4133 - import-fresh: 3.3.1 4134 - js-yaml: 4.1.1 4135 - minimatch: 3.1.5 4136 - strip-json-comments: 3.1.1 4036 + minimatch: 10.2.4 4137 4037 transitivePeerDependencies: 4138 4038 - supports-color 4139 4039 4140 - '@eslint/js@8.57.1': {} 4040 + '@eslint/config-helpers@0.5.3': 4041 + dependencies: 4042 + '@eslint/core': 1.1.1 4141 4043 4142 - '@humanwhocodes/config-array@0.13.0': 4044 + '@eslint/core@1.1.1': 4143 4045 dependencies: 4144 - '@humanwhocodes/object-schema': 2.0.3 4145 - debug: 4.4.3(supports-color@8.1.1) 4146 - minimatch: 3.1.5 4147 - transitivePeerDependencies: 4148 - - supports-color 4046 + '@types/json-schema': 7.0.15 4047 + 4048 + '@eslint/js@10.0.1(eslint@10.1.0)': 4049 + optionalDependencies: 4050 + eslint: 10.1.0 4051 + 4052 + '@eslint/object-schema@3.0.3': {} 4053 + 4054 + '@eslint/plugin-kit@0.6.1': 4055 + dependencies: 4056 + '@eslint/core': 1.1.1 4057 + levn: 0.4.1 4058 + 4059 + '@humanfs/core@0.19.1': {} 4060 + 4061 + '@humanfs/node@0.16.7': 4062 + dependencies: 4063 + '@humanfs/core': 0.19.1 4064 + '@humanwhocodes/retry': 0.4.3 4149 4065 4150 4066 '@humanwhocodes/module-importer@1.0.1': {} 4151 4067 4152 - '@humanwhocodes/object-schema@2.0.3': {} 4068 + '@humanwhocodes/retry@0.4.3': {} 4153 4069 4154 4070 '@ionic/cli-framework-output@2.2.8': 4155 4071 dependencies: ··· 4292 4208 '@jridgewell/resolve-uri': 3.1.2 4293 4209 '@jridgewell/sourcemap-codec': 1.5.5 4294 4210 4295 - '@nodelib/fs.scandir@2.1.5': 4296 - dependencies: 4297 - '@nodelib/fs.stat': 2.0.5 4298 - run-parallel: 1.2.0 4299 - 4300 - '@nodelib/fs.stat@2.0.5': {} 4301 - 4302 - '@nodelib/fs.walk@1.2.8': 4303 - dependencies: 4304 - '@nodelib/fs.scandir': 2.1.5 4305 - fastq: 1.20.1 4306 - 4307 4211 '@one-ini/wasm@0.1.1': {} 4308 4212 4309 4213 '@pkgjs/parseargs@0.11.0': ··· 4493 4397 4494 4398 '@types/chai@4.3.20': {} 4495 4399 4400 + '@types/esrecurse@4.3.1': {} 4401 + 4496 4402 '@types/estree@1.0.8': {} 4497 4403 4498 4404 '@types/fs-extra@8.1.5': ··· 4505 4411 dependencies: 4506 4412 undici-types: 7.18.2 4507 4413 4508 - '@types/semver@7.7.1': {} 4509 - 4510 4414 '@types/sinonjs__fake-timers@8.1.1': {} 4511 4415 4512 4416 '@types/sizzle@2.3.10': {} ··· 4518 4422 '@types/node': 25.5.0 4519 4423 optional: true 4520 4424 4521 - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': 4425 + '@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0)(typescript@5.9.3))(eslint@10.1.0)(typescript@5.9.3)': 4522 4426 dependencies: 4523 4427 '@eslint-community/regexpp': 4.12.2 4524 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) 4525 - '@typescript-eslint/scope-manager': 6.21.0 4526 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) 4527 - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) 4528 - '@typescript-eslint/visitor-keys': 6.21.0 4529 - debug: 4.4.3(supports-color@8.1.1) 4530 - eslint: 8.57.1 4531 - graphemer: 1.4.0 4532 - ignore: 5.3.2 4428 + '@typescript-eslint/parser': 8.57.1(eslint@10.1.0)(typescript@5.9.3) 4429 + '@typescript-eslint/scope-manager': 8.57.1 4430 + '@typescript-eslint/type-utils': 8.57.1(eslint@10.1.0)(typescript@5.9.3) 4431 + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0)(typescript@5.9.3) 4432 + '@typescript-eslint/visitor-keys': 8.57.1 4433 + eslint: 10.1.0 4434 + ignore: 7.0.5 4533 4435 natural-compare: 1.4.0 4534 - semver: 7.7.4 4535 - ts-api-utils: 1.4.3(typescript@5.9.3) 4536 - optionalDependencies: 4436 + ts-api-utils: 2.5.0(typescript@5.9.3) 4537 4437 typescript: 5.9.3 4538 4438 transitivePeerDependencies: 4539 4439 - supports-color 4540 4440 4541 - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': 4441 + '@typescript-eslint/parser@8.57.1(eslint@10.1.0)(typescript@5.9.3)': 4442 + dependencies: 4443 + '@typescript-eslint/scope-manager': 8.57.1 4444 + '@typescript-eslint/types': 8.57.1 4445 + '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) 4446 + '@typescript-eslint/visitor-keys': 8.57.1 4447 + debug: 4.4.3(supports-color@8.1.1) 4448 + eslint: 10.1.0 4449 + typescript: 5.9.3 4450 + transitivePeerDependencies: 4451 + - supports-color 4452 + 4453 + '@typescript-eslint/project-service@8.57.1(typescript@5.9.3)': 4542 4454 dependencies: 4543 - '@typescript-eslint/scope-manager': 6.21.0 4544 - '@typescript-eslint/types': 6.21.0 4545 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) 4546 - '@typescript-eslint/visitor-keys': 6.21.0 4455 + '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3) 4456 + '@typescript-eslint/types': 8.57.1 4547 4457 debug: 4.4.3(supports-color@8.1.1) 4548 - eslint: 8.57.1 4549 - optionalDependencies: 4550 4458 typescript: 5.9.3 4551 4459 transitivePeerDependencies: 4552 4460 - supports-color 4553 4461 4554 - '@typescript-eslint/scope-manager@6.21.0': 4462 + '@typescript-eslint/scope-manager@8.57.1': 4555 4463 dependencies: 4556 - '@typescript-eslint/types': 6.21.0 4557 - '@typescript-eslint/visitor-keys': 6.21.0 4464 + '@typescript-eslint/types': 8.57.1 4465 + '@typescript-eslint/visitor-keys': 8.57.1 4558 4466 4559 - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': 4467 + '@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)': 4560 4468 dependencies: 4561 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) 4562 - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) 4469 + typescript: 5.9.3 4470 + 4471 + '@typescript-eslint/type-utils@8.57.1(eslint@10.1.0)(typescript@5.9.3)': 4472 + dependencies: 4473 + '@typescript-eslint/types': 8.57.1 4474 + '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) 4475 + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0)(typescript@5.9.3) 4563 4476 debug: 4.4.3(supports-color@8.1.1) 4564 - eslint: 8.57.1 4565 - ts-api-utils: 1.4.3(typescript@5.9.3) 4566 - optionalDependencies: 4477 + eslint: 10.1.0 4478 + ts-api-utils: 2.5.0(typescript@5.9.3) 4567 4479 typescript: 5.9.3 4568 4480 transitivePeerDependencies: 4569 4481 - supports-color 4570 4482 4571 - '@typescript-eslint/types@6.21.0': {} 4483 + '@typescript-eslint/types@8.57.1': {} 4572 4484 4573 - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': 4485 + '@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3)': 4574 4486 dependencies: 4575 - '@typescript-eslint/types': 6.21.0 4576 - '@typescript-eslint/visitor-keys': 6.21.0 4487 + '@typescript-eslint/project-service': 8.57.1(typescript@5.9.3) 4488 + '@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3) 4489 + '@typescript-eslint/types': 8.57.1 4490 + '@typescript-eslint/visitor-keys': 8.57.1 4577 4491 debug: 4.4.3(supports-color@8.1.1) 4578 - globby: 11.1.0 4579 - is-glob: 4.0.3 4580 - minimatch: 9.0.3 4492 + minimatch: 10.2.4 4581 4493 semver: 7.7.4 4582 - ts-api-utils: 1.4.3(typescript@5.9.3) 4583 - optionalDependencies: 4494 + tinyglobby: 0.2.15 4495 + ts-api-utils: 2.5.0(typescript@5.9.3) 4584 4496 typescript: 5.9.3 4585 4497 transitivePeerDependencies: 4586 4498 - supports-color 4587 4499 4588 - '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': 4500 + '@typescript-eslint/utils@8.57.1(eslint@10.1.0)(typescript@5.9.3)': 4589 4501 dependencies: 4590 - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) 4591 - '@types/json-schema': 7.0.15 4592 - '@types/semver': 7.7.1 4593 - '@typescript-eslint/scope-manager': 6.21.0 4594 - '@typescript-eslint/types': 6.21.0 4595 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) 4596 - eslint: 8.57.1 4597 - semver: 7.7.4 4502 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0) 4503 + '@typescript-eslint/scope-manager': 8.57.1 4504 + '@typescript-eslint/types': 8.57.1 4505 + '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) 4506 + eslint: 10.1.0 4507 + typescript: 5.9.3 4598 4508 transitivePeerDependencies: 4599 4509 - supports-color 4600 - - typescript 4601 4510 4602 - '@typescript-eslint/visitor-keys@6.21.0': 4511 + '@typescript-eslint/visitor-keys@8.57.1': 4603 4512 dependencies: 4604 - '@typescript-eslint/types': 6.21.0 4605 - eslint-visitor-keys: 3.4.3 4606 - 4607 - '@ungap/structured-clone@1.3.0': {} 4513 + '@typescript-eslint/types': 8.57.1 4514 + eslint-visitor-keys: 5.0.1 4608 4515 4609 4516 '@vitejs/plugin-legacy@5.4.3(terser@5.46.1)(vite@5.4.21(@types/node@25.5.0)(terser@5.46.1))': 4610 4517 dependencies: ··· 4721 4628 dependencies: 4722 4629 rfdc: 1.4.1 4723 4630 4724 - '@vue/eslint-config-typescript@12.0.0(eslint-plugin-vue@9.33.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.9.3)': 4725 - dependencies: 4726 - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) 4727 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) 4728 - eslint: 8.57.1 4729 - eslint-plugin-vue: 9.33.0(eslint@8.57.1) 4730 - vue-eslint-parser: 9.4.3(eslint@8.57.1) 4731 - optionalDependencies: 4732 - typescript: 5.9.3 4733 - transitivePeerDependencies: 4734 - - supports-color 4735 - 4736 4631 '@vue/language-core@2.2.12(typescript@5.9.3)': 4737 4632 dependencies: 4738 4633 '@volar/language-core': 2.4.15 ··· 4831 4726 4832 4727 arch@2.2.0: {} 4833 4728 4834 - argparse@2.0.1: {} 4835 - 4836 - array-union@2.1.0: {} 4837 - 4838 4729 asn1@0.2.6: 4839 4730 dependencies: 4840 4731 safer-buffer: 2.1.2 ··· 4905 4796 dependencies: 4906 4797 big-integer: 1.6.52 4907 4798 4908 - brace-expansion@1.1.12: 4909 - dependencies: 4910 - balanced-match: 1.0.2 4911 - concat-map: 0.0.1 4912 - 4913 4799 brace-expansion@2.0.2: 4914 4800 dependencies: 4915 4801 balanced-match: 1.0.2 ··· 4918 4804 dependencies: 4919 4805 balanced-match: 4.0.4 4920 4806 4921 - braces@3.0.3: 4922 - dependencies: 4923 - fill-range: 7.1.1 4924 - 4925 4807 browserslist-to-esbuild@2.1.1(browserslist@4.28.1): 4926 4808 dependencies: 4927 4809 browserslist: 4.28.1 ··· 4957 4839 dependencies: 4958 4840 call-bind-apply-helpers: 1.0.2 4959 4841 get-intrinsic: 1.3.0 4960 - 4961 - callsites@3.1.0: {} 4962 4842 4963 4843 caniuse-lite@1.0.30001780: {} 4964 4844 ··· 5027 4907 commander@6.2.1: {} 5028 4908 5029 4909 common-tags@1.8.2: {} 5030 - 5031 - concat-map@0.0.1: {} 5032 4910 5033 4911 confbox@0.1.8: {} 5034 4912 ··· 5151 5029 5152 5030 diff-sequences@29.6.3: {} 5153 5031 5154 - dir-glob@3.0.1: 5155 - dependencies: 5156 - path-type: 4.0.0 5157 - 5158 - doctrine@3.0.0: 5159 - dependencies: 5160 - esutils: 2.0.3 5161 - 5162 5032 domexception@4.0.0: 5163 5033 dependencies: 5164 5034 webidl-conversions: 7.0.0 ··· 5255 5125 5256 5126 escape-string-regexp@4.0.0: {} 5257 5127 5258 - eslint-plugin-vue@9.33.0(eslint@8.57.1): 5128 + eslint-plugin-vue@10.8.0(@typescript-eslint/parser@8.57.1(eslint@10.1.0)(typescript@5.9.3))(eslint@10.1.0)(vue-eslint-parser@10.4.0(eslint@10.1.0)): 5259 5129 dependencies: 5260 - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) 5261 - eslint: 8.57.1 5262 - globals: 13.24.0 5130 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0) 5131 + eslint: 10.1.0 5263 5132 natural-compare: 1.4.0 5264 5133 nth-check: 2.1.1 5265 - postcss-selector-parser: 6.1.2 5134 + postcss-selector-parser: 7.1.1 5266 5135 semver: 7.7.4 5267 - vue-eslint-parser: 9.4.3(eslint@8.57.1) 5136 + vue-eslint-parser: 10.4.0(eslint@10.1.0) 5268 5137 xml-name-validator: 4.0.0 5269 - transitivePeerDependencies: 5270 - - supports-color 5138 + optionalDependencies: 5139 + '@typescript-eslint/parser': 8.57.1(eslint@10.1.0)(typescript@5.9.3) 5271 5140 5272 - eslint-scope@7.2.2: 5141 + eslint-scope@9.1.2: 5273 5142 dependencies: 5143 + '@types/esrecurse': 4.3.1 5144 + '@types/estree': 1.0.8 5274 5145 esrecurse: 4.3.0 5275 5146 estraverse: 5.3.0 5276 5147 5277 5148 eslint-visitor-keys@3.4.3: {} 5278 5149 5279 - eslint@8.57.1: 5150 + eslint-visitor-keys@5.0.1: {} 5151 + 5152 + eslint@10.1.0: 5280 5153 dependencies: 5281 - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) 5154 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0) 5282 5155 '@eslint-community/regexpp': 4.12.2 5283 - '@eslint/eslintrc': 2.1.4 5284 - '@eslint/js': 8.57.1 5285 - '@humanwhocodes/config-array': 0.13.0 5156 + '@eslint/config-array': 0.23.3 5157 + '@eslint/config-helpers': 0.5.3 5158 + '@eslint/core': 1.1.1 5159 + '@eslint/plugin-kit': 0.6.1 5160 + '@humanfs/node': 0.16.7 5286 5161 '@humanwhocodes/module-importer': 1.0.1 5287 - '@nodelib/fs.walk': 1.2.8 5288 - '@ungap/structured-clone': 1.3.0 5162 + '@humanwhocodes/retry': 0.4.3 5163 + '@types/estree': 1.0.8 5289 5164 ajv: 6.14.0 5290 - chalk: 4.1.2 5291 5165 cross-spawn: 7.0.6 5292 5166 debug: 4.4.3(supports-color@8.1.1) 5293 - doctrine: 3.0.0 5294 5167 escape-string-regexp: 4.0.0 5295 - eslint-scope: 7.2.2 5296 - eslint-visitor-keys: 3.4.3 5297 - espree: 9.6.1 5168 + eslint-scope: 9.1.2 5169 + eslint-visitor-keys: 5.0.1 5170 + espree: 11.2.0 5298 5171 esquery: 1.7.0 5299 5172 esutils: 2.0.3 5300 5173 fast-deep-equal: 3.1.3 5301 - file-entry-cache: 6.0.1 5174 + file-entry-cache: 8.0.0 5302 5175 find-up: 5.0.0 5303 5176 glob-parent: 6.0.2 5304 - globals: 13.24.0 5305 - graphemer: 1.4.0 5306 5177 ignore: 5.3.2 5307 5178 imurmurhash: 0.1.4 5308 5179 is-glob: 4.0.3 5309 - is-path-inside: 3.0.3 5310 - js-yaml: 4.1.1 5311 5180 json-stable-stringify-without-jsonify: 1.0.1 5312 - levn: 0.4.1 5313 - lodash.merge: 4.6.2 5314 - minimatch: 3.1.5 5181 + minimatch: 10.2.4 5315 5182 natural-compare: 1.4.0 5316 5183 optionator: 0.9.4 5317 - strip-ansi: 6.0.1 5318 - text-table: 0.2.0 5319 5184 transitivePeerDependencies: 5320 5185 - supports-color 5321 5186 5322 5187 esm-env@1.2.2: {} 5323 5188 5324 - espree@9.6.1: 5189 + espree@11.2.0: 5325 5190 dependencies: 5326 5191 acorn: 8.16.0 5327 5192 acorn-jsx: 5.3.2(acorn@8.16.0) 5328 - eslint-visitor-keys: 3.4.3 5193 + eslint-visitor-keys: 5.0.1 5329 5194 5330 5195 esquery@1.7.0: 5331 5196 dependencies: ··· 5375 5240 5376 5241 fast-deep-equal@3.1.3: {} 5377 5242 5378 - fast-glob@3.3.3: 5379 - dependencies: 5380 - '@nodelib/fs.stat': 2.0.5 5381 - '@nodelib/fs.walk': 1.2.8 5382 - glob-parent: 5.1.2 5383 - merge2: 1.4.1 5384 - micromatch: 4.0.8 5385 - 5386 5243 fast-json-stable-stringify@2.1.0: {} 5387 5244 5388 5245 fast-levenshtein@2.0.6: {} 5389 5246 5390 - fastq@1.20.1: 5391 - dependencies: 5392 - reusify: 1.1.0 5393 - 5394 5247 fd-slicer@1.1.0: 5395 5248 dependencies: 5396 5249 pend: 1.2.0 5250 + 5251 + fdir@6.5.0(picomatch@4.0.3): 5252 + optionalDependencies: 5253 + picomatch: 4.0.3 5397 5254 5398 5255 figures@3.2.0: 5399 5256 dependencies: 5400 5257 escape-string-regexp: 1.0.5 5401 5258 5402 - file-entry-cache@6.0.1: 5259 + file-entry-cache@8.0.0: 5403 5260 dependencies: 5404 - flat-cache: 3.2.0 5405 - 5406 - fill-range@7.1.1: 5407 - dependencies: 5408 - to-regex-range: 5.0.1 5261 + flat-cache: 4.0.1 5409 5262 5410 5263 find-up@5.0.0: 5411 5264 dependencies: 5412 5265 locate-path: 6.0.0 5413 5266 path-exists: 4.0.0 5414 5267 5415 - flat-cache@3.2.0: 5268 + flat-cache@4.0.1: 5416 5269 dependencies: 5417 5270 flatted: 3.4.2 5418 5271 keyv: 4.5.4 5419 - rimraf: 3.0.2 5420 5272 5421 5273 flatted@3.4.2: {} 5422 5274 ··· 5448 5300 jsonfile: 6.2.0 5449 5301 universalify: 2.0.1 5450 5302 5451 - fs.realpath@1.0.0: {} 5452 - 5453 5303 fsevents@2.3.3: 5454 5304 optional: true 5455 5305 ··· 5489 5339 dependencies: 5490 5340 assert-plus: 1.0.0 5491 5341 5492 - glob-parent@5.1.2: 5493 - dependencies: 5494 - is-glob: 4.0.3 5495 - 5496 5342 glob-parent@6.0.2: 5497 5343 dependencies: 5498 5344 is-glob: 4.0.3 ··· 5512 5358 minipass: 7.1.3 5513 5359 path-scurry: 2.0.2 5514 5360 5515 - glob@7.2.3: 5516 - dependencies: 5517 - fs.realpath: 1.0.0 5518 - inflight: 1.0.6 5519 - inherits: 2.0.4 5520 - minimatch: 3.1.5 5521 - once: 1.4.0 5522 - path-is-absolute: 1.0.1 5523 - 5524 5361 global-dirs@3.0.1: 5525 5362 dependencies: 5526 5363 ini: 2.0.0 5527 5364 5528 - globals@13.24.0: 5529 - dependencies: 5530 - type-fest: 0.20.2 5531 - 5532 - globby@11.1.0: 5533 - dependencies: 5534 - array-union: 2.1.0 5535 - dir-glob: 3.0.1 5536 - fast-glob: 3.3.3 5537 - ignore: 5.3.2 5538 - merge2: 1.4.1 5539 - slash: 3.0.0 5365 + globals@17.4.0: {} 5540 5366 5541 5367 gopd@1.2.0: {} 5542 5368 5543 5369 graceful-fs@4.2.11: {} 5544 - 5545 - graphemer@1.4.0: {} 5546 5370 5547 5371 has-flag@4.0.0: {} 5548 5372 ··· 5597 5421 5598 5422 ignore@5.3.2: {} 5599 5423 5600 - import-fresh@3.3.1: 5601 - dependencies: 5602 - parent-module: 1.0.1 5603 - resolve-from: 4.0.0 5424 + ignore@7.0.5: {} 5604 5425 5605 5426 imurmurhash@0.1.4: {} 5606 5427 5607 5428 indent-string@4.0.0: {} 5608 5429 5609 - inflight@1.0.6: 5610 - dependencies: 5611 - once: 1.4.0 5612 - wrappy: 1.0.2 5613 - 5614 5430 inherits@2.0.4: {} 5615 5431 5616 5432 ini@1.3.8: {} ··· 5645 5461 dependencies: 5646 5462 global-dirs: 3.0.1 5647 5463 is-path-inside: 3.0.3 5648 - 5649 - is-number@7.0.0: {} 5650 5464 5651 5465 is-path-inside@3.0.3: {} 5652 5466 ··· 5686 5500 5687 5501 js-tokens@4.0.0: {} 5688 5502 5689 - js-yaml@4.1.1: 5690 - dependencies: 5691 - argparse: 2.0.1 5692 - 5693 5503 jsbn@0.1.1: {} 5694 5504 5695 5505 jsdom@22.1.0: ··· 5785 5595 5786 5596 lodash.debounce@4.0.8: {} 5787 5597 5788 - lodash.merge@4.6.2: {} 5789 - 5790 5598 lodash.once@4.1.1: {} 5791 5599 5792 5600 lodash@4.17.23: {} ··· 5825 5633 5826 5634 merge-stream@2.0.0: {} 5827 5635 5828 - merge2@1.4.1: {} 5829 - 5830 - micromatch@4.0.8: 5831 - dependencies: 5832 - braces: 3.0.3 5833 - picomatch: 2.3.1 5834 - 5835 5636 mime-db@1.52.0: {} 5836 5637 5837 5638 mime-types@2.1.35: ··· 5843 5644 minimatch@10.2.4: 5844 5645 dependencies: 5845 5646 brace-expansion: 5.0.4 5846 - 5847 - minimatch@3.1.5: 5848 - dependencies: 5849 - brace-expansion: 1.1.12 5850 - 5851 - minimatch@9.0.3: 5852 - dependencies: 5853 - brace-expansion: 2.0.2 5854 5647 5855 5648 minimatch@9.0.9: 5856 5649 dependencies: ··· 5958 5751 5959 5752 package-json-from-dist@1.0.1: {} 5960 5753 5961 - parent-module@1.0.1: 5962 - dependencies: 5963 - callsites: 3.1.0 5964 - 5965 5754 parse5@7.3.0: 5966 5755 dependencies: 5967 5756 entities: 6.0.1 ··· 5969 5758 path-browserify@1.0.1: {} 5970 5759 5971 5760 path-exists@4.0.0: {} 5972 - 5973 - path-is-absolute@1.0.1: {} 5974 5761 5975 5762 path-key@3.1.1: {} 5976 5763 ··· 5986 5773 lru-cache: 11.2.7 5987 5774 minipass: 7.1.3 5988 5775 5989 - path-type@4.0.0: {} 5990 - 5991 5776 pathe@1.1.2: {} 5992 5777 5993 5778 pathe@2.0.3: {} ··· 6002 5787 6003 5788 picocolors@1.1.1: {} 6004 5789 6005 - picomatch@2.3.1: {} 5790 + picomatch@4.0.3: {} 6006 5791 6007 5792 pify@2.3.0: {} 6008 5793 ··· 6025 5810 base64-js: 1.5.1 6026 5811 xmlbuilder: 15.1.1 6027 5812 6028 - postcss-selector-parser@6.1.2: 5813 + postcss-selector-parser@7.1.1: 6029 5814 dependencies: 6030 5815 cssesc: 3.0.0 6031 5816 util-deprecate: 1.0.2 ··· 6076 5861 6077 5862 querystringify@2.2.0: {} 6078 5863 6079 - queue-microtask@1.2.3: {} 6080 - 6081 5864 react-is@18.3.1: {} 6082 5865 6083 5866 readable-stream@3.6.2: ··· 6117 5900 6118 5901 requires-port@1.0.0: {} 6119 5902 6120 - resolve-from@4.0.0: {} 6121 - 6122 5903 resolve@1.22.11: 6123 5904 dependencies: 6124 5905 is-core-module: 2.16.1 ··· 6130 5911 onetime: 5.1.2 6131 5912 signal-exit: 3.0.7 6132 5913 6133 - reusify@1.1.0: {} 6134 - 6135 5914 rfdc@1.4.1: {} 6136 5915 6137 - rimraf@3.0.2: 6138 - dependencies: 6139 - glob: 7.2.3 6140 - 6141 5916 rimraf@6.1.3: 6142 5917 dependencies: 6143 5918 glob: 13.0.6 ··· 6175 5950 fsevents: 2.3.3 6176 5951 6177 5952 rrweb-cssom@0.6.0: {} 6178 - 6179 - run-parallel@1.2.0: 6180 - dependencies: 6181 - queue-microtask: 1.2.3 6182 5953 6183 5954 rxjs@7.8.2: 6184 5955 dependencies: ··· 6242 6013 6243 6014 sisteransi@1.0.5: {} 6244 6015 6245 - slash@3.0.0: {} 6246 - 6247 6016 slice-ansi@3.0.0: 6248 6017 dependencies: 6249 6018 ansi-styles: 4.3.0 ··· 6311 6080 6312 6081 strip-final-newline@2.0.0: {} 6313 6082 6314 - strip-json-comments@3.1.1: {} 6315 - 6316 6083 strip-literal@1.3.0: 6317 6084 dependencies: 6318 6085 acorn: 8.16.0 ··· 6350 6117 commander: 2.20.3 6351 6118 source-map-support: 0.5.21 6352 6119 6353 - text-table@0.2.0: {} 6354 - 6355 6120 throttleit@1.0.1: {} 6356 6121 6357 6122 through2@4.0.2: ··· 6362 6127 6363 6128 tinybench@2.9.0: {} 6364 6129 6130 + tinyglobby@0.2.15: 6131 + dependencies: 6132 + fdir: 6.5.0(picomatch@4.0.3) 6133 + picomatch: 4.0.3 6134 + 6365 6135 tinypool@0.7.0: {} 6366 6136 6367 6137 tinyspy@2.2.1: {} ··· 6374 6144 6375 6145 tmp@0.2.5: {} 6376 6146 6377 - to-regex-range@5.0.1: 6378 - dependencies: 6379 - is-number: 7.0.0 6380 - 6381 6147 tough-cookie@4.1.4: 6382 6148 dependencies: 6383 6149 psl: 1.15.0 ··· 6395 6161 6396 6162 tree-kill@1.2.2: {} 6397 6163 6398 - ts-api-utils@1.4.3(typescript@5.9.3): 6164 + ts-api-utils@2.5.0(typescript@5.9.3): 6399 6165 dependencies: 6400 6166 typescript: 5.9.3 6401 6167 ··· 6413 6179 6414 6180 type-detect@4.1.0: {} 6415 6181 6416 - type-fest@0.20.2: {} 6417 - 6418 6182 type-fest@0.21.3: {} 6183 + 6184 + typescript-eslint@8.57.1(eslint@10.1.0)(typescript@5.9.3): 6185 + dependencies: 6186 + '@typescript-eslint/eslint-plugin': 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.1.0)(typescript@5.9.3))(eslint@10.1.0)(typescript@5.9.3) 6187 + '@typescript-eslint/parser': 8.57.1(eslint@10.1.0)(typescript@5.9.3) 6188 + '@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3) 6189 + '@typescript-eslint/utils': 8.57.1(eslint@10.1.0)(typescript@5.9.3) 6190 + eslint: 10.1.0 6191 + typescript: 5.9.3 6192 + transitivePeerDependencies: 6193 + - supports-color 6419 6194 6420 6195 typescript@5.9.3: {} 6421 6196 ··· 6542 6317 dependencies: 6543 6318 vue: 3.5.30(typescript@5.9.3) 6544 6319 6545 - vue-eslint-parser@9.4.3(eslint@8.57.1): 6320 + vue-eslint-parser@10.4.0(eslint@10.1.0): 6546 6321 dependencies: 6547 6322 debug: 4.4.3(supports-color@8.1.1) 6548 - eslint: 8.57.1 6549 - eslint-scope: 7.2.2 6550 - eslint-visitor-keys: 3.4.3 6551 - espree: 9.6.1 6323 + eslint: 10.1.0 6324 + eslint-scope: 9.1.2 6325 + eslint-visitor-keys: 5.0.1 6326 + espree: 11.2.0 6552 6327 esquery: 1.7.0 6553 - lodash: 4.17.23 6554 6328 semver: 7.7.4 6555 6329 transitivePeerDependencies: 6556 6330 - supports-color
+8 -2
src/components/common/ActivityCard.vue
··· 6 6 7 7 <ion-label class="activity-label"> 8 8 <div class="activity-text"> 9 - <span class="actor">{{ item.actorHandle }}</span> 9 + <button class="actor" type="button" @click.stop="emit('actorClick')">{{ item.actorHandle }}</button> 10 10 <span class="verb"> {{ config.verb }} </span> 11 11 <span v-if="item.targetName" class="target">{{ item.targetName }}</span> 12 12 </div> ··· 30 30 import type { ActivityItem } from "@/domain/models/activity.js"; 31 31 32 32 const props = defineProps<{ item: ActivityItem }>(); 33 - const emit = defineEmits<{ click: [] }>(); 33 + const emit = defineEmits<{ click: []; actorClick: [] }>(); 34 34 35 35 type KindConfig = { icon: string; color: string; dimColor: string; verb: string }; 36 36 ··· 108 108 } 109 109 110 110 .actor { 111 + appearance: none; 112 + background: transparent; 113 + border: 0; 114 + padding: 0; 115 + margin: 0; 116 + cursor: pointer; 111 117 font-family: var(--t-mono); 112 118 font-size: 12px; 113 119 font-weight: 600;
+9 -2
src/components/common/RepoCard.vue
··· 2 2 <ion-card class="repo-card" button @click="emit('click')"> 3 3 <ion-card-content class="card-body"> 4 4 <div class="repo-header"> 5 - <span class="repo-owner">{{ repo.ownerHandle }}/</span><span class="repo-name">{{ repo.name }}</span> 5 + <button class="repo-owner" type="button" @click.stop="emit('ownerClick')">{{ repo.ownerHandle }}/</button> 6 + <span class="repo-name">{{ repo.name }}</span> 6 7 <div v-if="repo.stars != null" class="stars"> 7 8 <ion-icon :icon="starOutline" class="star-icon" /> 8 9 <span class="star-count">{{ formatCount(repo.stars) }}</span> ··· 29 30 import type { RepoSummary } from "@/domain/models/repo"; 30 31 31 32 defineProps<{ repo: RepoSummary }>(); 32 - const emit = defineEmits<{ click: [] }>(); 33 + const emit = defineEmits<{ click: []; ownerClick: [] }>(); 33 34 34 35 const LANG_COLORS: Record<string, string> = { 35 36 TypeScript: "#3178c6", ··· 88 89 } 89 90 90 91 .repo-owner { 92 + appearance: none; 93 + background: transparent; 94 + border: 0; 95 + padding: 0; 96 + margin: 0; 97 + cursor: pointer; 91 98 font-family: var(--t-mono); 92 99 font-size: 13px; 93 100 color: var(--t-text-secondary);
+5
src/domain/models/follow.ts
··· 1 + import type { UserSummary } from "./user.js"; 2 + 3 + export type FollowSummary = { atUri: string; subjectDid: string; createdAt: string }; 4 + 5 + export type FollowedUserSummary = UserSummary & { followAtUri: string; followedAt: string };
+8
src/domain/models/string.ts
··· 1 + export type StringSummary = { 2 + atUri: string; 3 + rkey: string; 4 + filename: string; 5 + description?: string; 6 + contents: string; 7 + createdAt: string; 8 + };
+5 -96
src/features/activity/ActivityPage.vue
··· 13 13 </ion-toolbar> 14 14 </ion-header> 15 15 16 - <!-- Filter chips --> 17 - <div class="filters-wrap"> 18 - <ion-chip 19 - v-for="f in FILTERS" 20 - :key="f.value" 21 - class="filter-chip" 22 - :class="{ active: activeFilter === f.value }" 23 - @click="activeFilter = f.value"> 24 - {{ f.label }} 25 - </ion-chip> 26 - </div> 27 - 28 - <!-- Activity list --> 29 - <ion-list lines="inset"> 30 - <template v-if="loading"> 31 - <SkeletonLoader v-for="n in 6" :key="n" variant="list-item" /> 32 - </template> 33 - <template v-else-if="filteredActivity.length"> 34 - <ActivityCard v-for="item in filteredActivity" :key="item.id" :item="item" /> 35 - </template> 36 - <template v-else> 37 - <EmptyState :icon="pulseOutline" title="No activity" message="Nothing here yet for this filter." /> 38 - </template> 39 - </ion-list> 16 + <EmptyState 17 + :icon="pulseOutline" 18 + title="Activity is in progress" 19 + message="The public activity feed is being rebuilt separately. This tab stays as a placeholder until that work is ready." /> 40 20 </ion-content> 41 21 </ion-page> 42 22 </template> 43 23 44 24 <script setup lang="ts"> 45 - import { ref, computed, onMounted } from "vue"; 46 - import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonList, IonChip } from "@ionic/vue"; 25 + import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent } from "@ionic/vue"; 47 26 import { pulseOutline } from "ionicons/icons"; 48 - import ActivityCard from "@/components/common/ActivityCard.vue"; 49 - import SkeletonLoader from "@/components/common/SkeletonLoader.vue"; 50 27 import EmptyState from "@/components/common/EmptyState.vue"; 51 - import { getMockActivity } from "@/mocks/activity"; 52 - import type { ActivityItem } from "@/domain/models/activity"; 53 - 54 - const loading = ref(true); 55 - const allActivity = getMockActivity(); 56 - const activeFilter = ref<"all" | "repos" | "prs" | "issues" | "people">("all"); 57 - 58 - const FILTERS = [ 59 - { value: "all", label: "All" }, 60 - { value: "repos", label: "Repos" }, 61 - { value: "prs", label: "PRs" }, 62 - { value: "issues", label: "Issues" }, 63 - { value: "people", label: "People" }, 64 - ] as const; 65 - 66 - const KIND_GROUPS: Record<string, ActivityItem["kind"][]> = { 67 - repos: ["repo_created", "repo_starred"], 68 - prs: ["pr_opened", "pr_merged"], 69 - issues: ["issue_opened", "issue_closed"], 70 - people: ["user_followed"], 71 - }; 72 - 73 - const filteredActivity = computed(() => { 74 - if (activeFilter.value === "all") return allActivity; 75 - const kinds = KIND_GROUPS[activeFilter.value] ?? []; 76 - return allActivity.filter((a) => kinds.includes(a.kind)); 77 - }); 78 - 79 - onMounted(() => { 80 - setTimeout(() => { 81 - loading.value = false; 82 - }, 400); 83 - }); 84 28 </script> 85 - 86 - <style scoped> 87 - .filters-wrap { 88 - display: flex; 89 - gap: 6px; 90 - padding: 12px 16px 4px; 91 - overflow-x: auto; 92 - scrollbar-width: none; 93 - } 94 - 95 - .filters-wrap::-webkit-scrollbar { 96 - display: none; 97 - } 98 - 99 - .filter-chip { 100 - --background: var(--t-surface-raised); 101 - --color: var(--t-text-secondary); 102 - border: 1px solid var(--t-border); 103 - flex-shrink: 0; 104 - font-size: 13px; 105 - margin: 0; 106 - cursor: pointer; 107 - } 108 - 109 - .filter-chip.active { 110 - --background: var(--t-accent-dim); 111 - --color: var(--t-accent); 112 - border-color: var(--t-accent); 113 - } 114 - 115 - ion-list { 116 - background: transparent; 117 - padding: 0; 118 - } 119 - </style>
+12 -63
src/features/explore/ExplorePage.vue
··· 4 4 <ion-toolbar> 5 5 <ion-title>Explore</ion-title> 6 6 </ion-toolbar> 7 - <ion-toolbar> 8 - <ion-searchbar placeholder="Search repos and users…" :disabled="true" class="search-bar" /> 9 - </ion-toolbar> 10 - <ion-toolbar> 11 - <ion-segment v-model="tab" class="explore-segment"> 12 - <ion-segment-button value="repos">Repos</ion-segment-button> 13 - <ion-segment-button value="users">Users</ion-segment-button> 14 - </ion-segment> 15 - </ion-toolbar> 16 7 </ion-header> 17 8 18 9 <ion-content :fullscreen="true"> 19 - <template v-if="loading"> 20 - <SkeletonLoader v-for="n in 4" :key="n" :variant="tab === 'repos' ? 'card' : 'list-item'" /> 21 - </template> 22 - 23 - <template v-else-if="tab === 'repos'"> 24 - <RepoCard v-for="repo in repos" :key="repo.atUri" :repo="repo" @click="navigateToRepo(repo)" /> 25 - </template> 10 + <ion-header collapse="condense"> 11 + <ion-toolbar> 12 + <ion-title size="large">Explore</ion-title> 13 + </ion-toolbar> 14 + </ion-header> 26 15 27 - <template v-else> 28 - <UserCard v-for="user in users" :key="user.did" :user="user" /> 29 - </template> 16 + <EmptyState 17 + :icon="searchOutline" 18 + title="Search is in progress" 19 + message="Global repo and user search is being built separately. For now, use Home to jump to a known handle and browse that account's repos." /> 30 20 </ion-content> 31 21 </ion-page> 32 22 </template> 33 23 34 24 <script setup lang="ts"> 35 - import { ref, onMounted } from "vue"; 36 - import { useRouter } from "vue-router"; 37 - import { 38 - IonPage, 39 - IonHeader, 40 - IonToolbar, 41 - IonTitle, 42 - IonContent, 43 - IonSearchbar, 44 - IonSegment, 45 - IonSegmentButton, 46 - } from "@ionic/vue"; 47 - import RepoCard from "@/components/common/RepoCard.vue"; 48 - import UserCard from "@/components/common/UserCard.vue"; 49 - import SkeletonLoader from "@/components/common/SkeletonLoader.vue"; 50 - import { getMockRepos } from "@/mocks/repos"; 51 - import { getMockUsers } from "@/mocks/users"; 52 - import type { RepoSummary } from "@/domain/models/repo"; 53 - 54 - const router = useRouter(); 55 - const tab = ref<"repos" | "users">("repos"); 56 - const loading = ref(true); 57 - const repos = getMockRepos(); 58 - const users = getMockUsers(); 59 - 60 - onMounted(() => { 61 - setTimeout(() => { 62 - loading.value = false; 63 - }, 400); 64 - }); 65 - 66 - function navigateToRepo(repo: RepoSummary) { 67 - router.push(`/tabs/explore/repo/${repo.ownerHandle}/${repo.name}`); 68 - } 25 + import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent } from "@ionic/vue"; 26 + import { searchOutline } from "ionicons/icons"; 27 + import EmptyState from "@/components/common/EmptyState.vue"; 69 28 </script> 70 - 71 - <style scoped> 72 - .search-bar { 73 - --background: var(--t-surface-raised); 74 - } 75 - 76 - .explore-segment { 77 - padding: 0 12px 6px; 78 - } 79 - </style>
+258 -39
src/features/home/HomePage.vue
··· 13 13 </ion-toolbar> 14 14 </ion-header> 15 15 16 - <!-- Trending Repos --> 17 - <div class="section"> 18 - <h2 class="section-title">Trending</h2> 19 - <template v-if="loading"> 16 + <section class="hero"> 17 + <p class="eyebrow">Known-handle browsing</p> 18 + <h1 class="hero-title">Jump straight to a Tangled profile or browse that handle's repos.</h1> 19 + <p class="hero-copy"> 20 + Enter an AT Protocol handle, then open the profile directly or resolve the user's Personal Data Server and 21 + browse their public repositories here. 22 + </p> 23 + </section> 24 + 25 + <section class="lookup-card"> 26 + <label class="field-label" for="handle-input">AT Protocol handle</label> 27 + <ion-input 28 + id="handle-input" 29 + v-model="draftHandle" 30 + class="handle-input" 31 + inputmode="email" 32 + autocomplete="off" 33 + autocapitalize="off" 34 + :spellcheck="false" 35 + clear-input 36 + placeholder="desertthunder.dev" 37 + @keydown.enter="openProfile" /> 38 + 39 + <div class="action-row"> 40 + <ion-button class="primary-action" expand="block" @click="openProfile" :disabled="!normalizedHandle"> 41 + Open Profile 42 + </ion-button> 43 + <ion-button fill="outline" expand="block" @click="browseRepos" :disabled="!normalizedHandle || isBrowsing"> 44 + Browse Repos 45 + </ion-button> 46 + </div> 47 + 48 + <p class="hint-copy">Repo browsing is temporary here until search ships in a separate project.</p> 49 + </section> 50 + 51 + <section v-if="hasAttemptedBrowse" class="results-section"> 52 + <template v-if="isLoading"> 53 + <SkeletonLoader variant="profile" /> 20 54 <SkeletonLoader v-for="n in 3" :key="n" variant="card" /> 21 55 </template> 22 - <template v-else> 23 - <RepoCard v-for="repo in trendingRepos" :key="repo.atUri" :repo="repo" @click="navigateToRepo(repo)" /> 56 + 57 + <EmptyState 58 + v-else-if="isError" 59 + :icon="alertCircleOutline" 60 + title="Could not resolve handle" 61 + :message="errorMessage" 62 + action-label="Try Again" 63 + @action="browseRepos" /> 64 + 65 + <template v-else-if="hasResolvedIdentity"> 66 + <div class="resolved-header"> 67 + <div class="resolved-copy"> 68 + <p class="resolved-label">Resolved account</p> 69 + <h2 class="resolved-title mono">{{ normalizedHandle }}</h2> 70 + <p class="resolved-meta"> 71 + <span>{{ displayName }}</span> 72 + <span class="meta-separator">·</span> 73 + <span>{{ repoCountLabel }}</span> 74 + </p> 75 + </div> 76 + <ion-button fill="outline" size="small" @click="openResolvedProfile">View Profile</ion-button> 77 + </div> 78 + 79 + <template v-if="repos.length"> 80 + <RepoCard 81 + v-for="repo in repos" 82 + :key="repo.atUri" 83 + :repo="repo" 84 + @click="navigateToRepo(repo)" 85 + @owner-click="openResolvedProfile" /> 86 + </template> 87 + <EmptyState 88 + v-else 89 + :icon="folderOpenOutline" 90 + title="No public repos yet" 91 + message="This handle resolved successfully, but there are no public Tangled repositories to browse yet." /> 24 92 </template> 25 - </div> 93 + </section> 26 94 27 - <!-- Recent Activity --> 28 - <div class="section"> 29 - <h2 class="section-title">Recent Activity</h2> 30 - <ion-list lines="inset"> 31 - <template v-if="loading"> 32 - <SkeletonLoader v-for="n in 5" :key="n" variant="list-item" /> 33 - </template> 34 - <template v-else> 35 - <ActivityCard v-for="item in activity" :key="item.id" :item="item" /> 36 - </template> 37 - </ion-list> 38 - </div> 95 + <section v-else class="results-section"> 96 + <EmptyState 97 + :icon="compassOutline" 98 + title="Browse by handle" 99 + message="Use Home as the temporary public entry point while search and activity are still in progress." /> 100 + </section> 39 101 </ion-content> 40 102 </ion-page> 41 103 </template> 42 104 43 105 <script setup lang="ts"> 44 - import { ref, onMounted } from "vue"; 106 + import { computed, ref } from "vue"; 45 107 import { useRouter } from "vue-router"; 46 - import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonList } from "@ionic/vue"; 108 + import { IonPage, IonHeader, IonToolbar, IonTitle, IonContent, IonInput, IonButton } from "@ionic/vue"; 109 + import { alertCircleOutline, compassOutline, folderOpenOutline } from "ionicons/icons"; 47 110 import RepoCard from "@/components/common/RepoCard.vue"; 48 - import ActivityCard from "@/components/common/ActivityCard.vue"; 49 111 import SkeletonLoader from "@/components/common/SkeletonLoader.vue"; 50 - import { getTrendingRepos } from "@/mocks/repos.js"; 51 - import { getMockActivity } from "@/mocks/activity.js"; 112 + import EmptyState from "@/components/common/EmptyState.vue"; 113 + import { useIdentity, useUserRepos, useActorProfile } from "@/services/tangled/queries.js"; 52 114 import type { RepoSummary } from "@/domain/models/repo.js"; 53 115 54 116 const router = useRouter(); 55 - const loading = ref(true); 56 - const trendingRepos = ref(getTrendingRepos()); 57 - const activity = ref(getMockActivity().slice(0, 8)); 117 + 118 + const draftHandle = ref(""); 119 + const activeHandle = ref(""); 120 + const hasAttemptedBrowse = ref(false); 121 + 122 + const normalizedHandle = computed(() => draftHandle.value.trim().toLowerCase()); 123 + const hasHandle = computed(() => normalizedHandle.value.length > 0); 124 + 125 + const identity = useIdentity(activeHandle, { enabled: computed(() => !!activeHandle.value) }); 126 + const did = computed(() => identity.data.value?.did ?? ""); 127 + const pds = computed(() => identity.data.value?.pds ?? ""); 128 + const hasResolvedIdentity = computed(() => !!identity.data.value); 129 + 130 + const profileQuery = useActorProfile(pds, did, activeHandle, undefined, { enabled: hasResolvedIdentity }); 131 + const reposQuery = useUserRepos(pds, did, activeHandle, { enabled: hasResolvedIdentity }); 58 132 59 - onMounted(() => { 60 - setTimeout(() => { 61 - loading.value = false; 62 - }, 400); 133 + const repos = computed(() => reposQuery.data.value ?? []); 134 + const displayName = computed(() => profileQuery.data.value?.displayName ?? "Public Tangled account"); 135 + const repoCountLabel = computed(() => `${repos.value.length} repo${repos.value.length === 1 ? "" : "s"}`); 136 + const isBrowsing = computed(() => hasAttemptedBrowse.value && activeHandle.value === normalizedHandle.value && isLoading.value); 137 + const isLoading = computed( 138 + () => 139 + hasAttemptedBrowse.value && 140 + activeHandle.value.length > 0 && 141 + (identity.isPending.value || (hasResolvedIdentity.value && reposQuery.isPending.value)), 142 + ); 143 + const isError = computed(() => hasAttemptedBrowse.value && (identity.isError.value || reposQuery.isError.value)); 144 + const errorMessage = computed(() => { 145 + const err = identity.error.value ?? reposQuery.error.value; 146 + return err instanceof Error ? err.message : "An unexpected error occurred while resolving this handle."; 63 147 }); 64 148 149 + function openProfile() { 150 + if (!hasHandle.value) return; 151 + router.push(`/tabs/home/user/${normalizedHandle.value}`); 152 + } 153 + 154 + function browseRepos() { 155 + if (!hasHandle.value) return; 156 + hasAttemptedBrowse.value = true; 157 + activeHandle.value = normalizedHandle.value; 158 + } 159 + 160 + function openResolvedProfile() { 161 + if (!activeHandle.value) return; 162 + router.push(`/tabs/home/user/${activeHandle.value}`); 163 + } 164 + 65 165 function navigateToRepo(repo: RepoSummary) { 66 166 router.push(`/tabs/home/repo/${repo.ownerHandle}/${repo.name}`); 67 167 } 68 168 </script> 69 169 70 170 <style scoped> 71 - .section { 72 - margin-top: 8px; 171 + .hero { 172 + padding: 24px 20px 12px; 173 + } 174 + 175 + .eyebrow { 176 + margin: 0 0 10px; 177 + font-size: 12px; 178 + font-weight: 700; 179 + letter-spacing: 0.08em; 180 + text-transform: uppercase; 181 + color: var(--t-accent); 73 182 } 74 183 75 - .section-title { 184 + .hero-title { 185 + margin: 0; 186 + font-size: 28px; 187 + line-height: 1.15; 188 + color: var(--t-text-primary); 189 + } 190 + 191 + .hero-copy { 192 + margin: 12px 0 0; 193 + font-size: 14px; 194 + line-height: 1.6; 195 + color: var(--t-text-secondary); 196 + max-width: 34rem; 197 + } 198 + 199 + .lookup-card { 200 + margin: 0 16px; 201 + padding: 18px 16px 16px; 202 + border: 1px solid var(--t-border); 203 + border-radius: var(--t-radius-lg); 204 + background: linear-gradient(180deg, var(--t-surface-raised), var(--t-surface)); 205 + } 206 + 207 + .field-label { 208 + display: block; 209 + margin-bottom: 8px; 76 210 font-size: 13px; 77 211 font-weight: 600; 78 - text-transform: uppercase; 212 + color: var(--t-text-primary); 213 + } 214 + 215 + .handle-input { 216 + --background: rgba(255, 255, 255, 0.04); 217 + --border-radius: var(--t-radius-md); 218 + --color: var(--t-text-primary); 219 + --padding-start: 14px; 220 + --padding-end: 14px; 221 + margin-bottom: 12px; 222 + border: 1px solid var(--t-border); 223 + border-radius: var(--t-radius-md); 224 + font-family: var(--t-mono); 225 + } 226 + 227 + .action-row { 228 + display: grid; 229 + grid-template-columns: repeat(2, minmax(0, 1fr)); 230 + gap: 10px; 231 + } 232 + 233 + .primary-action { 234 + --background: var(--t-accent); 235 + --background-activated: var(--t-accent); 236 + --color: #0d1117; 237 + } 238 + 239 + .hint-copy { 240 + margin: 12px 0 0; 241 + font-size: 12px; 242 + line-height: 1.5; 243 + color: var(--t-text-muted); 244 + } 245 + 246 + .results-section { 247 + padding: 18px 0 24px; 248 + } 249 + 250 + .resolved-header { 251 + display: flex; 252 + align-items: flex-start; 253 + justify-content: space-between; 254 + gap: 12px; 255 + margin: 0 16px 10px; 256 + padding: 16px; 257 + border: 1px solid var(--t-border); 258 + border-radius: var(--t-radius-md); 259 + background: var(--t-surface); 260 + } 261 + 262 + .resolved-copy { 263 + min-width: 0; 264 + } 265 + 266 + .resolved-label { 267 + margin: 0 0 6px; 268 + font-size: 12px; 269 + font-weight: 600; 79 270 letter-spacing: 0.06em; 271 + text-transform: uppercase; 80 272 color: var(--t-text-muted); 81 - margin: 16px 16px 6px; 273 + } 274 + 275 + .resolved-title { 276 + margin: 0; 277 + font-size: 15px; 278 + color: var(--t-text-primary); 279 + word-break: break-word; 280 + } 281 + 282 + .resolved-meta { 283 + margin: 8px 0 0; 284 + font-size: 13px; 285 + color: var(--t-text-secondary); 286 + } 287 + 288 + .meta-separator { 289 + margin: 0 6px; 290 + color: var(--t-text-muted); 82 291 } 83 292 84 - ion-list { 85 - background: transparent; 86 - padding: 0; 293 + @media (max-width: 480px) { 294 + .hero-title { 295 + font-size: 24px; 296 + } 297 + 298 + .action-row { 299 + grid-template-columns: 1fr; 300 + } 301 + 302 + .resolved-header { 303 + flex-direction: column; 304 + align-items: stretch; 305 + } 87 306 } 88 307 </style>
+185 -32
src/features/profile/UserProfilePage.vue
··· 10 10 </ion-header> 11 11 12 12 <ion-content :fullscreen="true"> 13 - <!-- Loading --> 14 13 <template v-if="isLoading"> 15 14 <SkeletonLoader variant="profile" /> 16 15 <SkeletonLoader v-for="n in 3" :key="n" variant="card" /> 17 16 </template> 18 17 19 - <!-- Error --> 20 18 <EmptyState 21 19 v-else-if="isError" 22 20 :icon="alertCircleOutline" 23 21 title="Could not load profile" 24 22 :message="errorMessage" /> 25 23 26 - <!-- Content --> 27 24 <template v-else> 28 - <!-- Profile header --> 29 25 <div class="profile-header"> 30 26 <ion-avatar class="avatar"> 31 27 <img ··· 46 42 47 43 <div v-if="profile?.bio" class="profile-bio">{{ profile.bio }}</div> 48 44 49 - <!-- Meta: location, pronouns --> 50 45 <div v-if="profile?.location || profile?.pronouns" class="profile-meta"> 51 46 <span v-if="profile.location" class="meta-item"> 52 47 <ion-icon :icon="locationOutline" class="meta-icon" /> ··· 58 53 </span> 59 54 </div> 60 55 61 - <!-- Links --> 62 56 <div v-if="profile?.links?.length" class="profile-links"> 63 57 <a 64 58 v-for="link in profile.links" ··· 72 66 </a> 73 67 </div> 74 68 75 - <!-- Pinned repos --> 76 - <template v-if="pinnedRepos.length"> 77 - <h3 class="section-label">Pinned</h3> 78 - <RepoCard v-for="repo in pinnedRepos" :key="repo.atUri" :repo="repo" @click="navigateToRepo(repo)" /> 69 + <div class="stats-row"> 70 + <div v-for="stat in stats" :key="stat.label" class="stat-pill"> 71 + <span class="stat-value">{{ stat.value }}</span> 72 + <span class="stat-label">{{ stat.label }}</span> 73 + </div> 74 + </div> 75 + 76 + <ion-segment v-model="section" class="profile-segment" scrollable> 77 + <ion-segment-button value="repos">Repos</ion-segment-button> 78 + <ion-segment-button value="strings">Strings</ion-segment-button> 79 + <ion-segment-button value="issues">Issues</ion-segment-button> 80 + <ion-segment-button value="prs">PRs</ion-segment-button> 81 + <ion-segment-button value="following">Following</ion-segment-button> 82 + </ion-segment> 83 + 84 + <template v-if="section === 'repos'"> 85 + <template v-if="pinnedRepos.length"> 86 + <h3 class="section-label">Pinned</h3> 87 + <RepoCard 88 + v-for="repo in pinnedRepos" 89 + :key="repo.atUri" 90 + :repo="repo" 91 + @click="navigateToRepo(repo)" 92 + @owner-click="navigateToUser(repo.ownerHandle)" /> 93 + </template> 94 + 95 + <h3 class="section-label">Repositories</h3> 96 + <template v-if="reposQuery.isPending.value"> 97 + <SkeletonLoader v-for="n in 3" :key="n" variant="card" /> 98 + </template> 99 + <template v-else-if="otherRepos.length"> 100 + <RepoCard 101 + v-for="repo in otherRepos" 102 + :key="repo.atUri" 103 + :repo="repo" 104 + @click="navigateToRepo(repo)" 105 + @owner-click="navigateToUser(repo.ownerHandle)" /> 106 + </template> 107 + <EmptyState 108 + v-else-if="!pinnedRepos.length" 109 + :icon="codeSlashOutline" 110 + title="No repositories" 111 + message="This user hasn't created any repositories yet." /> 79 112 </template> 80 113 81 - <!-- All repos --> 82 - <h3 class="section-label">Repositories</h3> 83 - <template v-if="reposQuery.isPending.value"> 84 - <SkeletonLoader v-for="n in 3" :key="n" variant="card" /> 85 - </template> 86 - <template v-else-if="repos.length"> 87 - <RepoCard v-for="repo in repos" :key="repo.atUri" :repo="repo" @click="navigateToRepo(repo)" /> 114 + <UserStrings v-else-if="section === 'strings'" :strings="strings" :is-loading="stringsQuery.isPending.value" /> 115 + 116 + <RepoIssues 117 + v-else-if="section === 'issues'" 118 + :issues="issues" 119 + :is-loading="issuesQuery.isPending.value" 120 + @select="navigateToIssue" /> 121 + 122 + <RepoPRs 123 + v-else-if="section === 'prs'" 124 + :prs="pullRequests" 125 + :is-loading="pullRequestsQuery.isPending.value" 126 + @select="navigateToPullRequest" /> 127 + 128 + <template v-else> 129 + <template v-if="followingQuery.isPending.value"> 130 + <SkeletonLoader v-for="n in 3" :key="n" variant="list-item" /> 131 + </template> 132 + <template v-else-if="following.length"> 133 + <UserCard 134 + v-for="user in following" 135 + :key="user.followAtUri" 136 + :user="user" 137 + @click="navigateToUser(user.handle)" /> 138 + </template> 139 + <EmptyState 140 + v-else 141 + :icon="peopleOutline" 142 + title="Not following anyone" 143 + message="This user isn't following any profiles yet." /> 88 144 </template> 89 - <EmptyState 90 - v-else 91 - :icon="codeSlashOutline" 92 - title="No repositories" 93 - message="This user hasn't created any repositories yet." /> 94 145 </template> 95 146 </ion-content> 96 147 </ion-page> 97 148 </template> 98 149 99 150 <script setup lang="ts"> 100 - import { ref, computed } from "vue"; 151 + import { ref, computed, watch } from "vue"; 101 152 import { useRoute, useRouter } from "vue-router"; 102 153 import { 103 154 IonPage, ··· 109 160 IonBackButton, 110 161 IonAvatar, 111 162 IonIcon, 163 + IonSegment, 164 + IonSegmentButton, 112 165 } from "@ionic/vue"; 113 - import { alertCircleOutline, locationOutline, personOutline, linkOutline, codeSlashOutline } from "ionicons/icons"; 166 + import { 167 + alertCircleOutline, 168 + locationOutline, 169 + personOutline, 170 + linkOutline, 171 + codeSlashOutline, 172 + peopleOutline, 173 + } from "ionicons/icons"; 114 174 import SkeletonLoader from "@/components/common/SkeletonLoader.vue"; 115 175 import EmptyState from "@/components/common/EmptyState.vue"; 116 176 import RepoCard from "@/components/common/RepoCard.vue"; 117 - import { useIdentity, useActorProfile, useUserRepos } from "@/services/tangled/queries.js"; 177 + import UserCard from "@/components/common/UserCard.vue"; 178 + import RepoIssues from "@/features/repo/RepoIssues.vue"; 179 + import RepoPRs from "@/features/repo/RepoPRs.vue"; 180 + import UserStrings from "@/features/profile/UserStrings.vue"; 181 + import { 182 + useIdentity, 183 + useActorProfile, 184 + useUserRepos, 185 + useUserStrings, 186 + useUserIssues, 187 + useUserPullRequests, 188 + useUserFollowing, 189 + } from "@/services/tangled/queries.js"; 190 + import { parseAtUri } from "@/services/tangled/uris.js"; 191 + import type { IssueSummary } from "@/domain/models/issue.js"; 192 + import type { PullRequestSummary } from "@/domain/models/pull-request.js"; 118 193 import type { RepoSummary } from "@/domain/models/repo.js"; 119 194 120 195 const route = useRoute(); 121 196 const router = useRouter(); 122 - const handle = route.params.handle as string; 197 + const handle = computed(() => String(route.params.handle ?? "")); 198 + const section = ref<"repos" | "strings" | "issues" | "prs" | "following">("repos"); 123 199 124 200 const avatarError = ref(false); 125 201 ··· 127 203 const did = computed(() => identity.data.value?.did ?? ""); 128 204 const pds = computed(() => identity.data.value?.pds ?? ""); 129 205 const hasIdentity = computed(() => !!identity.data.value); 206 + const tabPrefix = computed(() => { 207 + if (route.path.startsWith("/tabs/explore")) return "/tabs/explore"; 208 + if (route.path.startsWith("/tabs/activity")) return "/tabs/activity"; 209 + return "/tabs/home"; 210 + }); 130 211 131 212 const profileQuery = useActorProfile(pds, did, handle, undefined, { enabled: hasIdentity }); 132 213 const reposQuery = useUserRepos(pds, did, handle, { enabled: hasIdentity }); 214 + const stringsQuery = useUserStrings(pds, did, { enabled: hasIdentity }); 215 + const issuesQuery = useUserIssues(pds, did, handle, { enabled: hasIdentity }); 216 + const pullRequestsQuery = useUserPullRequests(pds, did, handle, { enabled: hasIdentity }); 217 + const followingQuery = useUserFollowing(pds, did, { enabled: hasIdentity }); 133 218 134 219 const profile = computed(() => profileQuery.data.value); 135 220 const repos = computed(() => reposQuery.data.value ?? []); 221 + const strings = computed(() => stringsQuery.data.value ?? []); 222 + const issues = computed(() => issuesQuery.data.value ?? []); 223 + const pullRequests = computed(() => pullRequestsQuery.data.value ?? []); 224 + const following = computed(() => followingQuery.data.value ?? []); 136 225 137 226 const pinnedUris = computed(() => (profile.value as { pinnedRepos?: string[] } | undefined)?.pinnedRepos ?? []); 138 227 const pinnedRepos = computed(() => repos.value.filter((r) => pinnedUris.value.includes(r.atUri))); 228 + const otherRepos = computed(() => repos.value.filter((repo) => !pinnedUris.value.includes(repo.atUri))); 229 + const stats = computed(() => [ 230 + { label: "repos", value: repos.value.length }, 231 + { label: "strings", value: strings.value.length }, 232 + { label: "issues", value: issues.value.length }, 233 + { label: "prs", value: pullRequests.value.length }, 234 + { label: "following", value: following.value.length }, 235 + ]); 139 236 140 237 const isLoading = computed(() => identity.isPending.value || profileQuery.isPending.value); 141 238 const isError = computed(() => identity.isError.value || profileQuery.isError.value); ··· 144 241 return err instanceof Error ? err.message : "An unexpected error occurred."; 145 242 }); 146 243 244 + watch(handle, () => { 245 + avatarError.value = false; 246 + section.value = "repos"; 247 + }); 248 + 147 249 function navigateToRepo(repo: RepoSummary) { 148 - const tabPrefix = route.path.startsWith("/tabs/explore") 149 - ? "/tabs/explore" 150 - : route.path.startsWith("/tabs/activity") 151 - ? "/tabs/activity" 152 - : "/tabs/home"; 153 - router.push(`${tabPrefix}/repo/${repo.ownerHandle}/${repo.name}`); 250 + router.push(`${tabPrefix.value}/repo/${repo.ownerHandle}/${repo.name}`); 251 + } 252 + 253 + function navigateToUser(profileHandle: string) { 254 + router.push(`${tabPrefix.value}/user/${profileHandle}`); 255 + } 256 + 257 + function navigateToIssue(issue: IssueSummary) { 258 + const repoName = parseAtUri(issue.repoAtUri)?.rkey; 259 + if (!repoName) return; 260 + router.push(`${tabPrefix.value}/repo/${handle.value}/${repoName}/issues/${issue.rkey}`); 261 + } 262 + 263 + function navigateToPullRequest(pullRequest: PullRequestSummary) { 264 + const repoName = parseAtUri(pullRequest.targetRepoAtUri)?.rkey; 265 + if (!repoName) return; 266 + router.push(`${tabPrefix.value}/repo/${handle.value}/${repoName}/pulls/${pullRequest.rkey}`); 154 267 } 155 268 156 269 function displayLink(url: string): string { ··· 285 398 letter-spacing: 0.07em; 286 399 color: var(--t-text-muted); 287 400 margin: 16px 16px 8px; 401 + } 402 + 403 + .stats-row { 404 + display: flex; 405 + gap: 8px; 406 + padding: 0 16px 16px; 407 + overflow-x: auto; 408 + scrollbar-width: none; 409 + } 410 + 411 + .stats-row::-webkit-scrollbar { 412 + display: none; 413 + } 414 + 415 + .stat-pill { 416 + display: inline-flex; 417 + align-items: baseline; 418 + gap: 6px; 419 + padding: 10px 12px; 420 + border-radius: 999px; 421 + border: 1px solid var(--t-border); 422 + background: var(--t-surface-raised); 423 + flex-shrink: 0; 424 + } 425 + 426 + .stat-value { 427 + font-family: var(--t-mono); 428 + font-size: 12px; 429 + font-weight: 700; 430 + color: var(--t-text-primary); 431 + } 432 + 433 + .stat-label { 434 + font-size: 12px; 435 + color: var(--t-text-muted); 436 + text-transform: lowercase; 437 + } 438 + 439 + .profile-segment { 440 + padding: 0 12px 8px; 288 441 } 289 442 </style>
+126
src/features/profile/UserStrings.vue
··· 1 + <template> 2 + <div class="strings-view"> 3 + <div v-if="isLoading" class="loading-center"> 4 + <ion-spinner name="crescent" /> 5 + </div> 6 + 7 + <template v-else> 8 + <ion-list lines="inset" class="string-list"> 9 + <ion-item v-for="stringItem in strings" :key="stringItem.atUri" class="string-item" lines="none"> 10 + <ion-label class="string-label"> 11 + <div class="string-head"> 12 + <span class="string-file mono">{{ stringItem.filename }}</span> 13 + <span class="string-time">{{ relativeTime(stringItem.createdAt) }}</span> 14 + </div> 15 + <p v-if="stringItem.description" class="string-description">{{ stringItem.description }}</p> 16 + <pre class="string-preview">{{ preview(stringItem.contents) }}</pre> 17 + </ion-label> 18 + </ion-item> 19 + </ion-list> 20 + 21 + <EmptyState 22 + v-if="!strings.length" 23 + :icon="documentTextOutline" 24 + title="No strings" 25 + message="This user hasn't published any strings yet." /> 26 + </template> 27 + </div> 28 + </template> 29 + 30 + <script setup lang="ts"> 31 + import { IonItem, IonLabel, IonList, IonSpinner } from "@ionic/vue"; 32 + import { documentTextOutline } from "ionicons/icons"; 33 + import EmptyState from "@/components/common/EmptyState.vue"; 34 + import type { StringSummary } from "@/domain/models/string.js"; 35 + 36 + defineProps<{ strings: StringSummary[]; isLoading?: boolean }>(); 37 + 38 + function preview(contents: string): string { 39 + return contents.length > 280 ? `${contents.slice(0, 280)}...` : contents; 40 + } 41 + 42 + function relativeTime(iso: string): string { 43 + const diff = Date.now() - new Date(iso).getTime(); 44 + const minutes = Math.floor(diff / 60000); 45 + const hours = Math.floor(minutes / 60); 46 + const days = Math.floor(hours / 24); 47 + if (days > 0) return `${days}d ago`; 48 + if (hours > 0) return `${hours}h ago`; 49 + if (minutes > 0) return `${minutes}m ago`; 50 + return "just now"; 51 + } 52 + </script> 53 + 54 + <style scoped> 55 + .strings-view { 56 + padding-bottom: 32px; 57 + } 58 + 59 + .loading-center { 60 + display: flex; 61 + justify-content: center; 62 + padding: 48px 0; 63 + } 64 + 65 + .string-list { 66 + background: transparent; 67 + padding: 0; 68 + } 69 + 70 + .string-item { 71 + --background: transparent; 72 + --padding-start: 16px; 73 + --padding-end: 16px; 74 + --inner-padding-end: 0; 75 + } 76 + 77 + .string-label { 78 + white-space: normal; 79 + padding: 10px 0 14px; 80 + } 81 + 82 + .string-head { 83 + display: flex; 84 + align-items: center; 85 + justify-content: space-between; 86 + gap: 12px; 87 + } 88 + 89 + .string-file { 90 + font-size: 12px; 91 + font-weight: 600; 92 + color: var(--t-accent); 93 + } 94 + 95 + .mono { 96 + font-family: var(--t-mono); 97 + } 98 + 99 + .string-time { 100 + font-size: 11px; 101 + color: var(--t-text-muted); 102 + flex-shrink: 0; 103 + } 104 + 105 + .string-description { 106 + margin: 6px 0 8px; 107 + font-size: 13px; 108 + line-height: 1.45; 109 + color: var(--t-text-secondary); 110 + } 111 + 112 + .string-preview { 113 + margin: 0; 114 + padding: 12px; 115 + border-radius: var(--t-radius-sm); 116 + border: 1px solid var(--t-border); 117 + background: var(--t-surface-raised); 118 + color: var(--t-text-primary); 119 + font-family: var(--t-mono); 120 + font-size: 12px; 121 + line-height: 1.45; 122 + overflow-x: auto; 123 + white-space: pre-wrap; 124 + word-break: break-word; 125 + } 126 + </style>
+49 -7
src/services/tangled/endpoints.ts
··· 34 34 ShTangledRepoPull, 35 35 ShTangledRepoPullComment, 36 36 ShTangledRepoPullStatus, 37 + ShTangledGraphFollow, 38 + ShTangledString, 37 39 } from "@atcute/tangled"; 38 40 import { throwOnXrpcError } from "@/services/atproto/client.js"; 39 41 import { MalformedResponseError } from "@/core/errors/tangled.js"; ··· 195 197 return data.did; 196 198 } 197 199 198 - type DidDocument = { service?: Array<{ id: string; type: string; serviceEndpoint: string }> }; 200 + type DidDocument = { alsoKnownAs?: string[]; service?: Array<{ id: string; type: string; serviceEndpoint: string }> }; 199 201 200 - /** 201 - * Fetch the DID document for a DID and extract the PDS service endpoint hostname. 202 - * Supports did:plc (via plc.directory) and did:web. 203 - */ 204 - export async function resolvePds(did: string): Promise<string> { 202 + async function fetchDidDocument(did: string): Promise<DidDocument> { 205 203 let docUrl: string; 206 204 if (did.startsWith("did:plc:")) { 207 205 docUrl = `https://plc.directory/${did}`; ··· 211 209 } else { 212 210 throw new MalformedResponseError("resolveHandle", `Unsupported DID method: ${did}`); 213 211 } 212 + 214 213 const res = await fetch(docUrl); 215 214 if (!res.ok) throwOnXrpcError(res.status, "ResolveFailed", `Could not fetch DID document: ${did}`); 216 - const doc = (await res.json()) as DidDocument; 215 + return (await res.json()) as DidDocument; 216 + } 217 + 218 + /** 219 + * Fetch the DID document for a DID and extract the PDS service endpoint hostname. 220 + * Supports did:plc (via plc.directory) and did:web. 221 + */ 222 + export async function resolvePds(did: string): Promise<string> { 223 + const doc = await fetchDidDocument(did); 217 224 const svc = doc.service?.find((s) => s.id === "#atproto_pds"); 218 225 if (!svc?.serviceEndpoint) { 219 226 throw new MalformedResponseError("resolvePds", `No PDS endpoint in DID document: ${did}`); 220 227 } 221 228 return new URL(svc.serviceEndpoint).hostname; 229 + } 230 + 231 + export async function resolveHandleFromDid(did: string): Promise<string> { 232 + if (did.startsWith("did:web:")) return did.slice("did:web:".length); 233 + 234 + const doc = await fetchDidDocument(did); 235 + const alias = doc.alsoKnownAs?.find((entry) => entry.startsWith("at://")); 236 + if (!alias) { 237 + throw new MalformedResponseError("resolveHandleFromDid", `No handle alias in DID document: ${did}`); 238 + } 239 + 240 + return alias.slice("at://".length); 241 + } 242 + 243 + export async function resolveDidIdentity(did: string): Promise<{ did: string; handle: string; pds: string }> { 244 + const [handle, pds] = await Promise.all([resolveHandleFromDid(did), resolvePds(did)]); 245 + return { did, handle, pds }; 222 246 } 223 247 224 248 type ListRecordsResponse<T> = { records: Array<{ uri: string; cid: string; value: T }>; cursor?: string }; ··· 301 325 cursor?: string, 302 326 ): Promise<ListRecordsResponse<ShTangledRepoPullComment.Main>> { 303 327 return listRecords<ShTangledRepoPullComment.Main>(pds, did, "sh.tangled.repo.pull.comment", limit, cursor); 328 + } 329 + 330 + export async function listFollowRecords( 331 + pds: string, 332 + did: string, 333 + limit = 100, 334 + cursor?: string, 335 + ): Promise<ListRecordsResponse<ShTangledGraphFollow.Main>> { 336 + return listRecords<ShTangledGraphFollow.Main>(pds, did, "sh.tangled.graph.follow", limit, cursor); 337 + } 338 + 339 + export async function listStringRecords( 340 + pds: string, 341 + did: string, 342 + limit = 100, 343 + cursor?: string, 344 + ): Promise<ListRecordsResponse<ShTangledString.Main>> { 345 + return listRecords<ShTangledString.Main>(pds, did, "sh.tangled.string", limit, cursor); 304 346 } 305 347 306 348 /**
+19
src/services/tangled/normalizers.ts
··· 14 14 ShTangledRepoIssueComment, 15 15 ShTangledRepoPull, 16 16 ShTangledRepoPullComment, 17 + ShTangledGraphFollow, 18 + ShTangledString, 17 19 } from "@atcute/tangled"; 18 20 import type { RepoFile, RepoSummary, RepoDetail } from "@/domain/models/repo.js"; 19 21 import type { UserSummary } from "@/domain/models/user.js"; 20 22 import type { IssueSummary, IssueDetail } from "@/domain/models/issue.js"; 21 23 import type { PullRequestSummary, PullRequestDetail } from "@/domain/models/pull-request.js"; 22 24 import type { IssueComment, PullRequestComment } from "@/domain/models/comment.js"; 25 + import type { FollowSummary } from "@/domain/models/follow.js"; 26 + import type { StringSummary } from "@/domain/models/string.js"; 23 27 import { getAtUriRkey } from "./uris.js"; 24 28 25 29 function modeToFileKind(mode: string): RepoFile["type"] { ··· 312 316 } 313 317 314 318 return leftTime - rightTime; 319 + } 320 + 321 + export function normalizeStringRecord(record: ShTangledString.Main, atUri: string): StringSummary { 322 + return { 323 + atUri, 324 + rkey: getAtUriRkey(atUri), 325 + filename: record.filename, 326 + description: record.description, 327 + contents: record.contents, 328 + createdAt: record.createdAt, 329 + }; 330 + } 331 + 332 + export function normalizeFollowRecord(record: ShTangledGraphFollow.Main, atUri: string): FollowSummary { 333 + return { atUri, subjectDid: record.subject, createdAt: record.createdAt }; 315 334 } 316 335 317 336 export function buildIssueCommentThread(comments: IssueComment[]): IssueComment[] {
+152 -1
src/services/tangled/queries.ts
··· 17 17 import { computed, toValue } from "vue"; 18 18 import type { MaybeRef } from "vue"; 19 19 import { getKnotClient } from "@/services/atproto/client.js"; 20 + import type { FollowedUserSummary } from "@/domain/models/follow.js"; 21 + import type { StringSummary } from "@/domain/models/string.js"; 20 22 import { 21 23 fetchRepoTree, 22 24 fetchRepoBlob, ··· 38 40 listPullRecords, 39 41 listPullCommentRecords, 40 42 listPullStatusRecords, 43 + listFollowRecords, 44 + listStringRecords, 41 45 resolveHandle, 46 + resolveDidIdentity, 42 47 resolvePds, 43 48 } from "./endpoints.js"; 44 49 import { ··· 58 63 normalizePullRecord, 59 64 normalizePullDetail, 60 65 normalizePullComment, 66 + normalizeFollowRecord, 67 + normalizeStringRecord, 61 68 } from "./normalizers.js"; 62 69 63 70 export type { CommitEntry, BranchEntry, BlobContent, DefaultBranchInfo } from "./normalizers.js"; ··· 71 78 * Resolve an AT Protocol handle to its DID and PDS hostname. 72 79 * Result is cached for 10 minutes (handles rarely change). 73 80 */ 74 - export function useIdentity(handle: MaybeRef<string>) { 81 + export function useIdentity(handle: MaybeRef<string>, options: { enabled?: MaybeRef<boolean> } = {}) { 75 82 return useQuery({ 76 83 queryKey: computed(() => ["identity", toValue(handle)]), 77 84 queryFn: async (): Promise<Identity> => { ··· 79 86 const pds = await resolvePds(did); 80 87 return { did, pds }; 81 88 }, 89 + enabled: options.enabled, 82 90 staleTime: 10 * MIN, 83 91 gcTime: 60 * MIN, 84 92 }); ··· 409 417 return pullsRes.records 410 418 .filter((r) => !targetRepo || r.value.target.repo === targetRepo) 411 419 .map((r) => normalizePullRecord(r.value, r.uri, toValue(did), toValue(handle), statusMap.get(r.uri) ?? "open")); 420 + }, 421 + enabled: options.enabled, 422 + staleTime: 2 * MIN, 423 + gcTime: 10 * MIN, 424 + }); 425 + } 426 + 427 + export function useUserStrings( 428 + pds: MaybeRef<string>, 429 + did: MaybeRef<string>, 430 + options: { enabled?: MaybeRef<boolean> } = {}, 431 + ) { 432 + return useQuery({ 433 + queryKey: computed(() => ["userStrings", toValue(pds), toValue(did)]), 434 + queryFn: async (): Promise<StringSummary[]> => { 435 + const { records } = await listStringRecords(toValue(pds), toValue(did)); 436 + return records 437 + .map((record) => normalizeStringRecord(record.value, record.uri)) 438 + .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt)); 439 + }, 440 + enabled: options.enabled, 441 + staleTime: 2 * MIN, 442 + gcTime: 10 * MIN, 443 + }); 444 + } 445 + 446 + export function useUserFollowing( 447 + pds: MaybeRef<string>, 448 + did: MaybeRef<string>, 449 + options: { enabled?: MaybeRef<boolean> } = {}, 450 + ) { 451 + return useQuery({ 452 + queryKey: computed(() => ["userFollowing", toValue(pds), toValue(did)]), 453 + queryFn: async (): Promise<FollowedUserSummary[]> => { 454 + const { records } = await listFollowRecords(toValue(pds), toValue(did)); 455 + const follows = records 456 + .map((record) => normalizeFollowRecord(record.value, record.uri)) 457 + .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt)); 458 + 459 + return Promise.all( 460 + follows.map(async (follow) => { 461 + const subject = await resolveDidIdentity(follow.subjectDid); 462 + 463 + try { 464 + const { value } = await fetchActorProfile(subject.pds, subject.did); 465 + return { 466 + ...normalizeActorProfile(value, subject.did, subject.handle), 467 + followAtUri: follow.atUri, 468 + followedAt: follow.createdAt, 469 + }; 470 + } catch { 471 + return { 472 + did: subject.did, 473 + handle: subject.handle, 474 + avatar: `https://avatar.tangled.sh/${subject.did}`, 475 + followAtUri: follow.atUri, 476 + followedAt: follow.createdAt, 477 + }; 478 + } 479 + }), 480 + ); 481 + }, 482 + enabled: options.enabled, 483 + staleTime: 5 * MIN, 484 + gcTime: 30 * MIN, 485 + }); 486 + } 487 + 488 + export function useUserIssues( 489 + pds: MaybeRef<string>, 490 + did: MaybeRef<string>, 491 + handle: MaybeRef<string>, 492 + options: { enabled?: MaybeRef<boolean> } = {}, 493 + ) { 494 + return useQuery({ 495 + queryKey: computed(() => ["userIssues", toValue(pds), toValue(did)]), 496 + queryFn: async () => { 497 + const [issuesRes, statesRes] = await Promise.all([ 498 + listIssueRecords(toValue(pds), toValue(did)), 499 + listIssueStateRecords(toValue(pds), toValue(did)), 500 + ]); 501 + 502 + const stateMap = new Map<string, "open" | "closed">(); 503 + for (const stateRecord of statesRes.records) { 504 + const closed = stateRecord.value.state === "sh.tangled.repo.issue.state.closed"; 505 + stateMap.set(stateRecord.value.issue, closed ? "closed" : "open"); 506 + } 507 + 508 + return issuesRes.records 509 + .map((record) => 510 + normalizeIssueRecord( 511 + record.value, 512 + record.uri, 513 + toValue(did), 514 + toValue(handle), 515 + stateMap.get(record.uri) ?? "open", 516 + ), 517 + ) 518 + .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt)); 519 + }, 520 + enabled: options.enabled, 521 + staleTime: 2 * MIN, 522 + gcTime: 10 * MIN, 523 + }); 524 + } 525 + 526 + export function useUserPullRequests( 527 + pds: MaybeRef<string>, 528 + did: MaybeRef<string>, 529 + handle: MaybeRef<string>, 530 + options: { enabled?: MaybeRef<boolean> } = {}, 531 + ) { 532 + return useQuery({ 533 + queryKey: computed(() => ["userPullRequests", toValue(pds), toValue(did)]), 534 + queryFn: async () => { 535 + const [pullsRes, statusesRes] = await Promise.all([ 536 + listPullRecords(toValue(pds), toValue(did)), 537 + listPullStatusRecords(toValue(pds), toValue(did)), 538 + ]); 539 + 540 + const statusMap = new Map<string, "open" | "merged" | "closed">(); 541 + for (const statusRecord of statusesRes.records) { 542 + const raw = statusRecord.value.status ?? "sh.tangled.repo.pull.status.open"; 543 + const status = 544 + raw === "sh.tangled.repo.pull.status.merged" 545 + ? "merged" 546 + : raw === "sh.tangled.repo.pull.status.closed" 547 + ? "closed" 548 + : "open"; 549 + statusMap.set(statusRecord.value.pull, status); 550 + } 551 + 552 + return pullsRes.records 553 + .map((record) => 554 + normalizePullRecord( 555 + record.value, 556 + record.uri, 557 + toValue(did), 558 + toValue(handle), 559 + statusMap.get(record.uri) ?? "open", 560 + ), 561 + ) 562 + .sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt)); 412 563 }, 413 564 enabled: options.enabled, 414 565 staleTime: 2 * MIN,