+1263
-170
api/tangled/cbor_gen.go
+1263
-170
api/tangled/cbor_gen.go
·····················+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.label.definition"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.label.definition"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("sh.tangled.label.op"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("performedAt"))); err != nil {+if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.PerformedAt))); err != nil {·········
+43
api/tangled/labeldefinition.go
+43
api/tangled/labeldefinition.go
···+LexiconTypeID string `json:"$type,const=sh.tangled.label.definition" cborgen:"$type,const=sh.tangled.label.definition"`+// color: The hex value for the background color for the label. Appviews may choose to respect this.+// multiple: Whether this label can be repeated for a given entity, eg.: [reviewer:foo, reviewer:bar]+// scope: The area of the repo this label may apply to, eg.: sh.tangled.repo.issue. Appviews may choose to respect this.+LexiconTypeID string `json:"$type,const=sh.tangled.label.definition" cborgen:"$type,const=sh.tangled.label.definition"`
+34
api/tangled/labelop.go
+34
api/tangled/labelop.go
···+LexiconTypeID string `json:"$type,const=sh.tangled.label.op" cborgen:"$type,const=sh.tangled.label.op"`+// subject: The subject (task, pull or discussion) of this label. Appviews may apply a `scope` check and refuse this op.+// value: Stringified value of the label. This is first unstringed by appviews and then interpreted as a concrete value.
+3
-2
api/tangled/tangledrepo.go
+3
-2
api/tangled/tangledrepo.go
···
+65
-1
appview/db/db.go
+65
-1
appview/db/db.go
···+at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.label.definition' || '/' || rkey) stored,+-- ops are flattened, a record may contain several additions and deletions, but the table will include one row per add/del+at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.label.op' || '/' || rkey) stored,+-- we need two time values: performed is declared by the user, indexed is calculated by the av···
+21
-5
appview/db/issues.go
+21
-5
appview/db/issues.go
······
+721
appview/db/label.go
+721
appview/db/label.go
···+func LabelDefinitionFromRecord(did, rkey string, record tangled.LabelDefinition) LabelDefinition {+} else if _, exists = valueSet[op.OperandValue]; !exists { // if value DNE, then deletion is no-op
+2
-2
appview/db/profile.go
+2
-2
appview/db/profile.go
···
+138
-31
appview/db/repos.go
+138
-31
appview/db/repos.go
············-if err := row.Scan(&repo.Did, &repo.Name, &repo.Knot, &createdAt, &description, &spindle, &repo.Rkey); err != nil {···
+26
appview/issues/issues.go
+26
appview/issues/issues.go
·········
+1
-1
appview/issues/router.go
+1
-1
appview/issues/router.go
+240
appview/labels/labels.go
+240
appview/labels/labels.go
···+l.logger.Info("performed label op", "did", o.Did, "rkey", o.Rkey, "kind", o.Operation, "subjcet", o.Subject, "key", o.OperandKey)
+38
-37
appview/middleware/middleware.go
+38
-37
appview/middleware/middleware.go
······
+18
appview/notify/merged_notifier.go
+18
appview/notify/merged_notifier.go
···
+8
appview/notify/notifier.go
+8
appview/notify/notifier.go
······
+3
appview/pages/funcmap.go
+3
appview/pages/funcmap.go
+1
-1
appview/pages/markup/markdown.go
+1
-1
appview/pages/markup/markdown.go
···
+8
-6
appview/pages/pages.go
+8
-6
appview/pages/pages.go
·········
+2
-2
appview/pages/repoinfo/repoinfo.go
+2
-2
appview/pages/repoinfo/repoinfo.go
···
+90
appview/pages/templates/fragments/multiline-select.html
+90
appview/pages/templates/fragments/multiline-select.html
···
+8
appview/pages/templates/labels/fragments/label.html
+8
appview/pages/templates/labels/fragments/label.html
···+<span class="flex items-center gap-2 font-normal normal-case rounded py-1 px-2 border border-gray-200 dark:border-gray-700 text-sm">
+6
appview/pages/templates/labels/fragments/labelDef.html
+6
appview/pages/templates/labels/fragments/labelDef.html
+7
-2
appview/pages/templates/layouts/profilebase.html
+7
-2
appview/pages/templates/layouts/profilebase.html
···+<section class="bg-white dark:bg-gray-800 px-2 py-6 md:p-6 rounded w-full dark:text-white drop-shadow-sm">
+1
appview/pages/templates/repo/blob.html
+1
appview/pages/templates/repo/blob.html
+119
appview/pages/templates/repo/fragments/addLabelModal.html
+119
appview/pages/templates/repo/fragments/addLabelModal.html
···+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"+<select name="operand-val" class="w-full p-1 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600">+<select name="operand-val" class="w-full p-1 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600">
+6
appview/pages/templates/repo/fragments/colorBall.html
+6
appview/pages/templates/repo/fragments/colorBall.html
-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));"
+6
-1
appview/pages/templates/repo/fragments/shortTimeAgo.html
+6
-1
appview/pages/templates/repo/fragments/shortTimeAgo.html
···-{{ template "repo/fragments/timeWrapper" (dict "Time" . "Content" (print (. | shortRelTimeFmt) " ago")) }}
+1
-1
appview/pages/templates/repo/index.html
+1
-1
appview/pages/templates/repo/index.html
···
+28
-2
appview/pages/templates/repo/issues/issue.html
+28
-2
appview/pages/templates/repo/issues/issue.html
···<article id="body" class="mt-4 prose dark:prose-invert">{{ .Issue.Body | markdown }}</article>······+class="bg-white w-full sm:w-96 dark:bg-gray-800 p-6 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">+{{ template "repo/fragments/addLabelModal" (dict "root" $ "subject" $.Issue.AtUri.String "state" $.Issue.Labels) }}
+13
-3
appview/pages/templates/repo/issues/issues.html
+13
-3
appview/pages/templates/repo/issues/issues.html
······<a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a>
+104
appview/pages/templates/repo/settings/fragments/addLabelDefModal.html
+104
appview/pages/templates/repo/settings/fragments/addLabelDefModal.html
···+<p class="text-gray-500 dark:text-gray-400">Labels can have a name and a value. Set the value type to "none" to create a simple label.</p>+<input class="w-full" type="text" id="label-name" name="name" required placeholder="improvement"/>+<select id="value-type" name="valueType" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600">+<span class="group-open:hidden inline text-gray-500 dark:text-gray-400">{{ i "square-plus" "w-4 h-4" }}</span>+<span class="hidden group-open:inline text-gray-500 dark:text-gray-400">{{ i "square-minus" "w-4 h-4" }}</span>+<input type="text" id="enumValues" name="enumValues" placeholder="value1, value2, value3" class="w-full"/>+<p class="text-sm text-gray-400 dark:text-gray-500 mt-1">Enter comma-separated list of permitted values.</p>+<select id="scope" name="scope" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600">+{{ $colors := list "#ef4444" "#3b82f6" "#10b981" "#f59e0b" "#8b5cf6" "#ec4899" "#06b6d4" "#64748b" }}+<input type="radio" name="color" value="{{ $color }}" class="sr-only peer" {{ if eq $i 0 }} checked {{ end }}>+{{ template "repo/fragments/colorBall" (dict "color" $color "classes" "size-4 peer-checked:size-8 transition-all") }}+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"
+28
appview/pages/templates/repo/settings/fragments/labelListing.html
+28
appview/pages/templates/repo/settings/fragments/labelListing.html
···+class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
+42
appview/pages/templates/repo/settings/general.html
+42
appview/pages/templates/repo/settings/general.html
······+class="bg-white w-full sm:w-96 dark:bg-gray-800 p-6 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">+<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">···
+1
-1
appview/pages/templates/repo/settings/pipelines.html
+1
-1
appview/pages/templates/repo/settings/pipelines.html
···<p class="text-sm text-gray-500 dark:text-gray-400">Secrets are available as environment variables in the workflow.</p>
+3
-3
appview/pages/templates/repo/tree.html
+3
-3
appview/pages/templates/repo/tree.html
···<div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500">-<a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> /+<a href="{{ index . 1 }}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> /<div id="dir-info" class="text-gray-500 dark:text-gray-400 text-xs md:text-sm flex flex-wrap items-center gap-1 md:gap-0">+<span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ pathEscape $.Ref }}">{{ $.Ref }}</a></span>···-{{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (urlquery $.Ref) $.TreePath .Name }}+{{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (pathEscape $.Ref) $.TreePath .Name }}
+3
-2
appview/pages/templates/strings/string.html
+3
-2
appview/pages/templates/strings/string.html
·········<div id="blob-contents" class="whitespace-pre peer-target:bg-yellow-200 dark:peer-target:bg-yellow-900">{{ .String.Contents | escapeHtml }}</div>
+1
-1
appview/pages/templates/user/fragments/repoCard.html
+1
-1
appview/pages/templates/user/fragments/repoCard.html
···
+1
-1
appview/pages/templates/user/overview.html
+1
-1
appview/pages/templates/user/overview.html
···
+33
appview/posthog/notifier.go
+33
appview/posthog/notifier.go
···
+6
-2
appview/pulls/pulls.go
+6
-2
appview/pulls/pulls.go
···
+16
-17
appview/repo/index.go
+16
-17
appview/repo/index.go
·····················
+400
-166
appview/repo/repo.go
+400
-166
appview/repo/repo.go
·································// this is a bit of a pain because the golang atproto impl does not allow nil SwapRecord field// SwapRecord is optional and should happen automagically, but given that it does not, we have to perform two requests··················// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,-http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound)+redirectTo := fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), url.PathEscape(ref), result.Parent)-breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})+breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})-breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})+breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})············-breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})+breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})-breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})+breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})············removingSpindle := newSpindle == "[[none]]" // see pages/templates/repo/settings/pipelines.html for more info on why we use this value······+ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, newRepo.Did, newRepo.Rkey)+ex, err := client.RepoGetRecord(r.Context(), "", tangled.RepoNSID, newRepo.Did, newRepo.Rkey)····································
+2
appview/repo/router.go
+2
appview/repo/router.go
···
-1
appview/state/profile.go
-1
appview/state/profile.go
+8
-4
appview/state/router.go
+8
-4
appview/state/router.go
············-repo := repo.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.notifier, s.enforcer, logger)+repo := repo.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.notifier, s.enforcer, logger, s.validator)pipes := pipelines.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.enforcer)+ls := labels.New(s.oauth, s.repoResolver, s.pages, s.spindlestream, s.idResolver, s.db, s.config, s.enforcer)
+9
-8
appview/state/state.go
+9
-8
appview/state/state.go
···s.pages.Notice(w, "repo", fmt.Sprintf("You already have a repository by this name on %s", existingRepo.Knot))······
+8
-6
appview/strings/strings.go
+8
-6
appview/strings/strings.go
···············
+77
appview/validator/label.go
+77
appview/validator/label.go
···+return fmt.Errorf("label name contains invalid characters (use only letters, numbers, hyphens, and underscores)")+return fmt.Errorf("invalid value type: %q (must be one of: null, boolean, integer, string)", label.ValueType)+color = fmt.Sprintf("#%c%c%c%c%c%c", color[1], color[1], color[2], color[2], color[3], color[3])
+5
-1
cmd/gen.go
+5
-1
cmd/gen.go
···
+44
contrib/Tiltfile
+44
contrib/Tiltfile
···
+16
default.nix
+16
default.nix
···
+15
flake.lock
+15
flake.lock
···+"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1"···
+7
-1
flake.nix
+7
-1
flake.nix
············+${pkgs.tailwindcss}/bin/tailwindcss --watch=always -i input.css -o ./appview/pages/static/tw.css
+2
-5
input.css
+2
-5
input.css
······
+4
-1
knotserver/git/language.go
+4
-1
knotserver/git/language.go
······
+2
-2
knotserver/ingester.go
+2
-2
knotserver/ingester.go
···return fmt.Errorf("rejected pull record: not this knot, %s != %s", repo.Knot, h.c.Server.Hostname)···
-36
knotserver/util.go
-36
knotserver/util.go
···
+1
-10
knotserver/xrpc/list_keys.go
+1
-10
knotserver/xrpc/list_keys.go
······
+1
-10
knotserver/xrpc/owner.go
+1
-10
knotserver/xrpc/owner.go
······
+8
-7
knotserver/xrpc/repo_archive.go
+8
-7
knotserver/xrpc/repo_archive.go
······
+8
-15
knotserver/xrpc/repo_blob.go
+8
-15
knotserver/xrpc/repo_blob.go
···············
+5
-16
knotserver/xrpc/repo_branch.go
+5
-16
knotserver/xrpc/repo_branch.go
············
+11
-25
knotserver/xrpc/repo_branches.go
+11
-25
knotserver/xrpc/repo_branches.go
············
+7
-23
knotserver/xrpc/repo_compare.go
+7
-23
knotserver/xrpc/repo_compare.go
···············
+6
-30
knotserver/xrpc/repo_diff.go
+6
-30
knotserver/xrpc/repo_diff.go
······
+4
-19
knotserver/xrpc/repo_get_default_branch.go
+4
-19
knotserver/xrpc/repo_get_default_branch.go
·········
+4
-21
knotserver/xrpc/repo_languages.go
+4
-21
knotserver/xrpc/repo_languages.go
············
+3
-33
knotserver/xrpc/repo_log.go
+3
-33
knotserver/xrpc/repo_log.go
············
+6
-33
knotserver/xrpc/repo_tree.go
+6
-33
knotserver/xrpc/repo_tree.go
············
+1
-11
knotserver/xrpc/version.go
+1
-11
knotserver/xrpc/version.go
······
+14
-35
knotserver/xrpc/xrpc.go
+14
-35
knotserver/xrpc/xrpc.go
······
+90
lexicons/label/definition.json
+90
lexicons/label/definition.json
···+"description": "The type definition of this label. Appviews may allow sorting for certain types."+"description": "The area of the repo this label may apply to, eg.: sh.tangled.repo.issue. Appviews may choose to respect this."+"description": "The hex value for the background color for the label. Appviews may choose to respect this."+"description": "Whether this label can be repeated for a given entity, eg.: [reviewer:foo, reviewer:bar]"
+64
lexicons/label/op.json
+64
lexicons/label/op.json
···+"description": "The subject (task, pull or discussion) of this label. Appviews may apply a `scope` check and refuse this op."+"description": "Stringified value of the label. This is first unstringed by appviews and then interpreted as a concrete value."
+8
-5
lexicons/repo/repo.json
+8
-5
lexicons/repo/repo.json
······
+6
-6
spindle/ingester.go
+6
-6
spindle/ingester.go
······-l.Info("different spindle configured", "did", record.Owner, "name", record.Name, "spindle", *record.Spindle, "domain", domain)+l.Info("different spindle configured", "name", record.Name, "spindle", *record.Spindle, "domain", domain)
+1
-1
spindle/xrpc/add_secret.go
+1
-1
spindle/xrpc/add_secret.go
+1
-1
spindle/xrpc/list_secrets.go
+1
-1
spindle/xrpc/list_secrets.go
+1
-1
spindle/xrpc/remove_secret.go
+1
-1
spindle/xrpc/remove_secret.go
+10
xrpc/errors/errors.go
+10
xrpc/errors/errors.go
···