@timurtekb/tekjobs 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/LICENSE +21 -0
  3. package/README.md +145 -0
  4. package/app/README.md +22 -0
  5. package/app/dist/assets/index-DzGgV6NF.css +1 -0
  6. package/app/dist/assets/index-QteuouKv.js +77 -0
  7. package/app/dist/favicon.svg +1 -0
  8. package/app/dist/index.html +16 -0
  9. package/app/server/cover-letter.mjs +243 -0
  10. package/app/server/index.mjs +113 -0
  11. package/app/server/mail-check.mjs +234 -0
  12. package/app/server/mcp.mjs +129 -0
  13. package/app/server/people.mjs +164 -0
  14. package/app/server/store.mjs +1131 -0
  15. package/app/server/tailored-resume.mjs +213 -0
  16. package/cli.mjs +205 -0
  17. package/package.json +74 -0
  18. package/run.cmd +10 -0
  19. package/run.mjs +201 -0
  20. package/run.sh +12 -0
  21. package/samples/vault/Jobs/Basalt Systems - Senior Design Engineer, Growth (1010).md +56 -0
  22. package/samples/vault/Jobs/Brightline Studio - Design Engineer (1005).md +51 -0
  23. package/samples/vault/Jobs/Copperleaf - Principal Design Engineer (1006).md +53 -0
  24. package/samples/vault/Jobs/Example Studio - Senior Design Engineer (1013).md +52 -0
  25. package/samples/vault/Jobs/Fjord Analytics - Staff Frontend Engineer, Platform (1003).md +58 -0
  26. package/samples/vault/Jobs/Halcyon Robotics - Senior UX Engineer (1004).md +52 -0
  27. package/samples/vault/Jobs/Lumen Health - Design Systems Engineer (1002).md +51 -0
  28. package/samples/vault/Jobs/Meridian Pay - Staff Design Engineer (1009).md +67 -0
  29. package/samples/vault/Jobs/Northwind Labs - Staff Design Engineer, Design Systems (1000).md +57 -0
  30. package/samples/vault/Jobs/Orbital Software - Senior Design Engineer (1001).md +52 -0
  31. package/samples/vault/Jobs/Quill & Co - Design Engineer, Editor (1008).md +55 -0
  32. package/samples/vault/Jobs/Signalfire Design - Design Engineer (1012).md +53 -0
  33. package/samples/vault/Jobs/Tessellate - Senior Frontend Engineer, Design Systems (1007).md +52 -0
  34. package/samples/vault/Jobs/Verdant - UX Engineer (1011).md +51 -0
  35. package/samples/vault/Logs/2026-09-16.md +13 -0
  36. package/samples/vault/Logs/2026-09-23.md +13 -0
  37. package/samples/vault/People/Lee Example (Fjord Analytics).md +22 -0
  38. package/samples/vault/People/Priya Example (Meridian Pay).md +23 -0
  39. package/samples/vault/People/Sam Example (Northwind Labs).md +20 -0
  40. package/samples/vault/Profile/Positioning.md +16 -0
  41. package/samples/vault/Profile/Profile.md +41 -0
  42. package/samples/vault/Profile/Resume.md +32 -0
  43. package/samples/vault/Profile/Voice.md +15 -0
  44. package/samples/vault/README.md +11 -0
  45. package/samples/vault/Targets/Companies.md +318 -0
  46. package/samples/vault/Targets/Search Criteria.md +234 -0
  47. package/samples/vault/_Home.md +54 -0
  48. package/scraper/config.mjs +122 -0
  49. package/scraper/health.mjs +50 -0
  50. package/scraper/import-link.mjs +281 -0
  51. package/scraper/profile.mjs +186 -0
  52. package/scraper/rescore.mjs +236 -0
  53. package/scraper/resume-sync.mjs +224 -0
  54. package/scraper/resume.mjs +65 -0
  55. package/scraper/score.mjs +114 -0
  56. package/scraper/sources-email.mjs +242 -0
  57. package/scraper/sources-extra.mjs +558 -0
  58. package/scraper/sources-sites.mjs +280 -0
  59. package/scraper/sources.mjs +264 -0
  60. package/scraper/starter/companies-table.md +306 -0
  61. package/scraper/starter/criteria.json +56 -0
  62. package/scraper/starter/profile.md +38 -0
  63. package/scraper/vault.mjs +212 -0
package/run.mjs ADDED
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env node
2
+ // TekJobs runner. Usage: node run.mjs [--dry] [--min N] [--floor N] [--only slug] [--retry-failed] [--criteria <preset name | file>] [--no-remoteok] [--no-hn] [--check-slugs]
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { ensureDirs, loadCriteria, loadCompanies, writeCompanyStatuses, criteriaPresetFile, P } from './scraper/config.mjs';
6
+ import { fetchCompany, fetchRemoteOK, fetchHNWhoIsHiring, htmlToText } from './scraper/sources.mjs';
7
+ import { scoreJob, parseSalary } from './scraper/score.mjs';
8
+ import { loadSeen, saveSeen, writeJobNote, markClosedListings, appendLog, writeDashboard, readFrontmatter } from './scraper/vault.mjs';
9
+ import { weightsFingerprint } from './scraper/rescore.mjs';
10
+ import { loadHealth, saveHealth, record, healthState } from './scraper/health.mjs';
11
+
12
+ const args = process.argv.slice(2);
13
+ const flag = (f) => args.includes(f);
14
+ const opt = (f) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : undefined; };
15
+ const DRY = flag('--dry');
16
+ const ONLY = opt('--only');
17
+ // Only the boards whose latest attempt failed, and no feeds: the Sources page's Retry button.
18
+ const RETRY = flag('--retry-failed');
19
+
20
+ async function pool(items, n, fn) {
21
+ const out = new Array(items.length);
22
+ let i = 0;
23
+ await Promise.all(Array.from({ length: n }, async () => {
24
+ while (i < items.length) { const k = i++; out[k] = await fn(items[k], k); }
25
+ }));
26
+ return out;
27
+ }
28
+
29
+ const t0 = Date.now();
30
+ ensureDirs();
31
+ // Which criteria this run scores with: the active note, or a named preset / any criteria file for this run only.
32
+ let criteriaFile = P.criteria, criteriaName = 'Search Criteria';
33
+ if (opt('--criteria')) {
34
+ const want = opt('--criteria');
35
+ criteriaFile = fs.existsSync(want) ? path.resolve(want) : criteriaPresetFile(want);
36
+ if (!fs.existsSync(criteriaFile)) { console.error(`No criteria preset or file "${want}" (looked in ${P.criteriaDir}).`); process.exit(1); }
37
+ criteriaName = path.basename(criteriaFile, '.md');
38
+ }
39
+ const criteria = loadCriteria(criteriaFile);
40
+ const criteriaLabel = `${criteriaName} (${weightsFingerprint(criteria)})`;
41
+ if (opt('--min')) criteria.minScore = Number(opt('--min'));
42
+ if (opt('--floor')) { criteria.salary = { ...(criteria.salary || {}), minAnnual: Number(opt('--floor')) }; console.log(` pay floor overridden: $${criteria.salary.minAnnual}`); }
43
+ let companies = loadCompanies();
44
+ if (ONLY) companies = companies.filter((c) => c.slug === ONLY || c.name.toLowerCase() === ONLY.toLowerCase());
45
+ const health = loadHealth();
46
+ if (RETRY) {
47
+ companies = companies.filter((c) => healthState(health.boards[`${c.ats}:${c.slug}`], c.status) === 'failed');
48
+ if (companies.length === 0) { console.log('No failed boards to retry.'); process.exit(0); }
49
+ }
50
+ console.log(`TekJobs run ${new Date().toISOString()}${DRY ? ' (dry)' : ''} — ${companies.length} companies, minScore ${criteria.minScore}, criteria ${criteriaLabel}`);
51
+
52
+ // 1. Fetch company boards
53
+ const statusBySlug = {};
54
+ const liveIdsByCompanyKey = {};
55
+ const failed = [];
56
+ // Workday boards are slow (search + per-job detail); run them last so the fast boards finish first.
57
+ companies.sort((a, b) => (a.ats === 'workday') - (b.ats === 'workday'));
58
+ const results = await pool(companies, 8, async (c) => {
59
+ const key = `${c.ats}:${c.slug}`;
60
+ const r = await fetchCompany(c, criteria);
61
+ health.boards[key] = record(health.boards[key], r);
62
+ if (!r.ok) {
63
+ statusBySlug[key] = `bad-slug (${r.error})`;
64
+ failed.push({ ...c, error: r.error });
65
+ process.stdout.write(` x ${c.name} [${key}] ${r.error}\n`);
66
+ return [];
67
+ }
68
+ statusBySlug[key] = r.emptyBoard ? 'ok · 0 jobs (slug?)' : r.scanned != null ? `ok · ${r.jobs.length} of ${r.scanned} searched` : `ok · ${r.jobs.length}`;
69
+ liveIdsByCompanyKey[key] = new Set(r.jobs.map((j) => j.id));
70
+ for (const j of r.jobs) { j.companyKey = key; j.tier = c.tier; }
71
+ return r.jobs;
72
+ });
73
+ let jobs = results.flat();
74
+
75
+ // 2. Open sources (no slug). RemoteOK and HN are always on unless flagged; the rest toggle via criteria.openSources.
76
+ const extras = [];
77
+ const open = criteria.openSources || {};
78
+ if (!ONLY && !RETRY) {
79
+ const { OPEN_SOURCES } = await import('./scraper/sources-extra.mjs');
80
+ const tasks = [];
81
+ if (!flag('--no-remoteok') && open.remoteok !== false) tasks.push(['RemoteOK', fetchRemoteOK]);
82
+ if (!flag('--no-hn') && open.hn !== false) tasks.push(['HN Who is hiring', fetchHNWhoIsHiring]);
83
+ // A source marked defaultOn (TekJobs' own postings) runs unless the criteria say `false`; the rest are opt-in.
84
+ for (const [key, def] of Object.entries(OPEN_SOURCES)) if (def.defaultOn ? open[key] !== false : open[key]) tasks.push([def.label, def.fn]);
85
+ // Open-source fetchers are handed the criteria. Most ignore it; the ones that read the profile (Adzuna
86
+ // searches for its own titleTerms) need it, and the rest destructure their own options, so an extra
87
+ // argument is harmless.
88
+ // The inbox folder is a path, not a preference, so it is passed alongside the criteria rather than
89
+ // written into a file the person edits by hand.
90
+ const withPaths = { ...criteria, _inbox: P.inbox };
91
+ const results = await pool(tasks, 4, async ([label, fn]) => [label, await fn(withPaths)]);
92
+ for (const [label, r] of results) {
93
+ extras.push([label, r]);
94
+ health.feeds[label] = record(health.feeds[label], r);
95
+ if (r.ok) { for (const j of r.jobs) j.companyKey = `open:${label}`; jobs.push(...r.jobs); liveIdsByCompanyKey[`open:${label}`] = new Set(r.jobs.map((j) => j.id)); }
96
+ else console.log(` x ${label} ${r.error}`);
97
+ }
98
+ }
99
+ if (flag('--check-slugs')) {
100
+ console.table(Object.entries(statusBySlug).map(([k, v]) => ({ board: k, status: v })));
101
+ if (!DRY) writeCompanyStatuses(statusBySlug);
102
+ process.exit(0);
103
+ }
104
+
105
+ // 2b. Dedupe. The same opening shows up per-location on some boards (Rippling, Greenhouse multi-city) and again on
106
+ // aggregators. Key on company + title; keep the ATS copy over the aggregator copy; merge locations of the rest.
107
+ // Sources whose copy of a posting is thinner than the ATS's, and so loses when both are present: the
108
+ // aggregators, Adzuna (snippet descriptions) and anything imported from an alert email (a title and
109
+ // little else). USAJOBS is not one of them — it is the authority for the jobs it carries.
110
+ const AGG = new Set(['themuse', 'remotive', 'himalayas', 'jobicy', 'workingnomads', 'arbeitnow', 'wwr', 'remoteok', 'hn', 'adzuna', 'wellfound', 'builtin']);
111
+ const isThin = (j) => AGG.has(j.source) || String(j.source).startsWith('email:');
112
+ {
113
+ const norm = (s) => (s || '').toLowerCase().replace(/\(.*?\)|\[.*?\]/g, '').replace(/[^a-z0-9]+/g, ' ').trim();
114
+ const byKey = new Map();
115
+ const ordered = [...jobs].sort((a, b) => (isThin(a) - isThin(b)));
116
+ for (const j of ordered) {
117
+ const key = `${norm(j.company)}|${norm(j.title)}`;
118
+ const keep = byKey.get(key);
119
+ if (!keep) { byKey.set(key, j); continue; }
120
+ if (keep.source === j.source && j.location && !keep.location.includes(j.location)) keep.location = `${keep.location}; ${j.location}`.slice(0, 300);
121
+ if (!keep.salary && j.salary) { keep.salary = j.salary; }
122
+ if (!keep.remote && j.remote) keep.remote = true;
123
+ keep.dupes = (keep.dupes || 0) + 1;
124
+ // Some boards repeat the same posting id once per location; never drop the id the kept note points at.
125
+ if (j.id !== keep.id && j.companyKey && liveIdsByCompanyKey[j.companyKey]) liveIdsByCompanyKey[j.companyKey].delete(j.id);
126
+ }
127
+ const before = jobs.length;
128
+ jobs = [...byKey.values()];
129
+ console.log(` dedupe: ${before} → ${jobs.length}`);
130
+ }
131
+
132
+ // 3. Score
133
+ for (const j of jobs) {
134
+ j.descriptionText = htmlToText(j.descriptionHtml || '');
135
+ const range = parseSalary(`${j.salary || ''}\n${j.descriptionText}`);
136
+ if (range) { j.salaryMin = range.min; j.salaryMax = range.max; if (!j.salary) j.salary = `$${Math.round(range.min / 1000)}k–$${Math.round(range.max / 1000)}k`; }
137
+ j.scored = scoreJob(j, criteria);
138
+ }
139
+ const scored = jobs.filter((j) => !j.scored.excluded).sort((a, b) => b.scored.score - a.scored.score);
140
+ const matches = scored.filter((j) => j.scored.score >= criteria.minScore);
141
+
142
+ // 4. Write new matches to the vault
143
+ const seen = loadSeen();
144
+ const fresh = matches.filter((j) => !seen[j.id]);
145
+ const written = [];
146
+ for (const j of fresh) {
147
+ if (DRY) { written.push({ job: j, file: '(dry)' }); continue; }
148
+ const file = writeJobNote(j, j.scored, criteria);
149
+ seen[j.id] = { path: file, firstSeen: new Date().toISOString(), company: j.company, title: j.title, companyKey: j.companyKey || j.source, score: j.scored.score };
150
+ written.push({ job: j, file });
151
+ }
152
+ let closed = [];
153
+ if (!DRY) {
154
+ closed = markClosedListings(liveIdsByCompanyKey, seen);
155
+ saveSeen(seen);
156
+ writeCompanyStatuses(statusBySlug);
157
+ }
158
+
159
+ // Source health is diagnostic, so it is written on dry runs too.
160
+ saveHealth(health);
161
+
162
+ // 5. Debug snapshot: everything scored, so near-misses can be reviewed and criteria tuned
163
+ fs.writeFileSync(P.lastRun, JSON.stringify({
164
+ when: new Date().toISOString(), minScore: criteria.minScore, failed,
165
+ jobs: scored.slice(0, 800).map((j) => ({ score: j.scored.score, company: j.company, title: j.title, location: j.location, remote: !!j.remote, salary: j.salary || '', salaryMin: j.salaryMin || null, salaryMax: j.salaryMax || null, payBand: j.scored.payBand, posted: j.posted, url: j.url, source: j.source, reasons: j.scored.reasons, isNew: !seen[j.id] || fresh.includes(j) })),
166
+ }, null, 2));
167
+
168
+ // 6. Log + dashboard
169
+ const summary = {
170
+ when: new Date().toISOString().replace('T', ' ').slice(0, 16) + ' UTC',
171
+ companiesTotal: companies.length,
172
+ companiesOk: companies.length - failed.length,
173
+ companiesFailed: failed.length,
174
+ totalJobs: jobs.length,
175
+ newMatches: written.length,
176
+ };
177
+ const logLines = [
178
+ `## Run ${summary.when}${DRY ? ' (dry)' : ''}`,
179
+ `- Boards: ${summary.companiesOk}/${summary.companiesTotal} ok · postings scanned: ${summary.totalJobs} · scored ≥ ${criteria.minScore}: ${matches.length} · **new: ${written.length}** · closed: ${closed.length} · ${((Date.now() - t0) / 1000).toFixed(1)}s`,
180
+ `- Criteria: ${criteriaLabel}`,
181
+ // Who started it: the morning task (run.cmd / run.sh), the app, an agent over MCP, or someone at the CLI.
182
+ `- Via: ${process.env.TEKJOBS_RUN_VIA || 'cli'}`,
183
+ ...extras.map(([n, r]) => `- ${n}: ${r.ok ? `${r.jobs.length} postings${r.thread ? ` (${r.thread})` : ''}` : `failed: ${r.error}`}`),
184
+ ...(failed.length ? [`- Failed slugs: ${failed.map((f) => `${f.name} (${f.ats}:${f.slug} — ${f.error})`).join('; ')}`] : []),
185
+ ...(written.length ? ['', '### New matches', ...written.map(({ job }) => `- **${job.scored.score}** ${job.company} — [${job.title}](${job.url}) · ${job.location || 'n/a'}`)] : []),
186
+ ...(closed.length ? ['', '### Closed since last run', ...closed.map((c) => `- ${c.company} — ${c.title}`)] : []),
187
+ ];
188
+ if (!DRY) {
189
+ appendLog(logLines);
190
+ writeDashboard(summary);
191
+ }
192
+ console.log(logLines.join('\n'));
193
+ // The top of the list, with where each one stands. Ranking ignores status on purpose (the score is about the
194
+ // posting), but a person reading the log should not be shown a job they already applied to as if it were news.
195
+ const statusOf = (j) => { const p = seen[j.id]?.path; if (!p) return 'new'; const fm = readFrontmatter(p); return fm?.status || 'new'; };
196
+ const top = scored.map((j) => ({ j, status: statusOf(j) }));
197
+ const stillOpen = top.filter((t) => !['applied', 'interviewing', 'offer', 'rejected', 'passed'].includes(t.status));
198
+ console.log(`\nTop 15 still open (not yet applied or passed):`);
199
+ for (const { j, status } of stillOpen.slice(0, 15)) console.log(` ${String(j.scored.score).padStart(4)} ${status === 'new' ? '' : `[${status}] `}${j.company} — ${j.title} [${j.location}]`);
200
+ const done = top.slice(0, 30).filter((t) => ['applied', 'interviewing', 'offer', 'passed', 'rejected'].includes(t.status));
201
+ if (done.length) console.log(` (${done.length} of the top 30 already ${done.map((t) => t.status).filter((s, i, a) => a.indexOf(s) === i).join('/')}: ${done.map((t) => t.j.company).join(', ')})`);
package/run.sh ADDED
@@ -0,0 +1,12 @@
1
+ #!/bin/sh
2
+ # Daily TekJobs scan for cron and launchd: the scan, then the read-only mail pass. `tekjobs schedule` prints
3
+ # the one line that calls this at 07:30; run.cmd is the Windows twin.
4
+ cd "$(dirname "$0")" || exit 1
5
+ export TEKJOBS_RUN_VIA=schedule
6
+ mkdir -p data
7
+ {
8
+ echo "===== $(date) ====="
9
+ node run.mjs
10
+ echo "----- mail $(date) -----"
11
+ node cli.mjs mail
12
+ } >> data/runs.log 2>&1
@@ -0,0 +1,56 @@
1
+ ---
2
+ type: job
3
+ status: rejected
4
+ listing: open
5
+ company: "Basalt Systems"
6
+ title: "Senior Design Engineer, Growth"
7
+ location: "Remote US"
8
+ remote: true
9
+ source: ashby
10
+ url: "https://jobs.example.com/basalt/senior-design-engineer-growth"
11
+ score: 114
12
+ weights: 3e895160
13
+ posted: "2026-08-29"
14
+ found: 2026-08-30
15
+ salary: "$180k–$220k"
16
+ salary_max: 220000
17
+ pay_band: floor
18
+ department: "Growth"
19
+ job_id: "as:basalt:1010"
20
+ tags: [job]
21
+ ---
22
+
23
+ # Senior Design Engineer, Growth @ Basalt Systems
24
+
25
+ **[Open posting](https://jobs.example.com/basalt/senior-design-engineer-growth)** · Remote US · Remote · score **114** · $180k–$220k
26
+
27
+ ## Why it matched
28
+ - title +40: design engineer
29
+ - seniority +8 (senior)
30
+ - description +14: design system, react, typescript
31
+ - remote +15
32
+ - pay range tops out at $220k, at or above floor (+10, +2 for 20k above)
33
+ - posted 1d ago (+25)
34
+
35
+ ## Status log
36
+ - 2026-08-30 — found by scraper (score 114). Set `status:` above to `reviewing`, `applying`, `applied`, `interviewing`, `offer`, `rejected`, or `passed`.
37
+ - 2026-09-01 — new → **reviewing** (via app)
38
+ - 2026-09-03 — reviewing → **applying** (via app)
39
+ - 2026-09-05 — applying → **applied** (via app)
40
+ - 2026-09-17 — applied → **rejected** (via app)
41
+
42
+ ## Notes
43
+ - 2026-09-17 (app): Form rejection after twelve days. No reason given.
44
+
45
+ ## Application
46
+ - **Narrative:**
47
+ - **Resume variant:**
48
+ - **Cover letter:**
49
+ - **Applied on:**
50
+ - **Contact / referral:**
51
+ - **Follow-up due:**
52
+
53
+ ## Job description
54
+ > Source: ashby
55
+
56
+ Basalt Systems sells infrastructure monitoring. The growth team builds onboarding, pricing and the signup flow, and wants a design engineer who ships polished interface fast in React and TypeScript on our design system. Remote in the US.
@@ -0,0 +1,51 @@
1
+ ---
2
+ type: job
3
+ status: new
4
+ listing: open
5
+ company: "Brightline Studio"
6
+ title: "Design Engineer"
7
+ location: "Remote · United States"
8
+ remote: true
9
+ source: lever
10
+ url: "https://jobs.example.com/brightline/design-engineer"
11
+ score: 95
12
+ weights: 3e895160
13
+ posted: "2026-09-22"
14
+ found: 2026-09-22
15
+ salary: "$160k–$195k"
16
+ salary_max: 195000
17
+ pay_band: stretch
18
+ department: "Studio"
19
+ job_id: "le:brightline:1005"
20
+ tags: [job, stretch]
21
+ ---
22
+
23
+ # Design Engineer @ Brightline Studio
24
+
25
+ **[Open posting](https://jobs.example.com/brightline/design-engineer)** · Remote · United States · Remote · score **95** · $160k–$195k
26
+
27
+ ## Why it matched
28
+ - title +40: design engineer
29
+ - description +23: react, typescript, storybook, figma, accessibility, component library
30
+ - remote +15
31
+ - pay range tops out at $195k, stretch band (-8)
32
+ - posted 0d ago (+25)
33
+
34
+ ## Status log
35
+ - 2026-09-22 — found by scraper (score 95). Set `status:` above to `reviewing`, `applying`, `applied`, `interviewing`, `offer`, `rejected`, or `passed`.
36
+
37
+ ## Notes
38
+
39
+
40
+ ## Application
41
+ - **Narrative:**
42
+ - **Resume variant:**
43
+ - **Cover letter:**
44
+ - **Applied on:**
45
+ - **Contact / referral:**
46
+ - **Follow-up due:**
47
+
48
+ ## Job description
49
+ > Source: lever
50
+
51
+ Brightline is a 40-person product studio. You would be our first design engineer: build the component library our client work starts from, in React and TypeScript with Storybook, and set the accessibility bar for every project. Figma fluency matters; you will work in the file as much as in the editor. Remote in the US.
@@ -0,0 +1,53 @@
1
+ ---
2
+ type: job
3
+ status: reviewing
4
+ listing: open
5
+ company: "Copperleaf"
6
+ title: "Principal Design Engineer"
7
+ location: "Remote - US"
8
+ remote: true
9
+ source: greenhouse
10
+ url: "https://jobs.example.com/copperleaf/principal-design-engineer"
11
+ score: 134
12
+ weights: 3e895160
13
+ posted: "2026-09-19"
14
+ found: 2026-09-20
15
+ salary: "$240k–$290k"
16
+ salary_max: 290000
17
+ pay_band: floor
18
+ department: "Design Systems"
19
+ job_id: "gr:copperleaf:1006"
20
+ tags: [job]
21
+ ---
22
+
23
+ # Principal Design Engineer @ Copperleaf
24
+
25
+ **[Open posting](https://jobs.example.com/copperleaf/principal-design-engineer)** · Remote - US · Remote · score **134** · $240k–$290k
26
+
27
+ ## Why it matched
28
+ - title +40: design engineer
29
+ - seniority +10 (principal)
30
+ - description +26: design system, design systems, react, typescript, tokens, wcag
31
+ - remote +15
32
+ - pay range tops out at $290k, at or above floor (+10, +8 for 90k above)
33
+ - posted 1d ago (+25)
34
+
35
+ ## Status log
36
+ - 2026-09-20 — found by scraper (score 134). Set `status:` above to `reviewing`, `applying`, `applied`, `interviewing`, `offer`, `rejected`, or `passed`.
37
+ - 2026-09-20 — new → **reviewing** (via app)
38
+
39
+ ## Notes
40
+
41
+
42
+ ## Application
43
+ - **Narrative:**
44
+ - **Resume variant:**
45
+ - **Cover letter:**
46
+ - **Applied on:**
47
+ - **Contact / referral:**
48
+ - **Follow-up due:**
49
+
50
+ ## Job description
51
+ > Source: greenhouse
52
+
53
+ Copperleaf makes accounting software for small firms; 300,000 businesses use it. The design systems team is five engineers and two designers; the principal role sets technical direction for the system across web and the embedded views in our desktop app. React, TypeScript, tokens, theming for white-label partners, WCAG 2.1 AA across the board. Remote in the US.
@@ -0,0 +1,52 @@
1
+ ---
2
+ type: job
3
+ status: new
4
+ listing: open
5
+ company: "Example Studio"
6
+ title: "Senior Design Engineer"
7
+ location: "Remote · United States"
8
+ remote: true
9
+ source: tekjobs
10
+ url: "https://tekjobs.timurtek.com/jobs/example-tekjobs"
11
+ score: 130
12
+ weights: 3e895160
13
+ posted: "2026-09-23"
14
+ found: 2026-09-23
15
+ salary: "$180k–$240k"
16
+ salary_max: 240000
17
+ pay_band: floor
18
+ department: "Product Design"
19
+ job_id: "tj:tekjobs:1013"
20
+ tags: [job]
21
+ ---
22
+
23
+ # Senior Design Engineer @ Example Studio
24
+
25
+ **[Open posting](https://tekjobs.timurtek.com/jobs/example-tekjobs)** · Remote · United States · Remote · score **130** · $180k–$240k
26
+
27
+ ## Why it matched
28
+ - title +40: design engineer
29
+ - seniority +8 (senior)
30
+ - description +28: design system, react, typescript, storybook, figma, accessibility, tokens
31
+ - remote +15
32
+ - pay range tops out at $240k, at or above floor (+10, +4 for 40k above)
33
+ - posted 0d ago (+25)
34
+
35
+ ## Status log
36
+ - 2026-09-23 — found by scraper (score 130). Set `status:` above to `reviewing`, `applying`, `applied`, `interviewing`, `offer`, `rejected`, or `passed`.
37
+
38
+ ## Notes
39
+
40
+
41
+ ## Application
42
+ - **Narrative:**
43
+ - **Resume variant:**
44
+ - **Cover letter:**
45
+ - **Applied on:**
46
+ - **Contact / referral:**
47
+ - **Follow-up due:**
48
+
49
+ ## Job description
50
+ > Source: tekjobs
51
+
52
+ Example Studio is a fictional company whose posting exists to show what an employer posting on TekJobs looks like on the board. The role owns the product surface end to end: the React front end, the design system the team ships on, and the accessibility bar. TypeScript, Storybook, Figma tokens. Remote in the United States.
@@ -0,0 +1,58 @@
1
+ ---
2
+ type: job
3
+ status: applied
4
+ listing: open
5
+ company: "Fjord Analytics"
6
+ title: "Staff Frontend Engineer, Platform"
7
+ location: "Remote, United States"
8
+ remote: true
9
+ source: greenhouse
10
+ url: "https://jobs.example.com/fjord/staff-frontend-engineer-platform"
11
+ score: 111
12
+ weights: 3e895160
13
+ posted: "2026-09-14"
14
+ found: 2026-09-15
15
+ salary: "$220k–$270k"
16
+ salary_max: 270000
17
+ pay_band: floor
18
+ department: "Platform"
19
+ job_id: "gr:fjord:1003"
20
+ tags: [job]
21
+ ---
22
+
23
+ # Staff Frontend Engineer, Platform @ Fjord Analytics
24
+
25
+ **[Open posting](https://jobs.example.com/fjord/staff-frontend-engineer-platform)** · Remote, United States · Remote · score **111** · $220k–$270k
26
+
27
+ ## Why it matched
28
+ - title +15: frontend engineer
29
+ - seniority +10 (staff)
30
+ - description +29: design system, react, typescript, storybook, accessibility, tokens, component library
31
+ - remote +15
32
+ - pay range tops out at $270k, at or above floor (+10, +7 for 70k above)
33
+ - posted 1d ago (+25)
34
+
35
+ ## Status log
36
+ - 2026-09-15 — found by scraper (score 111). Set `status:` above to `reviewing`, `applying`, `applied`, `interviewing`, `offer`, `rejected`, or `passed`.
37
+ - 2026-09-16 — new → **reviewing** (via app)
38
+ - 2026-09-17 — reviewing → **applying** (via app)
39
+ - 2026-09-19 — applying → **applied** (via app)
40
+
41
+ ## Notes
42
+
43
+
44
+ ## Application
45
+ - **Narrative:**
46
+ - **Resume variant:**
47
+ - **Cover letter:**
48
+ - **Applied on:** 2026-09-19
49
+ - **Contact / referral:**
50
+ - **Follow-up due:** 2026-09-26
51
+
52
+ ## People
53
+ - [[People/Lee Example (Fjord Analytics)|Lee Example]] · referral · lee@example.com · submitted the internal referral
54
+
55
+ ## Job description
56
+ > Source: greenhouse
57
+
58
+ Fjord Analytics is a data platform for mid-size retailers. The platform team owns the web application shell, the build, the design system and the component library that eleven feature teams use. You would lead the component library: React, TypeScript, tokens, Storybook, performance budgets. Experience taking a system through an accessibility audit is a strong signal. Remote in the US.
@@ -0,0 +1,52 @@
1
+ ---
2
+ type: job
3
+ status: new
4
+ listing: open
5
+ company: "Halcyon Robotics"
6
+ title: "Senior UX Engineer"
7
+ location: "Remote (United States) · Boston, MA"
8
+ remote: true
9
+ source: ashby
10
+ url: "https://jobs.example.com/halcyon/senior-ux-engineer"
11
+ score: 106
12
+ weights: 3e895160
13
+ posted: "2026-09-08"
14
+ found: 2026-09-09
15
+ salary: "$180k–$215k"
16
+ salary_max: 215000
17
+ pay_band: floor
18
+ department: "Product"
19
+ job_id: "as:halcyon:1004"
20
+ tags: [job]
21
+ ---
22
+
23
+ # Senior UX Engineer @ Halcyon Robotics
24
+
25
+ **[Open posting](https://jobs.example.com/halcyon/senior-ux-engineer)** · Remote (United States) · Boston, MA · Remote · score **106** · $180k–$215k
26
+
27
+ ## Why it matched
28
+ - title +25: ux engineer
29
+ - seniority +8 (senior)
30
+ - description +21: design system, react, typescript, storybook, figma
31
+ - remote +15
32
+ - pay range tops out at $215k, at or above floor (+10, +2 for 15k above)
33
+ - posted 1d ago (+25)
34
+
35
+ ## Status log
36
+ - 2026-09-09 — found by scraper (score 106). Set `status:` above to `reviewing`, `applying`, `applied`, `interviewing`, `offer`, `rejected`, or `passed`.
37
+
38
+ ## Notes
39
+
40
+
41
+ ## Application
42
+ - **Narrative:**
43
+ - **Resume variant:**
44
+ - **Cover letter:**
45
+ - **Applied on:**
46
+ - **Contact / referral:**
47
+ - **Follow-up due:**
48
+
49
+ ## Job description
50
+ > Source: ashby
51
+
52
+ Halcyon builds fleet software for warehouse robots. The UX engineering team sits between design and the product engineers and owns the design system and prototyping. React, TypeScript, Figma, Storybook. Remote in the US, with a Boston office.
@@ -0,0 +1,51 @@
1
+ ---
2
+ type: job
3
+ status: new
4
+ listing: open
5
+ company: "Lumen Health"
6
+ title: "Design Systems Engineer"
7
+ location: "Remote US · Denver, CO"
8
+ remote: true
9
+ source: lever
10
+ url: "https://jobs.example.com/lumen/design-systems-engineer"
11
+ score: 109
12
+ weights: 3e895160
13
+ posted: "2026-09-11"
14
+ found: 2026-09-12
15
+ salary: "$175k–$205k"
16
+ salary_max: 205000
17
+ pay_band: floor
18
+ department: "Engineering"
19
+ job_id: "le:lumen:1002"
20
+ tags: [job]
21
+ ---
22
+
23
+ # Design Systems Engineer @ Lumen Health
24
+
25
+ **[Open posting](https://jobs.example.com/lumen/design-systems-engineer)** · Remote US · Denver, CO · Remote · score **109** · $175k–$205k
26
+
27
+ ## Why it matched
28
+ - title +35: design systems, design system
29
+ - description +23: react, typescript, storybook, accessibility, tokens, component library
30
+ - remote +15
31
+ - pay range tops out at $205k, at or above floor (+10, +1 for 5k above)
32
+ - posted 1d ago (+25)
33
+
34
+ ## Status log
35
+ - 2026-09-12 — found by scraper (score 109). Set `status:` above to `reviewing`, `applying`, `applied`, `interviewing`, `offer`, `rejected`, or `passed`.
36
+
37
+ ## Notes
38
+
39
+
40
+ ## Application
41
+ - **Narrative:**
42
+ - **Resume variant:**
43
+ - **Cover letter:**
44
+ - **Applied on:**
45
+ - **Contact / referral:**
46
+ - **Follow-up due:**
47
+
48
+ ## Job description
49
+ > Source: lever
50
+
51
+ Lumen Health runs a patient portal used by 900 clinics. We are rebuilding the front end on a shared component library and need an engineer who has done this before: tokens, theming, accessibility, documentation in Storybook. TypeScript and React; some Angular remains and you would help retire it. Remote in the US or Denver.
@@ -0,0 +1,67 @@
1
+ ---
2
+ type: job
3
+ status: interviewing
4
+ listing: open
5
+ company: "Meridian Pay"
6
+ title: "Staff Design Engineer"
7
+ location: "Remote, United States"
8
+ remote: true
9
+ source: greenhouse
10
+ url: "https://jobs.example.com/meridian/staff-design-engineer"
11
+ score: 137
12
+ weights: 3e895160
13
+ posted: "2026-09-02"
14
+ found: 2026-09-03
15
+ salary: "$225k–$275k"
16
+ salary_max: 275000
17
+ pay_band: floor
18
+ department: "Platform"
19
+ job_id: "gr:meridian:1009"
20
+ tags: [job]
21
+ ---
22
+
23
+ # Staff Design Engineer @ Meridian Pay
24
+
25
+ **[Open posting](https://jobs.example.com/meridian/staff-design-engineer)** · Remote, United States · Remote · score **137** · $225k–$275k
26
+
27
+ ## Why it matched
28
+ - title +40: design engineer
29
+ - seniority +10 (staff)
30
+ - description +29: design system, react, typescript, storybook, accessibility, tokens, component library
31
+ - remote +15
32
+ - pay range tops out at $275k, at or above floor (+10, +8 for 75k above)
33
+ - posted 1d ago (+25)
34
+
35
+ ## Status log
36
+ - 2026-09-03 — found by scraper (score 137). Set `status:` above to `reviewing`, `applying`, `applied`, `interviewing`, `offer`, `rejected`, or `passed`.
37
+ - 2026-09-04 — new → **reviewing** (via app)
38
+ - 2026-09-06 — reviewing → **applying** (via app)
39
+ - 2026-09-11 — applying → **applied** (via app)
40
+ - 2026-09-18 — applied → **interviewing** (via app)
41
+
42
+ ## Notes
43
+ - 2026-09-18 (app): Recruiter screen with Priya went well; technical interview with the platform lead scheduled for next week. She asked for the Storybook link.
44
+
45
+ ## Application
46
+ - **Narrative:** Design engineer; the Acme system numbers lead (62 components, 12 teams, defects down a third).
47
+ - **Resume variant:** Design Engineer (Staff)
48
+ - **Cover letter:** Drafted 2026-09-11, 107 words. Full text under "Cover letter" in this note.
49
+ - **Applied on:** 2026-09-11
50
+ - **Contact / referral:**
51
+ - **Follow-up due:** 2026-09-18
52
+ - **Tailored summary:** Design engineer with eight years on design systems; led the Acme Finance system from 4 to 62 components across 12 teams, including white-label theming for partner banks.
53
+ - **Tailored bullets:** System architecture and tokens · white-label theming · the ESM migration (1.9 MB to 640 kB) · the WCAG 2.1 AA audit with zero blockers
54
+ - **Risks:** No payroll domain experience; say so, and point at fintech at Acme.
55
+
56
+ ## Cover letter
57
+ > Drafted 2026-09-11 (via app), 107 words. A draft: read it, edit it, send it yourself.
58
+
59
+ Hello. Your posting says Meridian Pay's design system serves six product teams and the white-label theming your partners depend on. At Acme Finance I took the system from 4 components to 62, used by 12 teams, with partner theming built on the same tokens; UI defect reports fell by a third in the year after. The part I would bring first is the token pipeline, because a system that themes for partners lives or dies on it. In the first month I would inventory the two legacy apps against the system and write down what they still need, since that list is the migration plan. Jordan Example.
60
+
61
+ ## People
62
+ - [[People/Priya Example (Meridian Pay)|Priya Example]] · recruiter · priya@meridian.example.com · reached out after the application
63
+
64
+ ## Job description
65
+ > Source: greenhouse
66
+
67
+ Meridian Pay processes payroll for 30,000 companies. The platform team owns the design system used by six product teams and the white-label theming our partners depend on. You would lead the system: architecture, tokens, the component library in React and TypeScript, Storybook, accessibility, and the migration of the last two legacy apps. Remote in the US.