Skip to content

Hypercart-Dev-Tools/project-docs-deluxe

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Self-Reporting Project Plan (Single HTML File)

A zero-build, single-file project plan that renders its own progress report. Open project-plan.html in any browser β€” no server, no build step, no dependencies beyond one CDN script (Frappe Gantt, MIT, ~50KB, zero dependencies of its own).

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  MINI REPORT (generated)                β”‚  ← stall alerts, summary cards,
β”‚  stall banner Β· cards Β· phase bars      β”‚    per-phase progress
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  GANTT TIMELINE (generated)             β”‚  ← Frappe Gantt, SVG
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PHASES & CHECKLIST (generated)         β”‚  ← pretty render of the plan
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PLAN SOURCE (you edit this)            β”‚  ← inert plain-text block:
β”‚  <script type="text/plain">             β”‚    the single source of truth
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PARSER + RENDERER <script>             β”‚  ← ~150 lines of vanilla JS
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Core design decisions

1. Single source of truth β€” no sync step

There is exactly one data representation: a human-readable, line-based plain text block. The report, Gantt chart, and checklist are all derived from it at page load. There is no JSON block and no Python regeneration script, because two representations of the same plan will eventually drift; a derived view cannot.

2. Inert data block

The plan lives in:

<script type="text/plain" id="plan-source"> ... </script>

Browsers ignore (never execute, never display) <script> elements whose type is not a known script/JSON type, but the content remains in the DOM and is readable via element.textContent. This makes it a safe in-page data container β€” no escaping issues, no eval, no XSS surface from the data itself.

3. Script ordering, not events

The parser <script> sits at the very end of <body>. HTML parsing is sequential, so by the time it executes, every element above it β€” including the plan block β€” already exists. No DOMContentLoaded listener is needed, and the report containers at the top of the page can be filled immediately.

Plan source format

Strict, line-based, deterministic. One regex per line type; anything that matches nothing is reported in a red "unparseable lines" banner rather than silently dropped.

Header (key: value)

project: Storefront v2
owner: Noel
stall-threshold-days: 2

Phases

phase: Phase 2 β€” Core Features

Tasks (one per line, pipe-delimited)

[status] id | Task name | YYYY-MM-DD -> YYYY-MM-DD | deps: id, id
Marker Meaning
[x] done
[>] in progress
[ ] todo

The deps: segment is optional. Dependency ids draw arrows in the Gantt.

Progress log lines (indented under their task)

    YYYY-MM-DD NN% optional free-text note

To record work, append a log line β€” never rewrite history. The log is an append-only journal, which is what makes stall detection possible.

Grammar (the actual regexes)

RE_META  = /^([a-z-]+):\s*(.+)$/
RE_PHASE = /^phase:\s*(.+)$/
RE_TASK  = /^\[(x|>| )\]\s*(\S+)\s*\|\s*(.+?)\s*\|\s*(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2})\s*(?:\|\s*deps:\s*(.*))?$/
RE_LOG   = /^\s+(\d{4}-\d{2}-\d{2})\s+(\d{1,3})%\s*(.*)$/

The format is intentionally rigid so that AI coding agents can edit it reliably β€” there is exactly one way to write each line.

Derived state

For every task, the renderer computes:

  • progress β€” 100 if status is [x], otherwise the % of the last log entry, otherwise 0.
  • Overall progress β€” unweighted mean of all task progress values.
  • Phase progress β€” unweighted mean within the phase.
  • Target date β€” max end date across tasks (shown in the subtitle).

The stall algorithm

Goal: flag a bug/feature that has been "worked on" for 2–3 days without actual movement β€” e.g. daily log entries that all say 40%.

Definition: a task is stalled iff

  1. its status is [>] (in progress), and
  2. idleDays >= stall-threshold-days, where idleDays is the number of whole days between the last date its logged % actually increased and today.
// For each task: walk the log in order, tracking the previous %.
let lastMovement = null, prev = 0;
for (const e of t.log) {
  if (e.progress > prev) lastMovement = d(e.date);  // a real increase
  prev = e.progress;
}
// Fallbacks: a log with no increases β†’ first log date;
//            no log at all          β†’ the task's start date.
if (!lastMovement) lastMovement = t.log.length ? d(t.log[0].date) : d(t.start);

const idleDays = Math.floor((today - lastMovement) / 86400000);
const stalled  = t.status === "in-progress" && idleDays >= THRESH;

Properties

  • Deterministic. Output depends only on the log lines plus today's date β€” no heuristics, no state stored anywhere else. The same file gives the same verdict on any machine on the same day.
  • Activity β‰  progress. Log entries that repeat the same % do not reset the clock. Only an increase counts as movement. "Touched it every day, still at 40%" is exactly the situation that gets flagged.
  • Honest fallbacks. A task that was started ([>]) but never logged is measured from its planned start date, so silent tasks can't hide.
  • Done/todo tasks are exempt. [x] can't stall by definition; [ ] hasn't started, so lateness is visible in the Gantt instead (bar left of the today line).
  • Tunable per file. stall-threshold-days: 2 in the header. Use 3 for a looser policy.
  • Midnight-normalized. today is truncated to local midnight and dates are parsed as local dates, so a log entry from yesterday evening counts as 1 idle day, not 0.86.

What a stall produces

  1. A red alert banner at the top: task name, the % it's stuck at, days idle, and the most recent note (usually the blocker, e.g. "blocked on vendor API rate limits").
  2. A red Gantt bar (custom_class: "bar-stalled" + CSS fill override).
  3. A stalled Nd badge in the checklist, and a red dot icon.
  4. The Stalled summary card turns red with the count.

Known edge cases

  • A task whose % decreases (re-scoped estimate) does not count as movement; the clock keeps running from the last increase. If you re-scope, it's honest to keep the flag until the % moves up again.
  • Weekends count as idle days. If you don't want that, replace daysBetween with a business-day count β€” it's the only function you'd touch.
  • Two log entries on the same date: last one wins for current %, and an increase on that date still registers as movement.

Gantt rendering details

new Gantt("#gantt", tasks.map(...), {
  view_mode: "Week",          // whole project visible at typical widths
  readonly: true,             // report, not an editor β€” text is the editor
  infinite_padding: false,    // don't generate months of empty columns
  view_mode_select: true,     // Day/Week/Month dropdown in the header
  scroll_to: "start",
  container_height: "auto",   // never clip rows
});

Per-task styling uses Frappe's custom_class hook plus CSS overrides: bar-stalled (red fill) and bar-done (green progress fill).

One workaround worth knowing: Frappe still pads some empty columns before the first task, so after render the script finds the minimum x of all rect.bar elements and sets the container's scrollLeft to it. Without this the initial view can open on empty space or auto-scroll to today, hiding completed early-phase bars.

If the CDN is unreachable (offline), typeof Gantt === "undefined" is detected and the chart degrades to a notice β€” the report, alerts, and checklist are pure vanilla JS and still work.

Workflow

Daily update = append one line under the task you touched:

    2026-06-11 55% vendor unblocked us, fix in review

Flip [>] to [x] when done. Add tasks/phases as plain lines. Reload the page β€” the report recomputes. The raw source is also viewable in-page via the "View raw plan source" disclosure at the bottom, so reviewers never need to open the file in an editor.

Because the format is line-based and append-only, it diffs cleanly in git: one log line per update, no JSON re-serialization noise.

Extending it

  • Programmatic consumers: the four regexes above port to Python in ~30 lines if you ever want plan.html β†’ plan.json for external tooling. The HTML file needs nothing from that script β€” it would be a one-way export.
  • Multiple projects: copy the file; everything is self-contained.
  • Stricter stalls: also flag [>] tasks past their end date, or todo tasks whose start is in the past β€” both are one-line filters over the derived task list.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors