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
51 changes: 45 additions & 6 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>Dev Ledger — Code Metrics</title>
<title>Dev Ledger — Open-Source GitHub Analytics for Developers</title>
<meta
name="description"
content="A private, read-only view of your GitHub development history — commits, source growth, activity patterns, languages, and projects over time."
content="Open-source GitHub analytics for developers — commits, source growth, churn, languages, activity, projects, and releases across your repositories. Read-only."
/>
<meta name="robots" content="index,follow" />
<meta name="theme-color" content="#0a0a0a" />
Expand All @@ -17,10 +17,10 @@

<meta property="og:type" content="website" />
<meta property="og:site_name" content="Dev Ledger" />
<meta property="og:title" content="Dev Ledger — Code Metrics" />
<meta property="og:title" content="Dev Ledger — Open-Source GitHub Analytics for Developers" />
<meta
property="og:description"
content="A private, read-only view of your GitHub development history — commits, source growth, activity patterns, languages, and projects over time."
content="Open-source GitHub analytics for developers — commits, source growth, churn, languages, activity, projects, and releases across your repositories. Read-only."
/>
<meta property="og:url" content="https://devledger.site/" />
<meta property="og:image" content="https://devledger.site/og/dev-ledger.png" />
Expand All @@ -33,17 +33,56 @@
/>

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Dev Ledger — Code Metrics" />
<meta name="twitter:title" content="Dev Ledger — Open-Source GitHub Analytics for Developers" />
<meta
name="twitter:description"
content="A private, read-only view of your GitHub development history — commits, source growth, activity patterns, languages, and projects over time."
content="Open-source GitHub analytics for developers — commits, source growth, churn, languages, activity, projects, and releases across your repositories. Read-only."
/>
<meta name="twitter:image" content="https://devledger.site/og/dev-ledger.png" />
<meta
name="twitter:image:alt"
content="Dev Ledger — open-source GitHub developer analytics."
/>

<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebApplication",
"@id": "https://devledger.site/#app",
"name": "Dev Ledger",
"url": "https://devledger.site",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Web browser",
"license": "https://www.gnu.org/licenses/agpl-3.0.html",
"isAccessibleForFree": true,
"codeRepository": "https://github.com/Aliferous3/dev-ledger",
"sameAs": ["https://github.com/Aliferous3/dev-ledger"],
"description": "Dev Ledger is an open-source web application for understanding development history across GitHub repositories.",
"featureList": [
"Longitudinal commit and repository activity history",
"Net source growth, additions, deletions, and churn over time",
"Repository lifecycle states: active, dormant, revived",
"Programming language composition across repositories",
"Time-range comparison (7D, 30D, 90D, YTD, 1Y)",
"Commit frequency and commit-size distributions",
"Active days, streaks, and time-of-day activity patterns",
"Release and shipping cadence history",
"Range-aware shareable development records"
],
"isPartOf": { "@id": "https://devledger.site/#website" }
},
{
"@type": "WebSite",
"@id": "https://devledger.site/#website",
"name": "Dev Ledger",
"url": "https://devledger.site",
"about": { "@id": "https://devledger.site/#app" }
}
]
}
</script>
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' fill='%230a0a0a'/%3E%3Crect x='5' y='5' width='6' height='6' fill='%23d6ff3e'/%3E%3C/svg%3E" />
</head>
<body>
Expand Down
8 changes: 7 additions & 1 deletion src/ledger/LoginScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function LoginScreen({ onLogin }: { onLogin?: () => void }) {
text="DEV LEDGER"
className="font-editorial font-light text-[clamp(2.4rem,6.2vw,5.6rem)] leading-none mt-2"
/>
<p ref={registerM12} style={m12Delay(3)} className="m12 relative font-editorial italic text-sm text-neutral-400 mt-2">Your GitHub history, made legible.</p>
<p ref={registerM12} style={m12Delay(3)} className="m12 relative font-editorial italic text-sm text-neutral-400 mt-2">Open-source GitHub analytics — your development history, made legible.</p>
</div>

{/* mission line — full content width, 22px single line on desktop,
Expand Down Expand Up @@ -162,6 +162,12 @@ export function LoginScreen({ onLogin }: { onLogin?: () => void }) {
>
BLOG <span className="text-neutral-800">—</span> <span className="text-neutral-700">READING YOUR GITHUB HISTORY</span>
</a>
<a
href="/tools/github-analytics"
className="mono-tag text-[8px] tracking-[0.22em] text-neutral-600 hover:text-neutral-300 focus-visible:text-neutral-300 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[#d6ff3e]/40 transition-colors"
>
GITHUB ANALYTICS <span className="text-neutral-800">—</span> <span className="text-neutral-700">WHAT THE TOOL READS</span>
</a>
</div>
</div>
</div>
Expand Down
42 changes: 40 additions & 2 deletions test/launch-metadata.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const OG_PAGES = [
height: '941',
alt: 'Dev Ledger — open-source GitHub developer analytics.',
canonical: 'https://devledger.site/',
title: 'Dev Ledger — Code Metrics',
title: 'Dev Ledger — Open-Source GitHub Analytics for Developers',
},
{
html: 'security.html',
Expand Down Expand Up @@ -60,7 +60,7 @@ test('launch metadata exposes canonical Open Graph and Twitter cards', () => {
const html = src('index.html');

assert.match(html, /<link rel="canonical" href="https:\/\/devledger\.site\/" \/>/);
assert.match(html, /property="og:title" content="Dev Ledger — Code Metrics"/);
assert.match(html, /property="og:title" content="Dev Ledger — Open-Source GitHub Analytics for Developers"/);
assert.match(html, /property="og:image" content="https:\/\/devledger\.site\/og\/dev-ledger\.png"/);
assert.match(html, /property="og:image:width" content="1672"/);
assert.match(html, /property="og:image:height" content="941"/);
Expand All @@ -69,6 +69,44 @@ test('launch metadata exposes canonical Open Graph and Twitter cards', () => {
assert.match(html, /name="theme-color" content="#0a0a0a"/);
});

test('homepage JSON-LD declares the app and site entities with stable ids', () => {
const html = src('index.html');
const blocks = [...html.matchAll(/<script type="application\/ld\+json">([\s\S]*?)<\/script>/g)]
.map((m) => JSON.parse(m[1]));
assert.equal(blocks.length, 1, 'homepage emits exactly one JSON-LD block');
const graph = blocks[0]['@graph'] || [];
const app = graph.find((n) => n['@id'] === 'https://devledger.site/#app');
const site = graph.find((n) => n['@id'] === 'https://devledger.site/#website');
assert.ok(app, 'app entity missing');
assert.ok(site, 'website entity missing');
// SoftwareApplication is the required family — the publishing layer uses
// its WebApplication subtype, so the homepage reuses that identity scheme.
assert.equal(app['@type'], 'WebApplication');
assert.equal(app.name, 'Dev Ledger');
assert.equal(app.url, 'https://devledger.site');
assert.equal(app.applicationCategory, 'DeveloperApplication');
assert.equal(app.isAccessibleForFree, true);
assert.ok(app.sameAs.includes('https://github.com/Aliferous3/dev-ledger'));
assert.equal(app.license, 'https://www.gnu.org/licenses/agpl-3.0.html');
assert.equal(app.isPartOf?.['@id'], 'https://devledger.site/#website');
assert.equal(site['@type'], 'WebSite');
assert.equal(site.name, 'Dev Ledger');
assert.equal(site.url, 'https://devledger.site');
// Site relates to the app via `about` — `publisher` requires an
// Organization or Person and the graph declares neither.
assert.equal(site.about?.['@id'], 'https://devledger.site/#app');
assert.ok(!('publisher' in site), 'WebSite must not publish a WebApplication');
// No fabricated claims may ever land in the entity graph.
const blob = JSON.stringify(graph).toLowerCase();
for (const banned of [
'aggregaterating', '"review"', 'usercount', 'award',
'productivity score', 'employee monitoring', 'leaderboard',
'code ownership', 'bus factor', 'codeowners',
]) {
assert.ok(!blob.includes(banned), 'entity graph must not claim: ' + banned);
}
});

test('every public route ships its own page-specific OG image', () => {
for (const page of OG_PAGES) {
const html = src(page.html);
Expand Down
4 changes: 4 additions & 0 deletions test/ownership-accumulate.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -814,7 +814,11 @@ test('B-engine: same-page drift — no checkpoint, no cursor move, closed error'
assert.equal(sync.phase, 'error')
assert.equal(sync.error, 'OWNERSHIP_HISTORY_FAILED')
assert.equal(sync.history_cursor.current.cursor, null) // cursor unmoved
// Mask internal UUIDs before the substring scan — scope_id/lease tokens
// are server-minted ids, not provider identity, and a hex uuid can itself
// contain the two-char 'a1' needle (the login under test).
const persisted = JSON.stringify({ phase: sync.phase, error: sync.error, cursor: sync.history_cursor })
.replaceAll(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<uuid>')
for (const needle of ['person', 'U_a1', 'a1', 'login', 'node']) {
assert.ok(!persisted.includes(needle), `error state leaked ${needle}`)
}
Expand Down
9 changes: 8 additions & 1 deletion test/security.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ const FORBIDDEN_IDENTIFIERS = [
'Aliferous3/', // repo path prefix (the author's public profile link is fine)
];

// The public source repository is not a fixture identifier — the prefix
// ban targets private repo paths (Aliferous3/<private-repo>). Public pages
// legitimately link the open-source repo (e.g. JSON-LD codeRepository).
const PUBLIC_REPO_URL = 'github.com/Aliferous3/dev-ledger';

// The fictional replacements are dev-only too — they must also be absent
// from the production artifact (fixtures are aliased to an inert stub).
const FICTIONAL_FIXTURE_NAMES = [
Expand Down Expand Up @@ -85,7 +90,9 @@ test('built production artifact contains zero fixture identifiers', () => {
const files = distFilesOrSkip();
if (!files) return;
for (const f of files) {
const content = readFileSync(f, 'utf8');
// Mask the allowlisted public repo URL so its path prefix doesn't read
// as a private-fixture identifier; other Aliferous3/ paths still fail.
const content = readFileSync(f, 'utf8').replaceAll(PUBLIC_REPO_URL, 'github.com/dev-ledger');
const rel = path.relative(ROOT, f).replace(/\\/g, '/');
for (const id of [...FORBIDDEN_IDENTIFIERS, ...FICTIONAL_FIXTURE_NAMES]) {
assert.ok(!content.includes(id), `${rel} contains "${id}"`);
Expand Down
Loading