Monorepo for Tangled

appview: move template-specific constants to global funcmap

Constants those are mainly used to define a template shouldn't be
managed from http handlers. Define constant values in `const` funcmap so
we can easily access them without depending on template params.

This change will make more sense with following change `rusppvkn`

Signed-off-by: Seongmin Lee <git@boltless.me>

authored by

Seongmin Lee and committed by tangled.org fadf2b56 23fa27d4

+57 -116
+8 -9
appview/issues/issues.go
··· 129 } 130 131 rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 132 - LoggedInUser: user, 133 - RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 134 - Issue: issue, 135 - CommentList: issue.CommentList(), 136 - Backlinks: backlinks, 137 - OrderedReactionKinds: models.OrderedReactionKinds, 138 - Reactions: reactionMap, 139 - UserReacted: userReactions, 140 - LabelDefs: defs, 141 }) 142 } 143
··· 129 } 130 131 rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{ 132 + LoggedInUser: user, 133 + RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 134 + Issue: issue, 135 + CommentList: issue.CommentList(), 136 + Backlinks: backlinks, 137 + Reactions: reactionMap, 138 + UserReacted: userReactions, 139 + LabelDefs: defs, 140 }) 141 } 142
-15
appview/knots/knots.go
··· 40 Knotstream *eventconsumer.Consumer 41 } 42 43 - type tab = map[string]any 44 - 45 - var ( 46 - knotsTabs []tab = []tab{ 47 - {"Name": "profile", "Icon": "user"}, 48 - {"Name": "keys", "Icon": "key"}, 49 - {"Name": "emails", "Icon": "mail"}, 50 - {"Name": "notifications", "Icon": "bell"}, 51 - {"Name": "knots", "Icon": "volleyball"}, 52 - {"Name": "spindles", "Icon": "spool"}, 53 - } 54 - ) 55 - 56 func (k *Knots) Router() http.Handler { 57 r := chi.NewRouter() 58 ··· 84 k.Pages.Knots(w, pages.KnotsParams{ 85 LoggedInUser: user, 86 Registrations: registrations, 87 - Tabs: knotsTabs, 88 Tab: "knots", 89 }) 90 } ··· 148 Members: members, 149 Repos: repoMap, 150 IsOwner: true, 151 - Tabs: knotsTabs, 152 Tab: "knots", 153 }) 154 }
··· 40 Knotstream *eventconsumer.Consumer 41 } 42 43 func (k *Knots) Router() http.Handler { 44 r := chi.NewRouter() 45 ··· 71 k.Pages.Knots(w, pages.KnotsParams{ 72 LoggedInUser: user, 73 Registrations: registrations, 74 Tab: "knots", 75 }) 76 } ··· 134 Members: members, 135 Repos: repoMap, 136 IsOwner: true, 137 Tab: "knots", 138 }) 139 }
+22
appview/pages/funcmap.go
··· 32 "tangled.org/core/crypto" 33 ) 34 35 func (p *Pages) funcMap() template.FuncMap { 36 return template.FuncMap{ 37 "split": func(s string) []string { ··· 423 } 424 } 425 return result 426 }, 427 } 428 }
··· 32 "tangled.org/core/crypto" 33 ) 34 35 + type tab map[string]string 36 + 37 func (p *Pages) funcMap() template.FuncMap { 38 return template.FuncMap{ 39 "split": func(s string) []string { ··· 425 } 426 } 427 return result 428 + }, 429 + // constant values used to define a template 430 + "const": func() map[string]any { 431 + return map[string]any{ 432 + "OrderedReactionKinds": models.OrderedReactionKinds, 433 + // would be great to have ordered maps right about now 434 + "UserSettingsTabs": []tab{ 435 + {"Name": "profile", "Icon": "user"}, 436 + {"Name": "keys", "Icon": "key"}, 437 + {"Name": "emails", "Icon": "mail"}, 438 + {"Name": "notifications", "Icon": "bell"}, 439 + {"Name": "knots", "Icon": "volleyball"}, 440 + {"Name": "spindles", "Icon": "spool"}, 441 + }, 442 + "RepoSettingsTabs": []tab{ 443 + {"Name": "general", "Icon": "sliders-horizontal"}, 444 + {"Name": "access", "Icon": "users"}, 445 + {"Name": "pipelines", "Icon": "layers-2"}, 446 + }, 447 + } 448 }, 449 } 450 }
+19 -35
appview/pages/pages.go
··· 338 339 type UserProfileSettingsParams struct { 340 LoggedInUser *oauth.MultiAccountUser 341 - Tabs []map[string]any 342 Tab string 343 } 344 ··· 377 type UserKeysSettingsParams struct { 378 LoggedInUser *oauth.MultiAccountUser 379 PubKeys []models.PublicKey 380 - Tabs []map[string]any 381 Tab string 382 } 383 ··· 388 type UserEmailsSettingsParams struct { 389 LoggedInUser *oauth.MultiAccountUser 390 Emails []models.Email 391 - Tabs []map[string]any 392 Tab string 393 } 394 ··· 399 type UserNotificationSettingsParams struct { 400 LoggedInUser *oauth.MultiAccountUser 401 Preferences *models.NotificationPreferences 402 - Tabs []map[string]any 403 Tab string 404 } 405 ··· 419 type KnotsParams struct { 420 LoggedInUser *oauth.MultiAccountUser 421 Registrations []models.Registration 422 - Tabs []map[string]any 423 Tab string 424 } 425 ··· 433 Members []string 434 Repos map[string][]models.Repo 435 IsOwner bool 436 - Tabs []map[string]any 437 Tab string 438 } 439 ··· 452 type SpindlesParams struct { 453 LoggedInUser *oauth.MultiAccountUser 454 Spindles []models.Spindle 455 - Tabs []map[string]any 456 Tab string 457 } 458 ··· 462 463 type SpindleListingParams struct { 464 models.Spindle 465 - Tabs []map[string]any 466 - Tab string 467 } 468 469 func (p *Pages) SpindleListing(w io.Writer, params SpindleListingParams) error { ··· 475 Spindle models.Spindle 476 Members []string 477 Repos map[string][]models.Repo 478 - Tabs []map[string]any 479 Tab string 480 } 481 ··· 886 SubscribedLabels map[string]struct{} 887 ShouldSubscribeAll bool 888 Active string 889 - Tabs []map[string]any 890 Tab string 891 Branches []types.Branch 892 } ··· 900 LoggedInUser *oauth.MultiAccountUser 901 RepoInfo repoinfo.RepoInfo 902 Active string 903 - Tabs []map[string]any 904 Tab string 905 Collaborators []Collaborator 906 } ··· 914 LoggedInUser *oauth.MultiAccountUser 915 RepoInfo repoinfo.RepoInfo 916 Active string 917 - Tabs []map[string]any 918 Tab string 919 Spindles []string 920 CurrentSpindle string ··· 952 Backlinks []models.RichReferenceLink 953 LabelDefs map[string]*models.LabelDefinition 954 955 - OrderedReactionKinds []models.ReactionKind 956 - Reactions map[models.ReactionKind]models.ReactionDisplayData 957 - UserReacted map[models.ReactionKind]bool 958 } 959 960 func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { ··· 1115 ActiveRound int 1116 IsInterdiff bool 1117 1118 - OrderedReactionKinds []models.ReactionKind 1119 - Reactions map[models.ReactionKind]models.ReactionDisplayData 1120 - UserReacted map[models.ReactionKind]bool 1121 1122 LabelDefs map[string]*models.LabelDefinition 1123 } ··· 1128 } 1129 1130 type RepoPullPatchParams struct { 1131 - LoggedInUser *oauth.MultiAccountUser 1132 - RepoInfo repoinfo.RepoInfo 1133 - Pull *models.Pull 1134 - Stack models.Stack 1135 - Diff *types.NiceDiff 1136 - Round int 1137 - Submission *models.PullSubmission 1138 - OrderedReactionKinds []models.ReactionKind 1139 - DiffOpts types.DiffOpts 1140 } 1141 1142 // this name is a mouthful ··· 1145 } 1146 1147 type RepoPullInterdiffParams struct { 1148 - LoggedInUser *oauth.MultiAccountUser 1149 - RepoInfo repoinfo.RepoInfo 1150 - Pull *models.Pull 1151 - Round int 1152 - Interdiff *patchutil.InterdiffResult 1153 - OrderedReactionKinds []models.ReactionKind 1154 - DiffOpts types.DiffOpts 1155 } 1156 1157 // this name is a mouthful
··· 338 339 type UserProfileSettingsParams struct { 340 LoggedInUser *oauth.MultiAccountUser 341 Tab string 342 } 343 ··· 376 type UserKeysSettingsParams struct { 377 LoggedInUser *oauth.MultiAccountUser 378 PubKeys []models.PublicKey 379 Tab string 380 } 381 ··· 386 type UserEmailsSettingsParams struct { 387 LoggedInUser *oauth.MultiAccountUser 388 Emails []models.Email 389 Tab string 390 } 391 ··· 396 type UserNotificationSettingsParams struct { 397 LoggedInUser *oauth.MultiAccountUser 398 Preferences *models.NotificationPreferences 399 Tab string 400 } 401 ··· 415 type KnotsParams struct { 416 LoggedInUser *oauth.MultiAccountUser 417 Registrations []models.Registration 418 Tab string 419 } 420 ··· 428 Members []string 429 Repos map[string][]models.Repo 430 IsOwner bool 431 Tab string 432 } 433 ··· 446 type SpindlesParams struct { 447 LoggedInUser *oauth.MultiAccountUser 448 Spindles []models.Spindle 449 Tab string 450 } 451 ··· 455 456 type SpindleListingParams struct { 457 models.Spindle 458 + Tab string 459 } 460 461 func (p *Pages) SpindleListing(w io.Writer, params SpindleListingParams) error { ··· 467 Spindle models.Spindle 468 Members []string 469 Repos map[string][]models.Repo 470 Tab string 471 } 472 ··· 877 SubscribedLabels map[string]struct{} 878 ShouldSubscribeAll bool 879 Active string 880 Tab string 881 Branches []types.Branch 882 } ··· 890 LoggedInUser *oauth.MultiAccountUser 891 RepoInfo repoinfo.RepoInfo 892 Active string 893 Tab string 894 Collaborators []Collaborator 895 } ··· 903 LoggedInUser *oauth.MultiAccountUser 904 RepoInfo repoinfo.RepoInfo 905 Active string 906 Tab string 907 Spindles []string 908 CurrentSpindle string ··· 940 Backlinks []models.RichReferenceLink 941 LabelDefs map[string]*models.LabelDefinition 942 943 + Reactions map[models.ReactionKind]models.ReactionDisplayData 944 + UserReacted map[models.ReactionKind]bool 945 } 946 947 func (p *Pages) RepoSingleIssue(w io.Writer, params RepoSingleIssueParams) error { ··· 1102 ActiveRound int 1103 IsInterdiff bool 1104 1105 + Reactions map[models.ReactionKind]models.ReactionDisplayData 1106 + UserReacted map[models.ReactionKind]bool 1107 1108 LabelDefs map[string]*models.LabelDefinition 1109 } ··· 1114 } 1115 1116 type RepoPullPatchParams struct { 1117 + LoggedInUser *oauth.MultiAccountUser 1118 + RepoInfo repoinfo.RepoInfo 1119 + Pull *models.Pull 1120 + Stack models.Stack 1121 + Diff *types.NiceDiff 1122 + Round int 1123 + Submission *models.PullSubmission 1124 + DiffOpts types.DiffOpts 1125 } 1126 1127 // this name is a mouthful ··· 1130 } 1131 1132 type RepoPullInterdiffParams struct { 1133 + LoggedInUser *oauth.MultiAccountUser 1134 + RepoInfo repoinfo.RepoInfo 1135 + Pull *models.Pull 1136 + Round int 1137 + Interdiff *patchutil.InterdiffResult 1138 + DiffOpts types.DiffOpts 1139 } 1140 1141 // this name is a mouthful
+2 -2
appview/pages/templates/repo/issues/issue.html
··· 109 110 {{ define "issueReactions" }} 111 <div class="flex items-center gap-2"> 112 - {{ template "repo/fragments/reactionsPopUp" .OrderedReactionKinds }} 113 - {{ range $kind := .OrderedReactionKinds }} 114 {{ $reactionData := index $.Reactions $kind }} 115 {{ 116 template "repo/fragments/reaction"
··· 109 110 {{ define "issueReactions" }} 111 <div class="flex items-center gap-2"> 112 + {{ template "repo/fragments/reactionsPopUp" const.OrderedReactionKinds }} 113 + {{ range $kind := const.OrderedReactionKinds }} 114 {{ $reactionData := index $.Reactions $kind }} 115 {{ 116 template "repo/fragments/reaction"
+1 -1
appview/pages/templates/repo/pulls/fragments/pullHeader.html
··· 63 </article> 64 {{ end }} 65 66 - {{ with .OrderedReactionKinds }} 67 <div class="flex items-center gap-2 mt-2"> 68 {{ template "repo/fragments/reactionsPopUp" . }} 69 {{ range $kind := . }}
··· 63 </article> 64 {{ end }} 65 66 + {{ with const.OrderedReactionKinds }} 67 <div class="flex items-center gap-2 mt-2"> 68 {{ template "repo/fragments/reactionsPopUp" . }} 69 {{ range $kind := . }}
+1 -2
appview/pages/templates/repo/settings/fragments/sidebar.html
··· 1 {{ define "repo/settings/fragments/sidebar" }} 2 {{ $active := .Tab }} 3 - {{ $tabs := .Tabs }} 4 <div class="sticky top-2 grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 shadow-inner"> 5 {{ $activeTab := "bg-white dark:bg-gray-700 drop-shadow-sm" }} 6 {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800" }} 7 - {{ range $tabs }} 8 <a href="/{{ $.RepoInfo.FullName }}/settings?tab={{.Name}}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 9 <div class="flex gap-3 items-center p-2 {{ if eq .Name $active }} {{ $activeTab }} {{ else }} {{ $inactiveTab }} {{ end }}"> 10 {{ i .Icon "size-4" }}
··· 1 {{ define "repo/settings/fragments/sidebar" }} 2 {{ $active := .Tab }} 3 <div class="sticky top-2 grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 shadow-inner"> 4 {{ $activeTab := "bg-white dark:bg-gray-700 drop-shadow-sm" }} 5 {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800" }} 6 + {{ range const.RepoSettingsTabs }} 7 <a href="/{{ $.RepoInfo.FullName }}/settings?tab={{.Name}}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 8 <div class="flex gap-3 items-center p-2 {{ if eq .Name $active }} {{ $activeTab }} {{ else }} {{ $inactiveTab }} {{ end }}"> 9 {{ i .Icon "size-4" }}
+2 -3
appview/pages/templates/user/settings/fragments/sidebar.html
··· 1 {{ define "user/settings/fragments/sidebar" }} 2 {{ $active := .Tab }} 3 - {{ $tabs := .Tabs }} 4 <div class="sticky top-2 grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 shadow-inner"> 5 {{ $activeTab := "bg-white dark:bg-gray-700 drop-shadow-sm" }} 6 {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800" }} 7 - {{ range $tabs }} 8 <a href="/settings/{{.Name}}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 9 <div class="flex gap-3 items-center p-2 {{ if eq .Name $active }} {{ $activeTab }} {{ else }} {{ $inactiveTab }} {{ end }}"> 10 {{ i .Icon "size-4" }} ··· 13 </a> 14 {{ end }} 15 </div> 16 - {{ end }}
··· 1 {{ define "user/settings/fragments/sidebar" }} 2 {{ $active := .Tab }} 3 <div class="sticky top-2 grid grid-cols-1 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 shadow-inner"> 4 {{ $activeTab := "bg-white dark:bg-gray-700 drop-shadow-sm" }} 5 {{ $inactiveTab := "bg-gray-100 dark:bg-gray-800" }} 6 + {{ range const.UserSettingsTabs }} 7 <a href="/settings/{{.Name}}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 8 <div class="flex gap-3 items-center p-2 {{ if eq .Name $active }} {{ $activeTab }} {{ else }} {{ $inactiveTab }} {{ end }}"> 9 {{ i .Icon "size-4" }} ··· 12 </a> 13 {{ end }} 14 </div> 15 + {{ end }}
+2 -3
appview/pulls/pulls.go
··· 289 ActiveRound: roundIdInt, 290 IsInterdiff: interdiff, 291 292 - OrderedReactionKinds: models.OrderedReactionKinds, 293 - Reactions: reactionMap, 294 - UserReacted: userReactions, 295 296 LabelDefs: defs, 297 })
··· 289 ActiveRound: roundIdInt, 290 IsInterdiff: interdiff, 291 292 + Reactions: reactionMap, 293 + UserReacted: userReactions, 294 295 LabelDefs: defs, 296 })
-14
appview/repo/settings.go
··· 22 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 23 ) 24 25 - type tab = map[string]any 26 - 27 - var ( 28 - // would be great to have ordered maps right about now 29 - settingsTabs []tab = []tab{ 30 - {"Name": "general", "Icon": "sliders-horizontal"}, 31 - {"Name": "access", "Icon": "users"}, 32 - {"Name": "pipelines", "Icon": "layers-2"}, 33 - } 34 - ) 35 - 36 func (rp *Repo) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 37 l := rp.logger.With("handler", "SetDefaultBranch") 38 ··· 262 DefaultLabels: defaultLabels, 263 SubscribedLabels: subscribedLabels, 264 ShouldSubscribeAll: shouldSubscribeAll, 265 - Tabs: settingsTabs, 266 Tab: "general", 267 }) 268 } ··· 308 rp.pages.RepoAccessSettings(w, pages.RepoAccessSettingsParams{ 309 LoggedInUser: user, 310 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 311 - Tabs: settingsTabs, 312 Tab: "access", 313 Collaborators: collaborators, 314 }) ··· 369 rp.pages.RepoPipelineSettings(w, pages.RepoPipelineSettingsParams{ 370 LoggedInUser: user, 371 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 372 - Tabs: settingsTabs, 373 Tab: "pipelines", 374 Spindles: spindles, 375 CurrentSpindle: f.Spindle,
··· 22 indigoxrpc "github.com/bluesky-social/indigo/xrpc" 23 ) 24 25 func (rp *Repo) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 26 l := rp.logger.With("handler", "SetDefaultBranch") 27 ··· 251 DefaultLabels: defaultLabels, 252 SubscribedLabels: subscribedLabels, 253 ShouldSubscribeAll: shouldSubscribeAll, 254 Tab: "general", 255 }) 256 } ··· 296 rp.pages.RepoAccessSettings(w, pages.RepoAccessSettingsParams{ 297 LoggedInUser: user, 298 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 299 Tab: "access", 300 Collaborators: collaborators, 301 }) ··· 356 rp.pages.RepoPipelineSettings(w, pages.RepoPipelineSettingsParams{ 357 LoggedInUser: user, 358 RepoInfo: rp.repoResolver.GetRepoInfo(r, user), 359 Tab: "pipelines", 360 Spindles: spindles, 361 CurrentSpindle: f.Spindle,
-17
appview/settings/settings.go
··· 35 Config *config.Config 36 } 37 38 - type tab = map[string]any 39 - 40 - var ( 41 - settingsTabs []tab = []tab{ 42 - {"Name": "profile", "Icon": "user"}, 43 - {"Name": "keys", "Icon": "key"}, 44 - {"Name": "emails", "Icon": "mail"}, 45 - {"Name": "notifications", "Icon": "bell"}, 46 - {"Name": "knots", "Icon": "volleyball"}, 47 - {"Name": "spindles", "Icon": "spool"}, 48 - } 49 - ) 50 - 51 func (s *Settings) Router() http.Handler { 52 r := chi.NewRouter() 53 ··· 85 86 s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{ 87 LoggedInUser: user, 88 - Tabs: settingsTabs, 89 Tab: "profile", 90 }) 91 } ··· 104 s.Pages.UserNotificationSettings(w, pages.UserNotificationSettingsParams{ 105 LoggedInUser: user, 106 Preferences: prefs, 107 - Tabs: settingsTabs, 108 Tab: "notifications", 109 }) 110 } ··· 146 s.Pages.UserKeysSettings(w, pages.UserKeysSettingsParams{ 147 LoggedInUser: user, 148 PubKeys: pubKeys, 149 - Tabs: settingsTabs, 150 Tab: "keys", 151 }) 152 } ··· 161 s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{ 162 LoggedInUser: user, 163 Emails: emails, 164 - Tabs: settingsTabs, 165 Tab: "emails", 166 }) 167 }
··· 35 Config *config.Config 36 } 37 38 func (s *Settings) Router() http.Handler { 39 r := chi.NewRouter() 40 ··· 72 73 s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{ 74 LoggedInUser: user, 75 Tab: "profile", 76 }) 77 } ··· 90 s.Pages.UserNotificationSettings(w, pages.UserNotificationSettingsParams{ 91 LoggedInUser: user, 92 Preferences: prefs, 93 Tab: "notifications", 94 }) 95 } ··· 131 s.Pages.UserKeysSettings(w, pages.UserKeysSettingsParams{ 132 LoggedInUser: user, 133 PubKeys: pubKeys, 134 Tab: "keys", 135 }) 136 } ··· 145 s.Pages.UserEmailsSettings(w, pages.UserEmailsSettingsParams{ 146 LoggedInUser: user, 147 Emails: emails, 148 Tab: "emails", 149 }) 150 }
-15
appview/spindles/spindles.go
··· 39 Logger *slog.Logger 40 } 41 42 - type tab = map[string]any 43 - 44 - var ( 45 - spindlesTabs []tab = []tab{ 46 - {"Name": "profile", "Icon": "user"}, 47 - {"Name": "keys", "Icon": "key"}, 48 - {"Name": "emails", "Icon": "mail"}, 49 - {"Name": "notifications", "Icon": "bell"}, 50 - {"Name": "knots", "Icon": "volleyball"}, 51 - {"Name": "spindles", "Icon": "spool"}, 52 - } 53 - ) 54 - 55 func (s *Spindles) Router() http.Handler { 56 r := chi.NewRouter() 57 ··· 83 s.Pages.Spindles(w, pages.SpindlesParams{ 84 LoggedInUser: user, 85 Spindles: all, 86 - Tabs: spindlesTabs, 87 Tab: "spindles", 88 }) 89 } ··· 143 Spindle: spindle, 144 Members: members, 145 Repos: repoMap, 146 - Tabs: spindlesTabs, 147 Tab: "spindles", 148 }) 149 }
··· 39 Logger *slog.Logger 40 } 41 42 func (s *Spindles) Router() http.Handler { 43 r := chi.NewRouter() 44 ··· 70 s.Pages.Spindles(w, pages.SpindlesParams{ 71 LoggedInUser: user, 72 Spindles: all, 73 Tab: "spindles", 74 }) 75 } ··· 129 Spindle: spindle, 130 Members: members, 131 Repos: repoMap, 132 Tab: "spindles", 133 }) 134 }