Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ The current release includes the Next.js foundation, a bilingual visual kit, `Pr

## Architecture and code

- Custom section `title` is the editor name. V2 `showTitle?: boolean` controls only its exported heading; absence/true preserves earlier output and false retains the name and formatting while omitting title validation and HTML budget. Adapt implicit subheadings to the visible title, preserve explicit levels/HTML, and retain the flag through duplication and JSON. Do not add defaults on recovery. See [ADR 0042](docs/decisions/0042-custom-section-title-visibility.md).
- A working browser card preview does not prove public image access. With configured cards, export requires an explicit anonymous check of the exact canonical URLs, valid for five minutes and invalidated when URLs change. Never query during mounting/recovery/edits, follow redirects, forward cookies/tokens, or accept arbitrary hosts. Keep server concurrency/body/time/rate limits and temporary editor state outside the profile and data cache. See [ADR 0043](docs/decisions/0043-public-card-export-check.md).

- Use the Next.js App Router and strict TypeScript. Keep routes and layouts small; put behavior in the scope that owns it.
- Keep English as the default language and Spanish as a fully supported option. Use `next-intl`, routes under `src/app/[locale]`, and message namespaces; avoid hard-coded interface text in components. Changing languages preserves the equivalent route, uses language names without flags, and keeps the correct `lang` attribute.
- Prefer Server Components. Use `"use client"` at the smallest boundary that needs state, events, or browser APIs. Do not convert an entire layout into a client component for convenience.
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ The GitHub Profile Generator documentation describes the product, its available
| [Detailed contributions](decisions/0039-contribution-details-and-order.md) | Multiple links, public images, legacy repository preview compatibility, and contribution order. |
| [Activity card columns](decisions/0040-activity-card-columns.md) | Optional one- or two-column card rows and legacy output behavior. |
| [Activity and contribution order](decisions/0041-activity-contribution-order-and-readable-cards.md) | Choose content order and keep two-column card text readable. |
| [Internal section names](decisions/0042-custom-section-title-visibility.md) | Keep section names in the editor while optionally hiding README headings. |
| [Public card export checks](decisions/0043-public-card-export-check.md) | Explicit anonymous image checks, deployment failures, and export readiness. |
| [Third-party notices](../THIRD_PARTY_NOTICES.md) | Provenance and licenses for icons, flags, the code editor, and Extended cards. |
| [Contributing](../CONTRIBUTING.md) | Workflow, expectations for changes, and pull requests. |

Expand Down
6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ The contact renderer adds a decorative 20 × 20 icon inside a recognized link an

Icons identify tools and services without inferring skills or integrating providers. Some entries share a family brand, such as JetBrains AI or ChatGPT/OpenAI; these mappings are documented in attribution. See [ADR 0009](decisions/0009-profile-icons.md).

## Internal Section Names and Exported Titles

Custom sections keep an authored editor name in `title`. The optional v2 `showTitle` flag defaults to visible only by interpretation, without adding a property during parsing. `false` omits the generated title and its HTML budget/errors while retaining the internal name and formatting. Implicit subheadings use the untitled hierarchy; explicit HTML is unchanged. Navigation and the configuration heading use the internal name regardless of export visibility. See [ADR 0042](decisions/0042-custom-section-title-visibility.md).

Public card availability remains outside the pure generator. A small explicit-check endpoint validates the generated URLs against the configured origin and checks their anonymous HEAD responses. Its bounded network service is in `server/activity-export-check`; temporary readiness and cancellation live in the editor. No token, provider data or new profile field is part of this check. See [ADR 0043](decisions/0043-public-card-export-check.md).

## Custom images in custom sections

The document retains `image` as `{ id, type: "image", url, alt }`. `normalizePublicImageUrl` is a pure function that accepts syntactically public-domain HTTPS, without an IP address, local host or known private suffix, credentials, fragments, or non-standard port. It does not require a file extension and allows a query string. It does not query DNS, network, or availability; an accepted URL does not prove that the response is an image or will remain published. The application does not upload files or turn local, relative, `blob:`, or `data:` paths into public resources.
Expand Down
4 changes: 2 additions & 2 deletions docs/decisions/0035-compact-technologies.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ Add `data.technologies.layout?: "compact" | "grouped"` exclusively to v2. Its ab

The “README layout” selector offers compact tags and category headings. Changing it updates the same Markdown used by preview, copy, and download. It does not regroup, rename, or remove categories, items, identities, or notes. All form edits and organization operations preserve the selected option.

Compact mode displays the category name in bold on the same row as its tools, wrapping to fit the width. Each name is wrapped in `kbd` and, when available, includes the catalog's pinned 20 × 20 decorative icon. Custom names remain text without an inferred brand. Notes keep their order and paragraphs; they are not hidden to reduce height. Groups with no useful content are omitted.
Compact mode displays the category name in bold on the same row as its tools, wrapping to fit the width. Each catalog tool's pinned 20 × 20 decorative icon and visible name share one `kbd`, so the badge background covers both. Custom names remain text without an inferred brand. Notes keep their order and paragraphs; they are not hidden to reduce height. Groups with no useful content are omitted.

HTML is built from HAST text nodes, filtered with `renderProfileHtml`, and shares its limits and budget with the rest of the document. Exact UTF-16 ranges and allowed trees are provided through `customHtml`, without expanding `customImages`, enabling global HTML, or persisting metadata. `invalid_html` errors identify the affected group; the UI offers to fix it or return to category headings. Hidden technologies do not use the budget.

The preview recognizes only exact catalog URLs, an empty alternative text value, and 20 × 20 dimensions in order to use local SVGs. Compact tags use inline-flex alignment and bounded wrapping in the local preview so the icon and label occupy the available chip area without clipping. The rest of the image and HTML policy remains in place. No third-party badges are requested, no endpoint is created, and `APP_URL` is not required. Colors come from existing icons. GitHub controls the published `kbd` styling, so matching its appearance pixel-for-pixel is not promised.
The preview recognizes only exact catalog URLs, an empty alternative text value, and 20 × 20 dimensions in order to use local SVGs. It places the icon and label in the same badge, matching the exported HTML rather than relying on preview-only layout. The reader controls badge colors and wrapping; at extreme widths, rendering can differ between Markdown readers. The rest of the image and HTML policy remains in place. No third-party badges are requested, no endpoint is created, and `APP_URL` is not required.

## Alternatives Considered

Expand Down
25 changes: 25 additions & 0 deletions docs/decisions/0042-custom-section-title-visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# ADR 0042: Internal Section Names and Optional README Titles

- Status: accepted.
- Date: 2026-10-23.
- Extends [ADR 0013](0013-custom-sections.md) and [ADR 0019](0019-code-and-formatting.md).

## Context

A person may want a recognizable name in the editor without publishing a heading above the section's content. Using an empty title for both purposes makes several sections difficult to distinguish.

## Decision

Keep `CustomSection.title` as the authored name used in editor navigation and the configuration heading. Add `showTitle?: boolean` to the strict v2 schema. Absence and `true` preserve previous README output; `false` omits the generated section heading. Parsing and recovery do not add defaults, change storage keys, or modify v1.

The **Section name** field remains editable. **Show title in README** controls only the generated heading, independently from including the whole section. Unchecking retains the name, title level, alignment, blocks, identity and position. Checking again restores the saved formatting and removes the optional visibility override. Duplicating and JSON project files preserve the choice.

A hidden title does not consume the HTML budget or produce title-format export errors. Implicit subheadings use the same hierarchy as a section with no title. Explicit heading levels and authored HTML remain unchanged. Preview, copy and download continue to use the same generated Markdown and verified `customHtml` ranges.

## Alternatives and Consequences

- A separate name field would require two authored strings, recovery rules and another source of labels. Reusing the existing title keeps this bounded.
- Clearing the title loses the editor name. Hiding it with CSS would still publish it and does not work across Markdown readers.
- An older strict v2 reader can reject `showTitle`; it must preserve that incompatible draft. Existing documents without the property keep their shape and output.

Review this decision if internal names and exported titles must become independently authored text.
31 changes: 31 additions & 0 deletions docs/decisions/0043-public-card-export-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# ADR 0043: Explicit Public Card Checks Before Export

- Status: accepted.
- Date: 2026-10-23.
- Complements [ADR 0036](0036-production-card-origin.md) and [ADR 0041](0041-activity-contribution-order-and-readable-cards.md).

## Context

The editor renders cards from browser snapshots into temporary Blob images. An HTTPS origin that passes syntax validation may still require a hosting session, return HTML, lack newly generated options, or have no available provider data. A protected Vercel deployment redirects anonymous images to SSO, and a deployment older than the two-column contract rejects `columns=2`. A working local preview proves neither public availability nor deployment compatibility.

## Decision

Keep the pure generator and its configured-origin requirement. Before copying or downloading a README with cards, offer **Check public cards** as an explicit action. Do not query while mounting, restoring a draft, changing options or displaying the preview. Without cards, export remains immediate. A successful result unlocks export for five minutes for the exact ordered image URLs. Changing those URLs, removing cards, reloading, or expiration invalidates the result without a new request. Ordinary text edits do not change card availability. Verification is temporary UI state, outside the profile, Markdown, backups and data caches.

Use `POST /api/cards/export-check` with `{ sources: string[] }`. The server parses canonical card URLs only against its configured public origin. It never accepts another host, a redirect destination, a non-card path or arbitrary fetch options. The result is a small validated union in `core/activity/export-check`, with no response bodies, credentials or infrastructure headers.

The server sends anonymous `HEAD` requests with `credentials: omit`, `redirect: manual` and `cache: no-store`. It forwards no incoming cookies, authorization or headers. HTTP 200, an SVG MIME type and `X-Activity-State: ready | stale` are required. Redirects or 401/403 indicate protected access; 400/404 indicate incompatible routes/options; an unavailable image, invalid response, network failure, timeout or busy process has a separate result. Stale images are reported as cached data.

Bounds per process are 32 input URLs, 12,000 characters per URL, 512 KiB of JSON, five seconds to read the body, four simultaneous outbound requests, one concurrent check, 12 checks and 60 outbound requests per minute. Requests have 15-second timeouts and a 30-second overall check budget. Duplicate sources are checked once. Failure or client cancellation stops pending work; no automatic retries or unbounded queues are added. These are process limits, not distributed quotas.

The editor explains failure, offers a manual retry, opens an exported image in another tab, and lets the person exclude cards while keeping settings. It does not download a knowingly failed set. The separate verification action also keeps clipboard writes inside a later user click, avoiding lost clipboard activation after a network wait.

## Alternatives and Consequences

- Checking only origin syntax misses protected and incompatible deployments. Silently dropping exported options would change the selected output.
- Following redirects could accept a login page or contact another host. Loading the image with visitor cookies would test their session instead of anonymous access.
- Automatic checks on each edit add traffic and contradict the explicit-query flow. Hosting static snapshots would require a new upload and publication flow.
- Checks can time out on a cold or slow service; the UI reports an incomplete check and allows manual retry. Existing server/CDN caches still apply. HEAD headers do not certify future availability, the SVG body, GitHub Camo or rendering in every reader.
- Operators must publish a compatible version at a stable public origin. This check changes neither deployment protection nor hosting configuration. Previously downloaded READMEs retain their old URLs and need regenerating after configuration is corrected.

The command-line card checker also accepts the bounded `columns=1|2` option emitted by the current generator.
Loading
Loading