Skip to content

feat: add Vale ecosystem links to generated sites#2

Draft
armstrongl wants to merge 4 commits intomainfrom
feat/vale-ecosystem-links
Draft

feat: add Vale ecosystem links to generated sites#2
armstrongl wants to merge 4 commits intomainfrom
feat/vale-ecosystem-links

Conversation

@armstrongl
Copy link
Copy Markdown
Owner

Summary

  • Add default Vale ecosystem links (vale.sh, Vale Studio, Vale on GitHub) to every generated site
  • New dedicated /resources/ page with card-style layout for all ecosystem links
  • Footer links to key Vale resources on every page
  • Package authors can add custom links via resources.extra_links in rulebound.yml
  • Resources page can be suppressed with resources.enabled: false
  • README updated with config documentation and Vale resources section

Changes

Config (internal/config/)

  • New ResourceLink and ResourcesConfig types with *bool enable/disable pattern
  • 4 new tests covering extra links, defaults, and explicit disable

Generator (internal/generator/)

  • New resources.go: default links, link merging, resources page generation
  • Updated site.json to include resource_links array
  • 4 new integration tests

Theme (internal/hugo/theme/)

  • New resources/list.html layout with card grid
  • Footer updated to render ecosystem links from site.json data
  • CSS for .resources-* components

Docs

  • README: config table, YAML example, Vale resources section
  • Example package: extra_links demo

Test plan

  • go test ./... — all packages pass
  • Config: extra links parsed, defaults when absent, explicit disable
  • Generator: site.json contains default links, extra links appended, resources page created/suppressed
  • Theme: footer renders links, resources page layout works
  • Visual review of resources page and footer on local build

Add ResourceLink and ResourcesConfig types to support configurable
resources page and footer links. Follows existing *bool toggle pattern
(nil = enabled by default, explicit false = disabled).
…page

Hardcode 3 default links (vale.sh, Vale Studio, Vale GitHub) and merge
with any extra_links from config. Generate content/resources/_index.md
unless resources.enabled is explicitly false. Resource links always
appear in site.json so the footer can use them regardless.
Resources page renders link cards from site.json data. Footer shows
Vale and Vale Studio links (marked with footer: true) after the
Rulebound credit on every page.
Document the resources config fields, add a custom resource links
example, and list Vale ecosystem links in a dedicated README section.
Copilot AI review requested due to automatic review settings March 28, 2026 01:12
@armstrongl armstrongl marked this pull request as draft March 28, 2026 01:13
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “Resources” concept to generated Hugo sites, including default Vale ecosystem links, optional package-defined extra links, a dedicated /resources/ page, and footer rendering driven by data/site.json.

Changes:

  • Introduces resources config (enabled, extra_links) and related tests.
  • Generates content/resources/_index.md and emits resource_links into data/site.json.
  • Updates Hugo theme to render resource links in the footer and adds a resources list layout + CSS.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
internal/hugo/theme/static/css/style.css Adds styles for the new resources page card/grid components.
internal/hugo/theme/layouts/resources/list.html New list template that renders links from data/site.json.
internal/hugo/theme/layouts/_default/baseof.html Footer now renders ecosystem links from resource_links.
internal/generator/resources.go Implements defaults, merging extra links, and resources page generation.
internal/generator/index.go Extends site.json schema to include resource_links.
internal/generator/generator_test.go Adds generator tests for defaults, extra links, and page enable/disable.
internal/generator/generator.go Wires resource link building, page generation toggle, and site.json emission.
internal/config/config_test.go Adds config parsing tests for resources fields.
internal/config/config.go Adds ResourcesConfig / ResourceLink types to config schema.
examples/starter-package/rulebound.yml Demonstrates resources.extra_links usage in example package.
README.md Documents new config fields and adds a Vale resources section.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1149 to +1154
links := stats["resource_links"].([]interface{})
if len(links) != 4 {
t.Fatalf("resource_links length = %d, want 4 (3 defaults + 1 custom)", len(links))
}

last := links[3].(map[string]interface{})
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses an unchecked type assertion (stats["resource_links"].([]interface{})), which can panic and obscure the real failure if resource_links is missing or not an array. Mirror the prior test’s checked assertion and fail with a helpful message.

Suggested change
links := stats["resource_links"].([]interface{})
if len(links) != 4 {
t.Fatalf("resource_links length = %d, want 4 (3 defaults + 1 custom)", len(links))
}
last := links[3].(map[string]interface{})
rawLinks, ok := stats["resource_links"]
if !ok {
t.Fatalf("site.json missing 'resource_links' key: %#v", stats)
}
links, ok := rawLinks.([]interface{})
if !ok {
t.Fatalf("site.json 'resource_links' has unexpected type %T (value: %#v)", rawLinks, rawLinks)
}
if len(links) != 4 {
t.Fatalf("resource_links length = %d, want 4 (3 defaults + 1 custom)", len(links))
}
lastRaw := links[3]
last, ok := lastRaw.(map[string]interface{})
if !ok {
t.Fatalf("resource_links[3] has unexpected type %T (value: %#v)", lastRaw, lastRaw)
}

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +55
// buildResourceLinks merges defaults with extra links from config.
func buildResourceLinks(cfg *config.Config) []resourceLink {
links := defaultResourceLinks()
for _, extra := range cfg.Resources.ExtraLinks {
links = append(links, resourceLink{
Label: extra.Label,
URL: extra.URL,
Description: extra.Description,
Footer: false,
})
}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom resource URLs from cfg.Resources.ExtraLinks are written directly into site.json and then rendered into href attributes. This allows unsafe schemes (e.g., javascript:, data:) if a package config is untrusted. Validate extra.URL (and optionally defaults) to only allow http/https (or an explicit allowlist) and fail the build (or skip the link) when invalid; also consider rejecting empty label/URL values.

Copilot uses AI. Check for mistakes.
{{- $data := index hugo.Data "site" -}}
{{- range $data.resource_links -}}
{{- if .footer }} · <a href="{{ .url }}" target="_blank" rel="noopener noreferrer">{{ .label }}</a>{{- end -}}
{{- end }}
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /resources/ page is generated, but there’s no in-site link to it (footer only renders external links and sidebar doesn’t include it), so it’s effectively undiscoverable unless users guess the URL. Consider adding a “Resources” internal link (e.g., to /resources/) in the footer and/or sidebar when resources.enabled is true.

Suggested change
{{- end }}
{{- end }}
{{- if $data.resources.enabled }} · <a href="{{ "/resources/" | relURL }}">Resources</a>{{- end }}

Copilot uses AI. Check for mistakes.
Comment on lines +1114 to +1116
// Verify first default is Vale
first := links[0].(map[string]interface{})
if first["label"] != "Vale" {
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses an unchecked type assertion (links[0].(map[string]interface{})), which will panic with a less-informative stack trace if the JSON structure changes. Prefer a checked assertion with a clear test failure message.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants