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
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ The GitHub Profile Generator documentation describes the product, its available
| [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. |
| [README round-trip and guided import](decisions/0044-readme-roundtrip-and-guided-import.md) | Hidden versioned structure, explicit full-profile recovery, and reviewed module suggestions. |
| [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
8 changes: 4 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ Custom-section operations add, update, include, duplicate, and delete instances.

Hiding a section preserves its data; reordering also preserves hidden and retired IDs. Public activity does not determine a person's skills. Document validation checks the structure and invariants of the full draft without evaluating output safety.

`generateReadme` receives a valid v2 document and an optional explicit card origin. It returns `ReadmeResult`: Markdown with optional `exportIssues`, `customImages`, and `customHtml`, or errors with a code and path. It walks `sectionOrder` and uses pure renderers for visible fixed or custom sections; it composes activity into contributions and omits metrics as a separate output. It omits empty entries and headings without useful body content. Partial content is allowed without requiring a name, job, or repository. Generated labels use `contentLocale` while personal text is retained.
`generateReadme` receives a valid v2 document and optional explicit export settings. The editor requests a terminal, versioned comment manifest on the one Markdown result used by preview, copy, and download. It identifies emitted fixed/custom sections and their order without including hidden field values. An explicit opt-in adds the complete v2 snapshot to that same README; it includes hidden and retired data and is public in the raw source. The generator does not read network or storage. It walks `sectionOrder` and uses pure renderers for visible fixed or custom sections; it composes activity into contributions and omits metrics as a separate output. It omits empty entries and headings without useful body content. Partial content is allowed without requiring a name, job, or repository. Generated labels use `contentLocale` while personal text is retained.

`render-custom.ts` escapes titles and ordinary text, and generates paragraphs, ordered/unordered lists, HTTP/HTTPS links, subheadings, dividers, quotes, explicit images, literal code, and filtered HTML units. Without a chosen format, a subheading uses `###` when the section has a title and `##` otherwise; a divider uses `---` and a quote prefixes all its lines with `>`. Empty list items are omitted and continuations are indented to match marker width, including two-digit numbering. A generator-local index alternates equivalent markers between emitted lists so CommonMark retains separate lists, including in untitled sections; there is no global state. Errors point to `customSections` and `blocks` indices, independent of visual order. A hidden instance does not participate in output, metadata, or export validation. List-writing helpers belong only to paragraphs and lists in `modules/custom`; fixed forms still do not interpret Markdown.

Expand Down Expand Up @@ -162,13 +162,13 @@ The custom form shows code, help, warnings, problems, and inspectable exported H

## Local README import

HTML heading detection uses `readme-import-html.ts`: original HAST provides text and ranges; a bounded scanner retains context across nodes and confirms explicit closing tags before proposing a root-level split. Initial headings without a body stay together and do not determine later hierarchy. Segments retain exact UTF-16 slices and their review label, without adding a duplicate title to the HTML or changing v2. Uncertainty keeps the content together; modules are not classified by tags or URLs.
HTML heading detection uses `readme-import-html.ts`: original HAST provides text and ranges; a bounded scanner retains context across nodes and confirms explicit closing tags before proposing a root-level split. Initial headings without a body stay together and do not determine later hierarchy. Segments retain exact UTF-16 slices and their review label without adding duplicate headings. The app manifest identifies each unchanged section's original destination; an adapter returns it to a fixed module only when its fields can be reconstructed safely, otherwise the content stays custom. An optional validated snapshot restores the full profile; if its visible Markdown was edited, review requires choosing between that snapshot and the current body.

`src/core/import/` analyzes the entire document with unified, remark-parse, and remark-gfm; it uses mdast-util-to-hast and the existing HTML analyzer for compositions that do not fit visual blocks. It is pure, without React, network, DOM, or storage. `analyzeReadmeImport` returns source, ranges, sections, and diagnostics; `buildImportedProfile` validates the selection, builds v2 with custom sections, and checks the generator. References are resolved before splitting; HTML containers retain their context. The contract is not extended with raw Markdown, and modules are not assigned through heuristics.
`src/core/import/` analyzes the complete document with unified, remark-parse, and remark-gfm; it uses mdast-util-to-hast and the existing HTML analyzer for compositions that do not fit visual blocks. It is pure, without React, network, DOM, or storage. `analyzeReadmeImport` returns source, ranges, per-section fixed-module suggestions, custom fallbacks, recovery state, and diagnostics; `buildImportedProfile` validates explicit choices and checks the generator. The manifest identifies the original destination for unchanged emitted sections, while a module adapter maps content only when it can reconstruct the fixed fields safely; otherwise it retains the content as custom with a hint. Without a full snapshot, not every rendered module is reversible. Suggestions for other README files use heading aliases plus content structure, never a title alone. References are resolved before splitting; HTML containers retain their context. The contract is not extended with raw Markdown. Visible body import remains limited to 100,000 UTF-16 units, and a manifest-bearing file is limited to 200,000 units.

The panel is loaded on demand when opened from the editor, together with the parser. It keeps source, selection, and review in memory across language changes; confirmation is never resumed automatically. Originals and output are displayed as React code, without mounting remote resources. Editing source invalidates analysis, and late file-reading responses are discarded. The usual preview is used after the profile is applied.

`import-backup.ts` validates a sidecar `github-profile-generator.import-backup.v1` record with the complete previous profile and original source. The hook writes that record before replacing the draft and offers restoration without overwriting it. Synchronous review of state, `dirty`, and protection flags prevent an old debounce, `pagehide`, or unmount from restoring the previous profile over the imported one. Invalid or inaccessible values are not replaced: continuing in memory requires an explicit action and does not enable autosave. Downloadable JSON configuration can preserve the previous profile; opening JSON files belongs to the separate [project files](#project-files) flow. See [ADR 0018](decisions/0018-readme-import-with-backup.md).
`import-backup.ts` validates a sidecar `github-profile-generator.import-backup.v1` record with the complete previous profile and exact original source. The hook writes that record before replacing the draft and offers restoration without overwriting it. Synchronous review of state, `dirty`, and protection flags prevent an old debounce, `pagehide`, or unmount from restoring the previous profile over the imported one. Invalid or inaccessible values are not replaced: continuing in memory requires an explicit action and does not enable autosave. Project JSON remains a separate complete backup/import option; it is not a companion file required for README round-tripping. See [ADR 0018](decisions/0018-readme-import-with-backup.md) and [ADR 0044](decisions/0044-readme-roundtrip-and-guided-import.md).

## Public GitHub activity

Expand Down
2 changes: 1 addition & 1 deletion docs/decisions/0018-readme-import-with-backup.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR 0018: README Import with Review and Backup

- Status: accepted for the first import increment.
- Status: accepted for the first import increment; current module mapping is superseded by ADR 0044.
- Date: 2026-10-20.
- Complements ADRs 0004, 0013, 0015, and 0017.

Expand Down
2 changes: 1 addition & 1 deletion docs/decisions/0021-project-files-and-recovery.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR 0021: Project Files and Independent Recovery

- Status: accepted.
- Status: accepted; README recovery metadata is superseded by ADR 0044.
- Date: 2026-10-20.

## Context
Expand Down
36 changes: 36 additions & 0 deletions docs/decisions/0044-readme-roundtrip-and-guided-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# ADR 0044: README Round-Trip Metadata and Guided Import

- Status: accepted.
- Date: 2026-10-23.
- Supersedes the custom-only mapping decision in ADR 0018 and the rejection of README recovery metadata in ADR 0021.

## Context

The generated README is the file people publish and share, but visible Markdown does not identify the fixed modules that produced it and omits hidden profile data and editor settings. The existing project JSON preserves the complete `ProfileDocument`, but requiring a README and a second JSON file does not provide the desired one-file download/import flow. Arbitrary README files also vary too much for reliable classification from headings alone.

## Decision

- Keep `ProfileDocument` v2 as the persisted model and the existing project JSON flow as a separate full-fidelity option. Do not add manifest fields to the profile model.
- Append one exact, versioned HTML-comment manifest to app-generated Markdown. The manifest records emitted section identities, order, UTF-16 ranges, and a body fingerprint. It contains no hidden profile values by default. The fingerprint detects changed source only; it is not an authenticity check.
- Offer an explicit export choice to include the complete validated v2 profile snapshot in that same README. Clearly disclose that this includes hidden and retired data and is readable in the raw public source even though GitHub hides the comment from its rendered Markdown. Do not include the snapshot unless selected.
- Bound the visible README body to 100,000 UTF-16 code units and the complete manifest-bearing file to 200,000 units. Never truncate. If a requested full snapshot cannot fit or validate, block that export and direct the person to the separate project JSON option.
- On import, recognize only the exact terminal manifest syntax and validate it as untrusted data before using it. A matching full snapshot offers exact restoration from the same `.md`; after edits to the visible body, let the person choose between restoring the captured snapshot and importing the current Markdown. Structural metadata may guide section-to-module routing only when the body matches. Invalid, unsupported, or absent metadata produces a notice and falls back to the normal Markdown review without discarding the source.
- For ordinary or edited README files, use deterministic module-specific adapters supported by both heading aliases and content structure. Show the proposed destination, recognized fields, and conversion notices. Consume a heading used to identify a fixed module instead of duplicating it as a custom title. Ambiguous, repeated, partially supported, or unknown content remains recoverable as custom content or requires an explicit choice.
- Preserve the existing local-only analysis, HTML/URL policies, source ranges, inert review, explicit replacement confirmation, backup-before-write behavior, and protected-storage handling. Do not add AI, network calls, raw Markdown blocks, services, or dependencies.

## Alternatives

- Put the full snapshot in every README by default: rejected because hidden profile values would be exposed in public raw source without an explicit choice.
- Keep JSON as the only exact recovery route: retained as an option, but insufficient for the requested one-file README round trip.
- Infer fixed modules from titles alone: rejected because titles are ambiguous and do not establish that the content maps to the module's fields.
- Send arbitrary Markdown to an AI service: rejected because deterministic local conversion and explicit review meet the need without adding uncertainty, accounts, or network data handling.

## Consequences

People who opt in can download one README and later restore the exact captured profile by importing that same file. The opt-in README can expose data not displayed on the GitHub profile; the interface must explain this before export. People who decline the snapshot can still reimport the visible structure, while project JSON remains the complete alternative. Edited, third-party, stale, or unsupported Markdown is best-effort and must state what was recognized, converted, or retained as custom content. Future active modules need an import adapter when their data shape changes.

The manifest is part of the generated Markdown consumed by preview, copy, and download. It is not authored HTML and does not broaden the custom HTML allowlist. Older README files without the marker continue through the existing parser and review flow.

## Review conditions

Revisit the manifest version and size limits when the profile contract or README importer limits change. Never treat the public comment as secret storage or accept an unsupported snapshot without preserving the original file for review.
Loading
Loading