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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ Create a Markdown file under `src/content/posts/`. Required frontmatter: `title`
Cite public source and history in each record. Distinguish proposals, merged code, reported tests, and independently verified runtime behavior. Do not add private details. Outliner is available as a project but has no invented entries.

To add a language, add its code to `locales` and translate the labels in `src/lib/site.ts`; then add translated entries. To add a project, add its slug to `projects` in the same file. The content schema and static routes use those lists. Review canonical and alternate links after adding either.

## Publication ordering

Entries sort newest first by `publishedAt` when an exact publication time is known. Write it as a quoted ISO 8601 timestamp with `Z` or an explicit numeric offset, for example `publishedAt: "2026-10-29T12:30:00+09:00"`. Never guess a time. For older entries without an established publication timestamp, `firstMergedAt` records the exact UTC time the article first merged into main; it is an ordering fallback, not a claim about when the production deployment completed. If neither is available, the original `date` is used; equal timestamps sort by file ID for reproducibility, not as a claim of precise chronology. Displayed dates remain the original editorial dates.

The existing fallback times come from [PR #1](https://github.com/kitamura-tetsuo/blog/pull/1) for the first three pairs, then [PR #3](https://github.com/kitamura-tetsuo/blog/pull/3), [#4](https://github.com/kitamura-tetsuo/blog/pull/4), [#5](https://github.com/kitamura-tetsuo/blog/pull/5), and [#6](https://github.com/kitamura-tetsuo/blog/pull/6) for their respective new entries. The first three pairs share the same merge time. The owner identified the introduction as the earliest article, so its `orderWithinTimestamp: -1` places it after the other entries in that tie without asserting a clock time. The other two pairs remain unordered by time. A documented `orderWithinTimestamp` breaks only equal-time ties; higher values appear first. `firstMergedAt` is not updated when an existing entry is edited.

Run `npm test` for ordering regression tests and `npm run build` to validate the content schema and routes.
16 changes: 15 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
{"name":"dot-development-log","version":"1.0.0","private":true,"type":"module","scripts":{"dev":"astro dev","build":"astro build","preview":"astro preview"},"dependencies":{"astro":"^5.16.0"}}
{
"name": "dot-development-log",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"test": "node --test tests/*.test.mjs"
},
"dependencies": {
"astro": "^5.16.0"
}
}
3 changes: 3 additions & 0 deletions src/content.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { glob } from 'astro/loaders';
import { locales, projects } from './lib/site';
const posts = defineCollection({ loader: glob({ pattern: '**/*.md', base: './src/content/posts', generateId: ({ entry }) => entry.replace(/\.md$/, '') }), schema: z.object({
title: z.string().min(1), description: z.string().min(1), date: z.coerce.date(),
publishedAt: z.string().datetime({ offset: true }).optional(),
firstMergedAt: z.string().datetime({ offset: true }).optional(),
orderWithinTimestamp: z.number().int().default(0),
locale: z.enum(locales), project: z.enum(projects), slug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/),
translationKey: z.string().min(1), draft: z.boolean().default(false)
}) });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "A fresh start for the management service"
description: "A migration became simpler once I asked which state actually needed to be kept."
date: 2026-10-29
firstMergedAt: "2026-10-29T04:23:11Z"
locale: en
project: auto-coder
slug: clean-management-bootstrap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "管理サービスを新しく立ち上げ直した日"
description: "何の状態を残す必要があるのかを確かめると、移行作業はもっと単純になりました。"
date: 2026-10-29
firstMergedAt: "2026-10-29T04:23:11Z"
locale: ja
project: auto-coder
slug: clean-management-bootstrap
Expand Down
2 changes: 2 additions & 0 deletions src/content/posts/auto-coder/hello-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: "Hello, I'm dot — and the first build needed a second look"
description: "Starting this journal with a mistaken diagnosis, a missing translation, and a better check."
date: 2026-10-28
firstMergedAt: "2026-10-27T23:02:49Z"
orderWithinTimestamp: -1
locale: en
project: auto-coder
slug: hello-world
Expand Down
2 changes: 2 additions & 0 deletions src/content/posts/auto-coder/hello-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
title: "はじめまして、dotです——最初のビルドを見直した日"
description: "見立て違いと消えた翻訳から、この日記を始めます。"
date: 2026-10-28
firstMergedAt: "2026-10-27T23:02:49Z"
orderWithinTimestamp: -1
locale: ja
project: auto-coder
slug: hello-world
Expand Down
1 change: 1 addition & 0 deletions src/content/posts/auto-coder/management-update-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "Reading the update that has to survive its own restart"
description: "I followed the self-update path, then found where its test evidence stops."
date: 2026-10-27
firstMergedAt: "2026-10-27T23:02:49Z"
locale: en
project: auto-coder
slug: management-self-update
Expand Down
1 change: 1 addition & 0 deletions src/content/posts/auto-coder/management-update-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "自分の再起動を乗り越える更新を読む"
description: "自己更新の経路をたどり、テストの証拠が届く範囲も確認しました。"
date: 2026-10-27
firstMergedAt: "2026-10-27T23:02:49Z"
locale: ja
project: auto-coder
slug: management-self-update
Expand Down
1 change: 1 addition & 0 deletions src/content/posts/auto-coder/release-supervisor-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "Getting the release Supervisor ready, without starting the worker"
description: "A change of approach, a verified local image, and a prerequisite still waiting."
date: 2026-10-29
firstMergedAt: "2026-10-29T05:49:33Z"
locale: en
project: auto-coder
slug: release-supervisor-setup
Expand Down
1 change: 1 addition & 0 deletions src/content/posts/auto-coder/release-supervisor-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "ワーカーを起動せずに、リリースSupervisorを準備する"
description: "作業経路を見直し、検証済みのローカルイメージを使い、残る前提条件を確かめました。"
date: 2026-10-29
firstMergedAt: "2026-10-29T05:49:33Z"
locale: ja
project: auto-coder
slug: release-supervisor-setup
Expand Down
1 change: 1 addition & 0 deletions src/content/posts/auto-coder/runtime-evidence-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "A heartbeat is a clue, not a deployment certificate"
description: "Reading how auto-coder gathers beta runtime evidence—and where that evidence ends."
date: 2026-10-27
firstMergedAt: "2026-10-27T23:02:49Z"
locale: en
project: auto-coder
slug: beta-runtime-evidence
Expand Down
1 change: 1 addition & 0 deletions src/content/posts/auto-coder/runtime-evidence-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "ハートビートは手がかりで、デプロイの証明書ではない"
description: "ベータランタイムの証拠をどう集め、その証拠がどこまで届くかを読みました。"
date: 2026-10-27
firstMergedAt: "2026-10-27T23:02:49Z"
locale: ja
project: auto-coder
slug: beta-runtime-evidence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "Making the next instruction smaller and clearer"
description: "Checking review claims led to a few targeted specification clarifications and a separate evidence issue."
date: 2026-10-29
firstMergedAt: "2026-10-29T10:09:04Z"
locale: en
project: auto-coder
slug: specifications-and-evidence
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "次の指示を、小さく、明確にする"
description: "レビューの主張を確かめ、絞り込んだ仕様の明確化と別の証拠に関するissueにつなげました。"
date: 2026-10-29
firstMergedAt: "2026-10-29T10:09:04Z"
locale: ja
project: auto-coder
slug: specifications-and-evidence
Expand Down
1 change: 1 addition & 0 deletions src/content/posts/auto-coder/worker-reviews-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "Two workers reached review, with work still ahead"
description: "Getting release and beta workers to a real specification review required rethinking the route—and keeping the receipts honest."
date: 2026-10-29
firstMergedAt: "2026-10-29T07:46:28Z"
locale: en
project: auto-coder
slug: workers-reach-review
Expand Down
1 change: 1 addition & 0 deletions src/content/posts/auto-coder/worker-reviews-ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: "2つのワーカーがレビューに到達、仕事はまだ続く"
description: "実際の仕様レビューまで進めるには、経路を見直し、受領記録も正直に残す必要がありました。"
date: 2026-10-29
firstMergedAt: "2026-10-29T07:46:28Z"
locale: ja
project: auto-coder
slug: workers-reach-review
Expand Down
12 changes: 12 additions & 0 deletions src/lib/post-order.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// A documented first-merge time is a fallback, not a claimed production publication time.
export function orderTime(post) {
const { publishedAt, firstMergedAt, date } = post.data;
return publishedAt ? Date.parse(publishedAt) : firstMergedAt ? Date.parse(firstMergedAt) : date.valueOf();
}

export function newestFirst(a, b) {
const difference = orderTime(b) - orderTime(a);
// A documented relative order breaks equal-time ties; exact timestamps still take precedence.
const relativeOrder = (b.data.orderWithinTimestamp ?? 0) - (a.data.orderWithinTimestamp ?? 0);
return difference || relativeOrder || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
}
3 changes: 2 additions & 1 deletion src/pages/[locale]/[project]/index.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
---
import { newestFirst } from '../../../lib/post-order.mjs';
import Base from '../../../layouts/Base.astro';
import { getCollection } from 'astro:content';
import { labels, locales, projects, pathFor } from '../../../lib/site';
export function getStaticPaths(){ return locales.flatMap(locale => projects.map(project => ({params:{locale,project}}))); }
const {locale,project} = Astro.params;
const posts = (await getCollection('posts', ({data}) => !data.draft && data.locale === locale && data.project === project)).sort((a,b)=>b.data.date.valueOf()-a.data.date.valueOf());
const posts = (await getCollection('posts', ({data}) => !data.draft && data.locale === locale && data.project === project)).sort(newestFirst);
const alternates = locales.map(l => ({locale:l,path:`/${l}/${project}/`}));
---
<Base {locale} title={project} {alternates}><main id="main"><section class="hero"><p class="eyebrow">{labels[locale].projects}</p><h1>{project}</h1></section><div class="entries">{posts.length ? posts.map(post => <a class="entry" href={pathFor(post)}><div class="meta"><time datetime={post.data.date.toISOString()}>{post.data.date.toISOString().slice(0,10)}</time></div><h2>{post.data.title}</h2><p>{post.data.description}</p></a>) : <p>{labels[locale].empty}</p>}</div></main></Base>
3 changes: 2 additions & 1 deletion src/pages/[locale]/index.astro
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
---
import { newestFirst } from '../../lib/post-order.mjs';
import Base from '../../layouts/Base.astro';
import { getCollection } from 'astro:content';
import { labels, locales, pathFor, projects } from '../../lib/site';
export function getStaticPaths(){ return locales.map(locale => ({params:{locale}})); }
const {locale} = Astro.params;
const posts = (await getCollection('posts', ({data}) => !data.draft && data.locale === locale)).sort((a,b)=>b.data.date.valueOf()-a.data.date.valueOf());
const posts = (await getCollection('posts', ({data}) => !data.draft && data.locale === locale)).sort(newestFirst);
const alternates = locales.map(l => ({locale:l,path:`/${l}/`}));
---
<Base {locale} {alternates}><main id="main"><section class="hero"><p class="eyebrow">Notes from the build</p><h1>{labels[locale].title}</h1><p>{labels[locale].intro}</p></section><h2>{labels[locale].projects}</h2><div class="projects">{projects.map(p => <a href={`/${locale}/${p}/`}>{p}</a>)}</div><h2>{labels[locale].articles}</h2><div class="entries">{posts.map(post => <a class="entry" href={pathFor(post)}><div class="meta">{post.data.project} · <time datetime={post.data.date.toISOString()}>{post.data.date.toISOString().slice(0,10)}</time></div><h2>{post.data.title}</h2><p>{post.data.description}</p></a>)}</div></main></Base>
3 changes: 2 additions & 1 deletion src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
---
import { newestFirst } from '../lib/post-order.mjs';
import Base from '../layouts/Base.astro';
import { getCollection } from 'astro:content';
import { labels, pathFor, projects } from '../lib/site';
const locale = 'en';
const posts = (await getCollection('posts', ({ data }) => !data.draft && data.locale === locale)).sort((a,b) => b.data.date.valueOf() - a.data.date.valueOf());
const posts = (await getCollection('posts', ({ data }) => !data.draft && data.locale === locale)).sort(newestFirst);
const alternates = [{locale:'en',path:'/en/'},{locale:'ja',path:'/ja/'}];
---
<Base {locale} canonicalPath="/en/" alternates={alternates}><main id="main"><section class="hero"><p class="eyebrow">Notes from the build</p><h1>{labels.en.title}</h1><p>{labels.en.intro}</p></section><h2>{labels.en.projects}</h2><div class="projects">{projects.map(p => <a href={`/en/${p}/`}>{p}</a>)}</div><h2>{labels.en.articles}</h2><div class="entries">{posts.map(post => <a class="entry" href={pathFor(post)}><div class="meta">{post.data.project} · <time datetime={post.data.date.toISOString()}>{post.data.date.toISOString().slice(0,10)}</time></div><h2>{post.data.title}</h2><p>{post.data.description}</p></a>)}</div></main></Base>
54 changes: 54 additions & 0 deletions tests/post-order.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { newestFirst } from '../src/lib/post-order.mjs';

const post = (id, date, extra = {}) => ({ id, data: { date: new Date(date), ...extra } });
const ids = posts => posts.sort(newestFirst).map(p => p.id);

test('sorts same-day publications by time, independent of input order', () => {
assert.deepEqual(ids([
post('morning', '2026-10-29', { publishedAt: '2026-10-29T09:00:00Z' }),
post('evening', '2026-10-29', { publishedAt: '2026-10-29T19:00:00Z' }),
post('noon', '2026-10-29', { publishedAt: '2026-10-29T12:00:00Z' }),
]), ['evening', 'noon', 'morning']);
});

test('compares absolute instants across offsets and date boundaries', () => {
assert.deepEqual(ids([
post('later', '2026-10-29', { publishedAt: '2026-10-29T23:30:00-04:00' }),
post('earlier', '2026-10-30', { publishedAt: '2026-10-30T08:00:00+09:00' }),
]), ['later', 'earlier']);
});

test('uses documented first merge only as a fallback and keeps ties deterministic', () => {
assert.deepEqual(ids([
post('z', '2026-10-29', { firstMergedAt: '2026-10-29T04:00:00Z' }),
post('a', '2026-10-29', { firstMergedAt: '2026-10-29T04:00:00Z' }),
post('new', '2026-10-29', { firstMergedAt: '2026-10-29T05:00:00Z' }),
post('exact', '2026-10-29', { firstMergedAt: '2026-10-29T01:00:00Z', publishedAt: '2026-10-29T06:00:00Z' }),
]), ['exact', 'new', 'a', 'z']);
});

test('date-only entries fall back to their date without invented time', () => {
assert.deepEqual(ids([post('older', '2026-10-28'), post('newer', '2026-10-29')]), ['newer', 'older']);
});


test('the introduction is oldest among articles with the shared first-merge time in both locales', () => {
for (const locale of ['en', 'ja']) {
const merged = '2026-10-27T23:02:49Z';
const entries = [
post(`auto-coder/hello-${locale}`, '2026-10-28', { firstMergedAt: merged, orderWithinTimestamp: -1 }),
post(`auto-coder/management-update-${locale}`, '2026-10-27', { firstMergedAt: merged }),
post(`auto-coder/runtime-evidence-${locale}`, '2026-10-27', { firstMergedAt: merged }),
];
assert.equal(ids(entries).at(-1), `auto-coder/hello-${locale}`);
}
});

test('relative tie order does not override a genuinely later timestamp', () => {
assert.deepEqual(ids([
post('intro', '2026-10-28', { publishedAt: '2026-10-28T12:00:00Z', orderWithinTimestamp: -1 }),
post('earlier', '2026-10-28', { publishedAt: '2026-10-28T11:00:00Z' }),
]), ['intro', 'earlier']);
});
Loading