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
2 changes: 1 addition & 1 deletion docs/decisions/ADR-004-e2e-test-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ So the e2e suite stays at the level of "does the button call the right thing, do

State lives entirely in IndexedDB (Dexie); there is no server to load fixtures from. Two ways to get a CV into that state, picked per automated consumer by what it is actually checking.

Editor tests build their CV through the real UI, because the editor is the thing under test there. Dashboard, export, tailoring tests, and the asset-generation harness seed through `window.__hhTest`, a bridge that runs the production constructors (`createCVFromTemplate`, `createDummyCV`, `createTailoredCV`, the `db`). Building a populated nine-block master by typing, only to set up a sync test or a screenshot, would be slow and would tie every subsequent step to editor stability — a change to a field input would break setups that have nothing to do with editing.
Editor tests build their CV through the real UI, because the editor is the thing under test there. Dashboard, export, tailoring tests, and the asset-generation harness seed through `window.__hhTest`, a bridge that runs the production constructors (`createCV`, `createDummyCV`, `createTailoredCV`, the `db`). Building a populated nine-block master by typing, only to set up a sync test or a screenshot, would be slow and would tie every subsequent step to editor stability — a change to a field input would break setups that have nothing to do with editing.

The bridge runs production code rather than hand-rolled fixtures on purpose. A CV carries derived state — `blockHashes`, `syncBaseline`, `syncBaselineHashes` — that a raw fixture would have to reproduce and keep in step as the schema moves. Going through the real constructors keeps seeded data valid for free while the app is still changing shape.

Expand Down
10 changes: 5 additions & 5 deletions e2e/editor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,20 @@ test.describe('Editor', () => {
const masterId = await b.seedMaster();
await page.goto(`/cv/${masterId}`);

// Dummy CV has 3 jobs: Nimbus Labs (0), Cobalt Financial (1), Bright Pixel Studio (2)
// Dummy CV has 3 jobs: Dunder Mifflin (0), Schrute Farms (1), Sheriff's Dept (2)
const entries = page.getByTestId('job-entry');
await expect(entries).toHaveCount(3);
await expect(entries.nth(0)).toContainText('Nimbus Labs');
await expect(entries.nth(0)).toContainText('Dunder Mifflin');

// Drag first entry to the position of the third
await reorder(page, entries.nth(0).getByTestId('drag-handle'), entries.nth(2));

await waitForAutosave(page);
await page.reload();

// Nimbus Labs should now be at the end
// Dunder Mifflin should now be at the end
await expect(page.getByTestId('job-entry')).toHaveCount(3);
await expect(page.getByTestId('job-entry').nth(0)).not.toContainText('Nimbus Labs');
await expect(page.getByTestId('job-entry').nth(2)).toContainText('Nimbus Labs');
await expect(page.getByTestId('job-entry').nth(0)).not.toContainText('Dunder Mifflin');
await expect(page.getByTestId('job-entry').nth(2)).toContainText('Dunder Mifflin');
});
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"@fontsource-variable/space-grotesk": "^5.2.10",
"dexie": "^4.3.0",
"fuse.js": "^7.3.0",
"nanoid": "^5.1.11",
"pdfjs-dist": "^5.7.284",
"pdfmake": "^0.3.8",
"posthog-js": "^1.376.5",
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/lib/components/blocks/contacts-block.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import { DragDropProvider, DragOverlay } from '@dnd-kit/svelte';
import type { Draggable } from '@dnd-kit/dom';
import { Trash2, Plus, GripVertical } from '@lucide/svelte';
import type { ContactEntry, ObjectId } from '$lib/types/cv';
import { createObjectId } from '$lib/types/cv';
import { createObjectId, type ContactEntry, type ObjectId } from '$lib/types/cv';

interface Props {
contacts: ContactEntry[];
Expand Down
3 changes: 1 addition & 2 deletions src/lib/components/blocks/education-block.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import { DragDropProvider, DragOverlay } from '@dnd-kit/svelte';
import type { Draggable } from '@dnd-kit/dom';
import { Trash2, Plus, GripVertical } from '@lucide/svelte';
import type { EducationEntry, ObjectId } from '$lib/types/cv';
import { createObjectId } from '$lib/types/cv';
import { createObjectId, type EducationEntry, type ObjectId } from '$lib/types/cv';

interface Props {
education: EducationEntry[];
Expand Down
3 changes: 1 addition & 2 deletions src/lib/components/blocks/job-history-block.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
import { DragDropProvider, DragOverlay } from '@dnd-kit/svelte';
import type { Draggable } from '@dnd-kit/dom';
import { Trash2, Plus, GripVertical } from '@lucide/svelte';
import type { JobEntry, ObjectId } from '$lib/types/cv';
import { createObjectId } from '$lib/types/cv';
import { createObjectId, type JobEntry, type ObjectId } from '$lib/types/cv';

interface Props {
jobs: JobEntry[];
Expand Down
3 changes: 1 addition & 2 deletions src/lib/components/blocks/projects-block.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import { DragDropProvider, DragOverlay } from '@dnd-kit/svelte';
import type { Draggable } from '@dnd-kit/dom';
import { Trash2, Plus, GripVertical } from '@lucide/svelte';
import type { ProjectEntry, ObjectId } from '$lib/types/cv';
import { createObjectId } from '$lib/types/cv';
import { createObjectId, type ProjectEntry, type ObjectId } from '$lib/types/cv';

interface Props {
projects: ProjectEntry[];
Expand Down
3 changes: 1 addition & 2 deletions src/lib/components/blocks/skills-block.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import { DragDropProvider, DragOverlay } from '@dnd-kit/svelte';
import type { Draggable } from '@dnd-kit/dom';
import { Trash2, Plus, GripVertical } from '@lucide/svelte';
import type { SkillCategory, ObjectId } from '$lib/types/cv';
import { createObjectId } from '$lib/types/cv';
import { createObjectId, type SkillCategory, type ObjectId } from '$lib/types/cv';

interface Props {
skills: SkillCategory[];
Expand Down
7 changes: 4 additions & 3 deletions src/lib/components/dashboard/new-cv-button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import { Button } from '$lib/components/ui/button';
import { Plus } from '@lucide/svelte';
import { db } from '$lib/db/index';
import { createCVFromTemplate } from '$lib/services/cv/create';
import { createCV } from '$lib/services/cv/create';
import { capture } from '$lib/analytics';
import { createId } from '$lib/id.js';

interface Props {
onCreate: (id: string) => void;
Expand All @@ -12,8 +13,8 @@
let { onCreate }: Props = $props();

async function handleClick() {
const id = crypto.randomUUID();
const cv = createCVFromTemplate(id, 'Untitled CV');
const id = createId();
const cv = createCV({ id, name: 'Untitled CV' });
await db.cvs.add(cv);
capture('cv_created', { source: 'dashboard' });
onCreate(id);
Expand Down
137 changes: 74 additions & 63 deletions src/lib/components/dev/dummy-cv.ts
Original file line number Diff line number Diff line change
@@ -1,161 +1,172 @@
import { createObjectId } from '$lib/types/cv';
import type { CV, Tag } from '$lib/types/cv';
import { createCVFromTemplate } from '$lib/services/cv/create';
import { computeBlockHashes } from '$lib/features/tailoring/hash';
import { createCV } from '$lib/services/cv/create';
import { createObjectId, type CV, type Tag } from '$lib/types/cv';

function tags(...values: string[]): Tag[] {
return values.map((value) => ({ objectId: createObjectId(), value }));
}

export function createDummyCV(): CV {
const cv = createCVFromTemplate(crypto.randomUUID(), 'Dummy — Full CV');
const cv = createCV({ name: 'Dummy — Dwight Schrute' });

cv.blocks.fullName.value = 'Jordan Rivera';
cv.blocks.position.value = 'Senior Software Engineer';
cv.blocks.location.value = 'Berlin, Germany';
cv.blocks.fullName.value = 'Dwight K. Schrute';
cv.blocks.position.value = 'Assistant Regional Manager';
cv.blocks.location.value = 'Scranton, Pennsylvania';

cv.blocks.contacts.value = [
{ objectId: createObjectId(), label: 'Email', value: 'jordan.rivera@example.com' },
{ objectId: createObjectId(), label: 'Phone', value: '+49 30 1234567' },
{ objectId: createObjectId(), label: 'LinkedIn', value: 'linkedin.com/in/jordanrivera' },
{ objectId: createObjectId(), label: 'GitHub', value: 'github.com/jordanrivera' },
{ objectId: createObjectId(), label: 'Website', value: 'jordanrivera.dev' }
{ objectId: createObjectId(), label: 'Email', value: 'dwight@dundermifflin.com' },
{ objectId: createObjectId(), label: 'Phone', value: '+1 570 555 0198' },
{ objectId: createObjectId(), label: 'Website', value: 'schrutefarms.biz' },
{ objectId: createObjectId(), label: 'LinkedIn', value: 'linkedin.com/in/dwightschrute' }
];

cv.blocks.highlights.value = [
{
objectId: createObjectId(),
text: 'Eight years building and scaling web applications across fintech and developer tooling.'
text: 'Top-performing Dunder Mifflin salesman for six consecutive quarters.'
},
{
objectId: createObjectId(),
text: 'Led a team of five engineers through a full migration to a TypeScript monorepo.'
text: 'Owner and proprietor of Schrute Farms, a working beet farm and bed & breakfast.'
},
{
objectId: createObjectId(),
text: 'Cut median API latency by 40% by reworking the data access layer.'
text: "Volunteer sheriff's deputy with advanced training in hand-to-hand combat and surveillance."
},
{
objectId: createObjectId(),
text: 'Regular speaker at local meetups on frontend performance.'
text: 'Holds a black belt in Goju-Ryu karate and a brown belt in Judo.'
}
];

cv.blocks.skills.value = [
{
objectId: createObjectId(),
name: 'Languages',
skills: tags('TypeScript', 'JavaScript', 'Python', 'Go', 'SQL')
name: 'Sales',
skills: tags('Paper Sales', 'Cold Calling', 'Account Management', 'Client Retention')
},
{
objectId: createObjectId(),
name: 'Frameworks',
skills: tags('Svelte', 'SvelteKit', 'React', 'Node.js', 'Express')
name: 'Agriculture',
skills: tags('Beet Farming', 'Agritourism', 'Animal Husbandry', 'Beekeeping')
},
{
objectId: createObjectId(),
name: 'Tools',
skills: tags('Docker', 'PostgreSQL', 'Redis', 'GitHub Actions', 'AWS')
name: 'Security',
skills: tags('Surveillance', 'Brazilian Jiu-Jitsu', 'Nunchucks', 'Volunteer Deputy Work')
}
];

cv.blocks.jobHistory.value = [
{
objectId: createObjectId(),
company: 'Nimbus Labs',
role: 'Senior Software Engineer',
startDate: new Date('2021-03-01'),
company: 'Dunder Mifflin Paper Company',
role: 'Assistant Regional Manager',
startDate: new Date('2004-03-24'),
endDate: undefined,
current: true,
achievements: [
{
objectId: createObjectId(),
text: 'Designed the event-sourcing pipeline powering the billing system.'
text: 'Consistently led the Scranton branch in paper and office supply sales.'
},
{
objectId: createObjectId(),
text: 'Mentored three junior engineers to mid-level promotions.'
text: 'Managed the safety committee, reducing workplace incidents by 40%.'
},
{
objectId: createObjectId(),
text: 'Introduced end-to-end tests, dropping production regressions by half.'
text: 'Instituted a branch-wide volunteer militia programme, improving morale by 73%.'
},
{
objectId: createObjectId(),
text: 'Owned the design system adopted across four product teams.'
text: 'Served as Acting Regional Manager during three separate managerial vacancies.'
}
],
skills: tags('TypeScript', 'SvelteKit', 'PostgreSQL', 'AWS')
skills: tags('Paper Sales', 'Team Leadership', 'Safety Compliance')
},
{
objectId: createObjectId(),
company: 'Cobalt Financial',
role: 'Software Engineer',
startDate: new Date('2018-06-01'),
endDate: new Date('2021-02-01'),
current: false,
company: 'Schrute Farms',
role: 'Owner & Proprietor',
startDate: new Date('1998-05-01'),
endDate: undefined,
current: true,
achievements: [
{
objectId: createObjectId(),
text: 'Built the customer dashboard used by 200k monthly active users.'
text: 'Operates a 60-acre beet farm producing award-winning varieties of red and golden beets.'
},
{ objectId: createObjectId(), text: 'Reduced page load time from 4.2s to 1.1s.' },
{ objectId: createObjectId(), text: 'Integrated three third-party payment providers.' }
{
objectId: createObjectId(),
text: 'Converted the farmhouse into a bed & breakfast with five themed guest rooms.'
},
{
objectId: createObjectId(),
text: "Featured in TripAdvisor's top 10 most haunted B&Bs in Pennsylvania."
}
],
skills: tags('React', 'Node.js', 'Redis')
skills: tags('Farming', 'Hospitality', 'Business Operations')
},
{
objectId: createObjectId(),
company: 'Bright Pixel Studio',
role: 'Junior Developer',
startDate: new Date('2016-09-01'),
endDate: new Date('2018-05-01'),
current: false,
company: "Lackawanna County Sheriff's Department",
role: 'Volunteer Deputy',
startDate: new Date('2003-01-01'),
endDate: undefined,
current: true,
achievements: [
{
objectId: createObjectId(),
text: 'Delivered marketing sites for a dozen agency clients.'
text: 'Assisted with crowd control at three consecutive Steamtown Marathon events.'
},
{ objectId: createObjectId(), text: 'Automated the deploy process with a CI pipeline.' }
{
objectId: createObjectId(),
text: 'Trained in prisoner transport, evidence handling, and report writing.'
}
],
skills: tags('JavaScript', 'CSS', 'Docker')
skills: tags('Law Enforcement', 'Crowd Control')
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
];

cv.blocks.projects.value = [
{
objectId: createObjectId(),
name: 'OpenLedger',
description: 'An open-source double-entry accounting library with a plugin system.',
stack: tags('TypeScript', 'Node.js', 'PostgreSQL'),
link: 'github.com/jordanrivera/openledger'
name: 'Schrute Farms B&B',
description:
'An agritourism bed & breakfast with five themed rooms, farm tours, and seasonal beet harvest experiences.',
stack: tags('Hospitality', 'Agritourism', 'Marketing'),
link: 'schrutefarms.biz'
},
{
objectId: createObjectId(),
name: 'Pulse',
description: 'A real-time dashboard for monitoring self-hosted services.',
stack: tags('Svelte', 'Go', 'Redis'),
link: 'pulse.jordanrivera.dev'
name: 'Recyclops',
description:
'A grassroots office recycling programme that evolved into a full workplace environmental enforcement role.',
stack: tags('Sustainability', 'Waste Management', 'Community Outreach'),
link: ''
}
];

cv.blocks.education.value = [
{
objectId: createObjectId(),
institution: 'Technical University of Munich',
degree: 'M.Sc. Computer Science',
startDate: new Date('2014-10-01'),
endDate: new Date('2016-07-01'),
institution: 'Penn State University',
degree: 'B.A. Business Administration',
startDate: new Date('1992-09-01'),
endDate: new Date('1996-05-15'),
current: false
},
{
objectId: createObjectId(),
institution: 'University of Vienna',
degree: 'B.Sc. Software Engineering',
startDate: new Date('2011-10-01'),
endDate: new Date('2014-07-01'),
institution: 'Lackawanna County Community College',
degree: 'Certificate in Criminal Justice',
startDate: new Date('2001-01-15'),
endDate: new Date('2002-12-20'),
current: false
}
];

cv.blockHashes = computeBlockHashes(cv.blocks);
return cv;
}
3 changes: 1 addition & 2 deletions src/lib/components/ui/editable-list/editable-list.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import { cn } from '$lib/utils';
import { DragDropProvider, DragOverlay } from '@dnd-kit/svelte';
import type { Draggable } from '@dnd-kit/dom';
import { createObjectId } from '$lib/types/cv';
import type { ObjectId } from '$lib/types/cv';
import { createObjectId, type ObjectId } from '$lib/types/cv';

interface ListItem {
objectId: ObjectId;
Expand Down
Loading
Loading