diff --git a/config-appview.example.yaml b/config-appview.example.yaml index 7176efe..73932b1 100644 --- a/config-appview.example.yaml +++ b/config-appview.example.yaml @@ -50,6 +50,8 @@ ui: libsql_sync_interval: 1m0s # Source code URL displayed in the footer "Source" link. Defaults to the upstream ATCR project. source_url: https://tangled.org/evan.jarrett.net/at-container-registry + # Bluesky handle or DID linked from the footer. Prefer a DID: handles can change. Defaults to the ATCR project account. Set to "" in YAML to hide the link (an empty env var will not do it: Viper treats it as unset). + bluesky_profile: did:plc:wfj5kyialpmcv2fzk6uqwsln # Health check and cache settings. health: # How long to cache hold health check results. diff --git a/pkg/appview/config.go b/pkg/appview/config.go index 39a52d4..c2bfc2e 100644 --- a/pkg/appview/config.go +++ b/pkg/appview/config.go @@ -96,6 +96,10 @@ type UIConfig struct { // Source code URL displayed in the footer "Source" link. SourceURL string `yaml:"source_url" comment:"Source code URL displayed in the footer \"Source\" link. Defaults to the upstream ATCR project."` + + // Bluesky profile linked from the footer. Handle or DID; prefer a DID, + // since handles change and a stale handle link breaks silently. + BlueskyProfile string `yaml:"bluesky_profile" comment:"Bluesky handle or DID linked from the footer. Prefer a DID: handles can change. Defaults to the ATCR project account. Set to \"\" in YAML to hide the link (an empty env var will not do it: Viper treats it as unset)."` } // HealthConfig defines health check and cache settings @@ -236,6 +240,7 @@ func setDefaults(v *viper.Viper) { v.SetDefault("ui.libsql_auth_token", "") v.SetDefault("ui.libsql_sync_interval", "60s") v.SetDefault("ui.source_url", "https://tangled.org/evan.jarrett.net/at-container-registry") + v.SetDefault("ui.bluesky_profile", "did:plc:wfj5kyialpmcv2fzk6uqwsln") // Health defaults v.SetDefault("health.cache_ttl", "15m") diff --git a/pkg/appview/handlers/base.go b/pkg/appview/handlers/base.go index 9bd9a0f..83fe4d2 100644 --- a/pkg/appview/handlers/base.go +++ b/pkg/appview/handlers/base.go @@ -53,6 +53,7 @@ type BaseUIHandler struct { AIAdvisorEnabled bool // True when billing is fully configured AND Claude API key is set BillingEnabled bool // True when the billing build is compiled in and Stripe is configured SourceURL string // Source code URL for the footer "Source" link + BlueskyProfile string // Bluesky handle or DID for the footer link ("" hides it) } // IsManagedHold reports whether a hold DID is one of the appview's managed diff --git a/pkg/appview/handlers/common.go b/pkg/appview/handlers/common.go index d1bc30d..19e1573 100644 --- a/pkg/appview/handlers/common.go +++ b/pkg/appview/handlers/common.go @@ -20,6 +20,7 @@ type PageData struct { OciClient string // Preferred OCI client for pull commands (e.g., "docker", "podman") AIAdvisorEnabled bool // True when AI Image Advisor is available SourceURL string // Source code URL for the footer "Source" link + BlueskyProfile string // Bluesky handle or DID for the footer link ("" hides it) CurrentPath string // Request path (used for OAuth return_to) } @@ -51,6 +52,7 @@ func NewPageData(r *http.Request, h *BaseUIHandler) PageData { OciClient: ociClient, AIAdvisorEnabled: h.AIAdvisorEnabled, SourceURL: h.SourceURL, + BlueskyProfile: h.BlueskyProfile, CurrentPath: r.URL.RequestURI(), } } diff --git a/pkg/appview/routes/routes.go b/pkg/appview/routes/routes.go index f05bd75..85dc097 100644 --- a/pkg/appview/routes/routes.go +++ b/pkg/appview/routes/routes.go @@ -48,6 +48,7 @@ type UIDependencies struct { WebhookDispatcher *webhooks.Dispatcher // Webhook dispatcher (nil if not configured) ClaudeAPIKey string // Anthropic API key for AI advisor (empty = disabled) SourceURL string // Source code URL for the footer "Source" link + BlueskyProfile string // Bluesky handle or DID for the footer link ("" hides it) } // RegisterUIRoutes registers all web UI and API routes on the provided router @@ -88,6 +89,7 @@ func RegisterUIRoutes(router chi.Router, deps UIDependencies) { BillingEnabled: deps.BillingManager != nil && deps.BillingManager.Enabled(), AIAdvisorEnabled: deps.BillingManager != nil && deps.BillingManager.Enabled() && deps.ClaudeAPIKey != "", SourceURL: deps.SourceURL, + BlueskyProfile: deps.BlueskyProfile, } // OAuth login routes (public) diff --git a/pkg/appview/server.go b/pkg/appview/server.go index afa07ee..b5bb4f7 100644 --- a/pkg/appview/server.go +++ b/pkg/appview/server.go @@ -412,6 +412,7 @@ func NewAppViewServer(cfg *Config, branding *BrandingOverrides) (*AppViewServer, WebhookDispatcher: s.WebhookDispatcher, ClaudeAPIKey: cfg.AI.APIKey, SourceURL: cfg.UI.SourceURL, + BlueskyProfile: cfg.UI.BlueskyProfile, LegalConfig: routes.LegalConfig{ CompanyName: cfg.Legal.CompanyName, Jurisdiction: cfg.Legal.Jurisdiction, diff --git a/pkg/appview/templates/components/footer.html b/pkg/appview/templates/components/footer.html index e8190f0..6a66a40 100644 --- a/pkg/appview/templates/components/footer.html +++ b/pkg/appview/templates/components/footer.html @@ -5,11 +5,13 @@ Privacy Terms + {{ with .BlueskyProfile }} - + Bluesky + {{ end }} {{ with .SourceURL }}