This repository has no description
0

Configure Feed

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

web/api: add comment and issue crud ops

author
oppiliappan
committer
dawn
date (Jul 31, 2026, 10:54 PM +0300) commit d7317bd8 parent b419c838 change-id vkzzmkqz
+140
+2
web/src/app.css
··· 66 66 --color-background-info-subtle: #eef2ff; 67 67 --color-background-info-subtle-hover: #e0e7ff; 68 68 --color-background-info-emphasis: #4f46e5; 69 + --color-background-neutral-emphasis: #1f2937; 69 70 70 71 /* --- foreground --- */ 71 72 --color-foreground-default: #111827; ··· 251 252 --color-background-info-subtle: #312e81; 252 253 --color-background-info-subtle-hover: #3730a3; 253 254 --color-background-info-emphasis: #4f46e5; 255 + --color-background-neutral-emphasis: #374151; 254 256 255 257 /* --- foreground (Dark) --- */ 256 258 --color-foreground-default: #f3f4f6;
+37
web/src/lib/api/comment.ts
··· 1 + import { ok } from "@atcute/client"; 2 + import { mainSchema as putRecordSchema } from "@atcute/atproto/types/repo/putRecord"; 3 + import { mainSchema as deleteRecordSchema } from "@atcute/atproto/types/repo/deleteRecord"; 4 + import type { Nsid, RecordKey } from "@atcute/lexicons/syntax"; 5 + import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; 6 + import { createClient } from "$lib/auth/agent"; 7 + import type { CommentRecord, RecordView } from "./records"; 8 + 9 + const COMMENT_COLLECTION = "sh.tangled.feed.comment" as Nsid; 10 + 11 + export const putComment = async ( 12 + agent: OAuthUserAgent, 13 + rkey: string, 14 + record: CommentRecord 15 + ): Promise<RecordView<CommentRecord>> => { 16 + const rpc = createClient(agent); 17 + const { uri, cid } = await ok( 18 + rpc.call(putRecordSchema, { 19 + input: { 20 + repo: agent.sub, 21 + collection: COMMENT_COLLECTION, 22 + rkey: rkey as RecordKey, 23 + record 24 + } 25 + }) 26 + ); 27 + return { uri, cid, value: record }; 28 + }; 29 + 30 + export const deleteComment = async (agent: OAuthUserAgent, rkey: string): Promise<void> => { 31 + const rpc = createClient(agent); 32 + await ok( 33 + rpc.call(deleteRecordSchema, { 34 + input: { repo: agent.sub, collection: COMMENT_COLLECTION, rkey: rkey as RecordKey } 35 + }) 36 + ); 37 + };
+37
web/src/lib/api/issue.ts
··· 1 + import { ok } from "@atcute/client"; 2 + import { mainSchema as putRecordSchema } from "@atcute/atproto/types/repo/putRecord"; 3 + import { mainSchema as deleteRecordSchema } from "@atcute/atproto/types/repo/deleteRecord"; 4 + import type { Nsid, RecordKey } from "@atcute/lexicons/syntax"; 5 + import type { OAuthUserAgent } from "@atcute/oauth-browser-client"; 6 + import { createClient } from "$lib/auth/agent"; 7 + import type { IssueRecord, RecordView } from "./records"; 8 + 9 + const ISSUE_COLLECTION = "sh.tangled.repo.issue" as Nsid; 10 + 11 + export const putIssue = async ( 12 + agent: OAuthUserAgent, 13 + rkey: string, 14 + record: IssueRecord 15 + ): Promise<RecordView<IssueRecord>> => { 16 + const rpc = createClient(agent); 17 + const { uri, cid } = await ok( 18 + rpc.call(putRecordSchema, { 19 + input: { 20 + repo: agent.sub, 21 + collection: ISSUE_COLLECTION, 22 + rkey: rkey as RecordKey, 23 + record 24 + } 25 + }) 26 + ); 27 + return { uri, cid, value: record }; 28 + }; 29 + 30 + export const deleteIssue = async (agent: OAuthUserAgent, rkey: string): Promise<void> => { 31 + const rpc = createClient(agent); 32 + await ok( 33 + rpc.call(deleteRecordSchema, { 34 + input: { repo: agent.sub, collection: ISSUE_COLLECTION, rkey: rkey as RecordKey } 35 + }) 36 + ); 37 + };
+64
web/src/lib/api/records.ts
··· 1 1 import type { BobbinContext, XrpcRequestInit } from "./client"; 2 2 import { jsonGet } from "./_request"; 3 3 import type * as ShTangledActorProfile from "./lexicons/types/sh/tangled/actor/profile"; 4 + import type * as ShTangledFeedComment from "./lexicons/types/sh/tangled/feed/comment"; 4 5 import type * as ShTangledRepo from "./lexicons/types/sh/tangled/repo"; 5 6 import type * as ShTangledRepoIssue from "./lexicons/types/sh/tangled/repo/issue"; 7 + import type * as ShTangledRepoIssueState from "./lexicons/types/sh/tangled/repo/issue/state"; 6 8 import type * as ShTangledRepoPull from "./lexicons/types/sh/tangled/repo/pull"; 7 9 import type * as ShTangledString from "./lexicons/types/sh/tangled/string"; 8 10 ··· 19 21 export type RepoRecord = ShTangledRepo.Main; 20 22 export type ProfileRecord = ShTangledActorProfile.Main; 21 23 export type IssueRecord = ShTangledRepoIssue.Main; 24 + export type IssueStateRecord = ShTangledRepoIssueState.Main; 22 25 export type PullRecord = ShTangledRepoPull.Main; 23 26 export type StringRecord = ShTangledString.Main; 27 + export type CommentRecord = ShTangledFeedComment.Main; 24 28 25 29 export const BULK_LIMIT = 50; 26 30 ··· 47 51 48 52 export const getIssue = (ctx: BobbinContext, issue: string, init?: XrpcRequestInit) => 49 53 jsonGet<RecordView<IssueRecord>>(ctx, "sh.tangled.repo.getIssue", { issue }, init); 54 + 55 + export type IssueState = "open" | "closed"; 56 + 57 + // a listIssues row: the embedded issue record plus bobbin-derived state/counts. 58 + export interface IssueListItem { 59 + cid?: string; 60 + commentCount: number; 61 + state: string; 62 + stateUpdatedAt?: string; 63 + uri: string; 64 + value: IssueRecord; 65 + } 66 + 67 + export interface IssueListPage { 68 + cursor?: string | null; 69 + items: IssueListItem[]; 70 + } 71 + 72 + export interface ListIssuesFilter { 73 + state?: IssueState; 74 + author?: string; 75 + limit?: number; 76 + cursor?: string; 77 + order?: "asc" | "desc"; 78 + } 79 + 80 + export const listIssues = ( 81 + ctx: BobbinContext, 82 + subject: string, 83 + filter: ListIssuesFilter = {}, 84 + init?: XrpcRequestInit 85 + ) => jsonGet<IssueListPage>(ctx, "sh.tangled.repo.listIssues", { subject, ...filter }, init); 86 + 87 + // getIssue returns the raw record only; the derived open/closed state lives in 88 + // separate sh.tangled.repo.issue.state records, latest-wins. 89 + export interface IssueStateList { 90 + cursor?: string | null; 91 + items: RecordView<IssueStateRecord>[]; 92 + } 93 + 94 + export const listIssueStates = ( 95 + ctx: BobbinContext, 96 + subject: string, 97 + filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {}, 98 + init?: XrpcRequestInit 99 + ) => jsonGet<IssueStateList>(ctx, "sh.tangled.repo.issue.listStates", { subject, ...filter }, init); 100 + 101 + // comments on any record (issue/pull/string) are flat sh.tangled.feed.comment records; 102 + // threading is derived client-side from each comment's optional replyTo strongRef. 103 + export interface CommentListPage { 104 + cursor?: string | null; 105 + items: RecordView<CommentRecord>[]; 106 + } 107 + 108 + export const listComments = ( 109 + ctx: BobbinContext, 110 + subject: string, 111 + filter: { limit?: number; cursor?: string; order?: "asc" | "desc" } = {}, 112 + init?: XrpcRequestInit 113 + ) => jsonGet<CommentListPage>(ctx, "sh.tangled.feed.listComments", { subject, ...filter }, init); 50 114 51 115 export const getPull = (ctx: BobbinContext, pull: string, init?: XrpcRequestInit) => 52 116 jsonGet<RecordView<PullRecord>>(ctx, "sh.tangled.repo.getPull", { pull }, init);