Skip to content

Feat: export and import JSONs - #15

Merged
AxelUser merged 24 commits into
mainfrom
feat/export-jsons
Jun 5, 2026
Merged

Feat: export and import JSONs#15
AxelUser merged 24 commits into
mainfrom
feat/export-jsons

Conversation

@AxelUser

@AxelUser AxelUser commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Import and export CVs as JSON in HumbleHire JSON (lossless round-trip) or JSON Resume (interop) formats for full data portability.
    • Export dialog now supports multiple formats: PDF, HumbleHire JSON, and JSON Resume.
    • New import dialog enables loading JSON files into the app.
  • Documentation

    • Landing page highlights data portability and no lock-in messaging.
    • README updated with JSON round-tripping capabilities and schema references.

AxelUser added 20 commits June 3, 2026 22:39
…ion how inner CV model maps to exported JSON files
…rations for improved clarity and functionality
… type generation and validator creation, update package dependencies, and enhance JSON Resume compatibility tests
… generation, outlining decision rationale and versioning strategy
…SE_URL for improved configuration and flexibility
…ove ESM compatibility and streamline standalone code generation
…sync-drawer component for cleaner production code
… in dashboard and hero sections, replacing NewCvButton and enhancing import functionality
@AxelUser AxelUser self-assigned this Jun 5, 2026
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@AxelUser, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 28 minutes and 4 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f6eb4336-84c0-4c22-9ebe-cc419ed84a7b

📥 Commits

Reviewing files that changed from the base of the PR and between 54694da and b50e268.

⛔ Files ignored due to path filters (12)
  • docs/assets/dashboard-search.dark.gif is excluded by !**/*.gif
  • docs/assets/dashboard-search.light.gif is excluded by !**/*.gif
  • docs/assets/dashboard.dark.png is excluded by !**/*.png
  • docs/assets/dashboard.light.png is excluded by !**/*.png
  • docs/assets/editor.dark.gif is excluded by !**/*.gif
  • docs/assets/editor.dark.png is excluded by !**/*.png
  • docs/assets/editor.light.gif is excluded by !**/*.gif
  • docs/assets/editor.light.png is excluded by !**/*.png
  • docs/assets/export.dark.gif is excluded by !**/*.gif
  • docs/assets/export.light.gif is excluded by !**/*.gif
  • docs/assets/tailoring.dark.gif is excluded by !**/*.gif
  • docs/assets/tailoring.light.gif is excluded by !**/*.gif
📒 Files selected for processing (11)
  • assets-cli/src/recipes/export.ts
  • e2e/export.spec.ts
  • package.json
  • src/lib/components/export/export-button.svelte
  • src/lib/components/import/import-dialog.svelte
  • src/lib/features/serialization/__fixtures__/complete-cv.ts
  • src/lib/features/serialization/import.test.ts
  • src/lib/features/serialization/import.ts
  • src/lib/features/serialization/serialize.test.ts
  • src/lib/features/serialization/serialize.ts
  • static/schema/resume/v0.0.1.json
📝 Walkthrough

Walkthrough

This PR adds CV export/import with JSON Resume interoperability, introduces a self-contained JSON schema with generated types and validators, renames the test bridge infrastructure to dev bridge for clearer E2E semantics, expands the UI component library, and updates documentation with architectural decision records and feature guides.

Changes

Export/Import and Serialization Feature

Layer / File(s) Summary
JSON Schema definition and type generation
static/schema/resume/v0.0.1.json, scripts/gen-schema.mjs, src/lib/features/serialization/document.generated.ts, src/lib/features/serialization/validator.generated.*, .prettierignore, eslint.config.js
Defines HumbleHire Resume v0.0.1 schema with inlined JSON Resume base and HumbleHire extensions for lossless round-tripping; generates TypeScript types and AJV validators via schema-to-typescript and AJV standalone; configures build tools to ignore generated artifacts.
Bidirectional serialization (parse, serialize, import)
src/lib/features/serialization/parse.ts, src/lib/features/serialization/serialize.ts, src/lib/features/serialization/import.ts, src/lib/features/serialization/__fixtures__/complete-cv.ts
Implements JSON document parsing with schema validation and version gating; CV-to-document conversion for both lossless HumbleHire JSON and lossy JSON Resume projections; document-to-CV import with field coercion and temporal vs durable mapping handling; complete CV fixture for test seeding.
Serialization and schema compatibility tests
src/lib/features/serialization/parse.test.ts, src/lib/features/serialization/serialize.test.ts, src/lib/features/serialization/jsonresume-compat.test.ts, src/lib/features/serialization/jsonresume-schema-shims.d.ts
Tests parse/schema validation, round-trip serialization behavior, JSON Resume interop boundaries with canonical sample documents, and schema version gating.
Export and import UI components
src/lib/components/export/export-button.svelte, src/lib/components/export/index.ts, src/lib/components/import/import-button.svelte, src/lib/components/import/import-dialog.svelte, src/lib/components/import/index.ts
Export button with multi-format dropdown (PDF, HumbleHire JSON, JSON Resume); import button and dialog with file input, best-effort name parsing, error display, and success notifications.
CV creation service and actions component
src/lib/services/cv/add-blank-cv.ts, src/lib/components/cv/create-cv-actions.svelte, src/lib/components/cv/index.ts
New addBlankCv service that creates and persists untitled CVs with analytics tracking; CreateCvActions component combines import button and create button, replacing NewCvButton.
Dashboard and landing page integration
src/lib/components/dashboard/dashboard.svelte, src/lib/components/dashboard/index.ts, src/lib/components/landing/hero.svelte, src/lib/components/landing/about.svelte, src/routes/+page.svelte
Updates dashboard and hero to use CreateCvActions; adds portability messaging and schema link to about page; adds route handler to navigate to created CV; removes old NewCvButton exports.
Feature documentation and serialization reference
docs/features/export/OVERVIEW.md, docs/features/import/OVERVIEW.md, docs/reference/serialization-schema.md
Exports/imports feature overviews describing round-trip behavior and interoperability; detailed serialization schema reference with field mappings, durable vs temporal coercion explanations, and version-gating semantics.

Test Bridge to Dev Bridge Infrastructure Rename

Layer / File(s) Summary
Bridge package and contract rename
dev-bridge/package.json, dev-bridge/src/index.ts
Renames package from @humblehire/test-bridge to @humblehire/dev-bridge, updates interface HhTestBridgeDevBridge, changes window seam from __hhTest to __devBridge, updates Playwright adapter wiring.
Update all bridge imports and environment variables
assets-cli/package.json, assets-cli/src/helpers/dwight.ts, assets-cli/src/recipes/*.ts, assets-cli/src/server.ts, e2e/*.spec.ts, e2e/tsconfig.json, package.json, playwright.config.ts, pnpm-workspace.yaml
Updates imports across assets-cli and e2e tests to reference @humblehire/dev-bridge; changes build environment setup from VITE_E2E to VITE_DEV_BRIDGE + VITE_DEV_NO_SW; updates workspace packages configuration.
Application-level bridge setup and type updates
src/app.d.ts, src/lib/analytics.ts, src/routes/+layout.svelte, src/lib/dev-bridge.ts
Updates global window type from __hhTest to __devBridge; removes E2E-specific early-return from analytics initialization; conditionally initializes dev bridge and gates service-worker setup in layout based on environment flags.
ADR and test strategy documentation for dev bridge
docs/decisions/ADR-004-e2e-test-strategy.md
Documents E2E strategy as wiring-focused UI coverage with IndexedDB seeding; clarifies dev bridge attachment to window.__devBridge when VITE_DEV_BRIDGE=true and distinguishes from separate VITE_DEV_TOOLBOX flag.

UI Component Library Expansion

Layer / File(s) Summary
Complete dropdown menu component system
src/lib/components/ui/dropdown-menu/*.svelte, src/lib/components/ui/dropdown-menu/index.ts
Implements 16+ dropdown menu subcomponents (root, trigger, content, item variants, groups, checkboxes, radio items, separators, submenus, portal) wrapping bits-ui primitives with Svelte 5 syntax and typed props.
Alert UI component set
src/lib/components/ui/alert/*.svelte, src/lib/components/ui/alert/index.ts
Creates alert root with variant styling (default/destructive), plus description, title, and action subcomponents for composable alert displays.
Button group component system
src/lib/components/ui/button-group/*.svelte, src/lib/components/ui/button-group/index.ts
Adds button group root with orientation support (horizontal/vertical), text slot, and separator subcomponent.
Separator component orientation fix
src/lib/components/ui/separator/separator.svelte
Updates vertical separator styling from min-h-full to h-full.

Documentation, Configuration, and Supporting Changes

Layer / File(s) Summary
Architecture decision records (rewrites and new ADRs)
docs/decisions/ADR-001-*.md through docs/decisions/ADR-009-*.md
Condenses and clarifies existing ADRs (IndexedDB schema, PDF generation, search, E2E tests, PWA durability); adds new ADRs for export/import DTO boundary (ADR-006), PDF.js preview rendering (ADR-007), self-contained schema (ADR-008), and schema-driven type generation (ADR-009).
Context glossary and domain guidance updates
CONTEXT.md, docs/agents/domain.md
Expands vocabulary definitions (Hidden block, Snapshot, Local-first, Master CV, Sync mechanism, offline/durability, serialization); refines flagged ambiguities guidance; adds domain exploration instructions and ADR writing requirements.
Tooling configuration (ESLint, Prettier, Playwright, pnpm, SvelteKit)
.prettierignore, eslint.config.js, playwright.config.ts, pnpm-workspace.yaml, svelte.config.js
Adds generated file ignores, updates Playwright webServer env vars, workspace configuration, and SvelteKit asset path settings for root-relative URLs.
Package dependencies and scripts
package.json, src/lib/config.ts
Adds gen:schema build script; updates dev dependencies (replaces test-bridge with dev-bridge, bumps @lucide/svelte, adds schema/validation tools); introduces APP_BASE_URL config constant.
README feature bullet and projects URL update
README.md, src/stories/blocks/helpers/ProjectsWrapper.svelte
Adds JSON export/import feature description; updates ProjectsWrapper to use APP_BASE_URL instead of hardcoded domain.
Supporting updates
src/lib/pwa/sw-router.test.ts, src/lib/components/tailoring/sync-drawer.svelte
Switches sw-router test origin to APP_BASE_URL; removes dev-mode logging from sync drawer.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • AxelUser/HumbleHire#11: Updates README with feature sections; this PR adds a bullet about JSON export/import portability.
  • AxelUser/HumbleHire#13: Replaces crypto.randomUUID() with createId()/NanoID utilities; this PR's new CV creation service uses that updated ID generation.

Poem

🐰 Beneath the browser tabs, a rabbit mines for schemas,
Export and import dance where JSON dreams shimmer.
Bridges renamed, types generated, features bloom—
Data flows like clover through the app's new room! 🌱

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/export-jsons

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 13

🧹 Nitpick comments (8)
src/lib/components/ui/alert/alert.svelte (1)

4-16: 💤 Low value

Consider formatting the long base class string for readability.

The base class string on line 5 is over 400 characters long and difficult to read. Consider splitting it across multiple lines for better maintainability.

♻️ Proposed formatting
 export const alertVariants = tv({
-	base: "grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18 has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2 *:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current *:[svg:not([class*='size-'])]:size-4 group/alert relative w-full",
+	base: [
+		"grid gap-0.5 rounded-lg border px-2.5 py-2 text-left text-sm",
+		"has-data-[slot=alert-action]:relative has-data-[slot=alert-action]:pr-18",
+		"has-[>svg]:grid-cols-[auto_1fr] has-[>svg]:gap-x-2",
+		"*:[svg]:row-span-2 *:[svg]:translate-y-0.5 *:[svg]:text-current",
+		"*:[svg:not([class*='size-'])]:size-4",
+		"group/alert relative w-full"
+	].join(' '),
 	variants: {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ui/alert/alert.svelte` around lines 4 - 16, The long
single-line class string in alertVariants (the base property passed to tv) is
hard to read; refactor base by splitting it across multiple lines—either as an
array of individual class segments joined with ' ' or as a multiline template
literal concatenated into one string—and update the tv(...) call to use that
assembled string so the final value remains identical; reference alertVariants
and the base property to locate the change.
src/lib/components/ui/button-group/button-group.svelte (1)

4-17: 💤 Low value

Consider formatting the long class strings for readability.

The base, horizontal, and vertical class strings are very long (200-400+ characters each) and difficult to read. Consider splitting them across multiple lines for better maintainability.

♻️ Example formatting for base classes
 export const buttonGroupVariants = tv({
-	base: "has-[>[data-slot=button-group]]:gap-2 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg flex w-fit items-stretch [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+	base: [
+		"has-[>[data-slot=button-group]]:gap-2",
+		"has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-lg",
+		"flex w-fit items-stretch",
+		"[&>*]:focus-visible:relative [&>*]:focus-visible:z-10",
+		"[&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit",
+		"[&>input]:flex-1"
+	].join(' '),
 	variants: {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ui/button-group/button-group.svelte` around lines 4 - 17,
The long class strings in buttonGroupVariants (specifically the base property
and variants.orientation.horizontal and .vertical) should be broken into
smaller, readable fragments: replace each giant string with either an array of
class segments joined by ' ' or a multi-line template literal that trims
whitespace, keeping the same final class string; update the definitions for
base, horizontal and vertical to build the class string from these fragments so
the tv(...) call uses the identical combined string but with the source split
across multiple lines for readability.
src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte (1)

42-42: ⚡ Quick win

Consider forwarding state parameters to the children snippet.

This component renders childrenProp without parameters, while dropdown-menu-radio-item.svelte (line 32) forwards { checked }. For API consistency and to enable consumers to access checked/indeterminate state, consider passing these values.

See the related comment on dropdown-menu-radio-item.svelte for alignment options.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte` at
line 42, The childrenProp render call in dropdown-menu-checkbox-item.svelte
should forward the checkbox state so consumers can access it; update the usage
of childrenProp (the same prop used in the component) to be invoked with an
object containing checked and indeterminate (the internal state variables)
similar to how dropdown-menu-radio-item.svelte forwards { checked } so the API
is consistent and callers can read both checked and indeterminate.
src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte (1)

32-32: ⚡ Quick win

Inconsistent snippet parameter passing with checkbox-item.

This component passes { checked } to childrenProp (line 32), but dropdown-menu-checkbox-item.svelte (line 42) renders its childrenProp with no parameters. This creates an inconsistent API surface.

Either both components should forward state to the consumer's snippet, or neither should. Forwarding state enables consumers to conditionally style or render based on checked/indeterminate state.

🔄 Suggested alignment options

Option 1: Forward state in both components (recommended)

Update dropdown-menu-checkbox-item.svelte line 42:

-		{`@render` childrenProp?.()}
+		{`@render` childrenProp?.({ checked, indeterminate })}

And update its type signature (lines 15-17):

 }: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
-	children?: Snippet;
+	children?: Snippet<[{ checked: boolean; indeterminate: boolean }]>;
 } = $props();

Option 2: Remove params from radio-item

In this file, line 32:

-		{`@render` childrenProp?.({ checked })}
+		{`@render` childrenProp?.()}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte` at line
32, The radio-item component is passing a `{ checked }` object into the render
prop (`childrenProp`) while the checkbox-item does not, causing an inconsistent
API; fix by aligning both components — recommended: update
`dropdown-menu-checkbox-item.svelte` to call its `childrenProp` with the same
state object (e.g., `{ checked, indeterminate }` as applicable) and update that
component's prop/type signature to accept the state object, or alternatively
remove the `{ checked }` argument from `dropdown-menu-radio-item.svelte` so
neither forwards state; ensure the symbol names `childrenProp`, `checked`, and
`indeterminate` (if used) are used consistently across both components.
scripts/gen-schema.mjs (2)

13-27: ⚡ Quick win

Add error handling for file write operations.

If writeFileSync fails (e.g., permission denied, disk full), the script will crash without a clear error message. Consider wrapping file operations in try-catch blocks to provide better diagnostics.

🛡️ Proposed error handling
 	writeFile(filename, content) {
+		try {
 			this.written.push(join(this.outDir, filename));
 			writeFileSync(join(this.outDir, filename), content);
+		} catch (error) {
+			console.error(`Failed to write ${filename}:`, error);
+			throw error;
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/gen-schema.mjs` around lines 13 - 27, The TracedFileWriter.writeFile
currently calls writeFileSync without error handling; wrap the writeFileSync
call in a try-catch inside TracedFileWriter.writeFile (and only push to
this.written after a successful write) and in the catch produce a clear,
contextual error (include filename, outDir and the original error) either by
rethrowing a new Error with that context or by logging via the same
error-handling mechanism used elsewhere; ensure files() remains unchanged and
constructor behavior is preserved.

29-33: ⚡ Quick win

Add error handling for schema file loading.

If the schema file is missing or contains invalid JSON, readFileSync or JSON.parse will throw with a potentially unclear error. Consider adding try-catch with a helpful message.

🛡️ Proposed error handling
+try {
 	const schema = JSON.parse(readFileSync(schemaPath, 'utf8'));
+} catch (error) {
+	console.error(`Failed to load schema from ${schemaPath}:`, error);
+	process.exit(1);
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/gen-schema.mjs` around lines 29 - 33, The current loading of the
schema (variables root, schemaPath, outDir and the const schema =
JSON.parse(readFileSync(schemaPath, 'utf8')) line) lacks error handling; wrap
the readFileSync/JSON.parse in a try-catch, catch and log a clear error that
includes schemaPath and the original error message, and exit with a non-zero
code (or rethrow) so failure is explicit; ensure the catch references
readFileSync/JSON.parse and schemaPath so it's easy to locate and diagnose
missing file or invalid JSON issues.
src/lib/features/serialization/validator.generated.d.ts (1)

1-9: 💤 Low value

Generated file has inconsistent indentation.

Lines 2-8 have leading tabs while line 1 doesn't. This is a cosmetic issue in the generated output. Consider updating the generator script (lines 63-74 in scripts/gen-schema.mjs) to use consistent formatting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/features/serialization/validator.generated.d.ts` around lines 1 - 9,
The generated file validator.generated.d.ts has mixed tabs/spaces indentation;
update the schema generator (the gen-schema script that emits the ValidateFn and
validate declarations) to produce consistent indentation (e.g., replace tabs
with two spaces or use a single indent strategy / pass a consistent formatter)
so the emitted block containing ValidateFn, the validate declaration, and export
default validate uses the same spacing; regenerate the file after changing the
generator.
src/lib/components/export/export-button.svelte (1)

54-55: ⚡ Quick win

Log error before displaying toast.

The catch block swallows the error without logging, making debugging export failures difficult.

📝 Proposed fix to add error logging
-  } catch {
+  } catch (error) {
+    console.error('Export failed:', error);
     toast.error('Export failed. Please try again.');
   } finally {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/components/export/export-button.svelte` around lines 54 - 55, The
catch block in export-button.svelte is swallowing errors; change it to capture
the exception (e.g., catch (err)) and log the error before showing the toast in
the export handler (the export button's export function / click handler). Use
the project's logger if available (or console.error) to emit a descriptive
message like "Export failed" along with the error object, then keep the existing
toast.error('Export failed. Please try again.').
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/agents/domain.md`:
- Line 38: Change the header "Writing ADRs" from Title Case to sentence case to
match the ADR guidance rule below: update the heading text "Writing ADRs" so it
uses sentence case (capitalize only the first word and proper nouns/acronyms
like "ADRs") to become "Writing ADRs" (first word capitalized, rest lowercase
except "ADRs").

In `@docs/decisions/ADR-001-indexeddb-dexie.md`:
- Around line 25-28: The fenced code block showing the IndexedDB structure (the
block containing "humblehire (IndexedDB database) └── cvs   { id, updatedAt,
sourceId }") is missing a language identifier and triggers markdownlint MD040;
add a language tag (e.g., ```text) at the start of that fenced block in
ADR-001-indexeddb-dexie.md so the block becomes fenced with a language
identifier and closes as before.

In `@src/lib/components/export/export-button.svelte`:
- Around line 29-36: In triggerDownload, revoking the object URL immediately
after a.click() can abort some browser downloads; change the logic to defer
URL.revokeObjectURL(url) (for example by using a short setTimeout or listening
for the download to start) so the browser has time to begin the download before
revocation; update the function that creates the anchor (triggerDownload) to
revoke the URL asynchronously while keeping createObjectURL and a.download
intact.

In `@src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte`:
- Around line 19-20: The rendered attribute data-inset={inset} outputs
data-inset="false" when inset is false, causing Tailwind's data-[inset]:ps-8 to
still match; change the attribute so it is only present when inset is truthy
(e.g., replace data-inset={inset} with data-inset={inset ? true : undefined} or
spread an object only when inset is true) in the DropdownMenuGroupHeading Svelte
component, leaving the cn('px-2 py-1.5 text-sm font-semibold data-[inset]:ps-8',
className) class usage unchanged.

In `@src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte`:
- Line 23: The class list in the DropdownMenuItem component contains duplicate
inset padding tokens (`data-inset:pl-7` and `data-[inset]:pl-8`) so remove the
redundant one (keep the intended padding, e.g., remove `data-inset:pl-7`) from
the class attribute in
src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte so only a single
`data-[inset]`/`data-inset` padding class remains.

In `@src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte`:
- Line 21: In DropdownMenuLabel
(src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte) the class list
contains duplicate inset padding directives ('data-inset:pl-7' and
'data-[inset]:pl-8') causing redundancy and conflict; remove the
lesser/incorrect one (keep only the intended inset padding syntax, e.g.,
'data-[inset]:pl-8' or standardize to 'data-inset:pl-8') from the class string
so only a single inset padding class remains.

In `@src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte`:
- Around line 17-29: The class list on DropdownMenuPrimitive.SubTrigger contains
conflicting inset padding variants data-inset:pl-7 and data-[inset]:pl-8; remove
one so only a single data-inset variant remains (pick the desired padding, e.g.,
keep data-inset:pl-7 or data-[inset]:pl-8) in the class expression inside the cn
call to eliminate the conflicting CSS rules.

In `@src/lib/features/serialization/__fixtures__/complete-cv.ts`:
- Around line 84-93: The fixture currently computes blockHashes from the local
blocks variable before applying overrides, which can lead to mismatches when
overrides.blocks is provided; change the return construction so blockHashes is
computed from the effective blocks after merging overrides (e.g., determine
finalBlocks = overrides.blocks ?? blocks or merge overrides into the base
blocks, then call computeBlockHashes(finalBlocks)) and use that finalBlocks for
the blocks and blockHashes properties so they always stay consistent (refer to
computeBlockHashes, blocks, blockHashes, and overrides).

In `@src/lib/features/serialization/import.ts`:
- Around line 12-20: The importDocument function currently calls db.cvs.add(cv)
which can throw IndexedDB/write errors and those exceptions are not translated
into the ImportResult contract; wrap the persistence step (the await
db.cvs.add(cv) call inside importDocument) in a try/catch, and on failure return
{ ok: false, error: <meaningful Error or message> } instead of letting the
promise reject; keep the existing behavior for parsing failures (parsed.ok) and
ensure you still compute block hashes (computeBlockHashes), unmappedSections,
and convert with fromDocument before attempting to persist so callers always
receive an ImportResult object on error or success.

In `@src/lib/features/serialization/serialize.ts`:
- Around line 166-167: The export logic omits the explicit current flag which
causes entries with current:true and no dates to round-trip incorrectly; for
each place building the serialized object (the blocks using endDate: j.current ?
undefined : formatDate(j.endDate) and similar for startDate), include the
explicit current property (e.g., add current: j.current) so the exported JSON
preserves the current boolean even when startDate/endDate are absent; apply this
change to the other similar spots referenced (the blocks around the alternative
occurrences noted) so all serialized objects retain current explicitly.
- Around line 193-199: The current serialization for doc.projects uses
projects.map and sets keywords with p.stack.map(...).filter(Boolean) ||
undefined which always produces an array (possibly empty) and thus serializes
empty stacks as keywords: []; update the mapping for each ProjectEntry in the
projects.map callback to compute keywords first (e.g., from
p.stack?.map(...).filter(Boolean) or similar) and then assign keywords:
keywords.length ? keywords : undefined so the keywords field is omitted when
there are no non-empty tokens; modify the project mapping in serialize.ts (the
doc.projects assignment / projects.map callback) accordingly.

In `@src/routes/`+layout.svelte:
- Around line 21-25: The env checks use string-valued import.meta.env flags and
currently test truthiness; update all uses of VITE_DEV_BRIDGE, VITE_DEV_NO_SW,
and VITE_DEV_TOOLBOX to compare explicitly to the string "true" (e.g., use
import.meta.env.VITE_DEV_BRIDGE === 'true', import.meta.env.VITE_DEV_NO_SW !==
'true' for the negated case, and import.meta.env.VITE_DEV_TOOLBOX === 'true'
inside the Svelte {`#if` ...} block) so that values like "false" behave correctly.

In `@static/schema/resume/v0.0.1.json`:
- Around line 7-10: The "iso8601" schema's "pattern" currently allows invalid
months/days; update the regex for the "iso8601" property's "pattern" so the
month uses (0[1-9]|1[0-2]) and the day uses (0[1-9]|[12][0-9]|3[01]) while
preserving the existing alternatives for year-only and year-month forms in the
"iso8601" entry; locate the "iso8601" object and replace the overly-permissive
month/day character classes in its "pattern" value accordingly.

---

Nitpick comments:
In `@scripts/gen-schema.mjs`:
- Around line 13-27: The TracedFileWriter.writeFile currently calls
writeFileSync without error handling; wrap the writeFileSync call in a try-catch
inside TracedFileWriter.writeFile (and only push to this.written after a
successful write) and in the catch produce a clear, contextual error (include
filename, outDir and the original error) either by rethrowing a new Error with
that context or by logging via the same error-handling mechanism used elsewhere;
ensure files() remains unchanged and constructor behavior is preserved.
- Around line 29-33: The current loading of the schema (variables root,
schemaPath, outDir and the const schema = JSON.parse(readFileSync(schemaPath,
'utf8')) line) lacks error handling; wrap the readFileSync/JSON.parse in a
try-catch, catch and log a clear error that includes schemaPath and the original
error message, and exit with a non-zero code (or rethrow) so failure is
explicit; ensure the catch references readFileSync/JSON.parse and schemaPath so
it's easy to locate and diagnose missing file or invalid JSON issues.

In `@src/lib/components/export/export-button.svelte`:
- Around line 54-55: The catch block in export-button.svelte is swallowing
errors; change it to capture the exception (e.g., catch (err)) and log the error
before showing the toast in the export handler (the export button's export
function / click handler). Use the project's logger if available (or
console.error) to emit a descriptive message like "Export failed" along with the
error object, then keep the existing toast.error('Export failed. Please try
again.').

In `@src/lib/components/ui/alert/alert.svelte`:
- Around line 4-16: The long single-line class string in alertVariants (the base
property passed to tv) is hard to read; refactor base by splitting it across
multiple lines—either as an array of individual class segments joined with ' '
or as a multiline template literal concatenated into one string—and update the
tv(...) call to use that assembled string so the final value remains identical;
reference alertVariants and the base property to locate the change.

In `@src/lib/components/ui/button-group/button-group.svelte`:
- Around line 4-17: The long class strings in buttonGroupVariants (specifically
the base property and variants.orientation.horizontal and .vertical) should be
broken into smaller, readable fragments: replace each giant string with either
an array of class segments joined by ' ' or a multi-line template literal that
trims whitespace, keeping the same final class string; update the definitions
for base, horizontal and vertical to build the class string from these fragments
so the tv(...) call uses the identical combined string but with the source split
across multiple lines for readability.

In `@src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte`:
- Line 42: The childrenProp render call in dropdown-menu-checkbox-item.svelte
should forward the checkbox state so consumers can access it; update the usage
of childrenProp (the same prop used in the component) to be invoked with an
object containing checked and indeterminate (the internal state variables)
similar to how dropdown-menu-radio-item.svelte forwards { checked } so the API
is consistent and callers can read both checked and indeterminate.

In `@src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte`:
- Line 32: The radio-item component is passing a `{ checked }` object into the
render prop (`childrenProp`) while the checkbox-item does not, causing an
inconsistent API; fix by aligning both components — recommended: update
`dropdown-menu-checkbox-item.svelte` to call its `childrenProp` with the same
state object (e.g., `{ checked, indeterminate }` as applicable) and update that
component's prop/type signature to accept the state object, or alternatively
remove the `{ checked }` argument from `dropdown-menu-radio-item.svelte` so
neither forwards state; ensure the symbol names `childrenProp`, `checked`, and
`indeterminate` (if used) are used consistently across both components.

In `@src/lib/features/serialization/validator.generated.d.ts`:
- Around line 1-9: The generated file validator.generated.d.ts has mixed
tabs/spaces indentation; update the schema generator (the gen-schema script that
emits the ValidateFn and validate declarations) to produce consistent
indentation (e.g., replace tabs with two spaces or use a single indent strategy
/ pass a consistent formatter) so the emitted block containing ValidateFn, the
validate declaration, and export default validate uses the same spacing;
regenerate the file after changing the generator.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37325419-b09e-40e2-9b4b-32d26827c3f3

📥 Commits

Reviewing files that changed from the base of the PR and between 4aa2f99 and 54694da.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (105)
  • .prettierignore
  • CONTEXT.md
  • README.md
  • assets-cli/package.json
  • assets-cli/src/helpers/dwight.ts
  • assets-cli/src/recipes/dashboard-search.ts
  • assets-cli/src/recipes/dashboard.ts
  • assets-cli/src/recipes/editor.ts
  • assets-cli/src/recipes/export.ts
  • assets-cli/src/recipes/tailoring.ts
  • assets-cli/src/server.ts
  • dev-bridge/package.json
  • dev-bridge/src/index.ts
  • dev-bridge/tsconfig.json
  • docs/agents/domain.md
  • docs/decisions/ADR-001-indexeddb-dexie.md
  • docs/decisions/ADR-002-client-side-pdf-generation.md
  • docs/decisions/ADR-003-dashboard-search.md
  • docs/decisions/ADR-004-e2e-test-strategy.md
  • docs/decisions/ADR-005-pwa-and-durable-storage.md
  • docs/decisions/ADR-006-export-import-schema-as-dto.md
  • docs/decisions/ADR-007-pdf-js-preview.md
  • docs/decisions/ADR-008-self-contained-schema.md
  • docs/decisions/ADR-009-generated-types-from-schema.md
  • docs/features/export/OVERVIEW.md
  • docs/features/import/OVERVIEW.md
  • docs/reference/serialization-schema.md
  • e2e/dashboard.spec.ts
  • e2e/editor.spec.ts
  • e2e/export.spec.ts
  • e2e/tailoring.spec.ts
  • e2e/tsconfig.json
  • eslint.config.js
  • package.json
  • playwright.config.ts
  • pnpm-workspace.yaml
  • scripts/gen-schema.mjs
  • src/app.d.ts
  • src/lib/analytics.ts
  • src/lib/components/cv/create-cv-actions.svelte
  • src/lib/components/cv/index.ts
  • src/lib/components/dashboard/dashboard.svelte
  • src/lib/components/dashboard/index.ts
  • src/lib/components/dashboard/master-group.svelte
  • src/lib/components/dashboard/new-cv-button.svelte
  • src/lib/components/dashboard/tailored-row.svelte
  • src/lib/components/editor/cv-editor-toolbar.svelte
  • src/lib/components/editor/export-button.svelte
  • src/lib/components/editor/index.ts
  • src/lib/components/export/export-button.svelte
  • src/lib/components/export/index.ts
  • src/lib/components/import/import-button.svelte
  • src/lib/components/import/import-dialog.svelte
  • src/lib/components/import/index.ts
  • src/lib/components/landing/about.svelte
  • src/lib/components/landing/hero.svelte
  • src/lib/components/tailoring/sync-drawer.svelte
  • src/lib/components/ui/alert/alert-action.svelte
  • src/lib/components/ui/alert/alert-description.svelte
  • src/lib/components/ui/alert/alert-title.svelte
  • src/lib/components/ui/alert/alert.svelte
  • src/lib/components/ui/alert/index.ts
  • src/lib/components/ui/button-group/button-group-separator.svelte
  • src/lib/components/ui/button-group/button-group-text.svelte
  • src/lib/components/ui/button-group/button-group.svelte
  • src/lib/components/ui/button-group/index.ts
  • src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-group.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-portal.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-sub.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu-trigger.svelte
  • src/lib/components/ui/dropdown-menu/dropdown-menu.svelte
  • src/lib/components/ui/dropdown-menu/index.ts
  • src/lib/components/ui/separator/separator.svelte
  • src/lib/config.ts
  • src/lib/dev-bridge.ts
  • src/lib/features/serialization/__fixtures__/complete-cv.ts
  • src/lib/features/serialization/document.generated.ts
  • src/lib/features/serialization/import.ts
  • src/lib/features/serialization/jsonresume-compat.test.ts
  • src/lib/features/serialization/jsonresume-schema-shims.d.ts
  • src/lib/features/serialization/parse.test.ts
  • src/lib/features/serialization/parse.ts
  • src/lib/features/serialization/serialize.test.ts
  • src/lib/features/serialization/serialize.ts
  • src/lib/features/serialization/validator.generated.d.ts
  • src/lib/features/serialization/validator.generated.js
  • src/lib/pwa/sw-router.test.ts
  • src/lib/services/cv/add-blank-cv.ts
  • src/routes/+layout.svelte
  • src/routes/+page.svelte
  • src/stories/blocks/helpers/ProjectsWrapper.svelte
  • static/schema/resume/v0.0.1.json
  • svelte.config.js
💤 Files with no reviewable changes (6)
  • src/lib/components/editor/export-button.svelte
  • src/lib/components/tailoring/sync-drawer.svelte
  • src/lib/analytics.ts
  • src/lib/components/editor/index.ts
  • src/lib/components/dashboard/new-cv-button.svelte
  • src/lib/components/dashboard/index.ts

Comment thread docs/agents/domain.md
Comment thread docs/decisions/ADR-001-indexeddb-dexie.md
Comment thread src/lib/components/export/export-button.svelte
Comment thread src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
Comment thread src/lib/features/serialization/import.ts
Comment thread src/lib/features/serialization/serialize.ts
Comment thread src/lib/features/serialization/serialize.ts Outdated
Comment thread src/routes/+layout.svelte
Comment thread static/schema/resume/v0.0.1.json Outdated
@AxelUser
AxelUser merged commit 53e2e81 into main Jun 5, 2026
6 checks passed
@AxelUser
AxelUser deleted the feat/export-jsons branch June 5, 2026 01:51
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.

1 participant