┌─────────────────────────────────────────────┐
│ index.html │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
│ │ <style> │ │ HTML │ │ <script> │ │
│ │ CSS vars │ │ Structure│ │ Store │ │
│ │ Layout │ │ Modal │ │ UI │ │
│ │ Themes │ │ Views │ │ Events │ │
│ └──────────┘ └──────────┘ └───────────┘ │
└─────────────────────────────────────────────┘
-
python3 validate.py— no structural px issues -
python3 -c "compile(open('index.html').read())"— syntax passes - Open in browser — no console errors
- Create/edit/delete task — data persists on reload
- Switch all views — no crashes
- Dark/light toggle works
- Import/export works
- Keyboard shortcuts all work
Only apply CSS variables --g1 through --g8 and --r-xs through --r-xl to spacing properties: padding, margin, gap, border-radius.
Never replace these with Fibonacci values:
max-width,min-width,widthof containers/modals/inputsheight,min-height,max-heightof layout elements- Canvas dimensions (
width,heightattributes on<canvas>) z-indexvaluesflex-basisorgrid-template-columnsstructural valuesline-heightvalues (keep unitless or 1.5)
/* ✅ Correct */
padding: var(--g3) var(--g5);
gap: var(--g4);
border-radius: var(--r-md);
/* ❌ Wrong */
max-width: 34px; /* was 700px, wrongly converted */
width: 34px; /* was 200px, wrongly converted */
padding: 34px; /* was 34px? no such thing */Before running any automated conversion:
- Read this rule
- Add a comment in the conversion prompt: "DO NOT convert structural layout values (max-width, width, height, canvas dims)"
- After conversion, run
python3 validate.py - Manually review all
34pxvalues
- All data mutations go through
Storemethods Store.save()persists to IndexedDB + localStorage fallbackStore.audit(action, taskId, detail)logs every change- Never modify
Store.statedirectly from UI code
// ✅ Correct
Store.updateTask(id, { status: 'done' });
// ❌ Wrong — bypasses audit/save
Store.state.tasks.find(t => t.id === id).status = 'done';
Store.save();- All rendering goes through
UI.render()→ calls view-specific renderer - Renderers return HTML strings, set
el.innerHTML - Event handlers are inline
onclickattributes orsetupEvents()delegation UI.esc(s)escapes HTML to prevent XSS
// ✅ Correct — renderer returns HTML
renderBoard(tasks) {
const el = document.getElementById('v-board');
el.innerHTML = '<div class="board">...</div>';
},
// ❌ Wrong — direct DOM manipulation in render
renderBoard(tasks) {
document.getElementById('v-board').appendChild(...);
}- Task IDs:
PKGD-{100+length}(auto-increment) - Epic IDs:
EPIC-{n} - Feature IDs:
FEAT-{n} - Story IDs:
STORY-{n} - Sprint IDs:
SP-{n} - Methods:
camelCase - CSS classes:
kebab-case - CSS variables:
--kebab-case - Constants:
UPPER_SNAKE_CASE
// In Store.addTask or Store.updateTask
const task = {
...d,
id: 'PKGD-' + (this.state.tasks.length + 100),
createdAt: new Date().toISOString(),
// ...existing fields
};<div class="form-group">
<label>New Field</label>
<input class="input" id="f-newfield" placeholder="...">
</div>const d = {
newfield: document.getElementById('f-newfield').value,
// ...existing fields
};document.getElementById('f-newfield').value = t ? (t.newfield || '') : '';In UI._cardHTML(), add to the relevant section (card-tags, card-foot, etc.)
- Add nav item in HTML sidebar
- Add view div:
<div id="v-myview" class="view"></div> - Add renderer reference in
UI.render():myview: () => this.renderMyView() - Add keyboard shortcut in
UI.setupKeys() - Add nav highlight in
UI.go()
myNewMethod(arg) {
if (!arg) return;
this.state.tasks.push({...});
this.audit('MY_ACTION', taskId, detail);
this.save();
}// In UI.setupKeys()
if (e.key === 'x') this.go('myview');
// With modifier:
if ((e.metaKey || e.ctrlKey) && e.key === 'x') { e.preventDefault(); this.doThing(); }- Commit messages:
type: description - Types:
feat,fix,docs,refactor,test - One feature per commit
- Never commit broken syntax
python3 validate.pyChecks for:
max-width: 34pxin modal/container contexts (should be 400/500/700)width: 34pxin filter input contexts (should be 200)- Any other suspicious structural px values
Run before every commit.