+518
-732
api/tangled/cbor_gen.go
+518
-732
api/tangled/cbor_gen.go
···-if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("commitCount"))); err != nil {-if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("isDefaultRef"))); err != nil {-if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("langBreakdown"))); err != nil {························-func (t *GitRefUpdate_Meta_CommitCount_ByEmail_Elem) UnmarshalCBOR(r io.Reader) (err error) {···-return fmt.Errorf("GitRefUpdate_Meta_CommitCount_ByEmail_Elem: map struct too large (%d)", extra)+return fmt.Errorf("GitRefUpdate_IndividualEmailCommitCount: map struct too large (%d)", extra)·································+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("commitCount"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("isDefaultRef"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("langBreakdown"))); err != nil {···+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.knot"))); err != nil {··········································-if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("targetRepo"))); err != nil {-if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.TargetRepo))); err != nil {-if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("targetBranch"))); err != nil {-if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.TargetBranch))); err != nil {······························
+19
-15
api/tangled/gitrefUpdate.go
+19
-15
api/tangled/gitrefUpdate.go
···-LangBreakdown *GitRefUpdate_Meta_LangBreakdown `json:"langBreakdown,omitempty" cborgen:"langBreakdown,omitempty"`+// GitRefUpdate_CommitCountBreakdown is a "commitCountBreakdown" in the sh.tangled.git.refUpdate schema.+ByEmail []*GitRefUpdate_IndividualEmailCommitCount `json:"byEmail,omitempty" cborgen:"byEmail,omitempty"`-ByEmail []*GitRefUpdate_Meta_CommitCount_ByEmail_Elem `json:"byEmail,omitempty" cborgen:"byEmail,omitempty"`+// GitRefUpdate_IndividualEmailCommitCount is a "individualEmailCommitCount" in the sh.tangled.git.refUpdate schema.+// GitRefUpdate_IndividualLanguageSize is a "individualLanguageSize" in the sh.tangled.git.refUpdate schema.+Inputs []*GitRefUpdate_IndividualLanguageSize `json:"inputs,omitempty" cborgen:"inputs,omitempty"`+LangBreakdown *GitRefUpdate_LangBreakdown `json:"langBreakdown,omitempty" cborgen:"langBreakdown,omitempty"`
+1
-3
api/tangled/issuecomment.go
+1
-3
api/tangled/issuecomment.go
···LexiconTypeID string `json:"$type,const=sh.tangled.repo.issue.comment" cborgen:"$type,const=sh.tangled.repo.issue.comment"`
+53
api/tangled/knotlistKeys.go
+53
api/tangled/knotlistKeys.go
···+func KnotListKeys(ctx context.Context, c util.LexClient, cursor string, limit int64) (*KnotListKeys_Output, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.knot.listKeys", params, nil, &out); err != nil {
+30
api/tangled/knotversion.go
+30
api/tangled/knotversion.go
···+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.knot.version", nil, nil, &out); err != nil {
+4
-7
api/tangled/pullcomment.go
+4
-7
api/tangled/pullcomment.go
···-LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull.comment" cborgen:"$type,const=sh.tangled.repo.pull.comment"`+LexiconTypeID string `json:"$type,const=sh.tangled.repo.pull.comment" cborgen:"$type,const=sh.tangled.repo.pull.comment"`
+41
api/tangled/repoarchive.go
+41
api/tangled/repoarchive.go
···+func RepoArchive(ctx context.Context, c util.LexClient, format string, prefix string, ref string, repo string) ([]byte, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.archive", params, nil, buf); err != nil {
+80
api/tangled/repoblob.go
+80
api/tangled/repoblob.go
···+func RepoBlob(ctx context.Context, c util.LexClient, path string, raw bool, ref string, repo string) (*RepoBlob_Output, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.blob", params, nil, &out); err != nil {
+59
api/tangled/repobranch.go
+59
api/tangled/repobranch.go
···+func RepoBranch(ctx context.Context, c util.LexClient, name string, repo string) (*RepoBranch_Output, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.branch", params, nil, &out); err != nil {
+39
api/tangled/repobranches.go
+39
api/tangled/repobranches.go
···+func RepoBranches(ctx context.Context, c util.LexClient, cursor string, limit int64, repo string) ([]byte, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.branches", params, nil, buf); err != nil {
+35
api/tangled/repocompare.go
+35
api/tangled/repocompare.go
···+func RepoCompare(ctx context.Context, c util.LexClient, repo string, rev1 string, rev2 string) ([]byte, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.compare", params, nil, buf); err != nil {
+34
api/tangled/repocreate.go
+34
api/tangled/repocreate.go
···+if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.repo.create", nil, input, nil); err != nil {
+34
api/tangled/repodelete.go
+34
api/tangled/repodelete.go
···+if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.repo.delete", nil, input, nil); err != nil {
+33
api/tangled/repodiff.go
+33
api/tangled/repodiff.go
···+func RepoDiff(ctx context.Context, c util.LexClient, ref string, repo string) ([]byte, error) {
+45
api/tangled/repoforkStatus.go
+45
api/tangled/repoforkStatus.go
···+func RepoForkStatus(ctx context.Context, c util.LexClient, input *RepoForkStatus_Input) (*RepoForkStatus_Output, error) {+if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.repo.forkStatus", nil, input, &out); err != nil {
+36
api/tangled/repoforkSync.go
+36
api/tangled/repoforkSync.go
···+if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.repo.forkSync", nil, input, nil); err != nil {
+55
api/tangled/repogetDefaultBranch.go
+55
api/tangled/repogetDefaultBranch.go
···+// RepoGetDefaultBranch_Signature is a "signature" in the sh.tangled.repo.getDefaultBranch schema.+func RepoGetDefaultBranch(ctx context.Context, c util.LexClient, repo string) (*RepoGetDefaultBranch_Output, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.getDefaultBranch", params, nil, &out); err != nil {
-2
api/tangled/repoissue.go
-2
api/tangled/repoissue.go
···LexiconTypeID string `json:"$type,const=sh.tangled.repo.issue" cborgen:"$type,const=sh.tangled.repo.issue"`
+61
api/tangled/repolanguages.go
+61
api/tangled/repolanguages.go
···+func RepoLanguages(ctx context.Context, c util.LexClient, ref string, repo string) (*RepoLanguages_Output, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.languages", params, nil, &out); err != nil {
+45
api/tangled/repolog.go
+45
api/tangled/repolog.go
···+func RepoLog(ctx context.Context, c util.LexClient, cursor string, limit int64, path string, ref string, repo string) ([]byte, error) {
+44
api/tangled/repomerge.go
+44
api/tangled/repomerge.go
···+if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.repo.merge", nil, input, nil); err != nil {
+57
api/tangled/repomergeCheck.go
+57
api/tangled/repomergeCheck.go
···+Conflicts []*RepoMergeCheck_ConflictInfo `json:"conflicts,omitempty" cborgen:"conflicts,omitempty"`+func RepoMergeCheck(ctx context.Context, c util.LexClient, input *RepoMergeCheck_Input) (*RepoMergeCheck_Output, error) {+if err := c.LexDo(ctx, util.Procedure, "application/json", "sh.tangled.repo.mergeCheck", nil, input, &out); err != nil {
+7
-3
api/tangled/repopull.go
+7
-3
api/tangled/repopull.go
······
+72
api/tangled/repotree.go
+72
api/tangled/repotree.go
···+Last_commit *RepoTree_LastCommit `json:"last_commit,omitempty" cborgen:"last_commit,omitempty"`+func RepoTree(ctx context.Context, c util.LexClient, path string, ref string, repo string) (*RepoTree_Output, error) {+if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.tree", params, nil, &out); err != nil {
+22
api/tangled/tangledknot.go
+22
api/tangled/tangledknot.go
···+LexiconTypeID string `json:"$type,const=sh.tangled.knot" cborgen:"$type,const=sh.tangled.knot"`
+30
api/tangled/tangledowner.go
+30
api/tangled/tangledowner.go
···
+4
-1
appview/config/config.go
+4
-1
appview/config/config.go
···
+198
appview/db/db.go
+198
appview/db/db.go
·········+runMigration(conn, "rename-registrations-read-only-to-needs-upgrade", func(tx *sql.Tx) error {+at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.repo.issue' || '/' || rkey) stored,+at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.repo.issue.comment' || '/' || rkey) stored,···
+145
-42
appview/db/follow.go
+145
-42
appview/db/follow.go
······-if err := rows.Scan(&follow.UserDid, &follow.SubjectDid, &followedAt, &follow.Rkey); err != nil {···
+454
-311
appview/db/issues.go
+454
-311
appview/db/issues.go
······-`, issue.RepoAt, issue.OwnerDid, issue.Rkey, issue.AtUri(), issue.IssueId, issue.Title, issue.Body)-err := e.QueryRow(`select issue_at from issues where repo_at = ? and issue_id = ?`, repoAt, issueId).Scan(&issueAt)-err := e.QueryRow(`select owner_did from issues where repo_at = ? and issue_id = ?`, repoAt, issueId).Scan(&ownerDid)+return syntax.ATURI(fmt.Sprintf("at://%s/%s/%s", i.Did, tangled.RepoIssueCommentNSID, i.Rkey))-func GetIssues(e Execer, repoAt syntax.ATURI, isOpen bool, page pagination.Page) ([]Issue, error) {+func IssueCommentFromRecord(e Execer, did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {-err := rows.Scan(&issue.ID, &issue.OwnerDid, &issue.Rkey, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open, &metadata.CommentCount)···-err := row.Scan(&issue.ID, &issue.OwnerDid, &issue.Rkey, &createdAt, &issue.Title, &issue.Body, &issue.Open)+err := row.Scan(&issue.Id, &issue.Did, &issue.Rkey, &createdAt, &issue.Title, &issue.Body, &issue.Open)···-func GetIssueWithComments(e Execer, repoAt syntax.ATURI, issueId int) (*Issue, []Comment, error) {-query := `select id, owner_did, rkey, issue_id, created, title, body, open from issues where repo_at = ? and issue_id = ?`-err := row.Scan(&issue.ID, &issue.OwnerDid, &issue.Rkey, &issue.IssueId, &createdAt, &issue.Title, &issue.Body, &issue.Open)+query := fmt.Sprintf(`update issue_comments set body = "", deleted = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)-query := `insert into comments (owner_did, repo_at, rkey, issue_id, comment_id, body) values (?, ?, ?, ?, ?, ?)`-err := rows.Scan(&comment.OwnerDid, &comment.Issue, &comment.CommentId, &rkey, &comment.Body, &createdAt, &editedAt, &deletedAt)-func EditComment(e Execer, repoAt syntax.ATURI, issueId, commentId int, newBody string) error {-_, err := e.Exec(`update issues set open = 0 where repo_at = ? and issue_id = ?`, repoAt, issueId)-_, err := e.Exec(`update issues set open = 1 where repo_at = ? and issue_id = ?`, repoAt, issueId)
+25
-12
appview/db/profile.go
+25
-12
appview/db/profile.go
·····················
+31
-11
appview/db/pulls.go
+31
-11
appview/db/pulls.go
·····················
+4
-4
appview/db/punchcard.go
+4
-4
appview/db/punchcard.go
·········
+94
-130
appview/db/registration.go
+94
-130
appview/db/registration.go
···-err = rows.Scan(®istration.Id, ®istration.Domain, ®istration.ByDid, &createdAt, ®isteredAt)-`, domain).Scan(®istration.Id, ®istration.Domain, ®istration.ByDid, &createdAt, ®isteredAt)-on conflict(domain) do update set did = excluded.did, secret = excluded.secret, created = excluded.created+query := "update registrations set registered = strftime('%Y-%m-%dT%H:%M:%SZ', 'now'), needs_upgrade = 0"
+27
-130
appview/db/repos.go
+27
-130
appview/db/repos.go
······-rows, &repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &repo.Description, &repo.Created, &repo.Source,···-err := rows.Scan(&repo.Did, &repo.Name, &repo.Knot, &repo.Rkey, &nullableDescription, &createdAt, &repoStats.StarCount, &nullableSource)······-func scanRepo(rows *sql.Rows, did, name, knot, rkey, description *string, created *time.Time, source *string) error {-if err := rows.Scan(did, name, knot, rkey, &nullableDescription, &createdAt, &nullableSource); err != nil {
+14
-7
appview/db/spindle.go
+14
-7
appview/db/spindle.go
··················-query := fmt.Sprintf(`update spindles set verified = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now') %s`, whereClause)+query := fmt.Sprintf(`update spindles set verified = strftime('%%Y-%%m-%%dT%%H:%%M:%%SZ', 'now'), needs_upgrade = 0 %s`, whereClause)
+99
-3
appview/db/star.go
+99
-3
appview/db/star.go
···············
+25
-9
appview/db/strings.go
+25
-9
appview/db/strings.go
·········
+17
-35
appview/db/timeline.go
+17
-35
appview/db/timeline.go
···············
+295
-8
appview/ingester.go
+295
-8
appview/ingester.go
···························+l := i.Logger.With("handler", "ingestIssue", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)+l := i.Logger.With("handler", "ingestIssueComment", "nsid", e.Commit.Collection, "did", did, "rkey", rkey)
+477
-286
appview/issues/issues.go
+477
-286
appview/issues/issues.go
························+ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoIssueNSID, user.Did, newIssue.Rkey)············-rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issueIdInt, commentId))+rp.pages.HxLocation(w, fmt.Sprintf("/%s/issues/%d#comment-%d", f.OwnerSlashRepo(), issue.IssueId, commentId))+l.Error("incorrect number of comments returned", "id", commentId, "len(comments)", len(comments))+l.Error("incorrect number of comments returned", "id", commentId, "len(comments)", len(comments))·········-ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoIssueCommentNSID, user.Did, rkey)+ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoIssueCommentNSID, user.Did, comment.Rkey)+log.Println("failed to get record", "err", err, "did", newComment.Did, "rkey", newComment.Rkey)rp.pages.Notice(w, fmt.Sprintf("comment-%s-status", commentId), "Failed to update description, no record found on PDS.")+l.Error("incorrect number of comments returned", "id", commentId, "len(comments)", len(comments))+l.Error("incorrect number of comments returned", "id", commentId, "len(comments)", len(comments))+l.Error("incorrect number of comments returned", "id", commentId, "len(comments)", len(comments))··················
+24
-10
appview/issues/router.go
+24
-10
appview/issues/router.go
···
+416
-218
appview/knots/knots.go
+416
-218
appview/knots/knots.go
············-k.Pages.Notice(w, noticeId, fmt.Sprintf("Received status %d from knot, expected %d", resp.StatusCode, http.StatusNoContent))-l.Error("incorrect statuscode returned", "statuscode", resp.StatusCode, "expected", http.StatusNoContent)-k.Pages.Notice(w, noticeId, "Response signature mismatch, consider regenerating the secret and retrying.")+k.Pages.Notice(w, noticeId, "Failed to verify knot, XRPC queries are unsupported on this knot, consider upgrading!")-k.Pages.Notice(w, noticeId, fmt.Sprintf("Unexpected status code from knotserver %d, expected %d", ksResp.StatusCode, http.StatusNoContent))
+43
-3
appview/middleware/middleware.go
+43
-3
appview/middleware/middleware.go
············
+107
-84
appview/oauth/handler/handler.go
+107
-84
appview/oauth/handler/handler.go
············sessionReq, err := http.NewRequestWithContext(context.Background(), "POST", sessionURL, bytes.NewBuffer(sessionBytes))req, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(payloadBytes))
+3
appview/oauth/oauth.go
+3
appview/oauth/oauth.go
+35
appview/pages/cache.go
+35
appview/pages/cache.go
···
+16
appview/pages/funcmap.go
+16
appview/pages/funcmap.go
······
+12
appview/pages/markup/format.go
+12
appview/pages/markup/format.go
···
+12
-8
appview/pages/markup/markdown.go
+12
-8
appview/pages/markup/markdown.go
············
+17
appview/pages/markup/sanitizer.go
+17
appview/pages/markup/sanitizer.go
···
+333
-206
appview/pages/pages.go
+333
-206
appview/pages/pages.go
············-err := filepath.WalkDir(filepath.Join(p.templateDir, "templates"), func(path string, d fs.DirEntry, err error) error {-func (p *Pages) executeOrReload(templateName string, w io.Writer, base string, params any) error {······························func (p *Pages) EditIssueCommentFragment(w io.Writer, params EditIssueCommentParams) error {-func (p *Pages) SingleIssueCommentFragment(w io.Writer, params SingleIssueCommentParams) error {+func (p *Pages) ReplyIssueCommentPlaceholderFragment(w io.Writer, params ReplyIssueCommentPlaceholderParams) error {+func (p *Pages) ReplyIssueCommentFragment(w io.Writer, params ReplyIssueCommentParams) error {············
+2
-7
appview/pages/repoinfo/repoinfo.go
+2
-7
appview/pages/repoinfo/repoinfo.go
···
+24
-4
appview/pages/templates/errors/404.html
+24
-4
appview/pages/templates/errors/404.html
···+<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">+The page you're looking for doesn't exist. It may have been moved, deleted, or you have the wrong URL.
+35
-2
appview/pages/templates/errors/500.html
+35
-2
appview/pages/templates/errors/500.html
···+<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">+<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded p-3 text-sm text-yellow-800 dark:text-yellow-200">
+28
-5
appview/pages/templates/errors/503.html
+28
-5
appview/pages/templates/errors/503.html
···+<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-blue-100 dark:bg-blue-900/30 flex items-center justify-center">+We were unable to reach the knot hosting this repository. The service may be temporarily unavailable.
+28
appview/pages/templates/errors/knot404.html
+28
appview/pages/templates/errors/knot404.html
···+<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-orange-100 dark:bg-orange-900/30 flex items-center justify-center">+The repository you were looking for could not be found. The knot serving the repository may be unavailable.
+8
appview/pages/templates/fragments/logotype.html
+8
appview/pages/templates/fragments/logotype.html
+93
-28
appview/pages/templates/knots/dashboard.html
+93
-28
appview/pages/templates/knots/dashboard.html
···+<span class="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 {{$style}}">{{ i "shield-check" "w-4 h-4" }} verified</span>+<span class="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 {{$style}}">-<span class="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 {{$style}}">{{ i "shield-check" "w-4 h-4" }} verified</span>-<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} pending</span>+<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} unverified</span>-<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">+<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">······+class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"+class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
+6
-7
appview/pages/templates/knots/fragments/addMemberModal.html
+6
-7
appview/pages/templates/knots/fragments/addMemberModal.html
·········-<p class="text-sm text-gray-500 dark:text-gray-400">Members can create repositories on this knot.</p>+<p class="text-sm text-gray-500 dark:text-gray-400">Members can create repositories and run workflows on this knot.</p>···
+57
-25
appview/pages/templates/knots/fragments/knotListing.html
+57
-25
appview/pages/templates/knots/fragments/knotListing.html
···+<div id="knot-{{.Id}}" class="flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-700">+<a href="/knots/{{ .Domain }}" class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]">-<span class="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 {{$style}}">{{ i "shield-check" "w-4 h-4" }} verified</span>+<span class="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 {{$style}}">-<span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} pending</span>+class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"-class="btn dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 flex gap-2 items-center group"
-18
appview/pages/templates/knots/fragments/knotListingFull.html
-18
appview/pages/templates/knots/fragments/knotListingFull.html
···-<div class="flex items-center justify-center p-2 border-b border-gray-200 dark:border-gray-700 text-gray-500">
-10
appview/pages/templates/knots/fragments/secret.html
-10
appview/pages/templates/knots/fragments/secret.html
···-class="bg-gray-50 dark:bg-gray-700 border border-black dark:border-gray-500 rounded px-6 py-2 w-full lg:w-3xl">
+34
-17
appview/pages/templates/knots/index.html
+34
-17
appview/pages/templates/knots/index.html
···<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">-Knots are designed for either single or multi-tenant use which is perfect for self-hosting on a Raspberry Pi at home, or larger โcommunityโ servers.+<div class="flex items-center justify-center p-2 border-b border-gray-200 dark:border-gray-700 text-gray-500">······
+27
-12
appview/pages/templates/layouts/base.html
+27
-12
appview/pages/templates/layouts/base.html
···+<link rel="preload" href="/static/fonts/InterVariable.woff2" as="font" type="font/woff2" crossorigin />-<body class="min-h-screen grid grid-cols-1 grid-rows-[min-content_auto_min-content] md:grid-cols-12 gap-4 bg-slate-100 dark:bg-gray-900 dark:text-white transition-colors duration-200">+<body class="min-h-screen grid grid-cols-1 grid-rows-[min-content_auto_min-content] md:grid-cols-10 lg:grid-cols-12 gap-4 bg-slate-100 dark:bg-gray-900 dark:text-white transition-colors duration-200">+<header class="px-1 col-span-1 md:col-start-2 md:col-span-8 lg:col-start-3" style="z-index: 20;">···
-48
appview/pages/templates/layouts/footer.html
-48
appview/pages/templates/layouts/footer.html
···-<div class="flex flex-col lg:flex-row justify-between items-start text-gray-600 dark:text-gray-400 text-sm gap-8">-{{ $headerStyle := "text-gray-900 dark:text-gray-200 font-bold text-xs uppercase tracking-wide mb-1" }}-{{ $linkStyle := "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 hover:underline inline-flex gap-1 items-center" }}-<a href="https://blog.tangled.sh" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "book-open" $iconStyle }} blog</a>-<a href="https://tangled.sh/@tangled.sh/core/tree/master/docs" class="{{ $linkStyle }}">{{ i "book" $iconStyle }} docs</a>-<a href="https://tangled.sh/@tangled.sh/core" class="{{ $linkStyle }}">{{ i "code" $iconStyle }} source</a>-<a href="https://chat.tangled.sh" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "message-circle" $iconStyle }} discord</a>-<a href="https://web.libera.chat/#tangled" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ i "hash" $iconStyle }} irc</a>-<a href="https://bsky.app/profile/tangled.sh" class="{{ $linkStyle }}" target="_blank" rel="noopener noreferrer">{{ template "user/fragments/bluesky" $iconStyle }} bluesky</a>-<a href="mailto:team@tangled.sh" class="{{ $linkStyle }}">{{ i "mail" "w-4 h-4 flex-shrink-0" }} team@tangled.sh</a>-<a href="mailto:security@tangled.sh" class="{{ $linkStyle }}">{{ i "shield-check" "w-4 h-4 flex-shrink-0" }} security@tangled.sh</a>
+78
appview/pages/templates/layouts/fragments/topbar.html
+78
appview/pages/templates/layouts/fragments/topbar.html
···+<nav class="space-x-4 px-6 py-2 rounded bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm">+<a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2">+<div class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700">+class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700"
+104
appview/pages/templates/layouts/profilebase.html
+104
appview/pages/templates/layouts/profilebase.html
···+<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}?tab={{ .Active }}" />+<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />+class="px-4 py-1 mr-1 text-black dark:text-white min-w-[80px] text-center relative rounded-t whitespace-nowrap+class="aspect-square rounded-full transition-all duration-300 {{ $theme }} max-w-full max-h-full"
+19
-17
appview/pages/templates/layouts/repobase.html
+19
-17
appview/pages/templates/layouts/repobase.html
·········
-80
appview/pages/templates/layouts/topbar.html
-80
appview/pages/templates/layouts/topbar.html
···-<nav class="space-x-4 px-6 py-2 rounded bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm">-<a href="/signup" class="btn-create py-0 hover:no-underline hover:text-white flex items-center gap-2">-<div class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700">-class="absolute flex flex-col right-0 mt-4 p-4 rounded w-48 bg-white dark:bg-gray-800 dark:text-white border border-gray-200 dark:border-gray-700"
+4
-126
appview/pages/templates/legal/privacy.html
+4
-126
appview/pages/templates/legal/privacy.html
···-<p>This Privacy Policy describes how Tangled ("we," "us," or "our") collects, uses, and shares your personal information when you use our platform and services (the "Service").</p>-<div class="bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4 my-6">-<li><strong>Personal Data Servers (PDS):</strong> Accounts hosted on Tangled PDS (*.tngl.sh) are located in Finland</li>-<li><strong>Application Data:</strong> All other service data is stored on EU-based servers</li>-<div class="bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4 my-6">-<strong>Important:</strong> If your account is hosted on Bluesky's PDS or other self-hosted Personal Data Servers (not *.tngl.sh), we do not control that data. The data protection, storage location, and privacy practices for such accounts are governed by the respective PDS provider's policies, not this Privacy Policy. We only control data processing within our own services and infrastructure.-<li><strong>Purpose:</strong> Sending transactional emails (account verification, notifications)</li>-<li><strong>Data Shared:</strong> Public images and associated metadata for caching purposes</li>-<p>We do not sell, trade, or rent your personal information. We may share your information only in the following circumstances:</p>-<li>In connection with a merger, acquisition, or sale of assets (with appropriate protections)</li>-<p>We implement appropriate technical and organizational measures to protect your personal information against unauthorized access, alteration, disclosure, or destruction. However, no method of transmission over the Internet is 100% secure.</p>-<p>We retain your personal information for as long as necessary to provide the Service and fulfill the purposes outlined in this Privacy Policy, unless a longer retention period is required by law.</p>-<p>The Service is not intended for children under 16 years of age. We do not knowingly collect personal information from children under 16. If we become aware that we have collected such information, we will take steps to delete it.</p>-<p>While all our primary data processing occurs within the EU, some of our third-party processors may process data outside the EU. When this occurs, we ensure appropriate safeguards are in place, such as Standard Contractual Clauses or adequacy decisions.</p>-<p>We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date.</p>-<p>If you have any questions about this Privacy Policy or wish to exercise your rights, please contact us through our platform or via email.</p>-<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-600 dark:text-gray-400">-<p>This Privacy Policy complies with the EU General Data Protection Regulation (GDPR) and other applicable data protection laws.</p>
+2
-62
appview/pages/templates/legal/terms.html
+2
-62
appview/pages/templates/legal/terms.html
···-<p>Welcome to Tangled. These Terms of Service ("Terms") govern your access to and use of the Tangled platform and services (the "Service") operated by us ("Tangled," "we," "us," or "our").</p>-<p>By accessing or using our Service, you agree to be bound by these Terms. If you disagree with any part of these terms, then you may not access the Service.</p>-<p>To use certain features of the Service, you must register for an account. You agree to provide accurate, current, and complete information during the registration process and to update such information to keep it accurate, current, and complete.</p>-<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 my-6">-<strong>We reserve the right to terminate, suspend, or restrict access to your account at any time, for any reason, or for no reason at all, at our sole discretion.</strong> This includes, but is not limited to, termination for violation of these Terms, inappropriate conduct, spam, abuse, or any other behavior we deem harmful to the Service or other users.-Account termination may result in the loss of access to your repositories, data, and other content associated with your account. We are not obligated to provide advance notice of termination, though we may do so in our discretion.-<li>Upload, store, or share content that is illegal, harmful, threatening, abusive, harassing, defamatory, vulgar, obscene, or otherwise objectionable</li>-<p>You retain ownership of the content you upload to the Service. By uploading content, you grant us a non-exclusive, worldwide, royalty-free license to use, reproduce, modify, and distribute your content as necessary to provide the Service.</p>-<p>Your privacy is important to us. Please review our <a href="/privacy" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300">Privacy Policy</a>, which also governs your use of the Service.</p>-<p>The Service is provided on an "AS IS" and "AS AVAILABLE" basis. We make no warranties, expressed or implied, and hereby disclaim and negate all other warranties including without limitation, implied warranties or conditions of merchantability, fitness for a particular purpose, or non-infringement of intellectual property or other violation of rights.</p>-<p>In no event shall Tangled, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential, or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from your use of the Service.</p>-<p>You agree to defend, indemnify, and hold harmless Tangled and its affiliates, officers, directors, employees, and agents from and against any and all claims, damages, obligations, losses, liabilities, costs, or debt, and expenses (including attorney's fees).</p>-<p>These Terms shall be interpreted and governed by the laws of Finland, without regard to its conflict of law provisions.</p>-<p>We reserve the right to modify or replace these Terms at any time. If a revision is material, we will try to provide at least 30 days notice prior to any new terms taking effect.</p>-<p>If you have any questions about these Terms of Service, please contact us through our platform or via email.</p>-<div class="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700 text-sm text-gray-600 dark:text-gray-400">-<p>These terms are effective as of the last updated date shown above and will remain in effect except with respect to any changes in their provisions in the future, which will be in effect immediately after being posted on this page.</p>
+2
-2
appview/pages/templates/repo/commit.html
+2
-2
appview/pages/templates/repo/commit.html
······
+2
-2
appview/pages/templates/repo/compare/compare.html
+2
-2
appview/pages/templates/repo/compare/compare.html
······
+9
-3
appview/pages/templates/repo/fork.html
+9
-3
appview/pages/templates/repo/fork.html
···+<form hx-post="/{{ .RepoInfo.FullName }}/fork" class="space-y-12" hx-swap="none" hx-indicator="#spinner">······
+35
-83
appview/pages/templates/repo/fragments/diff.html
+35
-83
appview/pages/templates/repo/fragments/diff.html
···-<section class="border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">-<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">-<span class="bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400 {{ $markerstyle }}">ADDED</span>-<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">DELETED</span>-<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">COPIED</span>-<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">RENAMED</span>-<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">MODIFIED</span>-<a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $this }}href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.Old }}"{{end}}>-<a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $parent}}href="/{{ $repo }}/blob/{{ $parent }}/{{ .Name.Old }}"{{end}}>-<a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $this}}href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.New }}"{{end}}>-<a class="dark:text-white whitespace-nowrap overflow-x-auto" {{if $this}}href="/{{ $repo }}/blob/{{ $this }}/{{ .Name.New }}"{{end}}>-<a title="top of file" href="#file-{{ .Name.New }}" class="{{ $iconstyle }}">{{ i "arrow-up-to-line" "w-4 h-4" }}</a>-<a title="previous file" href="#file-{{ $prev.Name.New }}" class="{{ $iconstyle }}">{{ i "arrow-up" "w-4 h-4" }}</a>-<a title="next file" href="#file-{{ $next.Name.New }}" class="{{ $iconstyle }}">{{ i "arrow-down" "w-4 h-4" }}</a>+<details open id="file-{{ .Name.New }}" class="group border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm" tabindex="{{ add $idx 1 }}">+<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">
+4
appview/pages/templates/repo/fragments/duration.html
+4
appview/pages/templates/repo/fragments/duration.html
+44
-69
appview/pages/templates/repo/fragments/interdiff.html
+44
-69
appview/pages/templates/repo/fragments/interdiff.html
···-<section class="border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">-<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">-<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">CHANGED</span>-<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">UNCHANGED</span>-<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">REVERTED</span>-<span class="bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400 {{ $markerstyle }}">NEW</span>-<span class="bg-amber-100 text-amber-700 dark:bg-amber-800/50 dark:text-amber-400 {{ $markerstyle }}">REBASED</span>-<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">ERROR</span>-<a title="top of file" href="#file-{{ .Name }}" class="{{ $iconstyle }}">{{ i "arrow-up-to-line" "w-4 h-4" }}</a>-<a title="previous file" href="#file-{{ $prev.Name }}" class="{{ $iconstyle }}">{{ i "arrow-up" "w-4 h-4" }}</a>-<a title="next file" href="#file-{{ $next.Name }}" class="{{ $iconstyle }}">{{ i "arrow-down" "w-4 h-4" }}</a>+<details {{ if not (.Status.IsOnlyInOne) }}open{{end}} id="file-{{ .Name }}" class="border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm">+<div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between">+<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">CHANGED</span>+<span class="bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300 {{ $markerstyle }}">UNCHANGED</span>+<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">REVERTED</span>+<span class="bg-green-100 text-green-700 dark:bg-green-800/50 dark:text-green-400 {{ $markerstyle }}">NEW</span>+<span class="bg-amber-100 text-amber-700 dark:bg-amber-800/50 dark:text-amber-400 {{ $markerstyle }}">REBASED</span>+<span class="bg-red-100 text-red-700 dark:bg-red-800/50 dark:text-red-400 {{ $markerstyle }}">ERROR</span>
+6
appview/pages/templates/repo/fragments/languageBall.html
+6
appview/pages/templates/repo/fragments/languageBall.html
···+style="background: radial-gradient(circle at 35% 35%, color-mix(in srgb, {{ langColor . }} 70%, white), {{ langColor . }} 30%, color-mix(in srgb, {{ langColor . }} 85%, black));"
+4
appview/pages/templates/repo/fragments/shortTime.html
+4
appview/pages/templates/repo/fragments/shortTime.html
+4
appview/pages/templates/repo/fragments/shortTimeAgo.html
+4
appview/pages/templates/repo/fragments/shortTimeAgo.html
-16
appview/pages/templates/repo/fragments/time.html
-16
appview/pages/templates/repo/fragments/time.html
···-<time datetime="{{ .Time | iso8601DateTimeFmt }}" title="{{ .Time | longTimeFmt }}">{{ .Content }}</time>-{{ template "repo/fragments/timeWrapper" (dict "Time" . "Content" (print (. | shortRelTimeFmt) " ago")) }}-<time datetime="{{ . | iso8601DurationFmt }}" title="{{ . | longDurationFmt }}">{{ . | durationFmt }}</time>
+5
appview/pages/templates/repo/fragments/timeWrapper.html
+5
appview/pages/templates/repo/fragments/timeWrapper.html
+47
-55
appview/pages/templates/repo/index.html
+47
-55
appview/pages/templates/repo/index.html
···+<summary class="flex gap-[1px] h-4 scale-y-50 hover:scale-y-100 origin-top group-open:scale-y-100 transition-all hover:cursor-pointer overflow-hidden rounded-t">+<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-600 flex items-center gap-4 flex-wrap">······-class="p-6 mt-4 rounded-br rounded-bl bg-white dark:bg-gray-800 dark:text-white drop-shadow-sm w-full mx-auto overflow-auto {{ if not .Raw }}-<article class="{{ if .Raw }}whitespace-pre{{ end }}">{{- if .Raw -}}<pre class="dark:bg-gray-800 dark:text-white overflow-x-auto">+<div class="mt-4 rounded bg-white dark:bg-gray-800 drop-shadow-sm w-full mx-auto overflow-hidden">+<div class="px-4 py-2 bg-gray-50 dark:bg-gray-700 border-b border-gray-200 dark:border-gray-600 flex items-center gap-2">+<article class="{{ if .Raw }}whitespace-pre{{ end }}">{{- if .Raw -}}<pre class="dark:bg-gray-800 dark:text-white overflow-x-auto">
+58
appview/pages/templates/repo/issues/fragments/commentList.html
+58
appview/pages/templates/repo/issues/fragments/commentList.html
···+<div class="rounded border border-gray-300 dark:border-gray-700 w-full overflow-hidden shadow-sm">
+37
-45
appview/pages/templates/repo/issues/fragments/editIssueComment.html
+37
-45
appview/pages/templates/repo/issues/fragments/editIssueComment.html
···-class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-400 hover:underline no-underline"+<div class="flex flex-wrap items-center justify-end gap-2 text-gray-500 dark:text-gray-400 text-sm pt-2">
-58
appview/pages/templates/repo/issues/fragments/issueComment.html
-58
appview/pages/templates/repo/issues/fragments/issueComment.html
···-class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-400 hover:underline no-underline"
+34
appview/pages/templates/repo/issues/fragments/issueCommentActions.html
+34
appview/pages/templates/repo/issues/fragments/issueCommentActions.html
···
+9
appview/pages/templates/repo/issues/fragments/issueCommentBody.html
+9
appview/pages/templates/repo/issues/fragments/issueCommentBody.html
···+<div class="prose dark:prose-invert italic text-gray-500 dark:text-gray-400">[deleted by author]</div>
+56
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
+56
appview/pages/templates/repo/issues/fragments/issueCommentHeader.html
···+class="text-gray-500 dark:text-gray-400 hover:text-gray-500 dark:hover:text-gray-400 hover:underline no-underline"
+145
appview/pages/templates/repo/issues/fragments/newComment.html
+145
appview/pages/templates/repo/issues/fragments/newComment.html
···+hx-vals="js:{body: document.getElementById('comment-textarea').value.trim() !== '' ? document.getElementById('comment-textarea').value : ''}"
+57
appview/pages/templates/repo/issues/fragments/putIssue.html
+57
appview/pages/templates/repo/issues/fragments/putIssue.html
···+<input type="text" name="title" id="title" class="w-full" value="{{ if .Issue }}{{ .Issue.Title }}{{ end }}" />
+61
appview/pages/templates/repo/issues/fragments/replyComment.html
+61
appview/pages/templates/repo/issues/fragments/replyComment.html
···+hx-on:htmx:before-request="event.preventDefault(); document.getElementById('reply-form-{{ .Comment.Id }}').requestSubmit()"></textarea>+<div class="flex flex-wrap items-stretch justify-end gap-2 text-gray-500 dark:text-gray-400 text-sm">+hx-get="/{{ .RepoInfo.FullName }}/issues/{{ .Issue.IssueId }}/comment/{{ .Comment.Id }}/replyPlaceholder"
+20
appview/pages/templates/repo/issues/fragments/replyIssueCommentPlaceholder.html
+20
appview/pages/templates/repo/issues/fragments/replyIssueCommentPlaceholder.html
···
+95
-202
appview/pages/templates/repo/issues/issue.html
+95
-202
appview/pages/templates/repo/issues/issue.html
···-class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-2 px-4 relative w-full md:max-w-3/5 md:w-fit">-{{ template "repo/issues/fragments/issueComment" (dict "RepoInfo" $.RepoInfo "LoggedInUser" $.LoggedInUser "Issue" $.Issue "Comment" .)}}-<div class="bg-white dark:bg-gray-800 rounded drop-shadow-sm py-4 px-4 relative w-full md:w-3/5">-{{ template "user/fragments/picHandleLink" (didOrHandle .LoggedInUser.Did .LoggedInUser.Handle) }}-hx-vals="js:{body: document.getElementById('comment-textarea').value.trim() !== '' ? document.getElementById('comment-textarea').value : ''}"
+42
-44
appview/pages/templates/repo/issues/issues.html
+42
-44
appview/pages/templates/repo/issues/issues.html
···-<a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ .Metadata.CommentCount }} comment{{$s}}</a>+<a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a>
+1
-33
appview/pages/templates/repo/issues/new.html
+1
-33
appview/pages/templates/repo/issues/new.html
···
+60
appview/pages/templates/repo/needsUpgrade.html
+60
appview/pages/templates/repo/needsUpgrade.html
···+<div class="w-full h-full grid grid-cols-1 md:grid-cols-2 gap-4 md:divide-x divide-gray-300 dark:divide-gray-600 text-gray-300 dark:text-gray-600">+<div id="file-list" class="flex flex-col gap-2 col-span-1 w-full h-full p-4 items-start justify-start text-left">+<div id="commit-list" class="hidden md:flex md:flex-col gap-4 col-span-1 w-full h-full p-4 items-start justify-start text-left">+<div class="absolute inset-0 flex items-center justify-center py-12 text-red-500 dark:text-red-400 backdrop-blur">
+2
-2
appview/pages/templates/repo/new.html
+2
-2
appview/pages/templates/repo/new.html
······
+2
-2
appview/pages/templates/repo/pulls/fragments/pullCompareForks.html
+2
-2
appview/pages/templates/repo/pulls/fragments/pullCompareForks.html
···+<option value="{{ .Did }}/{{ .Name }}" {{ if eq .Name $.Selected }}selected{{ end }} class="py-1">
+1
-1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
+1
-1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
······<span class="text-xs rounded bg-gray-100 dark:bg-gray-700 text-black dark:text-white font-mono px-2 mx-1/2 inline-flex items-center"><a href="/{{ $owner }}/{{ .Pull.PullSource.Repo.Name }}" class="no-underline hover:underline">{{ $owner }}/{{ .Pull.PullSource.Repo.Name }}</a>:
+1
-1
appview/pages/templates/repo/pulls/fragments/pullStack.html
+1
-1
appview/pages/templates/repo/pulls/fragments/pullStack.html
···
+1
-1
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
+1
-1
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
+2
-2
appview/pages/templates/repo/pulls/interdiff.html
+2
-2
appview/pages/templates/repo/pulls/interdiff.html
······
+2
-2
appview/pages/templates/repo/pulls/patch.html
+2
-2
appview/pages/templates/repo/pulls/patch.html
······
+1
-1
appview/pages/templates/repo/pulls/pulls.html
+1
-1
appview/pages/templates/repo/pulls/pulls.html
···<a href="/{{ $root.RepoInfo.FullName }}/pulls/{{ $pull.PullId }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25">
+3
-1
appview/pages/templates/repo/settings/general.html
+3
-1
appview/pages/templates/repo/settings/general.html
······-<form hx-put="/{{ $.RepoInfo.FullName }}/settings/branches/default" class="col-span-1 md:col-span-1 md:justify-self-end group flex gap-2 items-stretch">+<form hx-put="/{{ $.RepoInfo.FullName }}/settings/branches/default" hx-swap="none" class="col-span-1 md:col-span-1 md:justify-self-end group flex gap-2 items-stretch"><select id="branch" name="branch" required class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700">···class="btn group text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 flex gap-2 items-center"
-192
appview/pages/templates/settings.html
-192
appview/pages/templates/settings.html
···-<section class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit">-<section class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit">-<p class="mb-8 dark:text-gray-300">SSH public keys added here will be broadcasted to knots that you are a member of, <br> allowing you to push to repositories there.</p>-<p class="text-sm text-gray-500 dark:text-gray-400">added {{ template "repo/fragments/time" .Created }}</p>-class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"-hx-delete="/settings/keys?name={{urlquery .Name}}&rkey={{urlquery .Rkey}}&key={{urlquery .Key}}"-class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"/>-class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"/>-<button class="btn dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 flex gap-2 items-center" type="submit">-<section class="rounded bg-white dark:bg-gray-800 drop-shadow-sm px-6 py-4 mb-6 w-full lg:w-fit">-<p class="mb-8 dark:text-gray-300">Commits authored using emails listed here will be associated with your Tangled profile.</p>-<span class="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded">verified</span>-<span class="text-xs bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 px-2 py-1 rounded">unverified</span>-<span class="text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 px-2 py-1 rounded">primary</span>-<p class="text-sm text-gray-500 dark:text-gray-400">added {{ template "repo/fragments/time" .CreatedAt }}</p>-class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 flex gap-2 items-center"-class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"
+2
-2
appview/pages/templates/spindles/fragments/addMemberModal.html
+2
-2
appview/pages/templates/spindles/fragments/addMemberModal.html
···class="bg-white w-full md:w-96 dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">
+17
-10
appview/pages/templates/spindles/fragments/spindleListing.html
+17
-10
appview/pages/templates/spindles/fragments/spindleListing.html
···<div id="spindle-{{.Id}}" class="flex items-center justify-between p-2 border-b border-gray-200 dark:border-gray-700"><a href="/spindles/{{ .Instance }}" class="hover:no-underline flex items-center gap-2 min-w-0 max-w-[60%]">···+<span class="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 {{$style}}"> {{ i "shield-alert" "w-4 h-4" }} needs upgrade </span><span class="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 {{$style}}">{{ i "shield-check" "w-4 h-4" }} verified</span><span class="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 {{$style}}">{{ i "shield-off" "w-4 h-4" }} unverified</span>class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"···
+10
-9
appview/pages/templates/spindles/index.html
+10
-9
appview/pages/templates/spindles/index.html
···<section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">···
+1
-1
appview/pages/templates/strings/fragments/form.html
+1
-1
appview/pages/templates/strings/fragments/form.html
···
-4
appview/pages/templates/strings/put.html
-4
appview/pages/templates/strings/put.html
+1
-5
appview/pages/templates/strings/string.html
+1
-5
appview/pages/templates/strings/string.html
······
-4
appview/pages/templates/strings/timeline.html
-4
appview/pages/templates/strings/timeline.html
+34
appview/pages/templates/timeline/fragments/hero.html
+34
appview/pages/templates/timeline/fragments/hero.html
···+<div class="mx-auto max-w-[100rem] flex flex-col text-black dark:text-white px-6 py-4 gap-6 items-center md:flex-row">+tangled is new social-enabled git collaboration platform built on <a class="underline" href="https://atproto.com/">atproto</a>.+<img src="https://assets.tangled.network/hero-repo.png" alt="Screenshot of the Tangled monorepo." class="max-w-md mx-auto md:max-w-none w-full md:w-[30vw] h-auto shadow-sm rounded" />
+116
appview/pages/templates/timeline/fragments/timeline.html
+116
appview/pages/templates/timeline/fragments/timeline.html
···+<div class="flex flex-col divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-sm">+<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">+<a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline">{{ $repo.Name }}</a>+<span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" $repo.Created }}</span>+<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">+<span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" .Created }}</span>+<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">+<span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" $follow.FollowedAt }}</span>+<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800 flex items-center gap-4">+<span class="font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap max-w-full">{{ $subjectHandle | truncateAt30 }}</span>+<div class="text-sm flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full">+<span id="followers"><a href="/{{ $subjectHandle }}?tab=followers">{{ .Followers }} followers</a></span>+<span id="following"><a href="/{{ $subjectHandle }}?tab=following">{{ .Following }} following</a></span>
+25
appview/pages/templates/timeline/fragments/trending.html
+25
appview/pages/templates/timeline/fragments/trending.html
···+<div class="py-8 px-6 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-sm">
+90
appview/pages/templates/timeline/home.html
+90
appview/pages/templates/timeline/home.html
···+<div class="prose dark:text-gray-200 space-y-12 px-6 py-4 bg-white dark:bg-gray-800 rounded drop-shadow-sm">+"Host your repositories on your own infrastructure using <em>knots</em>—tiny, headless servers that facilitate git operations."+"Run pipelines on your own infrastructure using <em>spindles</em>—lightweight CI runners."
+18
appview/pages/templates/timeline/timeline.html
+18
appview/pages/templates/timeline/timeline.html
···
-162
appview/pages/templates/timeline.html
-162
appview/pages/templates/timeline.html
···-tangled is new social-enabled git collaboration platform built on <a class="underline" href="https://atproto.com/">atproto</a>.-<div class="flex flex-col divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-sm">-<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">-<a href="/{{ $userHandle }}/{{ $repo.Name }}" class="no-underline hover:underline">{{ $repo.Name }}</a>-<span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" $repo.Created }}</span>-<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">-<span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" .Created }}</span>-<div class="pl-6 py-2 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-300 flex flex-wrap items-center gap-2 text-sm">-<span class="text-gray-700 dark:text-gray-400 text-xs">{{ template "repo/fragments/time" $follow.FollowedAt }}</span>-<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800 flex items-center gap-4">-<span class="font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap max-w-full">{{ $subjectHandle | truncateAt30 }}</span>-<div class="text-sm flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full text-sm">
+2
-4
appview/pages/templates/user/completeSignup.html
+2
-4
appview/pages/templates/user/completeSignup.html
···
+18
appview/pages/templates/user/followers.html
+18
appview/pages/templates/user/followers.html
···
+18
appview/pages/templates/user/following.html
+18
appview/pages/templates/user/following.html
···
+1
-1
appview/pages/templates/user/fragments/editBio.html
+1
-1
appview/pages/templates/user/fragments/editBio.html
+2
-2
appview/pages/templates/user/fragments/follow.html
+2
-2
appview/pages/templates/user/fragments/follow.html
······
+29
appview/pages/templates/user/fragments/followCard.html
+29
appview/pages/templates/user/fragments/followCard.html
···+<div class="flex flex-col divide-y divide-gray-200 dark:divide-gray-700 border border-gray-200 dark:border-gray-700 rounded-sm">+<div class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800 flex items-center gap-4">+<span class="font-bold dark:text-white overflow-hidden text-ellipsis whitespace-nowrap max-w-full">{{ $userIdent | truncateAt30 }}</span>+<div class="text-sm flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full">+<span id="followers"><a href="/{{ $userIdent }}?tab=followers">{{ .FollowersCount }} followers</a></span>+<span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .FollowingCount }} following</a></span>
+1
-1
appview/pages/templates/user/fragments/picHandle.html
+1
-1
appview/pages/templates/user/fragments/picHandle.html
+17
-16
appview/pages/templates/user/fragments/profileCard.html
+17
-16
appview/pages/templates/user/fragments/profileCard.html
·········<div class="flex flex-col gap-2 mb-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full">···<span class="flex-shrink-0">{{ template "user/fragments/bluesky" "w-4 h-4 text-black dark:text-white" }}</span>-<a id="bluesky-link" href="https://bsky.app/profile/{{ $.UserDid }}">{{ didOrHandle $.UserDid $.UserHandle }}</a>···-<div class="flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full text-sm">+<div class="flex items-center gap-2 my-2 overflow-hidden text-ellipsis whitespace-nowrap max-w-full text-sm">+<span id="followers"><a href="/{{ $userIdent }}?tab=followers">{{ .Stats.FollowersCount }} followers</a></span>+<span id="following"><a href="/{{ $userIdent }}?tab=following">{{ .Stats.FollowingCount }} following</a></span>
+5
-6
appview/pages/templates/user/fragments/repoCard.html
+5
-6
appview/pages/templates/user/fragments/repoCard.html
···+<div class="py-4 px-6 gap-1 flex flex-col drop-shadow-sm rounded bg-white dark:bg-gray-800 min-h-32">······-style="background: radial-gradient(circle at 35% 35%, color-mix(in srgb, {{ langColor . }} 70%, white), {{ langColor . }} 30%, color-mix(in srgb, {{ langColor . }} 85%, black));"></div>
+2
-2
appview/pages/templates/user/login.html
+2
-2
appview/pages/templates/user/login.html
···
+269
appview/pages/templates/user/overview.html
+269
appview/pages/templates/user/overview.html
···+class="flex text-black dark:text-white items-center gap-2 no-underline hover:no-underline group">
-318
appview/pages/templates/user/profile.html
-318
appview/pages/templates/user/profile.html
···-<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />-<div class="text-sm font-bold p-2 pr-0 dark:text-white flex items-center justify-between gap-2">-class="flex text-black dark:text-white items-center gap-2 no-underline hover:no-underline group">-class="aspect-square rounded-full transition-all duration-300 {{ $theme }} max-w-full max-h-full"
+7
-18
appview/pages/templates/user/repos.html
+7
-18
appview/pages/templates/user/repos.html
···-<meta property="og:url" content="https://tangled.sh/{{ or .Card.UserHandle .Card.UserDid }}/repos" />-<meta property="og:description" content="{{ or .Card.Profile.Description .Card.UserHandle .Card.UserDid }}" />
+94
appview/pages/templates/user/settings/emails.html
+94
appview/pages/templates/user/settings/emails.html
···+<div class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">+<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">+class="bg-white w-full md:w-96 dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">+<p class="text-sm text-gray-500 dark:text-gray-400">Commits using this email will be associated with your profile.</p>+class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
+62
appview/pages/templates/user/settings/fragments/emailListing.html
+62
appview/pages/templates/user/settings/fragments/emailListing.html
···+<span class="text-xs bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 px-2 py-1 rounded">verified</span>+<span class="text-xs bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 px-2 py-1 rounded">unverified</span>+<span class="text-xs bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200 px-2 py-1 rounded">primary</span>+class="btn text-sm px-2 py-1 text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"+class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
+31
appview/pages/templates/user/settings/fragments/keyListing.html
+31
appview/pages/templates/user/settings/fragments/keyListing.html
···+class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"+hx-delete="/settings/keys?name={{urlquery $key.Name}}&rkey={{urlquery $key.Rkey}}&key={{urlquery $key.Key}}"
+101
appview/pages/templates/user/settings/keys.html
+101
appview/pages/templates/user/settings/keys.html
···+<div class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">+<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">+class="bg-white w-full md:w-96 dark:bg-gray-800 p-4 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50">+<p class="text-sm text-gray-500 dark:text-gray-400">SSH keys allow you to push to repositories in knots you're a member of.</p>+class="w-full dark:bg-gray-700 dark:text-white dark:border-gray-600 dark:placeholder-gray-400"></textarea>+class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
+64
appview/pages/templates/user/settings/profile.html
+64
appview/pages/templates/user/settings/profile.html
···+<div class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto drop-shadow-sm dark:text-white">+<div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full">
+3
-1
appview/pages/templates/user/signup.html
+3
-1
appview/pages/templates/user/signup.html
···
+19
appview/pages/templates/user/starred.html
+19
appview/pages/templates/user/starred.html
···
+45
appview/pages/templates/user/strings.html
+45
appview/pages/templates/user/strings.html
···+<a href="/strings/{{ or $root.Card.UserHandle $root.Card.UserDid }}/{{ $s.Rkey }}">{{ $s.Filename }}</a>
+1
-1
appview/posthog/notifier.go
+1
-1
appview/posthog/notifier.go
···
+359
-190
appview/pulls/pulls.go
+359
-190
appview/pulls/pulls.go
··················-func (s *Pulls) mergeCheck(f *reporesolver.ResolvedRepo, pull *db.Pull, stack db.Stack) types.MergeCheckResponse {+func (s *Pulls) mergeCheck(r *http.Request, f *reporesolver.ResolvedRepo, pull *db.Pull, stack db.Stack) types.MergeCheckResponse {···-func (s *Pulls) resubmitCheck(f *reporesolver.ResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult {+func (s *Pulls) resubmitCheck(r *http.Request, f *reporesolver.ResolvedRepo, pull *db.Pull, stack db.Stack) pages.ResubmitResult {·····················s.pages.Notice(w, "pull", "This knot doesn't support format-patch. Unfortunately, there is no fallback for now.")······func (s *Pulls) handleForkBasedPull(w http.ResponseWriter, r *http.Request, f *reporesolver.ResolvedRepo, user *oauth.User, forkRepo string, title, body, targetBranch, sourceBranch string, isStacked bool) {······+forkXrpcBytes, err := tangled.RepoCompare(r.Context(), forkXrpcc, forkRepoId, hiddenRef, sourceBranch)·········-return sourceBranches[i].Commit.Committer.When.After(sourceBranches[j].Commit.Committer.When)+return sourceBranches.Branches[i].Commit.Committer.When.After(sourceBranches.Branches[j].Commit.Committer.When)···-comparison, err := ksClient.Compare(f.OwnerDid(), f.Name, pull.TargetBranch, pull.PullSource.Branch)+xrpcBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, pull.TargetBranch, pull.PullSource.Branch)···+forkXrpcBytes, err := tangled.RepoCompare(r.Context(), &indigoxrpc.Client{Host: forkHost}, forkRepoId, pull.TargetBranch, pull.PullSource.Branch)-resp, err := signedClient.NewHiddenRef(forkRepo.Did, forkRepo.Name, pull.PullSource.Branch, pull.TargetBranch)-comparison, err := ksClient.Compare(forkRepo.Did, forkRepo.Name, hiddenRef, pull.PullSource.Branch)·········-resp, err := ksClient.Merge([]byte(patch), f.OwnerDid(), f.Name, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address)
+26
-8
appview/repo/artifact.go
+26
-8
appview/repo/artifact.go
··················-func (rp *Repo) resolveTag(f *reporesolver.ResolvedRepo, tagParam string) (*types.TagReference, error) {+func (rp *Repo) resolveTag(ctx context.Context, f *reporesolver.ResolvedRepo, tagParam string) (*types.TagReference, error) {
+170
appview/repo/feed.go
+170
appview/repo/feed.go
···+func (rp *Repo) getRepoFeed(ctx context.Context, f *reporesolver.ResolvedRepo) (*feeds.Feed, error) {+pulls, err := db.GetPullsWithLimit(rp.db, feedLimitPerType, db.FilterEq("repo_at", f.RepoAt()))+Link: &feeds.Link{Href: fmt.Sprintf("%s/%s", rp.config.Core.AppviewHost, f.OwnerSlashRepo()), Type: "text/html", Rel: "alternate"},+func (rp *Repo) createPullItems(ctx context.Context, pull *db.Pull, f *reporesolver.ResolvedRepo) ([]*feeds.Item, error) {+Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId)},+Description: fmt.Sprintf("@%s submitted changes (at round #%d) on PR #%d in %s", owner.Handle, round.RoundNumber, pull.PullId, f.OwnerSlashRepo()),+Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/pulls/%d/round/%d/", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), pull.PullId, round.RoundNumber)},+func (rp *Repo) createIssueItem(ctx context.Context, issue db.Issue, f *reporesolver.ResolvedRepo) (*feeds.Item, error) {+Description: fmt.Sprintf("@%s %s issue #%d in %s", owner.Handle, state, issue.IssueId, f.OwnerSlashRepo()),+Link: &feeds.Link{Href: fmt.Sprintf("%s/%s/issues/%d", rp.config.Core.AppviewHost, f.OwnerSlashRepo(), issue.IssueId)},+func (rp *Repo) buildPullDescription(handle syntax.Handle, state string, pull *db.Pull, repoName string) string {
+194
-99
appview/repo/index.go
+194
-99
appview/repo/index.go
·····················+// buildIndexResponse creates a RepoIndexResponse by combining multiple xrpc calls in parallel+func (rp *Repo) buildIndexResponse(ctx context.Context, xrpcc *indigoxrpc.Client, f *reporesolver.ResolvedRepo, ref string) (*types.RepoIndexResponse, error) {-newHiddenRefResp, err := signedClient.NewHiddenRef(user.Did, repoInfo.Name, currentRef, currentRef)-forkSyncableResp, err := signedClient.RepoForkAheadBehind(user.Did, string(f.RepoAt()), repoInfo.Name, currentRef, hiddenRef)
+599
-331
appview/repo/repo.go
+599
-331
appview/repo/repo.go
············-url := fmt.Sprintf("%s://%s/%s/%s/archive/%s.tar.gz", uri, f.Knot, f.OwnerDid(), f.Name, url.PathEscape(refParam))···············-resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/commit/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref))···-resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/tree/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, treePath))// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,···············-resp, err := http.Get(fmt.Sprintf("%s://%s/%s/%s/blob/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, filePath))breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})·········-blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Name, ref, filePath)contentSrc = markup.GenerateCamoURL(rp.config.Camo.Host, rp.config.Camo.SharedSecret, blobURL)···-blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, filePath)log.Printf("knotserver returned non-OK status for raw blob %s: %d", blobURL, resp.StatusCode)···} else if strings.HasPrefix(contentType, "image/") || strings.HasPrefix(contentType, "video/") {······err = rp.enforcer.AddCollaborator(collaboratorIdent.DID.String(), f.Knot, f.DidSlashRepo())·········repoCollaborators, err := rp.enforcer.E.GetImplicitUsersForResourceByDomain(f.DidSlashRepo(), f.Knot)·········-// } else if resp, err := tangled.RepoListSecrets(r.Context(), spindleClient, f.RepoAt().String()); err != nil {···························
+1
appview/repo/router.go
+1
appview/repo/router.go
+148
appview/serververify/verify.go
+148
appview/serververify/verify.go
···
+44
-9
appview/settings/settings.go
+44
-9
appview/settings/settings.go
·····················
+10
-9
appview/spindles/spindles.go
+10
-9
appview/spindles/spindles.go
·········+s.Pages.Notice(w, noticeId, "Failed to verify spindle, XRPC queries are unsupported on this spindle, consider upgrading!")······
-118
appview/spindleverify/verify.go
-118
appview/spindleverify/verify.go
···
+9
-12
appview/state/git_http.go
+9
-12
appview/state/git_http.go
···-targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)+targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)···-targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)+targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)···-targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, knot, user.DID, repo, r.URL.RawQuery)+targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)···
+5
-2
appview/state/knotstream.go
+5
-2
appview/state/knotstream.go
···func Knotstream(ctx context.Context, c *config.Config, d *db.DB, enforcer *rbac.Enforcer, posthog posthog.Client) (*ec.Consumer, error) {
+376
-134
appview/state/profile.go
+376
-134
appview/state/profile.go
···+followPage, err := s.followPage(r, db.GetFollowers, func(f db.Follow) string { return f.UserDid })+followPage, err := s.followPage(r, db.GetFollowing, func(f db.Follow) string { return f.SubjectDid })···-func (s *State) GetProfileFeed(ctx context.Context, handle string, did string) (*feeds.Feed, error) {+func (s *State) getProfileFeed(ctx context.Context, id *identity.Identity) (*feeds.Feed, error) {-Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s", s.config.Core.AppviewHost, handle), Type: "text/html", Rel: "alternate"},+Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s", s.config.Core.AppviewHost, id.Handle), Type: "text/html", Rel: "alternate"},-Title: fmt.Sprintf("%s created pull request '%s' in @%s/%s", author.Name, pull.Title, owner.Handle, pull.Repo.Name),-Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.AppviewHost, owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"},-Title: fmt.Sprintf("%s submitted pull request '%s' (round #%d) in @%s/%s", author.Name, pull.Title, submission.RoundNumber, owner.Handle, pull.Repo.Name),-Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.AppviewHost, owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"},-Title: fmt.Sprintf("%s created issue '%s' in @%s/%s", author.Name, issue.Title, owner.Handle, issue.Metadata.Repo.Name),-Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.AppviewHost, owner.Handle, issue.Metadata.Repo.Name, issue.IssueId), Type: "text/html", Rel: "alternate"},-title = fmt.Sprintf("%s forked repository @%s/%s to '%s'", author.Name, id.Handle, repo.Source.Name, repo.Repo.Name)-Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.AppviewHost, handle, repo.Repo.Name), Type: "text/html", Rel: "alternate"},+func (s *State) addPullRequestItems(ctx context.Context, feed *feeds.Feed, pulls []*db.Pull, author *feeds.Author) error {+func (s *State) addIssueItems(ctx context.Context, feed *feeds.Feed, issues []*db.Issue, author *feeds.Author) error {+func (s *State) addRepoItems(ctx context.Context, feed *feeds.Feed, repos []db.RepoEvent, author *feeds.Author) error {+func (s *State) createPullRequestItem(pull *db.Pull, owner *identity.Identity, author *feeds.Author) *feeds.Item {+Title: fmt.Sprintf("%s created pull request '%s' in @%s/%s", author.Name, pull.Title, owner.Handle, pull.Repo.Name),+Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/pulls/%d", s.config.Core.AppviewHost, owner.Handle, pull.Repo.Name, pull.PullId), Type: "text/html", Rel: "alternate"},+func (s *State) createIssueItem(issue *db.Issue, owner *identity.Identity, author *feeds.Author) *feeds.Item {+Title: fmt.Sprintf("%s created issue '%s' in @%s/%s", author.Name, issue.Title, owner.Handle, issue.Repo.Name),+Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s/issues/%d", s.config.Core.AppviewHost, owner.Handle, issue.Repo.Name, issue.IssueId), Type: "text/html", Rel: "alternate"},+func (s *State) createRepoItem(ctx context.Context, repo db.RepoEvent, author *feeds.Author) (*feeds.Item, error) {+title = fmt.Sprintf("%s forked repository @%s/%s to '%s'", author.Name, sourceOwner.Handle, repo.Source.Name, repo.Repo.Name)+Link: &feeds.Link{Href: fmt.Sprintf("%s/@%s/%s", s.config.Core.AppviewHost, author.Name[1:], repo.Repo.Name), Type: "text/html", Rel: "alternate"}, // Remove @ prefix···
+7
-5
appview/state/router.go
+7
-5
appview/state/router.go
···············-issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier)+issues := issues.New(s.oauth, s.repoResolver, s.pages, s.idResolver, s.db, s.config, s.notifier, s.validator)
+181
-43
appview/state/state.go
+181
-43
appview/state/state.go
···············posthog, err := posthog.NewWithConfig(config.Posthog.ApiKey, posthog.Config{Endpoint: config.Posthog.Endpoint})·····················-s.pages.Notice(w, "repo", fmt.Sprintf("A repo by this name already exists on %s", existingRepo.Knot))+s.pages.Notice(w, "repo", fmt.Sprintf("You already have a repository by this name on %s", existingRepo.Knot))·········
+2
-71
appview/strings/strings.go
+2
-71
appview/strings/strings.go
······+http.Redirect(w, r, fmt.Sprintf("/%s?tab=strings", chi.URLParam(r, "user")), http.StatusFound)·········-fail("You cannot delete this gist", fmt.Errorf("unauthorized deletion, %s != %s", user.Did, id.DID.String()))+fail("You cannot delete this string", fmt.Errorf("unauthorized deletion, %s != %s", user.Did, id.DID.String()))
+53
appview/validator/issue.go
+53
appview/validator/issue.go
···
+18
appview/validator/validator.go
+18
appview/validator/validator.go
···
+31
appview/xrpcclient/xrpc.go
+31
appview/xrpcclient/xrpc.go
······
+3
cmd/appview/main.go
+3
cmd/appview/main.go
+6
-4
cmd/gen.go
+6
-4
cmd/gen.go
······
+3
-3
docs/contributing.md
+3
-3
docs/contributing.md
······
+63
-22
docs/hacking.md
+63
-22
docs/hacking.md
···+instructions](https://nix.dev/manual/nix/2.28/advanced-topics/distributed-builds.html#requirements)·········
+7
-5
docs/knot-hosting.md
+7
-5
docs/knot-hosting.md
······
+60
docs/migrations.md
+60
docs/migrations.md
···
+130
-54
docs/spindle/pipeline.md
+130
-54
docs/spindle/pipeline.md
···+Spindle workflows allow you to write CI/CD pipelines in a simple format. They're located in the `.tangled/workflows` directory at the root of your repository, and are defined using YAML.+- [Clone options](#clone-options): An **optional** field that defines how the repository should be cloned.+- [Dependencies](#dependencies): An **optional** field that allows you to list dependencies you may need.+- [Environment](#environment): An **optional** field that allows you to define environment variables.+- [Steps](#steps): An **optional** field that allows you to define what steps should run in the workflow.+The first thing to add to a workflow is the trigger, which defines when a workflow runs. This is defined using a `when` field, which takes in a list of conditions. Each condition has the following fields:+- `event`: This is a **required** field that defines when your workflow should run. It's a list that can take one or more of the following values:+- `branch`: This is a **required** field that defines which branches the workflow should run for. If used with the `push` event, commits to the branch(es) listed here will trigger the workflow. If used with the `pull_request` event, updates to pull requests targeting the branch(es) listed here will trigger the workflow. This field has no effect with the `manual` event.+For example, if you'd like define a workflow that runs when commits are pushed to the `main` and `develop` branches, or when pull requests that target the `main` branch are updated, or manually, you can do so with:+Next is the engine on which the workflow should run, defined using the **required** `engine` field. The currently supported engines are:+- `nixery`: This uses an instance of [Nixery](https://nixery.dev) to run steps, which allows you to add [dependencies](#dependencies) from [Nixpkgs](https://github.com/NixOS/nixpkgs). You can search for packages on https://search.nixos.org, and there's a pretty good chance the package(s) you're looking for will be there.+When a workflow starts, the first step is to clone the repository. You can customize this behavior using the **optional** `clone` field. It has the following fields:+- `skip`: Setting this to `true` will skip cloning the repository. This can be useful if your workflow is doing something that doesn't require anything from the repository itself. This is `false` by default.+- `depth`: This sets the number of commits, or the "clone depth", to fetch from the repository. For example, if you set this to 2, the last 2 commits will be fetched. By default, the depth is set to 1, meaning only the most recent commit will be fetched, which is the commit that triggered the workflow.+- `submodules`: If you use [git submodules](https://git-scm.com/book/en/v2/Git-Tools-Submodules) in your repository, setting this field to `true` will recursively fetch all submodules. This is `false` by default.+Usually when you're running a workflow, you'll need additional dependencies. The `dependencies` field lets you define which dependencies to get, and from where. It's a key-value map, with the key being the registry to fetch dependencies from, and the value being the list of dependencies to fetch.+Say you want to fetch Node.js and Go from `nixpkgs`, and a package called `my_pkg` you've made from your own registry at your repository at `https://tangled.sh/@example.com/my_pkg`. You can define those dependencies like so:+The `environment` field allows you define environment variables that will be available throughout the entire workflow. **Do not put secrets here, these environment variables are visible to anyone viewing the repository. You can add secrets for pipelines in your repository's settings.**+The `steps` field allows you to define what steps should run in the workflow. It's a list of step objects, each with the following fields:+- `name`: This field allows you to give your step a name. This name is visible in your workflow runs, and is used to describe what the step is doing.+- `command`: This field allows you to define a command to run in that step. The step is run in a Bash shell, and the logs from the command will be visible in the pipelines page on the Tangled website. The [dependencies](#dependencies) you added will be available to use here.+- `environment`: Similar to the global [environment](#environment) config, this **optional** field is a key-value map that allows you to set environment variables for the step. **Do not put secrets here, these environment variables are visible to anyone viewing the repository. You can add secrets for pipelines in your repository's settings.**-- `verbose-ci`, `ci-verbose`: enables diagnostics reporting for the CI pipeline, allowing you to see any issues when you push.+If you want another example of a workflow, you can look at the one [Tangled uses to build the project](https://tangled.sh/@tangled.sh/core/blob/master/.tangled/workflows/build.yml).
+1
-1
flake.nix
+1
-1
flake.nix
···rootDir=$(jj --ignore-working-copy root || git rev-parse --show-toplevel) || (echo "error: can't find repo root?"; exit 1)
+3
-1
go.mod
+3
-1
go.mod
······
+6
-1
go.sum
+6
-1
go.sum
···github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=github.com/whyrusleeping/cbor-gen v0.3.1/go.mod h1:pM99HXyEbSQHcosHc0iW7YFmwnscr+t9Te4ibko05so=+github.com/wyatt915/goldmark-treeblood v0.0.0-20250825231212-5dcbdb2f4b57 h1:UqtQdzLXnvdBdqn/go53qGyncw1wJ7Mq5SQdieM1/Ew=+github.com/wyatt915/goldmark-treeblood v0.0.0-20250825231212-5dcbdb2f4b57/go.mod h1:BxSCWByWSRSuembL3cDG1IBUbkBoO/oW/6tF19aA4hs=github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
+1
-1
input.css
+1
-1
input.css
···
-336
knotclient/signer.go
-336
knotclient/signer.go
···-func (s *SignedClient) newRequest(method, endpoint string, body []byte) (*http.Request, error) {-func (s *SignedClient) RepoLanguages(ownerDid, repoName, ref string) (*types.RepoLanguageResponse, error) {-func (s *SignedClient) RepoForkAheadBehind(ownerDid, source, name, branch, hiddenRef string) (*http.Response, error) {-func (s *SignedClient) SyncRepoFork(ownerDid, source, name, branch string) (*http.Response, error) {-func (s *SignedClient) SetDefaultBranch(ownerDid, repoName, branch string) (*http.Response, error) {-func (s *SignedClient) AddCollaborator(ownerDid, repoName, memberDid string) (*http.Response, error) {-func (s *SignedClient) MergeCheck(patch []byte, ownerDid, targetRepo, branch string) (*http.Response, error) {-func (s *SignedClient) NewHiddenRef(ownerDid, targetRepo, forkBranch, remoteBranch string) (*http.Response, error) {-endpoint := fmt.Sprintf("/%s/%s/hidden-ref/%s/%s", ownerDid, targetRepo, url.PathEscape(forkBranch), url.PathEscape(remoteBranch))
-250
knotclient/unsigned.go
-250
knotclient/unsigned.go
···-func (us *UnsignedClient) newRequest(method, endpoint string, query url.Values, body []byte) (*http.Request, error) {-func (us *UnsignedClient) Index(ownerDid, repoName, ref string) (*types.RepoIndexResponse, error) {-func (us *UnsignedClient) Log(ownerDid, repoName, ref string, page int) (*types.RepoLogResponse, error) {-func (us *UnsignedClient) Branches(ownerDid, repoName string) (*types.RepoBranchesResponse, error) {-func (us *UnsignedClient) Branch(ownerDid, repoName, branch string) (*types.RepoBranchResponse, error) {-func (us *UnsignedClient) DefaultBranch(ownerDid, repoName string) (*types.RepoDefaultBranchResponse, error) {-func (us *UnsignedClient) Compare(ownerDid, repoName, rev1, rev2 string) (*types.RepoFormatPatchResponse, error) {-endpoint := fmt.Sprintf("/%s/%s/compare/%s/%s", ownerDid, repoName, url.PathEscape(rev1), url.PathEscape(rev2))
+8
-1
knotserver/config/config.go
+8
-1
knotserver/config/config.go
···JetstreamEndpoint string `env:"JETSTREAM_ENDPOINT, default=wss://jetstream1.us-west.bsky.network/subscribe"`···
+40
knotserver/db/pubkeys.go
+40
knotserver/db/pubkeys.go
······
+2
-2
knotserver/events.go
+2
-2
knotserver/events.go
······
-48
knotserver/file.go
-48
knotserver/file.go
···-func (h *Handle) showFile(resp types.RepoBlobResponse, w http.ResponseWriter, l *slog.Logger) {
+58
-72
knotserver/git/merge.go
+58
-72
knotserver/git/merge.go
·········-func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool, opts *MergeOptions) error {+// else, will default to knot's global user.name & user.email configured via `KNOT_GIT_USER_*` env variables······-func (g *GitRepo) MergeWithOptions(patchData []byte, targetBranch string, opts *MergeOptions) error {+func (g *GitRepo) MergeWithOptions(patchData []byte, targetBranch string, opts MergeOptions) error {···
+9
-10
knotserver/git/post_receive.go
+9
-10
knotserver/git/post_receive.go
···
+4
-4
knotserver/git.go
+4
-4
knotserver/git.go
············-func (d *Handle) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {+func (d *Knot) RejectPush(w http.ResponseWriter, r *http.Request, unqualifiedRepoName string) {
-211
knotserver/handler.go
-211
knotserver/handler.go
···-func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger, n *notifier.Notifier) (http.Handler, error) {
-10
knotserver/http_util.go
-10
knotserver/http_util.go
···
+34
-44
knotserver/ingester.go
+34
-44
knotserver/ingester.go
··················resp, err := comatproto.RepoGetRecord(ctx, &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())+return fmt.Errorf("rejected pull record: not this knot, %s != %s", repo.Knot, h.c.Server.Hostname)············-if ok, err := h.e.IsCollaboratorInviteAllowed(did, rbac.ThisServer, didSlashRepo); !ok || err != nil {+return fmt.Errorf("insufficient permissions: %s, %s, %s", did, "IsCollaboratorInviteAllowed", didSlashRepo)·········
-2
knotserver/internal.go
-2
knotserver/internal.go
-53
knotserver/middleware.go
-53
knotserver/middleware.go
···
+152
knotserver/router.go
+152
knotserver/router.go
···+func Setup(ctx context.Context, c *config.Config, db *db.DB, e *rbac.Enforcer, jc *jetstream.JetstreamClient, l *slog.Logger, n *notifier.Notifier) (http.Handler, error) {+return fmt.Errorf("more than one owner in DB, try deleting %q and starting over", h.c.Server.DBPath)
-1356
knotserver/routes.go
-1356
knotserver/routes.go
···-writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden)-writeError(w, fmt.Sprintf("error resolving revision %s", data.HiddenRef), http.StatusBadRequest)-return fmt.Errorf("Repository name can only contain alphanumeric characters, periods, hyphens, and underscores")
+16
-13
knotserver/server.go
+16
-13
knotserver/server.go
···
+156
knotserver/xrpc/create_repo.go
+156
knotserver/xrpc/create_repo.go
···+resp, err := comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, actorDid.String(), rkey)+return fmt.Errorf("Repository name can only contain alphanumeric characters, periods, hyphens, and underscores")
+96
knotserver/xrpc/delete_repo.go
+96
knotserver/xrpc/delete_repo.go
···+_, err = comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, actorDid.String(), rkey)+isDeleteAllowed, err := x.Enforcer.IsRepoDeleteAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath)
+111
knotserver/xrpc/fork_status.go
+111
knotserver/xrpc/fork_status.go
···+if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil {+fail(xrpcerr.GenericError(fmt.Errorf("error resolving whether %s is ancestor of %s: %w", branch, hiddenRef, err)))
+73
knotserver/xrpc/fork_sync.go
+73
knotserver/xrpc/fork_sync.go
···+if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil {
+58
knotserver/xrpc/list_keys.go
+58
knotserver/xrpc/list_keys.go
···
+114
knotserver/xrpc/merge.go
+114
knotserver/xrpc/merge.go
···+if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, relativeRepoPath); !ok || err != nil {
+87
knotserver/xrpc/merge_check.go
+87
knotserver/xrpc/merge_check.go
···
+31
knotserver/xrpc/owner.go
+31
knotserver/xrpc/owner.go
···
+80
knotserver/xrpc/repo_archive.go
+80
knotserver/xrpc/repo_archive.go
···
+151
knotserver/xrpc/repo_blob.go
+151
knotserver/xrpc/repo_blob.go
···
+96
knotserver/xrpc/repo_branch.go
+96
knotserver/xrpc/repo_branch.go
···
+72
knotserver/xrpc/repo_branches.go
+72
knotserver/xrpc/repo_branches.go
···
+98
knotserver/xrpc/repo_compare.go
+98
knotserver/xrpc/repo_compare.go
···
+65
knotserver/xrpc/repo_diff.go
+65
knotserver/xrpc/repo_diff.go
···
+54
knotserver/xrpc/repo_get_default_branch.go
+54
knotserver/xrpc/repo_get_default_branch.go
···
+93
knotserver/xrpc/repo_languages.go
+93
knotserver/xrpc/repo_languages.go
···
+111
knotserver/xrpc/repo_log.go
+111
knotserver/xrpc/repo_log.go
···
+116
knotserver/xrpc/repo_tree.go
+116
knotserver/xrpc/repo_tree.go
···
-149
knotserver/xrpc/router.go
-149
knotserver/xrpc/router.go
···-WithError(fmt.Errorf("DID does not have sufficent access permissions for this operation: %s", d)),
+12
-10
knotserver/xrpc/set_default_branch.go
+12
-10
knotserver/xrpc/set_default_branch.go
···resp, err := comatproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())if ok, err := x.Enforcer.IsPushAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil {
+70
knotserver/xrpc/version.go
+70
knotserver/xrpc/version.go
···
+148
knotserver/xrpc/xrpc.go
+148
knotserver/xrpc/xrpc.go
···
+158
legal/privacy.md
+158
legal/privacy.md
···
+109
legal/terms.md
+109
legal/terms.md
···
+59
-52
lexicons/git/refUpdate.json
+59
-52
lexicons/git/refUpdate.json
···
+4
-11
lexicons/issue/comment.json
+4
-11
lexicons/issue/comment.json
···
+1
-14
lexicons/issue/issue.json
+1
-14
lexicons/issue/issue.json
···
+24
lexicons/knot/knot.json
+24
lexicons/knot/knot.json
···
+73
lexicons/knot/listKeys.json
+73
lexicons/knot/listKeys.json
···
+25
lexicons/knot/version.json
+25
lexicons/knot/version.json
···
+31
lexicons/owner.json
+31
lexicons/owner.json
···
-11
lexicons/pulls/comment.json
-11
lexicons/pulls/comment.json
+20
-12
lexicons/pulls/pull.json
+20
-12
lexicons/pulls/pull.json
······
+55
lexicons/repo/archive.json
+55
lexicons/repo/archive.json
···
+138
lexicons/repo/blob.json
+138
lexicons/repo/blob.json
···
+94
lexicons/repo/branch.json
+94
lexicons/repo/branch.json
···
+43
lexicons/repo/branches.json
+43
lexicons/repo/branches.json
···
+49
lexicons/repo/compare.json
+49
lexicons/repo/compare.json
···
+33
lexicons/repo/create.json
+33
lexicons/repo/create.json
···+"description": "A source URL to clone from, populate this when forking or importing a repository."
+32
lexicons/repo/delete.json
+32
lexicons/repo/delete.json
···
+40
lexicons/repo/diff.json
+40
lexicons/repo/diff.json
···
+53
lexicons/repo/forkStatus.json
+53
lexicons/repo/forkStatus.json
···
+42
lexicons/repo/forkSync.json
+42
lexicons/repo/forkSync.json
···
+82
lexicons/repo/getDefaultBranch.json
+82
lexicons/repo/getDefaultBranch.json
···
+99
lexicons/repo/languages.json
+99
lexicons/repo/languages.json
···
+60
lexicons/repo/log.json
+60
lexicons/repo/log.json
···
+52
lexicons/repo/merge.json
+52
lexicons/repo/merge.json
···
+79
lexicons/repo/mergeCheck.json
+79
lexicons/repo/mergeCheck.json
···
-1
lexicons/repo/repo.json
-1
lexicons/repo/repo.json
+123
lexicons/repo/tree.json
+123
lexicons/repo/tree.json
···
+8
-2
nix/gomod2nix.toml
+8
-2
nix/gomod2nix.toml
···
+5
-5
nix/modules/knot.nix
+5
-5
nix/modules/knot.nix
······
+16
nix/modules/spindle.nix
+16
nix/modules/spindle.nix
······
+17
-12
nix/pkgs/knot-unwrapped.nix
+17
-12
nix/pkgs/knot-unwrapped.nix
···
+4
-1
nix/vm.nix
+4
-1
nix/vm.nix
···# This is fine because any and all ports that are forwarded to host are explicitly marked above, we don't need a separate guest firewall-secretFile = builtins.toFile "knot-secret" ("KNOT_SERVER_SECRET=" + (envVar "TANGLED_VM_KNOT_SECRET"));···
+1
-1
patchutil/combinediff.go
+1
-1
patchutil/combinediff.go
+13
rbac/rbac.go
+13
rbac/rbac.go
······
+2
spindle/config/config.go
+2
spindle/config/config.go
···
+10
-6
spindle/engines/nixery/engine.go
+10
-6
spindle/engines/nixery/engine.go
······
+13
-11
spindle/server.go
+13
-11
spindle/server.go
······+logger.Info("initialized queue", "queueSize", cfg.Server.QueueSize, "numWorkers", cfg.Server.MaxJobCount)······
+11
-10
spindle/xrpc/add_secret.go
+11
-10
spindle/xrpc/add_secret.go
···resp, err := atproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil {···
+10
-9
spindle/xrpc/list_secrets.go
+10
-9
spindle/xrpc/list_secrets.go
···resp, err := atproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil {
+31
spindle/xrpc/owner.go
+31
spindle/xrpc/owner.go
···
+10
-9
spindle/xrpc/remove_secret.go
+10
-9
spindle/xrpc/remove_secret.go
···resp, err := atproto.RepoGetRecord(r.Context(), &xrpcc, "", tangled.RepoNSID, repoAt.Authority().String(), repoAt.RecordKey().String())if ok, err := x.Enforcer.IsSettingsAllowed(actorDid.String(), rbac.ThisServer, didPath); !ok || err != nil {···
+19
-107
spindle/xrpc/xrpc.go
+19
-107
spindle/xrpc/xrpc.go
······-WithError(fmt.Errorf("DID does not have sufficent access permissions for this operation: %s", d)),
+115
xrpc/errors/errors.go
+115
xrpc/errors/errors.go
···+WithError(fmt.Errorf("DID does not have sufficent access permissions for this operation: %s", d)),
+65
xrpc/serviceauth/service_auth.go
+65
xrpc/serviceauth/service_auth.go
···+func NewServiceAuth(logger *slog.Logger, resolver *idresolver.Resolver, audienceDid string) *ServiceAuth {