WS-PIM · Task / Timeline
| Module | Task / Timeline (ws_pim_milestones, WSPIM_Milestone, WSPIM_Public_Timeline) |
| Status | Stage 1 shipped (standalone + in-context capture); Stage 2 home/dashboard shipped; digest, auto-create, and live embed remaining. Planned additions (this revision): item history panel, project health snapshot, baseline/variance reporting |
| Source specs | ws-pim-timeline-spec.md, ws-pim-project-contract.md, ws-pim-access-spec.md, ws-pim-agenda-spec.md |
| Related modules | Agenda, Resume (in-context strip hosts); Settings 2.0 (catalog/overlay pattern); Access (grants) |
1. Overview
The Timeline module is the home for everything with a target date and a did-it-happen answer — the planning-lifecycle checkpoints and ad-hoc reminders that sit between static project facts (Settings) and within-event scheduling (Agenda).
Planners previously ran this as a workback spreadsheet and rejected it as busywork: it required constant manual upkeep, lived outside their workflow, and did nothing to stop a missed deadline. The module’s purpose is to replace that with something low-friction and reminder-driven enough that planners use it without being told to — the deadlines leave their heads and their inboxes and live in one place that nudges them at the right moment.
The design bet is behavioral, not archival. Two properties carry it: near-zero-setup timelines (a client’s standard checkpoints are offered as a pre-checked starting set on every new project, with lead-time dates that fill in automatically) and reminders that actually reach the planner (an in-app upcoming surface plus an opt-in email digest).
2. Goals and non-goals
Goals
- Get every dated obligation out of planners’ heads and spreadsheets and into one project-scoped, reminder-driven surface.
- Make a new project’s timeline near-zero-setup by seeding it from a reusable, client-tunable catalog of standard checkpoints.
- Capture and surface reminders in context — where planners already work (Agenda and Resume), not in a module they must remember to open.
- Never make the planner maintain a status column: state is derived from the date, with a single one-tap “done.”
- Ensure a rescheduled event or function moves its dependent deadlines automatically, with no manual rework.
Non-goals
- No Smartsheet or cross-system record migration. That attempt failed and is explicitly out of scope; this module is an ambient assistant, not a system-of-record importer.
- No task dependencies / predecessor logic. Deliberately excluded — too much setup for planners. A date plus a functional area is sufficient; temporal order comes from the resolved target.
- No manual status enum. No “in progress / blocked” states to maintain; status is implied.
- No owner assignment. Ownership is implicitly the project owner; the interesting axis is who needs to know (stakeholders), not who owns it.
- No manual re-scheduling of dependent chains — with dependencies out of scope, a slipped predecessor doesn’t auto-shift dependents; anchored targets follow the event/function only.
Scope change (this revision): baseline/variance tracking, previously deferred, is now in scope for reporting — see FR-14. It’s the one place the module deliberately stores a frozen value rather than deriving it.
3. Users
- Planner (
pim_planner, capabilitymanage_pim) — the primary user. Owns projects they create, works the timeline, captures reminders in context, receives the digest. Edits any project they own or hold an editor grant on. - Manager / Lead (
pim_manager, capabilitymanage_pim_all) — sees and edits everything, owns the global surfaces, and is the tier that edits the milestone catalog (the cross-client standard timeline). Non-managers can view a client’s catalog/timeline read-only and request changes rather than make them. - Granted collaborator — a planner given an
editororviewergrant on a specific project or client. Editors can write timeline items; viewers see them read-only.
Permissions are resolved live through WSPIM_Capabilities: a project is editable iff the user is a manager, its owner, or an editor-grantee (directly or via a client-scoped grant). The timeline REST surface gates every read behind can_read and every write behind can_write accordingly.
4. Success metrics
- Adoption without mandate: share of active projects with a non-empty timeline; share of planners who accept (rather than skip) the seeded starting set.
- In-context capture: proportion of timeline items created from the Agenda/Resume strip vs. the timeline page — a proxy for whether ambient capture is working.
- Reminder efficacy: opt-in rate for the email digest; reduction in “forgot the deadline” incidents (the catering-guarantee class of miss the module exists to prevent).
- Low friction: median clicks from “new project” to “a usable timeline”; frequency of one-tap done vs. any heavier editing.
5. Core concepts
The unit is a timeline item — one row type with two origins. A milestone (origin = catalog) is templated, cross-project, and seeded from the catalog; a task (origin = custom) is ad-hoc and quick-captured. Same storage, same behavior; origin is the only difference and is what powers cross-project rollups (“registration launch across all events”).
Status is implied, never filled in. The live state is derived from the resolved target date: upcoming → due_soon → overdue. The only manual gesture is a sticky one-tap done (or dismissed), which wins over the date once set. This is what makes “confirmations are tasks” work — needs confirmation is an open attached task; confirmed is that task marked done. There is no status checkbox anywhere.
Definitions live in a three-layer catalog (identical to Settings 2.0), so a client’s standard timeline is defined once and never rebuilt per project:
- Catalog (global) — what checkpoints exist: key, label, section, default lead time, default reminders, on-by-default.
- Client overlay — visibility (on/off), client-specific additions, and per-client tweaks to lead time and reminders (e.g. “Client X launches registration 60 days out, not 45”).
- Project overlay — the same shape at project scope.
Resolution is live: active = project.state[key] ?? client.state[key] ?? catalog.default_active. A definition change at a higher layer is reflected wherever a lower layer hasn’t overridden it, with no push or migration.
Dates are a union and are optional. A target is either absolute (a picked date that stays put) or anchored (an offset from the event start/end or an attached Agenda function’s day, resolved live so it follows any reschedule with zero maintenance). Seeded and auto-created items are anchored; manually dated ones are absolute. Undated is a first-class state — a project needs no start date up front; anchored items simply stay undated, group under “Waiting on a date,” and fire no reminders until the event date is set, at which point every anchored item resolves live.
6. Functional requirements
FR-1 · Per-project timeline
A project has a timeline at /pim/{key}/timeline/ listing its items, grouped by resolved target with an area (section) filter and expand/collapse-all. Each row shows a state dot, title, section, and resolved target. On this page — and only this page — a row expands inline (accordion) into a detail panel with a view → edit → save cycle. [Built]
FR-2 · Implied status and completion
Every item’s state (upcoming / due_soon / overdue / undated / done / dismissed) is derived, not stored. A one-tap done on any row sets the sticky disposition and stops it nudging; dismissed is the same gesture for “don’t remind me.” Done/dismissed are reversible (reopen). The due-soon window is per-item (from reminder offsets), with a 7-day fallback. [Built]
FR-3 · Dates, anchoring, and reschedule-follow
Support absolute and anchored targets per §5. Anchored targets resolve live against event start/end or the attached function’s day, so moving the event or the function moves the deadline and its reminders automatically. Undated anchored items are first-class, grouped separately, and fire no reminders until a date exists; setting the event date activates them with no batch write. A gentle “set the event date to activate reminders” prompt appears when a project has undated items and no event date. [Built]
FR-4 · Catalog and overlays (near-zero-setup)
The milestone catalog and client/project overlays implement the three-layer model in §5. Managers edit the catalog globally; clients and projects carry visibility state, additions, and tweaks. Definitions resolve live so a catalog change propagates wherever it isn’t overridden. Overlay-added definitions are cf_-prefixed; keys are frozen once created. [Built]
FR-5 · Seeding via a reviewable starting picker
On a new project’s first timeline visit, present the catalog’s active milestones (per the client overlay’s defaults) as a pre-checked checklist the planner confirms — one click to accept, deselect what doesn’t apply — never a silent dump. Seeding is idempotent. Planners add more later from the catalog (“add from template,” offering only what’s not already present) or as a custom task. The starting set can be picked before an event date exists; anchored items stay undated until it’s set. [Built]
FR-6 · In-context capture — the Agenda/Resume strip (the flagged requirement)
Timeline items are created and seen where planners already work. Each Agenda function and Resume item is addressable; a timeline item soft-links to it via attached_key. The strip that replaces those modules’ retired status-flag areas gives every host card its own attached to-dos:
- Next-due inline — a chip showing the soonest attached to-do, colored by derived state (upcoming / due-soon / overdue).
- “+N more” — opens a single, timeline-owned shared modal listing all of that host’s attached to-dos, populated from the page’s already-loaded bucket (no round-trip). Each is a shared display row with state dot, title, target, and one-tap done.
- Dashed “+ reminder” — a quick-add (title + a date preset) that creates a
customtask attached to that function or item. The empty state is this chip, so a host with nothing attached invites capture. This is the primary “add a task to the timeline from my screen” gesture. - Editing from the strip is the modal, never the accordion — a focused overlay leaves the host card’s layout untouched (an inline accordion would blow it out).
- The host’s own at-a-glance state derives from its attached to-dos — an overdue one tints it; all-done reads settled. This is implied status at the host level, replacing the deleted status flags.
Performance requirement: one batched query per page (by_attachments($project_id, $keys[]) → { key => items[] }) feeds every card its bucket — never a per-card lookup. [Built]
Related adjacent-module retirements (shipped together with the strip, hard-deleted on this fresh install): the Agenda status-flag array and config, and the Resume item’s separate due-date field and manual status enum — including the Resume print/export path, so all dated things route through one date store and host state derives from attached to-dos. [Built]
FR-7 · Reminders and delivery
- In-app (always on): the home page is the primary nudge layer — a read-only, cross-project list of upcoming and overdue dated items. Only active, unresolved, dated items in the window appear (archived, done/dismissed, and undated items are excluded at the query level). Each row deep-links to the item in its timeline (
/pim/{project_key}/timeline/?item={item_key}), which expands and scrolls to that row on load. [Built] - Email digest (opt-in, off by default): a per-planner preference; a daily WP-Cron scan finds items entering their reminder window or overdue on projects the planner owns and sends one deep-linked digest via
wp_mail. It may surface stakeholders (“loop in: Susan, hotel contact”) but does not auto-email them in v1. [Remaining]
FR-8 · Cross-project dashboard
A global /pim/timeline/ surface (can_write_global-style read) rolls up “what’s due” across all projects the user may see, visibility-filtered per the Access model. Shares the home/upcoming query. [Built]
FR-9 · Notes, stakeholders, and light evidence
- Notes use the shared
PIM.rterich-text editor (HTML viawp_kses_post), consistent with the rest of the plugin. [Built] - Stakeholders are a soft-linked contact list with an optional per-person notify-offset (“who needs to know what and when”), surfaced in v1 and auto-emailed later. [Built; auto-email remaining]
- Evidence hook (v1, light): when a deliverable document is attached and uploaded, surface a “looks done — confirm?” nudge rather than silently flipping the row. [Remaining]
FR-10 · Agenda-triggered auto-create
A catalog definition may carry an auto_create trigger keyed to a function type: adding a “Meal” function spawns “Final guarantee due” attached to it, anchored to that function’s day − 72h. Ship a small curated set, not a general rules engine. [Remaining]
FR-11 · Live embed block
A kind=milestones Resume/Agenda block (parallel to kind=attendee_view) that resolves “upcoming milestones” live, renders read-only and in print, and degrades gracefully. [Remaining]
FR-12 · Per-item history & authored-content panel
Each timeline item exposes a history panel — on the timeline-page accordion detail, and read-only inside the strip modal — that presents one chronological stream merging two sources:
- System events (display-only), read from the audit log. Because milestone audit records are event-keyed with correlation ids, the panel is a filtered read view over the existing log —
created,dated,re-anchored,done,dismissed,archived,restored,re-baselined, and seed/catalog changes that touched the item. The audit log remains the source of truth; the panel never becomes the record. - Authored content (editable), stored as its own records. Planner-added notes and attached documents, interleaved at the point they occurred. The existing single RTE
notesfield remains the current “working note”; the panel adds the surrounding history and the ability to attach files. Attachments ride the Documents module as softpub_keylinks (live-resolved, broken links flagged), consistent with the contract’s soft-link pattern.
Requirement: a GET /projects/{key}/timeline/{id}/history returning the merged, chronologically-sorted stream (system events + authored entries), and an authored-note/attachment write path. The audit portion is display-only; only authored entries are editable. [Remaining]
FR-13 · Project health snapshot
A derived, project-level health signal that aggregates the item states already computed (FR-2) — nothing new stored, no manual status column:
- In trouble (red): any active, dated, not-done item is
overdue. - At risk (amber): nothing overdue, but something is
due_soon(and not done) — or is flagged at-risk (see below). - On track (green): everything else (upcoming or done).
Undated items are excluded (no window to judge), but a project with undated items and no event date carries a neutral “N waiting on a date” caveat so the snapshot doesn’t read as complete when it isn’t. Computed live in PHP on the same scan that powers the home/dashboard query (FR-7, FR-8); surfaced as a badge on the project directory, dashboard, and project header.
⚙ Optional (to confirm): a one-tap at-risk / flag gesture — a second sticky marker alongside
done/dismissed— lets a planner force amber for trouble a date can’t see (a stalled approval, a silent vendor). It preserves the “no status enum” principle (one gesture, not a maintained column). Include it only if that human-known-trouble case is worth the added surface.
[Remaining]
FR-14 · Baseline & variance for reporting
Capture enough to report planned vs. actual, for on-time performance and schedule drift. This is the module’s one deliberate stored exception to “derive, never store” — a baseline is a frozen historical fact and cannot be resolved live.
- Completion variance (near-free):
completed_dateminus resolved target — derivable from existing fields; “did we hit it, and by how much.” - Schedule drift (needs a baseline): a stored
baseline_target, captured once, at the moment an anchored item first resolves to a real date (i.e. when the event date is confirmed); absolute-dated items baseline at creation. A later event reschedule then reports the gap as drift instead of hiding it. Abaseline_captured_attimestamp accompanies it. Re-baselining is a deliberate, audited action (for a large reschedule), never automatic — and, being correlation-keyed, a bulk re-baseline is one audit event across many items. - Scope-change handling falls out for free: an item added after the baseline was captured has a null
baseline_target, so it reports as scope added, not slippage — no separate flag required. - Variance stays derived: the three stored dates (
baseline_target, resolved target,completed_date) feed computed on-time %, average slippage, and late/early/added buckets. The cross-projectcross_project()query is the reporting surface.
⚙ Optional (to confirm): a second provisional baseline captured off a target/estimated date at project start (in addition to the confirmed baseline) would let reporting separate estimate volatility (target → confirmed) from execution performance (confirmed → actual). v1 recommendation: single confirmed baseline; add the provisional one only if that distinction earns its keep.
New/changed data: baseline_target (nullable date), baseline_captured_at (nullable), an item→document attachment link (FR-12), and — if adopted — an at-risk flag (FR-13). [Remaining]
7. Architecture and non-functional constraints
The module obeys the project contract’s spine: no foreign keys (referential integrity enforced in PHP); rows are addressable via a unique pub_key (what the strip, deep-links, and future embed reference); cross-area links are soft, nullable, and live-resolved (a link to an archived record degrades, never breaks); and archive, don’t delete (status = active | archived, distinct from the disposition completion axis).
Storage is a dedicated, project-scoped, promoted-columns-plus-JSON table (ws_pim_milestones, SCHEMA_VERSION 7) rather than generic nodes, because the two features that justify the module — the cross-project rollup and the reminder digest — are indexed-query shaped and want a single table to scan. Derived values (resolved target, live state, resolved reminder dates, stakeholder badges, and the project health snapshot of FR-13) are computed in PHP, never stored, and batch-resolved via shape_list(). Two deliberate exceptions: the baseline_target (FR-14) is a frozen historical fact and must be stored; and the item history panel (FR-12) is a read view over the existing audit log — the log stays the source of truth, and the panel merges it with authored notes/attachments rather than duplicating history onto the row.
REST surface (wspim/v1): per-project GET/POST /projects/{key}/timeline, PUT/DELETE /projects/{key}/timeline/{id}, .../{id}/restore, .../{id}/disposition (one-tap done), .../timeline/catalog (picker), .../timeline/seed; and the cross-project GET /timeline for the dashboard and digest. Inline/quick writes POST whitelisted bodies with key/id stripped so a JSON field can’t shadow the URL’s project key.
Ops note: WP-Cron fires on site traffic, so on a low-traffic internal site the digest can be late — point a real system cron at wp-cron.php.
8. Phasing and as-built status
| Stage | Scope | Status |
|---|---|---|
| 1a | Standalone module: table, three-layer catalog, auto-seeded timelines, per-project accordion view, implied status, quick-add, one-tap done, create modal, full REST (incl. cross-project read) | Shipped |
| 1b | In-context capture: the Agenda/Resume strip, shared display row, edit-via-modal, ?item= deep-link, and the status-flag / due-date / status-enum retirements (print path included) | Shipped |
| 2 | Habit layer: home-page conversion to the upcoming surface, global cross-project dashboard, opt-in email digest | Home + dashboard shipped; digest remaining |
| 3 | Auto-create + enrichment: Agenda-triggered auto-create, stakeholder surfacing in the digest, the doc-upload evidence nudge | Remaining |
| 4 | The live embed: kind=milestones block | Remaining |
| 5 | Reporting & context: item history panel (FR-12), project health snapshot (FR-13), baseline/variance (FR-14) | Planned (this revision) |
9. Risks and open questions
- Section-vocabulary convergence. Settings, Agenda, and Resume don’t share one area taxonomy today; the module aligns to the Settings labels and maps on auto-create. Unifying them is a separate cleanup that would make grouping and filters consistent across modules.
- Live-resolve vs. seeded values. Overlay definitions resolve live, but once a milestone is seeded onto a project its values live on the row. Confirm the intended behavior when a catalog tweak changes after seeding (does an existing project’s already-seeded item pick up the new lead time, or only future seeds?) — this is the practical edge of “the catalog got smarter mid-project.” Note the interaction with FR-14: if a re-tweak moves an already-baselined item’s target, that gap should read as drift, so baseline capture must precede (or be independent of) later tweaks.
- Baseline capture timing. FR-14 freezes
baseline_targetwhen an anchored item first resolves to a real date. Confirm the exact trigger (event-date confirmation vs. first-ever resolution) and how it behaves if the event date is set, cleared, then reset — the baseline should freeze once and survive a clear, not re-freeze on every date edit. - Health-snapshot completeness. The snapshot (FR-13) excludes undated items by design; on a project that’s mostly undated (early planning, no event date), green can misleadingly read as “fine.” The “N waiting on a date” caveat mitigates this but should be visually prominent, not a footnote.
- Digest latency. WP-Cron on a low-traffic site can delay the one reminder channel that reaches planners outside the app; the system-cron mitigation is a deploy step, not code, so it depends on ops discipline.
- Evidence inference scope. v1 is date-derivation plus one doc-upload nudge; richer inference (a filled Settings field, a synced Cvent flag flipping a task done) is deferred and should not be over-promised.
- Stakeholder auto-email. Surfaced but not sent in v1; actually sending needs an outbound-mail path with opt-out handling.
10. Out of scope (v1)
Smartsheet/record migration; task dependencies; a manual status enum; owner assignment; lifecycle “phase” as a stored column (temporal order comes from the target, functional grouping from section); stakeholder auto-email; and evidence-based auto-completion beyond the single doc-upload nudge.
This PRD describes the Timeline module as built in WS-PIM. The engineering source of truth is ws-pim-timeline-spec.md; this document is the product-level companion for scoping, communication, and future planning.