···1+// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
2+3+package tangled
4+5+// schema: sh.tangled.repo.tag
6+7+import (
8+ "bytes"
9+ "context"
10+11+ "github.com/bluesky-social/indigo/lex/util"
12+)
13+14+const (
15+ RepoTagNSID = "sh.tangled.repo.tag"
16+)
17+18+// RepoTag calls the XRPC method "sh.tangled.repo.tag".
19+//
20+// repo: Repository identifier in format 'did:plc:.../repoName'
21+// tag: Name of tag, such as v1.3.0
22+func RepoTag(ctx context.Context, c util.LexClient, repo string, tag string) ([]byte, error) {
23+ buf := new(bytes.Buffer)
24+25+ params := map[string]interface{}{}
26+ params["repo"] = repo
27+ params["tag"] = tag
28+ if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.tag", params, nil, buf); err != nil {
29+ return nil, err
30+ }
31+32+ return buf.Bytes(), nil
33+}
+14-2
api/tangled/repotree.go
···1617// RepoTree_LastCommit is a "lastCommit" in the sh.tangled.repo.tree schema.
18type RepoTree_LastCommit struct {
019 // hash: Commit hash
20 Hash string `json:"hash" cborgen:"hash"`
21 // message: Commit message
···27// RepoTree_Output is the output of a sh.tangled.repo.tree call.
28type RepoTree_Output struct {
29 // dotdot: Parent directory path
30- Dotdot *string `json:"dotdot,omitempty" cborgen:"dotdot,omitempty"`
31- Files []*RepoTree_TreeEntry `json:"files" cborgen:"files"`
032 // parent: The parent path in the tree
33 Parent *string `json:"parent,omitempty" cborgen:"parent,omitempty"`
34 // readme: Readme for this file tree
···43 Contents string `json:"contents" cborgen:"contents"`
44 // filename: Name of the readme file
45 Filename string `json:"filename" cborgen:"filename"`
000000000046}
4748// RepoTree_TreeEntry is a "treeEntry" in the sh.tangled.repo.tree schema.
···1617// RepoTree_LastCommit is a "lastCommit" in the sh.tangled.repo.tree schema.
18type RepoTree_LastCommit struct {
19+ Author *RepoTree_Signature `json:"author,omitempty" cborgen:"author,omitempty"`
20 // hash: Commit hash
21 Hash string `json:"hash" cborgen:"hash"`
22 // message: Commit message
···28// RepoTree_Output is the output of a sh.tangled.repo.tree call.
29type RepoTree_Output struct {
30 // dotdot: Parent directory path
31+ Dotdot *string `json:"dotdot,omitempty" cborgen:"dotdot,omitempty"`
32+ Files []*RepoTree_TreeEntry `json:"files" cborgen:"files"`
33+ LastCommit *RepoTree_LastCommit `json:"lastCommit,omitempty" cborgen:"lastCommit,omitempty"`
34 // parent: The parent path in the tree
35 Parent *string `json:"parent,omitempty" cborgen:"parent,omitempty"`
36 // readme: Readme for this file tree
···45 Contents string `json:"contents" cborgen:"contents"`
46 // filename: Name of the readme file
47 Filename string `json:"filename" cborgen:"filename"`
48+}
49+50+// RepoTree_Signature is a "signature" in the sh.tangled.repo.tree schema.
51+type RepoTree_Signature struct {
52+ // email: Author email
53+ Email string `json:"email" cborgen:"email"`
54+ // name: Author name
55+ Name string `json:"name" cborgen:"name"`
56+ // when: Author timestamp
57+ When string `json:"when" cborgen:"when"`
58}
5960// RepoTree_TreeEntry is a "treeEntry" in the sh.tangled.repo.tree schema.
+12-1
appview/config/config.go
···13 CookieSecret string `env:"COOKIE_SECRET, default=00000000000000000000000000000000"`
14 DbPath string `env:"DB_PATH, default=appview.db"`
15 ListenAddr string `env:"LISTEN_ADDR, default=0.0.0.0:3000"`
16- AppviewHost string `env:"APPVIEW_HOST, default=https://tangled.org"`
17 AppviewName string `env:"APPVIEW_Name, default=Tangled"`
18 Dev bool `env:"DEV, default=false"`
19 DisallowedNicknamesFile string `env:"DISALLOWED_NICKNAMES_FILE"`
···2324 // uhhhh this is because knot1 is under icy's did
25 TmpAltAppPassword string `env:"ALT_APP_PASSWORD"`
0000000000026}
2728type OAuthConfig struct {
···1181 return err
1182 })
11831184+ orm.RunMigration(conn, logger, "remove-profile-stats-column-constraint", func(tx *sql.Tx) error {
1185+ _, err := tx.Exec(`
1186+ -- create new table without the check constraint
1187+ create table profile_stats_new (
1188+ id integer primary key autoincrement,
1189+ did text not null,
1190+ kind text not null, -- no constraint this time
1191+ foreign key (did) references profile(did) on delete cascade
1192+ );
1193+1194+ -- copy data from old table
1195+ insert into profile_stats_new (id, did, kind)
1196+ select id, did, kind
1197+ from profile_stats;
1198+1199+ -- drop old table
1200+ drop table profile_stats;
1201+1202+ -- rename new table
1203+ alter table profile_stats_new rename to profile_stats;
1204+ `)
1205+ return err
1206+ })
1207+1208 return &DB{
1209 db,
1210 logger,
+7
appview/db/profile.go
···450 case models.VanityStatRepositoryCount:
451 query = `select count(id) from repos where did = ?`
452 args = append(args, did)
0000000453 }
454455 var result uint64
···450 case models.VanityStatRepositoryCount:
451 query = `select count(id) from repos where did = ?`
452 args = append(args, did)
453+ case models.VanityStatStarCount:
454+ query = `select count(id) from stars where subject_at like 'at://' || ? || '%'`
455+ args = append(args, did)
456+ case models.VanityStatNone:
457+ return 0, nil
458+ default:
459+ return 0, fmt.Errorf("invalid vanity stat kind: %s", stat)
460 }
461462 var result uint64
+1-1
appview/ingester.go
···317 var stats [2]models.VanityStat
318 for i, s := range record.Stats {
319 if i < 2 {
320- stats[i].Kind = models.VanityStatKind(s)
321 }
322 }
323
···317 var stats [2]models.VanityStat
318 for i, s := range record.Stats {
319 if i < 2 {
320+ stats[i].Kind = models.ParseVanityStatKind(s)
321 }
322 }
323
+25-17
appview/issues/opengraph.go
···124 }
125126 // Split stats area: left side for status/comments (80%), right side for dolly (20%)
127- statusCommentsArea, dollyArea := statsArea.Split(true, 80)
128129 // Draw status and comment count in status/comments area
130- statsBounds := statusCommentsArea.Img.Bounds()
131 statsX := statsBounds.Min.X + 60 // left padding
132 statsY := statsBounds.Min.Y
133···140 // Draw status (open/closed) with colored icon and text
141 var statusIcon string
142 var statusText string
143- var statusBgColor color.RGBA
144145 if issue.Open {
146 statusIcon = "circle-dot"
147 statusText = "open"
148- statusBgColor = color.RGBA{34, 139, 34, 255} // green
149 } else {
150 statusIcon = "ban"
151 statusText = "closed"
152- statusBgColor = color.RGBA{52, 58, 64, 255} // dark gray
153 }
154155- badgeIconSize := 36
000000156157- // Draw icon with status color (no background)
158- err = statusCommentsArea.DrawLucideIcon(statusIcon, statsX, statsY+iconBaselineOffset-badgeIconSize/2+5, badgeIconSize, statusBgColor)
0000159 if err != nil {
160 log.Printf("failed to draw status icon: %v", err)
161 }
162163- // Draw text with status color (no background)
164- textX := statsX + badgeIconSize + 12
165- badgeTextSize := 32.0
166- err = statusCommentsArea.DrawTextAt(statusText, textX, statsY+iconBaselineOffset, statusBgColor, badgeTextSize, ogcard.Middle, ogcard.Left)
167 if err != nil {
168 log.Printf("failed to draw status text: %v", err)
169 }
170171- statusTextWidth := len(statusText) * 20
172- currentX := statsX + badgeIconSize + 12 + statusTextWidth + 50
173174 // Draw comment count
175- err = statusCommentsArea.DrawLucideIcon("message-square", currentX, statsY+iconBaselineOffset-iconSize/2+5, iconSize, iconColor)
176 if err != nil {
177 log.Printf("failed to draw comment icon: %v", err)
178 }
···182 if commentCount == 1 {
183 commentText = "1 comment"
184 }
185- err = statusCommentsArea.DrawTextAt(commentText, currentX, statsY+iconBaselineOffset, iconColor, textSize, ogcard.Middle, ogcard.Left)
186 if err != nil {
187 log.Printf("failed to draw comment text: %v", err)
188 }
···205 openedDate := issue.Created.Format("Jan 2, 2006")
206 metaText := fmt.Sprintf("opened by %s ยท %s", authorHandle, openedDate)
207208- err = statusCommentsArea.DrawTextAt(metaText, statsX, labelY, iconColor, labelSize, ogcard.Top, ogcard.Left)
209 if err != nil {
210 log.Printf("failed to draw metadata: %v", err)
211 }
···5051 for _, tt := range tests {
52 t.Run(tt.name, func(t *testing.T) {
53- md := NewMarkdown()
5455 var buf bytes.Buffer
56 if err := md.Convert([]byte(tt.markdown), &buf); err != nil {
···105106 for _, tt := range tests {
107 t.Run(tt.name, func(t *testing.T) {
108- md := NewMarkdown()
109110 var buf bytes.Buffer
111 if err := md.Convert([]byte(tt.markdown), &buf); err != nil {
···5051 for _, tt := range tests {
52 t.Run(tt.name, func(t *testing.T) {
53+ md := NewMarkdown("tangled.org")
5455 var buf bytes.Buffer
56 if err := md.Convert([]byte(tt.markdown), &buf); err != nil {
···105106 for _, tt := range tests {
107 t.Run(tt.name, func(t *testing.T) {
108+ md := NewMarkdown("tangled.org")
109110 var buf bytes.Buffer
111 if err := md.Convert([]byte(tt.markdown), &buf); err != nil {
+4-7
appview/pages/markup/reference_link.go
···18// like issues, PRs, comments or even @-mentions
19// This funciton doesn't actually check for the existence of records in the DB
20// or the PDS; it merely returns a list of what are presumed to be references.
21-func FindReferences(baseUrl string, source string) ([]string, []models.ReferenceLink) {
22 var (
23 refLinkSet = make(map[models.ReferenceLink]struct{})
24 mentionsSet = make(map[string]struct{})
25- md = NewMarkdown()
26 sourceBytes = []byte(source)
27 root = md.Parser().Parse(text.NewReader(sourceBytes))
28 )
29- // trim url scheme. the SSL shouldn't matter
30- baseUrl = strings.TrimPrefix(baseUrl, "https://")
31- baseUrl = strings.TrimPrefix(baseUrl, "http://")
3233 ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
34 if !entering {
···41 return ast.WalkSkipChildren, nil
42 case ast.KindLink:
43 dest := string(n.(*ast.Link).Destination)
44- ref := parseTangledLink(baseUrl, dest)
45 if ref != nil {
46 refLinkSet[*ref] = struct{}{}
47 }
···50 an := n.(*ast.AutoLink)
51 if an.AutoLinkType == ast.AutoLinkURL {
52 dest := string(an.URL(sourceBytes))
53- ref := parseTangledLink(baseUrl, dest)
54 if ref != nil {
55 refLinkSet[*ref] = struct{}{}
56 }
···18// like issues, PRs, comments or even @-mentions
19// This funciton doesn't actually check for the existence of records in the DB
20// or the PDS; it merely returns a list of what are presumed to be references.
21+func FindReferences(host string, source string) ([]string, []models.ReferenceLink) {
22 var (
23 refLinkSet = make(map[models.ReferenceLink]struct{})
24 mentionsSet = make(map[string]struct{})
25+ md = NewMarkdown(host)
26 sourceBytes = []byte(source)
27 root = md.Parser().Parse(text.NewReader(sourceBytes))
28 )
0002930 ast.Walk(root, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
31 if !entering {
···38 return ast.WalkSkipChildren, nil
39 case ast.KindLink:
40 dest := string(n.(*ast.Link).Destination)
41+ ref := parseTangledLink(host, dest)
42 if ref != nil {
43 refLinkSet[*ref] = struct{}{}
44 }
···47 an := n.(*ast.AutoLink)
48 if an.AutoLinkType == ast.AutoLinkURL {
49 dest := string(an.URL(sourceBytes))
50+ ref := parseTangledLink(host, dest)
51 if ref != nil {
52 refLinkSet[*ref] = struct{}{}
53 }
···502Note that you should add a newline at the end if setting a non-empty message
503since the knot won't do this for you.
50400000000000000000000000000000505# Spindles
506507## Pipelines
···1561Refer to the [jujutsu
1562documentation](https://jj-vcs.github.io/jj/latest/config/#commit-trailers)
1563for more information.
000000000000000000000000000000000000000000000000000000
···502Note that you should add a newline at the end if setting a non-empty message
503since the knot won't do this for you.
504505+## Troubleshooting
506+507+If you run your own knot, you may run into some of these
508+common issues. You can always join the
509+[IRC](https://web.libera.chat/#tangled) or
510+[Discord](https://chat.tangled.org/) if this section does
511+not help.
512+513+### Unable to push
514+515+If you are unable to push to your knot or repository:
516+517+1. First, ensure that you have added your SSH public key to
518+ your account
519+2. Check to see that your knot has synced the key by running
520+ `knot keys`
521+3. Check to see if git is supplying the correct private key
522+ when pushing: `GIT_SSH_COMMAND="ssh -v" git push ...`
523+4. Check to see if `sshd` on the knot is rejecting the push
524+ for some reason: `journalctl -xeu ssh` (or `sshd`,
525+ depending on your machine). These logs are unavailable if
526+ using docker.
527+5. Check to see if the knot itself is rejecting the push,
528+ depending on your setup, the logs might be in one of the
529+ following paths:
530+ * `/tmp/knotguard.log`
531+ * `/home/git/log`
532+ * `/home/git/guard.log`
533+534# Spindles
535536## Pipelines
···1590Refer to the [jujutsu
1591documentation](https://jj-vcs.github.io/jj/latest/config/#commit-trailers)
1592for more information.
1593+1594+# Troubleshooting guide
1595+1596+## Login issues
1597+1598+Owing to the distributed nature of OAuth on AT Protocol, you
1599+may run into issues with logging in. If you run a
1600+self-hosted PDS:
1601+1602+- You may need to ensure that your PDS is timesynced using
1603+ NTP:
1604+ * Enable the `ntpd` service
1605+ * Run `ntpd -qg` to synchronize your clock
1606+- You may need to increase the default request timeout:
1607+ `NODE_OPTIONS="--network-family-autoselection-attempt-timeout=500"`
1608+1609+## Empty punchcard
1610+1611+For Tangled to register commits that you make across the
1612+network, you need to setup one of following:
1613+1614+- The committer email should be a verified email associated
1615+ to your account. You can add and verify emails on the
1616+ settings page.
1617+- Or, the committer email should be set to your account's
1618+ DID: `git config user.email "did:plc:foobar". You can find
1619+ your account's DID on the settings page
1620+1621+## Commit is not marked as verified
1622+1623+Presently, Tangled only supports SSH commit signatures.
1624+1625+To sign commits using an SSH key with git:
1626+1627+```
1628+git config --global gpg.format ssh
1629+git config --global user.signingkey ~/.ssh/tangled-key
1630+```
1631+1632+To sign commits using an SSH key with jj, add this to your
1633+config:
1634+1635+```
1636+[signing]
1637+behavior = "own"
1638+backend = "ssh"
1639+key = "~/.ssh/tangled-key"
1640+```
1641+1642+## Self-hosted knot issues
1643+1644+If you need help troubleshooting a self-hosted knot, check
1645+out the [knot troubleshooting
1646+guide](/knot-self-hosting-guide.html#troubleshooting).