project-logbook 0.3.4 → 0.4.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.
- package/dist/commands/build.js +25 -2
- package/dist/commands/lint.js +18 -0
- package/dist/commands/status.js +23 -7
- package/dist/lib/build-helpers.js +7 -22
- package/dist/lib/build-steps.d.ts +4 -0
- package/dist/lib/build-steps.js +18 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.js +7 -3
- package/dist/lib/git-helpers.d.ts +21 -0
- package/dist/lib/git-helpers.js +91 -0
- package/dist/lib/image-helpers.js +2 -2
- package/dist/lib/markdown-processors.d.ts +5 -1
- package/dist/lib/markdown-processors.js +28 -2
- package/dist/lib/rss.d.ts +29 -0
- package/dist/lib/rss.js +77 -0
- package/dist/lib/template-helpers.d.ts +3 -3
- package/dist/lib/template-helpers.js +19 -6
- package/dist/lib/template-types.d.ts +3 -0
- package/dist/lib/templates.d.ts +5 -2
- package/dist/lib/templates.js +24 -14
- package/dist/linters/index.js +2 -0
- package/dist/linters/technical-log.d.ts +7 -0
- package/dist/linters/technical-log.js +72 -0
- package/dist/templates/WELCOME.md +12 -0
- package/dist/templates/index.md +4 -0
- package/dist/templates/log.md +5 -0
- package/dist/templates/logbook-client.js +29 -0
- package/dist/templates/steer.txt +21 -5
- package/dist/templates/styles.css +182 -0
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.js +12 -0
- package/dist/utils/log-timeline.d.ts +69 -0
- package/dist/utils/log-timeline.js +218 -0
- package/package.json +3 -1
- package/src/templates/WELCOME.md +12 -0
- package/src/templates/index.md +4 -0
- package/src/templates/log.md +5 -0
- package/src/templates/logbook-client.js +29 -0
- package/src/templates/steer.txt +21 -5
- package/src/templates/styles.css +182 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { linkJiraIds } from './jira-helpers.js';
|
|
2
2
|
import { formatAbsoluteDate } from '../utils/date.js';
|
|
3
3
|
import { escapeHtml } from './html-escape.js';
|
|
4
|
+
import { getCommitUrl, getTagUrl } from './git-helpers.js';
|
|
4
5
|
import { ATTR_ENTRY_SLUG, ATTR_DATA_DATE, CLASS_COMMIT_SHA, CLASS_COMMIT_TIME, CLASS_COMMIT_MESSAGE, } from './html-attributes.js';
|
|
5
6
|
export const html = (strings, ...values) => {
|
|
6
7
|
return strings.reduce((acc, str, i) => acc + str + (values[i] ?? ''), '');
|
|
@@ -27,7 +28,7 @@ export const renderTagsAndWorkspaces = (tags, workspaces) => {
|
|
|
27
28
|
return html `<div class="tags-wrapper">${tagsHtml}${wsHtml}</div>`;
|
|
28
29
|
};
|
|
29
30
|
// Renders a compact list of git commits to embed in the Technical Log tab.
|
|
30
|
-
export const renderCommits = (commits) => {
|
|
31
|
+
export const renderCommits = (commits, repositoryUrl) => {
|
|
31
32
|
if (!commits || commits.length === 0)
|
|
32
33
|
return '';
|
|
33
34
|
const rows = commits
|
|
@@ -35,7 +36,11 @@ export const renderCommits = (commits) => {
|
|
|
35
36
|
const formatted = formatAbsoluteDate(c.timestamp);
|
|
36
37
|
const sha = escapeHtml(c.sha);
|
|
37
38
|
const message = escapeHtml(c.message);
|
|
38
|
-
|
|
39
|
+
const commitUrl = getCommitUrl(repositoryUrl, c.sha);
|
|
40
|
+
const shaHtml = commitUrl
|
|
41
|
+
? `<a href="${escapeHtml(commitUrl)}" class="${CLASS_COMMIT_SHA}" target="_blank" rel="noopener noreferrer">${sha}</a>`
|
|
42
|
+
: `<span class="${CLASS_COMMIT_SHA}">${sha}</span>`;
|
|
43
|
+
return `<div class="commit-item">${shaHtml}<span class="${CLASS_COMMIT_MESSAGE}">${message}</span><span class="${CLASS_COMMIT_TIME}" title="${c.timestamp}" ${ATTR_DATA_DATE}="${c.timestamp}">${formatted}</span></div>`;
|
|
39
44
|
})
|
|
40
45
|
.join('');
|
|
41
46
|
return `<div class="commit-list"><h3 class="commit-list-heading">Git Commits</h3>${rows}</div>`;
|
|
@@ -64,16 +69,24 @@ export const renderTimelineEntryItem = (e) => {
|
|
|
64
69
|
>
|
|
65
70
|
</div>`;
|
|
66
71
|
};
|
|
67
|
-
export const renderTimelineCommitItem = (c, jiraBaseUrl, jiraPrefix) => {
|
|
72
|
+
export const renderTimelineCommitItem = (c, jiraBaseUrl, jiraPrefix, repositoryUrl) => {
|
|
68
73
|
const message = jiraBaseUrl && jiraPrefix ? linkJiraIds(c.message, jiraBaseUrl, jiraPrefix) : escapeHtml(c.message);
|
|
69
74
|
const formatted = formatAbsoluteDate(c.timestamp);
|
|
70
75
|
const sha = escapeHtml(c.sha);
|
|
71
|
-
|
|
76
|
+
const commitUrl = getCommitUrl(repositoryUrl, c.sha);
|
|
77
|
+
const shaHtml = commitUrl
|
|
78
|
+
? `<a href="${escapeHtml(commitUrl)}" class="${CLASS_COMMIT_SHA}" target="_blank" rel="noopener noreferrer">${sha}</a>`
|
|
79
|
+
: `<span class="${CLASS_COMMIT_SHA}">${sha}</span>`;
|
|
80
|
+
return `<div class="timeline-item timeline-item--commit"><div class="commit-chip">${shaHtml}<span class="${CLASS_COMMIT_MESSAGE}">${message}</span><span class="${CLASS_COMMIT_TIME}" title="${c.timestamp}" ${ATTR_DATA_DATE}="${c.timestamp}">${formatted}</span></div></div>`;
|
|
72
81
|
};
|
|
73
|
-
export const renderTimelineTagItem = (t) => {
|
|
82
|
+
export const renderTimelineTagItem = (t, repositoryUrl) => {
|
|
74
83
|
const formatted = formatAbsoluteDate(t.timestamp);
|
|
75
84
|
const name = escapeHtml(t.name);
|
|
76
|
-
|
|
85
|
+
const tagUrl = getTagUrl(repositoryUrl, t.name);
|
|
86
|
+
const tagHtml = tagUrl
|
|
87
|
+
? `<a href="${escapeHtml(tagUrl)}" class="tag-chip-name" target="_blank" rel="noopener noreferrer">${name}</a>`
|
|
88
|
+
: `<span class="tag-chip-name">${name}</span>`;
|
|
89
|
+
return `<div class="timeline-item timeline-item--tag"><div class="tag-chip"><span class="tag-chip-icon">🏷</span>${tagHtml}<span class="tag-chip-date" title="${t.timestamp}" ${ATTR_DATA_DATE}="${t.timestamp}">${formatted}</span></div></div>`;
|
|
77
90
|
};
|
|
78
91
|
export const paginationLinks = (currentPage, totalPages) => {
|
|
79
92
|
let links = '';
|
|
@@ -16,8 +16,10 @@ export interface TimelineTemplateProps {
|
|
|
16
16
|
groups: TimelineGroup[];
|
|
17
17
|
readmeHtml: string;
|
|
18
18
|
aboutHtml: string;
|
|
19
|
+
welcomeHtml?: string;
|
|
19
20
|
jiraBaseUrl?: string;
|
|
20
21
|
jiraPrefix?: string;
|
|
22
|
+
repositoryUrl?: string;
|
|
21
23
|
currentPage?: number;
|
|
22
24
|
totalPages?: number;
|
|
23
25
|
}
|
|
@@ -101,6 +103,7 @@ export interface PostTemplateProps {
|
|
|
101
103
|
commits?: GitCommit[];
|
|
102
104
|
version: string;
|
|
103
105
|
jiraUrl?: string;
|
|
106
|
+
repositoryUrl?: string;
|
|
104
107
|
prevEntry: EntryLink | null;
|
|
105
108
|
nextEntry: EntryLink | null;
|
|
106
109
|
slug?: string;
|
package/dist/lib/templates.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { TabConfig, TimelineTemplateProps, PostTemplateProps } from './template-types.js';
|
|
2
2
|
export declare const tabsComponent: (tabs: TabConfig[], defaultActive?: number) => string;
|
|
3
|
-
export declare const layout: ({ title, header, content, projectName, basePath, description, bodySlug, buildMeta, }: {
|
|
3
|
+
export declare const layout: ({ title, header, content, projectName, basePath, description, bodySlug, buildMeta, includeRssLink, }: {
|
|
4
4
|
title: string;
|
|
5
5
|
header: string;
|
|
6
6
|
content: string;
|
|
@@ -9,8 +9,11 @@ export declare const layout: ({ title, header, content, projectName, basePath, d
|
|
|
9
9
|
description?: string;
|
|
10
10
|
bodySlug?: string;
|
|
11
11
|
buildMeta?: string;
|
|
12
|
+
includeRssLink?: boolean;
|
|
12
13
|
}) => string;
|
|
13
|
-
export declare const timelineTemplate: (props: TimelineTemplateProps
|
|
14
|
+
export declare const timelineTemplate: (props: TimelineTemplateProps & {
|
|
15
|
+
includeRssLink?: boolean;
|
|
16
|
+
}) => {
|
|
14
17
|
header: string;
|
|
15
18
|
content: string;
|
|
16
19
|
};
|
package/dist/lib/templates.js
CHANGED
|
@@ -13,39 +13,49 @@ export const tabsComponent = (tabs, defaultActive = 0) => {
|
|
|
13
13
|
${sectionsHtml}
|
|
14
14
|
</div>`;
|
|
15
15
|
};
|
|
16
|
-
export const layout = ({ title, header, content, projectName, basePath = './', description, bodySlug, buildMeta, }) => html `<!DOCTYPE html>
|
|
16
|
+
export const layout = ({ title, header, content, projectName, basePath = './', description, bodySlug, buildMeta, includeRssLink = false, }) => html `<!DOCTYPE html>
|
|
17
17
|
<html lang="en">
|
|
18
18
|
<head>
|
|
19
19
|
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
20
20
|
<title>${escapeHtml(title)} | ${escapeHtml(projectName)}</title>
|
|
21
21
|
${description ? `<meta name="description" content="${escapeHtml(description).replace(/"/g, '"').substring(0, 160)}">` : ''}
|
|
22
|
+
${includeRssLink ? `<link rel="alternate" type="application/rss+xml" title="Subscribe to Project RSS Feed" href="${basePath}rss.xml">` : ''}
|
|
22
23
|
<link rel="icon" type="image/svg+xml" href="${basePath}favicon.svg">
|
|
23
24
|
<link rel="stylesheet" href="${basePath}style.css">
|
|
24
25
|
</head>
|
|
25
26
|
<body${bodySlug ? ` ${ATTR_PAGE_SLUG}="${bodySlug}"` : ''}>
|
|
26
27
|
${header}<main>${content}</main>
|
|
27
|
-
<footer
|
|
28
|
+
<footer>
|
|
29
|
+
${buildMeta ? `<p class="build-time">${buildMeta}${includeRssLink ? ` • <a href="${basePath}rss.xml">RSS Feed</a>` : ''}</p>` : ''}
|
|
30
|
+
</footer>
|
|
28
31
|
<script src="${basePath}logbook.js"></script>
|
|
29
32
|
</body></html>`;
|
|
30
33
|
export const timelineTemplate = (props) => {
|
|
31
|
-
const { projectName, groups, readmeHtml, aboutHtml, jiraBaseUrl, jiraPrefix, currentPage, totalPages } = props;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
+
const { projectName, groups, readmeHtml, aboutHtml, welcomeHtml, jiraBaseUrl, jiraPrefix, repositoryUrl, currentPage, totalPages, } = props;
|
|
35
|
+
const welcomeBannerHtml = welcomeHtml
|
|
36
|
+
? `<div id="welcome-banner" class="welcome-banner" style="display:none;">
|
|
37
|
+
<button id="welcome-banner-close" class="welcome-banner-close" aria-label="Dismiss welcome banner">×</button>
|
|
38
|
+
<div class="welcome-banner-content">${welcomeHtml}</div>
|
|
39
|
+
</div>`
|
|
40
|
+
: '';
|
|
41
|
+
const timelineTab = html `${welcomeBannerHtml}
|
|
42
|
+
<div class="timeline">
|
|
43
|
+
${groups
|
|
34
44
|
.map((g) => html `<section class="month-group">
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
<h2>${g.month}</h2>
|
|
46
|
+
${g.items
|
|
37
47
|
.map((item) => {
|
|
38
48
|
if (item.kind === 'entry')
|
|
39
49
|
return renderTimelineEntryItem(item);
|
|
40
50
|
if (item.kind === 'tag')
|
|
41
|
-
return renderTimelineTagItem(item);
|
|
42
|
-
return renderTimelineCommitItem(item, jiraBaseUrl, jiraPrefix);
|
|
51
|
+
return renderTimelineTagItem(item, repositoryUrl);
|
|
52
|
+
return renderTimelineCommitItem(item, jiraBaseUrl, jiraPrefix, repositoryUrl);
|
|
43
53
|
})
|
|
44
54
|
.join('')}
|
|
45
|
-
|
|
55
|
+
</section>`)
|
|
46
56
|
.join('')}
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
${paginationLinks(currentPage, totalPages)}
|
|
58
|
+
</div>`;
|
|
49
59
|
const header = html `<header>
|
|
50
60
|
<h1><a href="./index.html">${escapeHtml(projectName)}</a></h1>
|
|
51
61
|
<p class="tagline">A brief summary of the recent changes to the project.</p>
|
|
@@ -58,7 +68,7 @@ export const timelineTemplate = (props) => {
|
|
|
58
68
|
return { header, content };
|
|
59
69
|
};
|
|
60
70
|
export const postTemplate = (props) => {
|
|
61
|
-
const { title, displayDate, dateStart, harness, llm, prompter, content, ticketHtml, logHtml, commits, version, jiraUrl, prevEntry, nextEntry, tags, workspaces } = props; // prettier-ignore
|
|
71
|
+
const { title, displayDate, dateStart, harness, llm, prompter, content, ticketHtml, logHtml, commits, version, jiraUrl, repositoryUrl, prevEntry, nextEntry, tags, workspaces } = props; // prettier-ignore
|
|
62
72
|
// Validate jiraUrl scheme to prevent javascript: URLs
|
|
63
73
|
const validatedJiraUrl = jiraUrl ? validateUrlScheme(jiraUrl) : undefined;
|
|
64
74
|
const jiraBtn = validatedJiraUrl
|
|
@@ -89,7 +99,7 @@ export const postTemplate = (props) => {
|
|
|
89
99
|
</div>
|
|
90
100
|
<div id="spec" class="content-section" role="tabpanel" style="display:none;">${ticketHtml}</div>
|
|
91
101
|
<div id="log" class="content-section" role="tabpanel" style="display:none;">
|
|
92
|
-
${logHtml}${renderCommits(commits)}
|
|
102
|
+
${logHtml}${renderCommits(commits, repositoryUrl)}
|
|
93
103
|
</div>
|
|
94
104
|
</div>
|
|
95
105
|
<nav class="post-nav">
|
package/dist/linters/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import lockfile from './lockfile.js';
|
|
|
6
6
|
import placeholders from './placeholders.js';
|
|
7
7
|
import projectIntegrity from './project-integrity.js';
|
|
8
8
|
import readability from './readability.js';
|
|
9
|
+
import technicalLog from './technical-log.js';
|
|
9
10
|
import workspaces from './workspaces.js';
|
|
10
11
|
export const linters = [
|
|
11
12
|
diffToNarrative,
|
|
@@ -16,5 +17,6 @@ export const linters = [
|
|
|
16
17
|
placeholders,
|
|
17
18
|
projectIntegrity,
|
|
18
19
|
readability,
|
|
20
|
+
technicalLog,
|
|
19
21
|
workspaces,
|
|
20
22
|
];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Linter that checks for adequate technical logging.
|
|
5
|
+
* Ensures log.md has more than just the initial "Started investigation" entry.
|
|
6
|
+
*/
|
|
7
|
+
const technicalLog = {
|
|
8
|
+
name: 'technical-log',
|
|
9
|
+
description: 'Checks for adequate technical logging in log.md',
|
|
10
|
+
async check(context) {
|
|
11
|
+
const issues = [];
|
|
12
|
+
// Only check the active entry (the one with the lockfile)
|
|
13
|
+
// Old entries cannot be updated, so we don't warn about them
|
|
14
|
+
if (!context.entryName || !context.entryPath) {
|
|
15
|
+
return issues;
|
|
16
|
+
}
|
|
17
|
+
// Check if this is the active entry by verifying lockfile exists
|
|
18
|
+
const lockfilePath = join(process.cwd(), '.logbook-active');
|
|
19
|
+
if (!(await fs.pathExists(lockfilePath))) {
|
|
20
|
+
// No active entry, skip this linter
|
|
21
|
+
return issues;
|
|
22
|
+
}
|
|
23
|
+
const lockfileContent = await fs.readFile(lockfilePath, 'utf8');
|
|
24
|
+
const lockfileData = JSON.parse(lockfileContent);
|
|
25
|
+
const activeEntrySlug = lockfileData.slug;
|
|
26
|
+
// Only check if this entry matches the active entry slug
|
|
27
|
+
if (context.entryName !== activeEntrySlug) {
|
|
28
|
+
return issues;
|
|
29
|
+
}
|
|
30
|
+
const logPath = join(context.entryPath, 'log.md');
|
|
31
|
+
if (!(await fs.pathExists(logPath))) {
|
|
32
|
+
issues.push({
|
|
33
|
+
category: 'TECHNICAL_LOG',
|
|
34
|
+
level: 'warning',
|
|
35
|
+
message: 'Missing log.md file. Create one to document your technical decisions.',
|
|
36
|
+
});
|
|
37
|
+
return issues;
|
|
38
|
+
}
|
|
39
|
+
const logContent = await fs.readFile(logPath, 'utf8');
|
|
40
|
+
// Extract all protocol entries (lines starting with "- " under ## Protocol)
|
|
41
|
+
const protocolSection = logContent.split('## Protocol')[1];
|
|
42
|
+
if (!protocolSection) {
|
|
43
|
+
// No Protocol section found, might be malformed
|
|
44
|
+
issues.push({
|
|
45
|
+
category: 'TECHNICAL_LOG',
|
|
46
|
+
level: 'warning',
|
|
47
|
+
message: 'log.md missing "## Protocol" section. Add one to structure your log entries.',
|
|
48
|
+
});
|
|
49
|
+
return issues;
|
|
50
|
+
}
|
|
51
|
+
const lines = protocolSection.split('\n').filter((line) => line.trim().startsWith('- '));
|
|
52
|
+
const entryCount = lines.length;
|
|
53
|
+
// Check if there's only the initial "Started investigation" entry
|
|
54
|
+
if (entryCount <= 1) {
|
|
55
|
+
issues.push({
|
|
56
|
+
category: 'TECHNICAL_LOG',
|
|
57
|
+
level: 'warning',
|
|
58
|
+
message: 'log.md only contains the initial "Started investigation" entry. Log your work in real-time using `logbook log "<message>"` after each significant step.',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
else if (entryCount < 5) {
|
|
62
|
+
// Less than 5 entries might indicate insufficient logging
|
|
63
|
+
issues.push({
|
|
64
|
+
category: 'TECHNICAL_LOG',
|
|
65
|
+
level: 'warning',
|
|
66
|
+
message: `log.md has only ${entryCount} entries. Consider adding more detail about your implementation process, decisions, and challenges.`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return issues;
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
export default technicalLog;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Welcome to the Project Logbook!
|
|
2
|
+
|
|
3
|
+
This website is a **Project Logbook** — a chronological timeline of this project's evolution, design decisions, and development history.
|
|
4
|
+
|
|
5
|
+
Unlike traditional static documentation, a logbook behaves like a **dev log** or **news feed** of the codebase. Each item in the timeline below represents a completed feature or bugfix, containing:
|
|
6
|
+
- **Original Specifications:** The initial requirements and user stories.
|
|
7
|
+
- **Technical Log:** Step-by-step notes and decisions made during development.
|
|
8
|
+
- **The Story:** An engaging narrative explaining the rationale, pivots, and solutions.
|
|
9
|
+
|
|
10
|
+
Click around to explore how this project was built step by step!
|
|
11
|
+
|
|
12
|
+
Wait, did we mention it has an [**RSS Feed**](rss.xml)? Yes, you can subscribe to stay updated on new logbook entries in real-time!
|
package/dist/templates/index.md
CHANGED
|
@@ -21,6 +21,10 @@ dateEnd: "[DATE_END]"
|
|
|
21
21
|
## Summary
|
|
22
22
|
TODO: Write a polished, highly readable ticket summary that reads like an engaging technical narrative (similar to a well-written dev blog post).
|
|
23
23
|
|
|
24
|
+
### Before You Start:
|
|
25
|
+
- **Check `log.md`**: Review your technical log for all the decisions, errors, and pivots you recorded during implementation.
|
|
26
|
+
- **Reference the log**: Use your real-time log entries as source material for the narrative — don't try to reconstruct from memory.
|
|
27
|
+
|
|
24
28
|
### Formatting & Style Rules:
|
|
25
29
|
- **Maintain the narrative tone:** TODO: Keep the storytelling flair (e.g., describing how problems accumulated or how gaps surfaced), but stay strictly factual based on the provided changes.
|
|
26
30
|
- **Add thematic headings:** TODO: Break the narrative down into logical chapters using Markdown headings (e.g., ### The Friction Points, ### The Fix, ### Closing the Gap).
|
package/dist/templates/log.md
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
# Technical Log: {{id}}-{{slug}}
|
|
2
2
|
|
|
3
|
+
> **REMINDER**: Log your work in real-time using `logbook log "<message>"`. Don't wait until the end!
|
|
4
|
+
>
|
|
5
|
+
> Log after every significant step: investigation, errors, decisions, code changes, test runs, etc.
|
|
6
|
+
>
|
|
7
|
+
|
|
3
8
|
## Protocol
|
|
4
9
|
- {{fullIso}}: Started investigation.
|
|
@@ -168,6 +168,32 @@
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/* ── 4. Welcome banner functionality ────────────────────────────────────── */
|
|
172
|
+
function initWelcomeBanner() {
|
|
173
|
+
var banner = document.getElementById('welcome-banner');
|
|
174
|
+
if (!banner) return;
|
|
175
|
+
try {
|
|
176
|
+
if (localStorage.getItem('welcome-banner-dismissed') !== 'true') {
|
|
177
|
+
banner.style.display = 'block';
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
// In case localStorage is disabled/unavailable, default to showing
|
|
181
|
+
banner.style.display = 'block';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
var closeBtn = document.getElementById('welcome-banner-close');
|
|
185
|
+
if (closeBtn) {
|
|
186
|
+
closeBtn.addEventListener('click', function () {
|
|
187
|
+
banner.style.display = 'none';
|
|
188
|
+
try {
|
|
189
|
+
localStorage.setItem('welcome-banner-dismissed', 'true');
|
|
190
|
+
} catch (e) {
|
|
191
|
+
/* localStorage unavailable */
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
171
197
|
/* ── Bootstrap ─────────────────────────────────────────────────────────── */
|
|
172
198
|
document.addEventListener('DOMContentLoaded', function () {
|
|
173
199
|
updateRelativeDates();
|
|
@@ -181,6 +207,9 @@
|
|
|
181
207
|
|
|
182
208
|
// Initialize tab functionality
|
|
183
209
|
initTabs();
|
|
210
|
+
|
|
211
|
+
// Initialize welcome banner if present
|
|
212
|
+
initWelcomeBanner();
|
|
184
213
|
});
|
|
185
214
|
|
|
186
215
|
// Expose showTab globally for onclick handlers
|
package/dist/templates/steer.txt
CHANGED
|
@@ -8,11 +8,27 @@ Phase 1: Understand
|
|
|
8
8
|
2. Review `AGENTS.md` and `CONTRIBUTING.md` if you need architectural or workflow context.
|
|
9
9
|
|
|
10
10
|
Phase 2: Execute & Trace
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
|
|
12
|
+
**CRITICAL: Real-Time Logging**
|
|
13
|
+
You MUST log your work continuously using `logbook log "<message>"`. This is not optional.
|
|
14
|
+
|
|
15
|
+
1. Use `log.md` as your live technical scratchpad.
|
|
16
|
+
2. After EVERY significant step, run: `logbook log "<what you just did>"`
|
|
17
|
+
- Investigated a file? Log it.
|
|
18
|
+
- Found an error? Log it.
|
|
19
|
+
- Made a design decision? Log it.
|
|
20
|
+
- Fixed a bug? Log it.
|
|
21
|
+
- Ran tests? Log the result.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
- `logbook log "Investigated src/lib/config.ts - found missing validation"`
|
|
25
|
+
- `logbook log "Created new RSS module with generateRssFeed() function"`
|
|
26
|
+
- `logbook log "Test failed: TypeScript error on line 42 - fixed type assertion"`
|
|
27
|
+
- `logbook log "All 127 tests pass, pre-commit suite successful"`
|
|
28
|
+
|
|
29
|
+
3. **One message per command call.** For multiple entries, call the command multiple times.
|
|
30
|
+
4. Do NOT reconstruct the log at the end. If the log is empty when you finish, you did it wrong.
|
|
31
|
+
5. Stick to the active entry; never modify past entries in the logbook folder.
|
|
16
32
|
|
|
17
33
|
Phase 3: Synthesize
|
|
18
34
|
1. When implementation is finished, write the narrative in `index.md`.
|
|
@@ -696,3 +696,185 @@ article img[src$='.webp'],
|
|
|
696
696
|
article img[src$='.svg'] {
|
|
697
697
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
698
698
|
}
|
|
699
|
+
|
|
700
|
+
/* Technical Log Timeline */
|
|
701
|
+
#log .timeline {
|
|
702
|
+
list-style: none;
|
|
703
|
+
padding: 0;
|
|
704
|
+
margin: 0;
|
|
705
|
+
position: relative;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
#log .timeline::before {
|
|
709
|
+
content: '';
|
|
710
|
+
position: absolute;
|
|
711
|
+
left: 8px;
|
|
712
|
+
top: 0;
|
|
713
|
+
bottom: 0;
|
|
714
|
+
width: 2px;
|
|
715
|
+
background: var(--border);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
#log .timeline-entry {
|
|
719
|
+
position: relative;
|
|
720
|
+
padding-left: 2rem;
|
|
721
|
+
margin-bottom: 0.75rem;
|
|
722
|
+
min-height: 1.5rem;
|
|
723
|
+
display: grid;
|
|
724
|
+
grid-template-columns: 55px 1fr;
|
|
725
|
+
gap: 0.5rem;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
#log .timeline-entry.no-time .timeline-message {
|
|
729
|
+
grid-column: 2;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
#log .timeline-entry.has-time::before {
|
|
733
|
+
content: '';
|
|
734
|
+
position: absolute;
|
|
735
|
+
left: 5px;
|
|
736
|
+
top: 4px;
|
|
737
|
+
width: 8px;
|
|
738
|
+
height: 8px;
|
|
739
|
+
border-radius: 50%;
|
|
740
|
+
background: var(--primary);
|
|
741
|
+
border: 2px solid var(--bg);
|
|
742
|
+
z-index: 1;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
#log .timeline-entry.no-time::before {
|
|
746
|
+
content: '';
|
|
747
|
+
position: absolute;
|
|
748
|
+
left: 7px;
|
|
749
|
+
top: 0.6rem;
|
|
750
|
+
width: 4px;
|
|
751
|
+
height: 4px;
|
|
752
|
+
border-radius: 50%;
|
|
753
|
+
background: var(--text-muted);
|
|
754
|
+
opacity: 0.6;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
#log .timeline-time {
|
|
758
|
+
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
|
|
759
|
+
font-size: 0.75rem;
|
|
760
|
+
font-weight: 600;
|
|
761
|
+
color: var(--primary);
|
|
762
|
+
display: inline-block;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
#log .timeline-message {
|
|
766
|
+
font-size: 0.9375rem;
|
|
767
|
+
color: var(--text);
|
|
768
|
+
line-height: 1.5;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
#log .timeline-entry.no-time .timeline-message {
|
|
772
|
+
color: var(--text);
|
|
773
|
+
opacity: 0.85;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
#log .timeline-gap {
|
|
777
|
+
position: relative;
|
|
778
|
+
padding-left: 2rem;
|
|
779
|
+
margin: 1.5rem 0;
|
|
780
|
+
min-height: 1rem;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
#log .timeline-gap::before {
|
|
784
|
+
content: '';
|
|
785
|
+
position: absolute;
|
|
786
|
+
left: 7px;
|
|
787
|
+
top: 0.4rem;
|
|
788
|
+
width: 4px;
|
|
789
|
+
height: 4px;
|
|
790
|
+
border-radius: 50%;
|
|
791
|
+
background: var(--text-muted);
|
|
792
|
+
opacity: 0.5;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
#log .gap-indicator {
|
|
796
|
+
font-size: 0.8125rem;
|
|
797
|
+
color: var(--text-muted);
|
|
798
|
+
font-style: italic;
|
|
799
|
+
display: inline-flex;
|
|
800
|
+
align-items: center;
|
|
801
|
+
gap: 0.25rem;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
#log .log-fallback {
|
|
805
|
+
font-size: 0.9375rem;
|
|
806
|
+
color: var(--text);
|
|
807
|
+
line-height: 1.6;
|
|
808
|
+
white-space: pre-wrap;
|
|
809
|
+
background: var(--primary-soft);
|
|
810
|
+
padding: 1rem;
|
|
811
|
+
border-radius: 0.5rem;
|
|
812
|
+
font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
|
|
813
|
+
font-size: 0.8125rem;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/* Welcome Banner */
|
|
817
|
+
.welcome-banner {
|
|
818
|
+
background: var(--bg-card, #ffffff);
|
|
819
|
+
border: 1px solid var(--border, #e2e8f0);
|
|
820
|
+
padding: 1.5rem;
|
|
821
|
+
border-radius: 0.5rem;
|
|
822
|
+
margin-top: 1.5rem;
|
|
823
|
+
margin-bottom: 2rem;
|
|
824
|
+
position: relative;
|
|
825
|
+
box-shadow:
|
|
826
|
+
0 1px 3px 0 rgba(0, 0, 0, 0.1),
|
|
827
|
+
0 1px 2px -1px rgba(0, 0, 0, 0.1);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
.welcome-banner-content h1 {
|
|
831
|
+
font-size: 1.25rem;
|
|
832
|
+
margin-top: 0;
|
|
833
|
+
margin-bottom: 0.75rem;
|
|
834
|
+
color: var(--text-main, #1e293b);
|
|
835
|
+
display: flex;
|
|
836
|
+
align-items: center;
|
|
837
|
+
gap: 0.5rem;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
.welcome-banner-content p {
|
|
841
|
+
margin: 0 0 0.75rem 0;
|
|
842
|
+
line-height: 1.5;
|
|
843
|
+
color: var(--text, #334155);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.welcome-banner-content ul {
|
|
847
|
+
margin: 0 0 1rem 0;
|
|
848
|
+
padding-left: 1.25rem;
|
|
849
|
+
color: var(--text, #334155);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.welcome-banner-content li {
|
|
853
|
+
margin-bottom: 0.5rem;
|
|
854
|
+
line-height: 1.4;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
.welcome-banner-close {
|
|
858
|
+
position: absolute;
|
|
859
|
+
top: 1rem;
|
|
860
|
+
right: 1rem;
|
|
861
|
+
background: none;
|
|
862
|
+
border: none;
|
|
863
|
+
font-size: 1.25rem;
|
|
864
|
+
cursor: pointer;
|
|
865
|
+
color: var(--text-muted, #64748b);
|
|
866
|
+
line-height: 1;
|
|
867
|
+
padding: 0.25rem;
|
|
868
|
+
border-radius: 0.25rem;
|
|
869
|
+
display: flex;
|
|
870
|
+
align-items: center;
|
|
871
|
+
justify-content: center;
|
|
872
|
+
transition:
|
|
873
|
+
background-color 0.2s,
|
|
874
|
+
color 0.2s;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
.welcome-banner-close:hover {
|
|
878
|
+
background-color: var(--primary-soft, #f0fdf4);
|
|
879
|
+
color: var(--text-main, #1e293b);
|
|
880
|
+
}
|
package/dist/utils/date.d.ts
CHANGED
|
@@ -28,3 +28,10 @@ export declare function getSortTime(dateStart: string | Date, dateEnd?: string |
|
|
|
28
28
|
* Alias for formatAbsoluteDate for clarity in build context.
|
|
29
29
|
*/
|
|
30
30
|
export declare function formatDateTimeForDisplay(date: string | Date): string;
|
|
31
|
+
/**
|
|
32
|
+
* Format a date for RSS feed pubDate element.
|
|
33
|
+
* RSS requires RFC 822 date format: "Mon, 26 May 2026 10:51:00 GMT"
|
|
34
|
+
* @param date - The date to format
|
|
35
|
+
* @returns RFC 822 formatted date string
|
|
36
|
+
*/
|
|
37
|
+
export declare function formatDateTimeForRss(date: string | Date): string;
|
package/dist/utils/date.js
CHANGED
|
@@ -86,3 +86,15 @@ export function getSortTime(dateStart, dateEnd) {
|
|
|
86
86
|
export function formatDateTimeForDisplay(date) {
|
|
87
87
|
return formatAbsoluteDate(date);
|
|
88
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Format a date for RSS feed pubDate element.
|
|
91
|
+
* RSS requires RFC 822 date format: "Mon, 26 May 2026 10:51:00 GMT"
|
|
92
|
+
* @param date - The date to format
|
|
93
|
+
* @returns RFC 822 formatted date string
|
|
94
|
+
*/
|
|
95
|
+
export function formatDateTimeForRss(date) {
|
|
96
|
+
const d = parseDate(date);
|
|
97
|
+
if (!d)
|
|
98
|
+
return new Date().toUTCString();
|
|
99
|
+
return d.toUTCString();
|
|
100
|
+
}
|