From 46dd0384f5f05a6d51e486930123603f8fc07386 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:53:07 +0500 Subject: [PATCH 001/107] docs: add project documentation map --- docs/README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/README.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..94b496b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,49 @@ +# PMO01 Documentation Map + +This directory is the source of truth for product, learning, content, architecture, and delivery decisions. + +## Current strategy + +PMO01 follows an **A → C** evolution path: + +1. Treat the current `main` implementation as a **reference prototype**. +2. Validate which learning and product mechanisms are worth keeping. +3. Design a separate scalable v1 architecture. +4. Migrate only validated content, interactions, and data contracts into v1. + +The reference prototype is not the long-term architecture. + +## Documents + +### Product +- `product/PRODUCT.md` — product purpose, users, value proposition, principles, non-goals. +- `product/LEARNING_MODEL.md` — how PMO01 expects learning to happen. +- `product/CURRICULUM.md` — competency map and curriculum contract. +- `product/PRODUCT_REQUIREMENTS.md` — product capabilities and staged requirements. +- `product/METRICS.md` — validation and learning-effectiveness metrics. + +### Content +- `content/CONTENT_MODEL.md` — canonical entities and rules for lessons, drills, cases, assessments, and artifacts. + +### Architecture +- `architecture/ARCHITECTURE.md` — current-state and target-state architecture. +- `architecture/adr/0001-reference-prototype-to-v1.md` — decision record for the A → C strategy. + +### Delivery +- `ROADMAP.md` — gates from prototype validation to scalable v1. + +### Design specs +- `superpowers/specs/2026-09-03-pmo01-v0-design.md` — historical V0 design. It remains useful context but is not the current architecture source of truth. +- `superpowers/specs/2026-09-06-pmo01-platform-foundation-design.md` — current platform foundation design. + +## Source-of-truth precedence + +When documents conflict, use this order: + +1. Accepted ADRs. +2. Current platform foundation design. +3. Product and architecture documents listed above. +4. Historical V0 specs. +5. Existing prototype implementation. + +The prototype describes what exists today. It does not override an accepted product or architecture decision for v1. From 3e44a2f5b7ddc8ef1f7f5406260ccdd027481b59 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:53:30 +0500 Subject: [PATCH 002/107] docs: define PMO01 product contract --- docs/product/PRODUCT.md | 78 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/product/PRODUCT.md diff --git a/docs/product/PRODUCT.md b/docs/product/PRODUCT.md new file mode 100644 index 0000000..6975520 --- /dev/null +++ b/docs/product/PRODUCT.md @@ -0,0 +1,78 @@ +# PMO01 Product Contract + +## Product purpose + +PMO01 is a learning platform for developing senior-level Project Management judgment through diagnosis, decisions, field application, and reflection. + +It is not primarily a reference library, certification-prep course, or tool tutorial. + +## Core promise + +Help a learner move from managing tasks and ceremonies to diagnosing projects as systems and making higher-quality management decisions under uncertainty. + +## Primary learner + +PMO01 initially targets working or recently working project/process/delivery managers who already understand basic PM vocabulary and want to improve decision quality, systems thinking, and practical execution. + +V1 must not require that the learner works in a specific framework such as Scrum, Kanban, SAFe, or PMBOK. + +## Job to be done + +When a project becomes delayed, uncertain, overloaded, politically difficult, or hard to diagnose, the learner should be able to: + +1. identify the system mechanism producing the visible symptom; +2. distinguish output from outcome; +3. surface assumptions and uncertainty; +4. reason about dependencies, queues, constraints, decision latency, and feedback; +5. choose an intervention with explicit trade-offs; +6. collect evidence from the real project; +7. update the diagnosis after observing the result. + +## Product principles + +1. **Decision quality over content consumption.** Reading does not equal mastery. +2. **Real-project transfer over trivia.** Exercises should connect to work the learner actually manages. +3. **Systems thinking over ceremony memorization.** Frameworks are tools, not the organizing model. +4. **Evidence over confidence.** Completion and mastery must be tied to observable evidence where possible. +5. **Reflection over correctness theater.** Plausible trade-offs are more useful than simplistic green/red answers. +6. **Content and learning engine are separate.** The platform must support future curricula without rewriting the application. +7. **Quiet interface.** Editorial readability and reasoning take precedence over decorative gamification. +8. **Progressive architecture.** Do not introduce backend, AI, accounts, or generalized engines before a validated need exists. + +## Product boundaries + +### In scope for the PM curriculum +- systems diagnosis; +- value and outcomes; +- dependencies and flow; +- uncertainty and risk; +- decision architecture; +- information and feedback; +- operating mechanisms; +- interventions and learning loops. + +### Not the primary focus +- Jira usage; +- Scrum role memorization; +- PMBOK terminology drills; +- generic productivity advice; +- motivational content; +- certification exam preparation. + +## Platform ambition + +PMO01 should eventually support multiple professional learning programs while keeping the first PM curriculum coherent and deep. + +Possible future curricula may include Product Management, Team Leadership, Process Management, and AI-enabled management, but none are v1 requirements. + +## Current product state + +The current `main` branch is a reference prototype containing a static browser application, 10 modules / 20 lessons, seven-flow diagnostics, local progress and notes, and a toolkit. + +This prototype is used to validate product and learning assumptions. It is not the target architecture. + +## Definition of product success + +PMO01 succeeds if learners can demonstrate better project diagnosis and intervention reasoning after using the platform, not merely finish lessons. + +The validation model is defined in `METRICS.md`. From e3fed941127377756fe18179f94a788b06436919 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:53:59 +0500 Subject: [PATCH 003/107] docs: define PMO01 learning model --- docs/product/LEARNING_MODEL.md | 143 +++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 docs/product/LEARNING_MODEL.md diff --git a/docs/product/LEARNING_MODEL.md b/docs/product/LEARNING_MODEL.md new file mode 100644 index 0000000..b756c74 --- /dev/null +++ b/docs/product/LEARNING_MODEL.md @@ -0,0 +1,143 @@ +# PMO01 Learning Model + +## Learning objective + +PMO01 is designed to change how a learner diagnoses and manages projects, not only what they can recall. + +The core instructional loop is: + +```text +Concept + ↓ +Case + ↓ +Decision + ↓ +Feedback + ↓ +Reflection + ↓ +Transfer to a real project + ↓ +Evidence + ↓ +Updated mental model +``` + +## What counts as learning + +A learner has not mastered a concept because they opened a page or selected the expected answer. + +Evidence of learning should progress through four levels: + +1. **Recognize** — identify the concept in a described situation. +2. **Reason** — explain mechanism, consequences, and trade-offs. +3. **Apply** — use the concept in an unfamiliar case. +4. **Transfer** — apply it to a real project and produce evidence from the intervention. + +The platform may track completion before it can reliably track mastery, but it must not label page visits as mastery. + +## Canonical lesson sequence + +A lesson should normally contain: + +1. **Problem frame** — why the concept matters. +2. **Principle** — the central claim. +3. **Mental model** — a reusable representation. +4. **Mechanism** — why the system behaves this way. +5. **Failure pattern** — a plausible but weak management response. +6. **Worked example** — the model applied to a concrete situation. +7. **Decision drill** — a realistic choice with consequences and trade-offs. +8. **Application task** — work on the learner's own project. +9. **Evidence criteria** — observable conditions for claiming the task was completed. +10. **Reflection prompt** — what changed in the learner's model or next decision. + +Not every lesson needs separate UI blocks for all ten stages. The sequence is an instructional contract, not a layout requirement. + +## Decision drills + +Decision drills are not trivia questions. + +A valid drill: + +- describes a realistic project situation; +- provides multiple plausible actions; +- forces prioritization or trade-offs; +- reveals analysis after the learner chooses; +- explains downstream consequences; +- can have a preferred action without pretending all ambiguity disappears. + +A weak drill asks for a definition or rewards superficial recall. + +## Boss cases / integrative cases + +Each module should end with an integrative case that combines multiple concepts from the module. + +A boss case should require the learner to: + +1. diagnose the system; +2. identify missing or misleading evidence; +3. choose an intervention; +4. explain why competing interventions are weaker or premature; +5. state what evidence would cause them to change course. + +## Field practice + +Every module should produce at least one reusable artifact or intervention on a real or recent project. + +Examples: + +- system map; +- assumption map; +- dependency graph; +- decision-latency map; +- experiment design; +- feedback-loop design; +- operating rule; +- intervention review. + +The artifact exists to improve a decision, not to satisfy a template requirement. + +## Feedback model + +Feedback should explain: + +- what mechanism the learner noticed or missed; +- what trade-off their action creates; +- what evidence matters next; +- what alternative action becomes appropriate under different conditions. + +Avoid celebratory correctness UI as the primary signal of learning. + +## Mastery model + +V1 should separate these states: + +- `unseen` — not engaged with; +- `studied` — lesson completed; +- `applied` — application task submitted or explicitly evidenced; +- `mastered` — demonstrated reasoning/application under a defined assessment rule. + +The exact mastery algorithm is intentionally deferred until PMO01 has enough evidence to define it without fake precision. + +## Spacing and retrieval + +For scalable v1, the content model must permit later addition of spaced review and retrieval practice. These are optional future capabilities, not requirements for the reference prototype. + +If added, review should target mental models and decisions, not rote terminology. + +## Personalization + +Personalization should eventually adapt sequence, examples, and review based on demonstrated gaps. It must not replace the curriculum's competency model with opaque AI recommendations. + +The diagnostic can recommend a starting point, but diagnostic scores are hypotheses about learning needs, not proof of competence. + +## Learning-quality gate + +Before a learning interaction is promoted into scalable v1, it should pass three questions: + +1. Does it expose a meaningful reasoning difference between stronger and weaker PM judgment? +2. Does the feedback explain mechanism and trade-offs rather than only the expected answer? +3. Can the learner transfer the idea to a different case or real project? + +If not, the interaction should be revised or removed. From 5fff23c08143c23944694076d316e8e7df14ab13 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:54:42 +0500 Subject: [PATCH 004/107] docs: define PMO01 curriculum contract --- docs/product/CURRICULUM.md | 123 +++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 docs/product/CURRICULUM.md diff --git a/docs/product/CURRICULUM.md b/docs/product/CURRICULUM.md new file mode 100644 index 0000000..2c0a1e7 --- /dev/null +++ b/docs/product/CURRICULUM.md @@ -0,0 +1,123 @@ +# PMO01 Curriculum Contract + +## Purpose + +The curriculum defines what a strong PM should be able to diagnose and do after completing PMO01. It is independent from the current number of modules or the UI used to deliver them. + +## Organizing model: seven project flows + +PMO01 uses seven flows as the primary diagnostic lens: + +1. **Value** — how work connects to an observable user or business outcome. +2. **Work** — how work moves through the delivery system. +3. **Information** — how quickly relevant facts reach the people who need them. +4. **Decisions** — how decisions are made, owned, and delayed. +5. **Dependencies** — what blocks downstream valuable action. +6. **Uncertainty** — what must be true but has not yet been demonstrated. +7. **Feedback** — how the system detects error and updates behavior. + +These flows are the stable conceptual backbone. Module boundaries may change during content validation. + +## Target competencies + +A learner completing the core PM curriculum should be able to: + +### C1. System diagnosis +- define the project as a system producing an outcome; +- distinguish symptom, mechanism, systemic condition, and intervention; +- identify the flow in which a failure originates rather than only where it appears. + +### C2. Outcome and value reasoning +- distinguish output from outcome; +- express causal assumptions between work and business/user effect; +- identify weak links in an outcome hypothesis. + +### C3. Dependency and criticality reasoning +- model technical, organizational, decision, and external dependencies; +- reason about downstream impact rather than list priority; +- identify constraints and high-leverage nodes. + +### C4. Flow management +- identify queues, work accumulation, batching, handoff loss, and bottlenecks; +- understand why local utilization can reduce system throughput; +- select interventions that improve flow rather than local productivity optics. + +### C5. Uncertainty and risk +- make assumptions explicit; +- prioritize uncertainty by confidence, impact, and reversibility; +- design evidence-producing experiments before irreversible commitments. + +### C6. Decision architecture +- identify decision rights and decision latency; +- design escalation and ownership mechanisms; +- separate reversible from difficult-to-reverse decisions. + +### C7. Information and feedback +- identify missing, delayed, or distorted information; +- design feedback loops with clear consumers and decisions; +- distinguish status reporting from information that changes action. + +### C8. Intervention design +- choose a management intervention based on mechanism rather than symptom; +- define expected effect, risk, early signal, and stop/change condition; +- review intervention results and update the mental model. + +## Curriculum progression + +The canonical progression is: + +```text +Observe the system +→ Model causes and flows +→ Expose uncertainty and constraints +→ Choose an intervention +→ Collect evidence +→ Update the system +``` + +This progression matters more than preserving any historical module numbering. + +## Module contract + +Every production module must define: + +- `id` and title; +- competency targets; +- prerequisite competencies, if truly required; +- 2–5 lessons with distinct learning outcomes; +- at least one decision drill; +- one integrative/boss case; +- one field application artifact or intervention; +- evidence criteria; +- expected learner effort; +- assessment rule for any claimed mastery. + +## Lesson outcome contract + +Each lesson outcome must use observable behavior. Prefer: + +> "Given X project evidence, the learner can diagnose Y and choose Z with an explicit rationale." + +Avoid outcomes such as: + +- understand queues; +- learn dependencies; +- know risk management. + +## Current prototype mapping + +The existing prototype contains 10 modules and 20 lessons organized around the seven flows and adjacent system-management concepts. That content is treated as **candidate curriculum**, not automatically canonical curriculum. + +Before migration to v1, each lesson must be audited against: + +1. a target competency; +2. a unique learning outcome; +3. a valid decision/application activity; +4. evidence criteria; +5. redundancy with neighboring lessons. + +Lessons that fail this audit should be merged, rewritten, or removed rather than migrated unchanged. + +## Future curricula + +The platform architecture should permit other professional curricula, but PMO01 core content remains a separately versioned curriculum package. Future programs must define their own competency maps rather than reuse the seven PM flows by default. From bbfdba660025fa87e522a3438ab6eeb5c332e278 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:55:14 +0500 Subject: [PATCH 005/107] docs: define PMO01 content model --- docs/content/CONTENT_MODEL.md | 188 ++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 docs/content/CONTENT_MODEL.md diff --git a/docs/content/CONTENT_MODEL.md b/docs/content/CONTENT_MODEL.md new file mode 100644 index 0000000..d13019a --- /dev/null +++ b/docs/content/CONTENT_MODEL.md @@ -0,0 +1,188 @@ +# PMO01 Content Model + +## Goal + +Separate learning content from presentation and runtime behavior so that curricula can evolve, be versioned, validated, and reused without editing application code. + +## Canonical entities + +### Program + +A complete curriculum package. + +Required fields: + +```yaml +id: pm-core +version: 1 +slug: project-management +locale: ru +status: draft | validated | published | retired +``` + +### Module + +A coherent competency unit inside a program. + +Required fields: + +```yaml +id: flow-management +program: pm-core +order: 4 +title: Flow management +competencies: + - C4 +estimated_minutes: 240 +``` + +A module also references: + +- lesson IDs; +- integrative case ID; +- field application ID; +- prerequisite competency IDs when necessary. + +### Lesson + +A focused learning unit with one primary outcome. + +Required metadata: + +```yaml +id: queues +module: flow-management +order: 2 +title: Queues +estimated_minutes: 40 +competencies: + - C4 +outcome: "Given delivery evidence, diagnose harmful queue formation and choose a first intervention." +status: draft | validated | published | retired +``` + +Canonical lesson content sections: + +1. problem frame; +2. principle; +3. mental model; +4. mechanism; +5. failure pattern; +6. worked example; +7. decision drill references; +8. application task reference; +9. evidence criteria; +10. reflection prompt. + +The storage format may use Markdown plus structured front matter. UI components must consume parsed content rather than import lesson text from application source files. + +### Decision Drill + +```yaml +id: queues-drill-01 +competencies: + - C4 +scenario: ... +choices: + - id: a + text: ... +analysis: + preferred_choice: b + mechanism: ... + tradeoffs: ... + change_conditions: ... +``` + +A drill may have a preferred action, but feedback must explain context and trade-offs. + +### Integrative Case + +A multi-concept scenario used near the end of a module. + +Required properties: + +- case evidence; +- learner diagnosis prompt; +- intervention decision; +- rationale prompt; +- evidence/change-condition prompt; +- assessment rubric. + +### Field Application + +A task performed on a real or recent project. + +Required properties: + +- context requirement; +- action steps; +- expected artifact; +- evidence criteria; +- reflection prompts; +- privacy guidance when real work data is involved. + +### Artifact Template + +A reusable working document such as an assumption map or dependency map. + +Templates are content assets, not hard-coded download strings in application code. + +### Assessment Rubric + +Rubrics define how reasoning or application is evaluated. + +A rubric must contain observable dimensions rather than generic labels such as "good answer". + +Example dimensions: + +- mechanism identified; +- relevant evidence used; +- trade-offs acknowledged; +- intervention matches diagnosis; +- change condition stated. + +## Learner-state entities + +Content definitions must not contain learner state. + +Learner state belongs to the learning runtime and references immutable content IDs. + +Minimum conceptual state: + +```ts +type LearningState = + | 'unseen' + | 'studied' + | 'applied' + | 'mastered'; +``` + +Runtime records should be keyed by program version + content ID so future content changes do not silently corrupt historical progress. + +## Versioning rules + +1. Editorial correction with unchanged learning outcome may keep the same content version. +2. A changed learning outcome, rubric, or assessment semantics requires a new version. +3. Published IDs are stable and must not be reused for different concepts. +4. Retired content remains resolvable for historical learner records. +5. Migration between program versions must be explicit when accounts/server persistence exist. + +## Validation rules + +Before content is published, automated or editorial validation should verify: + +- unique IDs; +- valid module/lesson references; +- valid competency references; +- deterministic ordering; +- required fields present; +- no broken drill/case/artifact references; +- no published lesson without a measurable outcome. + +## V1 storage recommendation + +Use structured Markdown/YAML or an equivalent repository-native content format first. + +Do not introduce a CMS until authoring friction, multi-author workflows, publishing permissions, or content volume demonstrate the need. + +The application must depend on a content interface, not on the repository storage format directly. This keeps a future CMS migration possible without rewriting learning UI. From cfa04658bb8dd327df309190ed4c42a75c1db10e Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:55:44 +0500 Subject: [PATCH 006/107] docs: define staged PMO01 product requirements --- docs/product/PRODUCT_REQUIREMENTS.md | 166 +++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 docs/product/PRODUCT_REQUIREMENTS.md diff --git a/docs/product/PRODUCT_REQUIREMENTS.md b/docs/product/PRODUCT_REQUIREMENTS.md new file mode 100644 index 0000000..9367d92 --- /dev/null +++ b/docs/product/PRODUCT_REQUIREMENTS.md @@ -0,0 +1,166 @@ +# PMO01 Product Requirements + +## Scope model + +PMO01 evolves through explicit validation gates. Requirements are separated into: + +- **Reference prototype requirements** — what the current implementation is allowed to prove. +- **V1 core requirements** — what the first scalable architecture must support. +- **Deferred capabilities** — features that require evidence before implementation. + +## Reference prototype + +The prototype exists to validate learning format and curriculum assumptions. + +Required behavior: + +- present PM learning content in a readable editorial interface; +- support navigation across the current curriculum; +- store lightweight progress locally; +- store learner notes locally; +- provide a diagnostic starting point; +- expose reusable field-work templates; +- support real-project application tasks; +- remain deployable as a static site. + +The current implementation already demonstrates most of this behavior. + +## V1 core capabilities + +### P1. Structured content ingestion + +The application must load program/module/lesson/case/drill/artifact definitions through a validated content interface. + +Acceptance conditions: + +- UI code contains no lesson body text; +- invalid content references fail validation before publication; +- one content package can be replaced by another without changing core learning UI. + +### P2. Curriculum navigation + +The learner can: + +- browse programs, modules, and lessons; +- continue from recent work; +- understand what a module develops; +- see prerequisite guidance when it materially affects learning. + +### P3. Learning interactions + +The runtime supports at minimum: + +- decision drills; +- integrative cases; +- field application prompts; +- reflection prompts; +- rubric-based assessment data structures. + +Not every assessment must be automatically scored. + +### P4. Progress model + +The product must distinguish engagement from learning evidence. + +Minimum states: + +- unseen; +- studied; +- applied; +- mastered. + +V1 may initially support only part of the transition logic, but the data model must not collapse these states into a single completion boolean. + +### P5. Learner work + +The learner can persist: + +- notes; +- drill/case decisions where useful; +- field application artifacts or structured responses; +- progress state. + +The first scalable release may still store data locally if validation does not yet require accounts. Storage implementation must be isolated behind an interface. + +### P6. Diagnostics + +Diagnostics may recommend where to start or what to revisit. + +Requirements: + +- recommendation logic is inspectable and deterministic unless explicitly redesigned; +- diagnostic result is described as guidance, not proof of mastery; +- diagnostic questions map to competencies or flows. + +### P7. Accessibility and readability + +The learning experience must support: + +- keyboard navigation for interactive controls; +- readable narrow and wide layouts; +- semantic document structure; +- visible focus states; +- sufficient text contrast; +- no essential information conveyed by color alone. + +### P8. Content version awareness + +Learner state must reference stable content IDs and program/content versions so future curriculum changes do not silently rewrite historical meaning. + +### P9. Observability + +Before large-scale growth work, the product must be able to observe the minimum learning funnel defined in `METRICS.md`. + +Instrumentation can be added only when there is a deployment model that can collect data ethically and legally. + +## Deferred capabilities + +Do not implement these without a separate decision/spec: + +- accounts and authentication; +- cloud sync; +- team/organization dashboards; +- payments; +- CMS; +- AI tutor; +- AI assessment; +- social features; +- leaderboards; +- XP/currency systems; +- generalized knowledge graph; +- adaptive sequencing engine; +- native mobile applications; +- certificates; +- enterprise reporting. + +## Quality attributes + +### Maintainability +- content is separated from runtime; +- domain modules have explicit interfaces; +- no single application file owns routing, rendering, learner state, and learning logic at scale. + +### Portability +- core content and learning model should not depend on GitHub Pages; +- hosting can change without rewriting curricula. + +### Testability +- content validation is automated; +- learning-state transitions are unit-testable; +- drill/case behavior is testable independently from full-page rendering; +- critical navigation paths have integration coverage. + +### Performance +- static content should render with minimal client JavaScript; +- interactive code should load only where required where practical; +- performance budgets should be defined when the v1 framework is selected. + +## Release gate for scalable v1 + +Do not start broad feature expansion until: + +1. the prototype learning format has been tested with real learners; +2. at least one module has validated drills, integrative assessment, and field transfer; +3. the canonical content model is stable enough to represent that module without special cases; +4. migration requirements are known; +5. the target architecture has an accepted ADR/spec. From c2c399523bed801e6d2377a8c09189d2425f7a0b Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:56:27 +0500 Subject: [PATCH 007/107] docs: define current and target PMO01 architecture --- docs/architecture/ARCHITECTURE.md | 259 ++++++++++++++++++++++++++++++ 1 file changed, 259 insertions(+) create mode 100644 docs/architecture/ARCHITECTURE.md diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md new file mode 100644 index 0000000..e94a1af --- /dev/null +++ b/docs/architecture/ARCHITECTURE.md @@ -0,0 +1,259 @@ +# PMO01 Architecture + +## Architecture status + +PMO01 currently has two architectural descriptions: + +1. the working static prototype on `main`; +2. the historical V0 Astro design spec. + +Neither is automatically the target v1 architecture. + +The accepted strategy is **A → C**: + +- preserve the current prototype as a product/learning reference; +- validate useful mechanisms; +- design a scalable v1 architecture from validated requirements; +- migrate selectively. + +## Current reference prototype + +Current implementation characteristics: + +```text +index.html + ↓ +app.js + ├── routing + ├── rendering + ├── learner state + ├── diagnostics + ├── interaction binding + └── toolkit behavior + +course-data.js + ├── flows + ├── modules + ├── lessons + ├── diagnostics + └── tools + +localStorage + └── learner progress / notes / diagnostic answers +``` + +Strengths: + +- extremely low operational complexity; +- deployable as static files; +- useful as a learning-format prototype; +- no backend dependency; +- fast to change during discovery. + +Scaling constraints: + +- content is embedded in JavaScript application data; +- routing, rendering, interaction logic, and learner state are concentrated in one application file; +- binary completion cannot represent stronger learning evidence; +- no explicit content version model; +- diagnostics are coupled directly to prototype data structures; +- adding many programs or authors would increase coupling and review risk; +- server-backed identity, sync, analytics, or AI would require new boundaries rather than incremental additions inside `app.js`. + +## Architectural principles for v1 + +1. **Content is data, not application code.** +2. **Learning domain logic is independent from page rendering.** +3. **Learner state is behind an interface.** Local and remote persistence must be replaceable. +4. **Content IDs and versions are stable.** +5. **Static-first delivery remains preferred while requirements permit it.** +6. **Interactive behavior is introduced only where learning needs it.** +7. **AI is an optional adapter, not the source of curriculum truth.** +8. **Framework choice follows validated product requirements.** Do not select v1 technology solely because the old V0 spec named Astro. + +## Target logical architecture + +```text + ┌─────────────────────┐ + │ Content packages │ + │ programs/modules/...│ + └──────────┬──────────┘ + │ validate/parse + ┌──────────▼──────────┐ + │ Content service │ + │ stable read model │ + └──────────┬──────────┘ + │ + ┌────────────────────┼────────────────────┐ + │ │ │ +┌─────────▼────────┐ ┌─────────▼────────┐ ┌────────▼─────────┐ +│ Learning runtime│ │ Curriculum nav │ │ Assessment logic │ +│ state transitions│ │ sequence/context │ │ rubrics/evidence │ +└─────────┬────────┘ └─────────┬────────┘ └────────┬─────────┘ + └────────────────────┼────────────────────┘ + │ + ┌──────────▼──────────┐ + │ Presentation layer │ + │ pages/components/UI │ + └──────────┬──────────┘ + │ + ┌──────────▼──────────┐ + │ Learner repository │ + │ local or remote │ + └─────────────────────┘ +``` + +## Core boundaries + +### 1. Content package + +Owns curriculum definitions and editorial material. + +Does not own: + +- learner progress; +- UI components; +- authentication; +- analytics transport. + +### 2. Content service + +Consumes validated content definitions and exposes a stable application-facing interface. + +Examples of conceptual reads: + +```ts +getProgram(programId) +getModule(moduleId) +getLesson(lessonId) +getNextLearningUnit(currentId) +getAssessment(assessmentId) +``` + +Storage format is hidden behind this boundary. + +### 3. Learning runtime + +Owns state transitions and rules such as: + +- studied; +- applied; +- mastered; +- recent activity; +- evidence references. + +It must not render HTML or know where content files live. + +### 4. Assessment domain + +Owns decision drills, case responses, rubrics, and evidence semantics. + +Automated scoring is optional. The model must allow human/self/AI-assisted evaluation later without redefining content IDs. + +### 5. Learner repository + +Persistence interface for progress, responses, and notes. + +Initial implementations may use browser storage. Future implementations may use a backend. + +The UI must not call `localStorage` directly. + +### 6. Presentation layer + +Owns navigation, reading experience, controls, feedback presentation, and accessibility. + +It consumes domain interfaces and does not define curriculum semantics. + +## Data flow + +### Lesson load + +```text +route +→ content service resolves lesson +→ runtime loads learner state +→ page renders lesson + state +``` + +### Decision drill + +```text +learner selects action +→ assessment domain records response +→ feedback model resolves analysis +→ learner repository persists response +→ UI displays mechanism/trade-offs +``` + +### Field application + +```text +application task +→ learner creates evidence / response +→ repository stores reference or content +→ assessment/runtime updates applied state when criteria are met +``` + +## Error handling principles + +V1 should fail explicitly for content-integrity errors. + +Examples: + +- duplicate IDs; +- missing referenced drill; +- invalid module order; +- unknown competency reference; +- incompatible content version. + +These should fail build/publish validation rather than produce broken learner pages. + +Runtime persistence failures should preserve the learning page and surface a clear state-saving error rather than discard learner input silently. + +## Technology decision + +No final v1 framework is accepted yet. + +The historical Astro recommendation remains a candidate because static-first content and interactive islands fit the product well, but framework selection should be made in a dedicated architecture decision after the validation gate. + +Candidate evaluation criteria: + +- content pipeline quality; +- static generation; +- TypeScript support; +- accessibility ergonomics; +- testability; +- deployment portability; +- incremental interactivity; +- migration cost from the prototype; +- future support for authenticated/server-backed features without corrupting domain boundaries. + +## Migration strategy + +Do not rewrite the entire prototype before validation. + +Migration should happen vertically: + +1. select one validated module; +2. express it in the canonical content model; +3. implement content validation; +4. implement the minimum learning runtime; +5. reproduce its learning experience in v1; +6. compare behavior and usability; +7. only then migrate remaining validated modules. + +The reference prototype remains available for comparison until v1 reaches functional parity for validated learning paths. + +## Testing architecture + +Required test layers for v1: + +- content schema/relationship validation; +- unit tests for learning-state transitions; +- unit tests for diagnostic recommendation rules; +- interaction tests for drills/cases; +- navigation integration tests; +- a small number of end-to-end learner paths; +- accessibility checks for interactive controls. + +Do not build a large E2E suite before domain contracts stabilize. From 9aa2add1073a0817524f7657969f98522ff455ab Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:56:59 +0500 Subject: [PATCH 008/107] docs: define PMO01 validation metrics --- docs/product/METRICS.md | 168 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 docs/product/METRICS.md diff --git a/docs/product/METRICS.md b/docs/product/METRICS.md new file mode 100644 index 0000000..2271d1b --- /dev/null +++ b/docs/product/METRICS.md @@ -0,0 +1,168 @@ +# PMO01 Metrics and Validation + +## Principle + +PMO01 should optimize for improvement in project-management judgment, not lesson completion alone. + +Metrics are split into: + +1. learning-quality metrics; +2. behavior/engagement metrics; +3. product-quality metrics; +4. migration gates. + +## North-star learning question + +> After using PMO01, can a learner diagnose a project situation and choose a stronger intervention with better reasoning than before? + +No single percentage can prove this. Validation should combine structured assessment and qualitative evidence. + +## Learning-quality metrics + +### L1. Diagnostic reasoning delta + +Measure performance on structurally similar but non-identical cases before and after a module. + +Score dimensions: + +- mechanism identified; +- relevant evidence selected; +- trade-offs acknowledged; +- intervention matches diagnosis; +- change condition stated. + +Use a stable rubric. Do not reuse identical questions for pre/post measurement. + +### L2. Transfer rate + +Definition: + +```text +learners who complete a real-project application with evidence +-------------------------------------------------------------- +learners who study the corresponding module +``` + +This is more important than page completion. + +### L3. Reflection quality + +Sample learner reflections and classify whether they show: + +- changed diagnosis; +- changed planned action; +- new evidence requirement; +- unchanged/restated lesson content only. + +This can be reviewed manually during prototype validation. + +### L4. Delayed retrieval / application + +Where feasible, check whether the learner can apply the concept to a new case after a delay rather than immediately after reading. + +This becomes more important before any mastery label is introduced. + +## Engagement metrics + +These diagnose friction; they are not proof of learning. + +Track when infrastructure exists: + +- module start rate; +- lesson completion rate; +- decision-drill participation; +- field-application start/completion; +- return rate after first session; +- time to first meaningful application; +- abandonment point by learning unit. + +Avoid optimizing for total minutes spent. + +## Product-quality metrics + +### P1. Content integrity + +- zero broken references in published content; +- zero duplicate published IDs; +- 100% of published lessons mapped to measurable outcomes and competencies. + +### P2. Reliability + +For persisted learner work: + +- no silent loss of notes/responses; +- recoverable state after ordinary page reload/navigation; +- explicit error when persistence fails. + +### P3. Accessibility + +Critical learner paths must be keyboard operable and semantically readable. + +### P4. Performance + +Define numerical budgets when the v1 framework/deployment model is selected. Until then, the constraint is static-first rendering with minimal client JavaScript. + +## Prototype validation protocol + +Before broad v1 implementation, validate at least one complete module with real learners. + +Recommended minimum study: + +1. baseline case; +2. module learning experience; +3. boss/integrative case; +4. real-project application; +5. short interview about reasoning and friction; +6. delayed follow-up case when practical. + +The goal is to discover learning and product failure modes, not to establish statistically generalizable effect size at prototype scale. + +## Promotion gate: interaction + +A learning interaction can be promoted into v1 when it demonstrates at least one of: + +- reveals a meaningful reasoning difference; +- changes the learner's diagnosis; +- improves transfer to a real project; +- exposes a misconception worth addressing. + +Interactions that only create engagement or cosmetic progress should not become architectural requirements. + +## Promotion gate: module + +A prototype module is ready for migration when: + +- learning outcomes are explicit; +- every lesson maps to competencies; +- at least one decision drill has useful feedback; +- the integrative case exercises multiple concepts; +- field transfer is possible; +- rubric/evidence criteria are usable; +- major learner friction is known; +- no unresolved content-model special case is required to represent it. + +## V1 success gate + +Do not claim scalable v1 is successful because migration is technically complete. + +V1 must preserve or improve: + +- reading usability; +- reasoning quality; +- transfer completion; +- learner-state reliability; + +while making content maintenance and future program growth materially easier. + +## Metrics anti-patterns + +Do not use these as primary success measures: + +- number of pages; +- number of lessons; +- raw time on site; +- XP earned; +- streak length; +- percentage of content opened. + +They may describe use but not the product's learning promise. From 075603e070c9943cfbc29f0c414915c40150e8f3 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:57:25 +0500 Subject: [PATCH 009/107] docs: record A to C evolution strategy --- .../adr/0001-reference-prototype-to-v1.md | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/architecture/adr/0001-reference-prototype-to-v1.md diff --git a/docs/architecture/adr/0001-reference-prototype-to-v1.md b/docs/architecture/adr/0001-reference-prototype-to-v1.md new file mode 100644 index 0000000..c470d51 --- /dev/null +++ b/docs/architecture/adr/0001-reference-prototype-to-v1.md @@ -0,0 +1,101 @@ +# ADR 0001 — Evolve from Reference Prototype to Scalable V1 + +- Status: Accepted +- Date: 2026-09-06 + +## Context + +PMO01 has a working static prototype on `main` and a historical V0 design that proposed Astro, Markdown-first content, and a single FLOW vertical slice. + +The working prototype moved beyond that V0 scope: it contains a broader curriculum, diagnostics, local learner state, notes, and tools. At the same time, its implementation couples content, routing, rendering, learner state, and interactions too tightly for long-term multi-program growth. + +Three options were considered: + +### Option A — Keep evolving the current prototype + +Advantages: + +- low immediate cost; +- preserves working behavior; +- fastest path for experiments. + +Disadvantages: + +- increasing coupling; +- difficult content/version management; +- poor foundation for accounts, richer assessment, multiple curricula, or teams; +- high risk of turning prototype structure into permanent architecture. + +### Option B — Return to the historical V0 Astro design + +Advantages: + +- simpler vertical slice; +- clean static/content-first direction. + +Disadvantages: + +- discards useful work and product discoveries; +- treats an older design document as more authoritative than observed prototype requirements; +- framework choice precedes renewed validation. + +### Option C — Immediately rewrite as a scalable v1 + +Advantages: + +- strongest theoretical separation of concerns; +- removes prototype constraints early. + +Disadvantages: + +- high overengineering risk; +- architecture could encode unvalidated learning interactions; +- migration scope is large before the product has proven what must be retained. + +## Decision + +Use **A → C**. + +1. Preserve the current `main` implementation as a **reference prototype**. +2. Stop treating prototype architecture as the default place for long-term expansion. +3. Validate learning format, curriculum elements, diagnostics, and interactions with real users. +4. Define stable product, learning, content, and data contracts. +5. Design scalable v1 after those contracts are sufficiently validated. +6. Migrate vertically, starting with one validated module. +7. Retire prototype behavior only after v1 reproduces or improves the validated learning experience. + +## Consequences + +### Positive + +- avoids throwing away working product evidence; +- delays irreversible architecture decisions; +- provides a controlled migration path; +- makes content and learning logic first-class domains; +- enables future programs without making them current requirements. + +### Negative + +- two implementations may coexist temporarily; +- some prototype work will not be migrated; +- short-term feature requests may need to be rejected if they deepen prototype coupling; +- migration requires explicit comparison and content audit. + +## Guardrails + +Until a v1 architecture spec is accepted: + +- do not add backend/auth/CMS/AI architecture to the prototype; +- do not expand `course-data.js` as if it were the permanent content system; +- do not turn `app.js` into a generalized platform runtime; +- prototype-only experiments are allowed when cheap and reversible; +- new prototype work should be justified by a validation question. + +## Revisit conditions + +Revisit this ADR only if: + +- the reference prototype proves inadequate even for validation; +- a hard external requirement forces server-backed functionality before the validation gate; +- the content model cannot represent a validated learning module without major special cases; +- evidence shows a different product direction. From 5c8024b70f5161ac20b2122291e080ab6b66c91e Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:57:55 +0500 Subject: [PATCH 010/107] docs: add PMO01 staged roadmap --- docs/ROADMAP.md | 169 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 docs/ROADMAP.md diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..04b1614 --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,169 @@ +# PMO01 Roadmap + +## Strategy + +PMO01 evolves through validation gates, not feature accumulation. + +```text +Reference prototype +→ learning validation +→ content contract validation +→ v1 architecture decision +→ one-module vertical migration +→ measured parity/improvement +→ broader migration +→ optional platform expansion +``` + +## Phase 0 — Stabilize the reference prototype + +Goal: make the existing product reliable enough to use as a learning experiment without turning it into the permanent architecture. + +Deliverables: + +- current product behavior documented; +- current curriculum treated as candidate content; +- no major new platform subsystems added; +- obvious prototype-breaking defects fixed when they block validation; +- a small set of representative learning paths selected for testing. + +Exit gate: + +- the prototype can support a learner through at least one complete module without critical usability/data-loss issues. + +## Phase 1 — Validate the learning model + +Goal: determine which learning mechanisms actually improve PM reasoning and transfer. + +Deliverables: + +- select one representative module; +- audit lesson outcomes against `CURRICULUM.md`; +- improve at least one decision drill; +- create or validate one integrative case; +- create one real-project field application; +- define a rubric; +- run learner tests using the protocol in `METRICS.md`. + +Exit gate: + +- evidence indicates the module format can reveal and improve reasoning, not merely deliver content; +- major interaction/content failures are known; +- the validated module can be represented by `CONTENT_MODEL.md` without ad hoc exceptions. + +## Phase 2 — Freeze v1 domain contracts + +Goal: define the minimum stable contracts that implementation may depend on. + +Deliverables: + +- final v1 content schema; +- stable content IDs/version rules; +- learning-state transition model; +- assessment/rubric model; +- learner repository interface; +- diagnostic mapping contract; +- migration rules from prototype content/state where relevant. + +Exit gate: + +- contracts can represent the validated module end-to-end; +- unresolved questions are implementation details, not domain ambiguity. + +## Phase 3 — Select v1 technical architecture + +Goal: choose framework and deployment architecture based on validated requirements. + +Deliverables: + +- compare candidate approaches; +- architecture ADR; +- repository/file structure; +- build/test/deploy strategy; +- performance/accessibility constraints; +- plan for local persistence and future remote persistence boundary. + +Likely candidates may include Astro or another TypeScript static-first framework, but no framework is selected by this roadmap. + +Exit gate: + +- selected architecture implements the domain contracts without coupling content to presentation; +- migration cost is understood; +- no deferred subsystem has been smuggled into v1 requirements. + +## Phase 4 — Build one-module v1 vertical slice + +Goal: prove the target architecture with one validated module. + +Deliverables: + +- content validation pipeline; +- content service; +- curriculum navigation; +- learning runtime; +- learner repository implementation; +- decision drill interaction; +- integrative case interaction; +- field application workflow; +- accessibility and core tests; +- deployment. + +Exit gate: + +- the validated module works end-to-end in v1; +- learner experience is at least as good as the prototype; +- content can be edited without touching UI runtime code; +- state transitions and content relationships have automated tests. + +## Phase 5 — Migrate validated PM curriculum + +Goal: move only content that passes curriculum and learning-quality audits. + +Process per module: + +1. audit outcomes and competency mapping; +2. remove duplication; +3. validate drills/cases/application; +4. convert to canonical content model; +5. migrate assets/templates; +6. test content integrity; +7. publish; +8. compare learner behavior/feedback with prototype where possible. + +Exit gate: + +- all production modules satisfy curriculum/content contracts; +- prototype is no longer required for validated PM paths. + +## Phase 6 — Introduce server-backed learner state only if needed + +Possible triggers: + +- cross-device progress is a validated retention need; +- learner work must persist beyond one browser; +- authenticated cohorts/organizations are required; +- analytics require durable learner/event identity; +- paid product requires account entitlement. + +Deliverables require a separate spec/ADR. + +## Phase 7 — Optional intelligence and scale features + +Only after core learning quality and state model are stable, evaluate separately: + +- AI tutor; +- AI-assisted rubric feedback; +- adaptive review; +- personalized sequencing; +- multi-program catalog; +- team/enterprise learning; +- authoring/CMS workflows; +- payments/certificates. + +Each is an independent product decision, not a default consequence of “scaling”. + +## Immediate next decision + +Before code migration begins, review and accept the platform foundation documents in `docs/`. + +After acceptance, write an implementation plan for **Phase 1 only** rather than a single plan spanning the entire roadmap. From 840a06056052811fb7e2b18f102368bc468a0322 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:58:44 +0500 Subject: [PATCH 011/107] docs: define PMO01 platform foundation design --- ...-09-06-pmo01-platform-foundation-design.md | 332 ++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-06-pmo01-platform-foundation-design.md diff --git a/docs/superpowers/specs/2026-09-06-pmo01-platform-foundation-design.md b/docs/superpowers/specs/2026-09-06-pmo01-platform-foundation-design.md new file mode 100644 index 0000000..3364abc --- /dev/null +++ b/docs/superpowers/specs/2026-09-06-pmo01-platform-foundation-design.md @@ -0,0 +1,332 @@ +# PMO01 Platform Foundation Design + +## Status + +Proposed design approved in principle on 2026-09-06: evolve via **A → C** — preserve the current implementation as a reference prototype, validate learning mechanisms, then build a separate scalable v1 architecture from validated contracts. + +This document is the current platform-foundation design. The earlier `2026-09-03-pmo01-v0-design.md` remains historical context and does not define the target architecture. + +## Problem + +PMO01 currently has useful product behavior but conflicting architectural direction: + +- the working `main` branch is a static JavaScript application containing a broad PM curriculum, diagnostics, learner progress, notes, and tools; +- the historical V0 design proposed Astro, Markdown-first content, one FLOW module, and a smaller vertical slice. + +Continuing the current implementation indefinitely would make content, learning logic, state, rendering, and future platform capabilities increasingly coupled. Rewriting immediately would risk designing around unvalidated assumptions. + +## Decision + +Adopt a staged architecture transition. + +### A — Reference prototype + +The current `main` implementation is retained as the reference implementation for product and learning validation. + +It answers questions such as: + +- Is the curriculum useful? +- Do learners understand the seven-flow model? +- Do application tasks transfer to real projects? +- Do diagnostics help choose a learning path? +- Which interactions change reasoning? + +It is not the long-term platform architecture. + +### C — Scalable v1 + +A separate v1 architecture will be implemented only after one representative module has validated learning outcomes, interactions, assessment, and field transfer. + +V1 will separate: + +1. content packages; +2. content validation/read model; +3. curriculum navigation; +4. learning-state runtime; +5. assessment/evidence logic; +6. learner persistence; +7. presentation. + +Framework selection is deferred until the domain contracts are validated. Astro remains a candidate, not a decision. + +## Product contract + +The product exists to improve senior-level PM diagnosis and intervention reasoning under uncertainty. + +The learner should progress from: + +```text +task/status management +→ system diagnosis +→ mechanism reasoning +→ explicit trade-offs +→ evidence-producing intervention +→ reflection and model update +``` + +The platform is not primarily a Scrum/Jira/PMBOK tutorial or certification course. + +Canonical product rules are in `docs/product/PRODUCT.md`. + +## Learning architecture + +The core learning loop is: + +```text +Concept +→ Case +→ Decision +→ Feedback +→ Reflection +→ Transfer +→ Evidence +→ Updated mental model +``` + +Learning evidence progresses through: + +- recognize; +- reason; +- apply; +- transfer. + +Runtime state must not treat page visits as mastery. + +Canonical runtime learning states are: + +- `unseen`; +- `studied`; +- `applied`; +- `mastered`. + +The exact mastery algorithm is deferred until evidence supports one. + +See `docs/product/LEARNING_MODEL.md`. + +## Curriculum architecture + +The seven project flows remain the core PM diagnostic model: + +- value; +- work; +- information; +- decisions; +- dependencies; +- uncertainty; +- feedback. + +The stable contract is competency-based, not tied to the current 10-module numbering. + +The existing 10 modules / 20 lessons are candidate curriculum. Before migration, each lesson must demonstrate: + +- competency mapping; +- measurable outcome; +- non-redundant purpose; +- decision/application activity; +- evidence criteria. + +See `docs/product/CURRICULUM.md`. + +## Content architecture + +Content becomes repository-native structured data rather than JavaScript runtime data. + +Canonical entities: + +- Program; +- Module; +- Lesson; +- Decision Drill; +- Integrative Case; +- Field Application; +- Artifact Template; +- Assessment Rubric. + +Content must use stable IDs and version-aware references. Published IDs are never reused for a different concept. + +V1 UI must consume a content interface rather than import lesson bodies from application source files. + +See `docs/content/CONTENT_MODEL.md`. + +## Target logical architecture + +```text +Content package + │ + ▼ +validation/parser + │ + ▼ +Content service + │ + ┌────┼─────────────┐ + ▼ ▼ ▼ +Nav Learning Assessment + runtime domain + └────┼─────────────┘ + ▼ +Presentation + │ + ▼ +Learner repository +(local first; remote later if validated) +``` + +Key constraints: + +- presentation does not own learning semantics; +- learner state is behind an interface; +- content storage format is behind an interface; +- diagnostics map to explicit competencies/flows; +- assessment feedback is deterministic/inspectable unless separately redesigned; +- AI cannot become the source of curriculum truth. + +See `docs/architecture/ARCHITECTURE.md`. + +## Product requirements + +The scalable v1 must support: + +1. structured content ingestion and validation; +2. curriculum navigation; +3. decision drills, integrative cases, field applications, and reflection; +4. multi-state learning progress; +5. persisted learner work through an isolated repository interface; +6. inspectable diagnostics; +7. accessibility/readability; +8. content version awareness; +9. minimum observability when an ethical/legal collection mechanism exists. + +Explicitly deferred: + +- authentication; +- cloud sync; +- CMS; +- payments; +- AI tutor; +- AI assessment; +- social/gamification engines; +- knowledge graphs; +- enterprise dashboards; +- native mobile applications. + +See `docs/product/PRODUCT_REQUIREMENTS.md`. + +## Validation design + +The next implementation phase is not “build v1”. It is **validate one representative module end-to-end**. + +Validation requires: + +1. baseline case; +2. module learning experience; +3. integrative case; +4. real-project application; +5. rubric-based review; +6. qualitative friction/reasoning review; +7. delayed follow-up case when practical. + +The goal is to identify which mechanisms deserve promotion into v1. + +Primary success question: + +> Can the learner make a better diagnosis and intervention decision after using the module? + +See `docs/product/METRICS.md`. + +## Migration design + +Migration is vertical, not wholesale. + +For the first migrated module: + +1. validate learning outcomes and interactions in the prototype; +2. express the module in the canonical content model; +3. implement content validation; +4. implement minimum content service; +5. implement minimum learning runtime and learner repository; +6. reproduce drills/case/application in v1; +7. compare usability and learning evidence with the prototype; +8. fix architecture/content-contract failures; +9. only then migrate additional modules. + +Prototype and v1 may coexist temporarily. + +## Error handling + +### Content integrity + +Broken references, duplicate IDs, unknown competency mappings, and incompatible versions must fail validation before publication. + +### Learner persistence + +Persistence failures must not silently discard learner work. UI must preserve unsaved input where possible and surface a clear error. + +### Unsupported content + +If the v1 content runtime encounters an unsupported entity/version, it should fail explicitly during validation or render a controlled unavailable state rather than partially interpreting content. + +## Testing design + +V1 testing should prioritize domain contracts: + +- schema/relationship validation; +- learning-state transition unit tests; +- diagnostic-rule unit tests; +- drill/case interaction tests; +- navigation integration tests; +- accessibility checks; +- a small number of end-to-end learning paths. + +Large E2E coverage is deferred until contracts stabilize. + +Prototype testing remains limited to defects that block learning validation. + +## Documentation architecture + +The following documents are required sources of truth: + +- `docs/README.md` — documentation precedence and map; +- `docs/product/PRODUCT.md` — product contract; +- `docs/product/LEARNING_MODEL.md` — instructional model; +- `docs/product/CURRICULUM.md` — competencies and curriculum rules; +- `docs/product/PRODUCT_REQUIREMENTS.md` — staged product capabilities; +- `docs/product/METRICS.md` — learning/product validation; +- `docs/content/CONTENT_MODEL.md` — content entities and versioning; +- `docs/architecture/ARCHITECTURE.md` — architecture boundaries and migration; +- `docs/architecture/adr/0001-reference-prototype-to-v1.md` — accepted A → C decision; +- `docs/ROADMAP.md` — gated delivery sequence. + +Documents that are intentionally deferred until their triggering phase: + +- detailed v1 framework ADR; +- backend/auth architecture; +- analytics/event taxonomy; +- privacy/data-retention policy for server-side learner data; +- AI tutor/assessment policy; +- billing/entitlement model; +- CMS/editorial workflow; +- multi-tenant/enterprise model. + +Creating these now would imply requirements that have not been validated. + +## Acceptance criteria for platform foundation + +The foundation design is complete when: + +1. A → C is recorded as an accepted architecture strategy. +2. The reference prototype and target v1 are clearly distinguished. +3. Product purpose and non-goals are explicit. +4. The learning model separates study, application, and mastery. +5. Curriculum is competency-based and not coupled to historical module numbering. +6. Content has stable entities, IDs, and version rules. +7. Target architecture separates content, learning logic, persistence, and presentation. +8. Deferred capabilities are explicit. +9. Validation metrics and promotion gates are defined. +10. Roadmap starts with learning validation, not a framework rewrite. + +## Next step after approval + +After this foundation is reviewed and approved, create a detailed implementation plan for **Phase 1 — validate one representative module**. + +Do not create a full v1 implementation plan yet. The v1 plan depends on evidence and domain-contract changes discovered during Phase 1. From 8a688037cfe1790317153ddd506992416ff9e6c6 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 18:59:06 +0500 Subject: [PATCH 012/107] docs: mark current app as reference prototype --- README.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/README.md b/README.md index 9d80d9f..c1ee772 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Практическая программа по Project Management уровня senior+. +> **Статус:** текущая реализация — reference prototype для проверки продукта и модели обучения. Она не считается целевой архитектурой масштабируемой платформы. Актуальная стратегия и источники истины находятся в [`docs/`](docs/README.md). + Это не курс по Scrum, Jira или PMBOK. Программа учит рассматривать проект как систему преобразования неопределенности в ценный результат и управлять семью потоками: 1. ценность; @@ -21,6 +23,31 @@ - сохранение прогресса и заметок в браузере; - итоговый capstone длительностью 2–4 недели. +## Стратегия развития + +Проект развивается по схеме **A → C**: + +1. текущий сайт сохраняется как reference prototype; +2. на нем проверяются учебные механики и curriculum; +3. подтвержденные требования фиксируются как продуктовые и доменные контракты; +4. после validation gate строится отдельная масштабируемая v1; +5. контент и механики мигрируют в v1 вертикально, только после проверки. + +Не следует расширять текущие `app.js` и `course-data.js` как постоянную платформенную архитектуру. + +## Документация + +Начать с [`docs/README.md`](docs/README.md). + +Ключевые документы: + +- [`docs/product/PRODUCT.md`](docs/product/PRODUCT.md) — продуктовый контракт; +- [`docs/product/LEARNING_MODEL.md`](docs/product/LEARNING_MODEL.md) — модель обучения; +- [`docs/product/CURRICULUM.md`](docs/product/CURRICULUM.md) — competency/curriculum contract; +- [`docs/content/CONTENT_MODEL.md`](docs/content/CONTENT_MODEL.md) — модель контента; +- [`docs/architecture/ARCHITECTURE.md`](docs/architecture/ARCHITECTURE.md) — текущая и целевая архитектура; +- [`docs/ROADMAP.md`](docs/ROADMAP.md) — этапы и validation gates. + ## Запуск локально Сайт не требует сборки. Откройте `index.html` или запустите любой статический HTTP-сервер из корня репозитория. From c06e02879cac72ae6605f1d2d32d6d26aa1b777b Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:43:07 +0500 Subject: [PATCH 013/107] docs: plan M01 learning validation --- .../2026-09-06-m01-learning-validation.md | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-06-m01-learning-validation.md diff --git a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md new file mode 100644 index 0000000..16d39ee --- /dev/null +++ b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md @@ -0,0 +1,123 @@ +# M01 Learning Validation Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Turn M01 `Проект как система` into the first measurable end-to-end learning-validation slice: baseline case → lessons/drills → integrative post-case → field application → reflection/evidence. + +**Architecture:** Keep the existing static prototype. Add a small pure-JavaScript learning domain module for scoring and state semantics, structured M01 validation content in `course-data.js`, and one dedicated validation route in `app.js`. Persist validation work in the existing localStorage state without introducing backend/auth/framework migration. + +**Tech Stack:** Static HTML/CSS/JavaScript, browser `localStorage`, Node.js built-in test runner in GitHub Actions. + +**Spec:** `docs/superpowers/specs/2026-09-06-pmo01-platform-foundation-design.md` + +## Global Constraints + +- Current application remains a reference prototype, not the target v1 architecture. +- Do not add backend, authentication, CMS, AI tutor/assessment, payments, or cloud sync. +- Do not assign `mastered` automatically from page visits or a single immediate assessment. +- Baseline and post-case must be structurally similar but non-identical. +- Promotion signal: post-case score improves by at least 3 points and at least 2 rubric dimensions improve. +- Learner work must survive ordinary reload/navigation via localStorage. +- New domain logic must be testable outside the DOM. + +--- + +### Task 1: Add CI and failing learning-domain tests + +**Files:** +- Create: `.github/workflows/ci.yml` +- Create: `tests/learning-domain.test.js` + +**Interfaces:** +- Consumes: future CommonJS exports from `learning-domain.js`. +- Produces: executable contract for `scoreAssessment`, `promotionDecision`, and `deriveLearningState`. + +- [ ] Create a GitHub Actions workflow that runs `node --test tests/*.test.js` on pushes and pull requests. +- [ ] Add tests asserting rubric totals, per-dimension scores, promotion delta + dimension gate, and the rule that immediate assessment cannot produce `mastered`. +- [ ] Open/refresh the feature PR and verify CI fails specifically because `learning-domain.js` does not yet exist. + +### Task 2: Implement the pure learning domain + +**Files:** +- Create: `learning-domain.js` + +**Interfaces:** +- Produces: `scoreAssessment(questions, answers) -> { total, byDimension, max }`. +- Produces: `promotionDecision(baseline, post, options?) -> { promoted, delta, improvedDimensions }`. +- Produces: `deriveLearningState({ studied, fieldApplied, transferEvidence }) -> 'unseen'|'studied'|'applied'|'mastered'`. + +- [ ] Implement a browser + CommonJS compatible module with no DOM/storage dependencies. +- [ ] Validate answer scores are integers 0..3 and ignore unanswered questions rather than inventing credit. +- [ ] Implement the default promotion gate: `delta >= 3` and at least two dimensions improve. +- [ ] Require explicit `transferEvidence` for `mastered`; `fieldApplied` alone yields `applied`. +- [ ] Verify the Task 1 test suite passes. + +### Task 3: Add structured M01 validation content and content tests + +**Files:** +- Modify: `course-data.js` +- Create: `tests/m01-content.test.js` + +**Interfaces:** +- Produces: `window.PM01.m01Validation` with `rubricDimensions`, `baseline`, `decisionDrills`, `postCase`, `fieldApplication`, `reflection`. + +- [ ] First add a failing test that evaluates `course-data.js` in a VM sandbox and checks the M01 validation contract. +- [ ] Add five stable rubric dimensions: mechanism, evidence, tradeoffs, intervention, changeCondition. +- [ ] Add a baseline case with five scored questions; each answer option carries `score: 0..3` and inspectable feedback. +- [ ] Add two decision drills mapped to the existing M01 lessons. +- [ ] Add a non-identical integrative post-case using the same five dimensions. +- [ ] Add a field-application prompt requiring a real project, intervention, expected signal, observed evidence, and next decision. +- [ ] Add reflection prompts comparing baseline reasoning with post-case reasoning. +- [ ] Verify content-contract tests pass. + +### Task 4: Integrate the validation route and persisted learner state + +**Files:** +- Modify: `index.html` +- Modify: `app.js` +- Create: `tests/static-contract.test.js` + +**Interfaces:** +- `index.html` loads `learning-domain.js` after `course-data.js` and before `app.js`. +- `app.js` persists `state.validation.m01` under existing `pm01-state-v1` storage. +- Route: `#/validation/m01`. + +- [ ] First add failing static-contract tests for script order, route support, and the M01 validation CTA. +- [ ] Extend default state with `validation: { m01: {} }` while preserving existing stored state. +- [ ] Add a `validation/m01` route with staged sections: baseline, lesson links/drills, post-case, field application, reflection/result. +- [ ] Save assessment answers and reasoning before revealing feedback. +- [ ] Show baseline score only after baseline submission; show post score and delta only after post submission. +- [ ] Use `promotionDecision` to show a neutral validation signal, not celebratory correctness UI. +- [ ] Use `deriveLearningState` to show `studied`/`applied`; never auto-show `mastered` unless explicit transfer evidence is recorded. +- [ ] Add a CTA from M01 in the course view to the validation route without changing other modules. +- [ ] Verify static-contract and domain tests pass. + +### Task 5: Add validation UI styling and accessibility checks + +**Files:** +- Modify: `styles.css` +- Modify: `tests/static-contract.test.js` + +**Interfaces:** +- Reuse existing `.question-card`, `.option`, `.practice`, `.button` patterns where possible. + +- [ ] Add only the styles required for the validation stepper, rubric result rows, reasoning textareas, and evidence summary. +- [ ] Ensure all inputs have visible labels/legends and keyboard-operable native controls. +- [ ] Ensure score/learning-state meaning is conveyed by text, not color alone. +- [ ] Add static checks for required fieldsets/labels/aria-live result region. +- [ ] Verify all tests pass. + +### Task 6: Document the experiment and prepare review + +**Files:** +- Create: `docs/validation/M01-VALIDATION-PROTOCOL.md` +- Modify: `README.md` + +**Interfaces:** +- Produces an operational protocol for real learner sessions and a clear statement that scores are prototype evidence, not validated mastery. + +- [ ] Document who to test with, baseline/post protocol, what to observe, interview prompts, and how to record qualitative failures. +- [ ] Define the module promotion review: learning delta, transfer evidence, interaction usefulness, friction, and content-model exceptions. +- [ ] Add a README link to the M01 validation route/protocol. +- [ ] Run/inspect GitHub Actions and require green CI before marking the PR ready. +- [ ] Review the final diff for scope creep: no backend/auth/framework migration or unrelated refactor. From 9f0f059a8e71269023b518583f25a54e37f24e99 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:43:40 +0500 Subject: [PATCH 014/107] test: add prototype validation CI --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3be428d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: Prototype CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Run tests + run: node --test tests/*.test.js From 97a426cd291dc27fae4ea03587a0d9e7057fc3f9 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:43:58 +0500 Subject: [PATCH 015/107] test: define M01 learning domain contracts --- tests/learning-domain.test.js | 70 +++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 tests/learning-domain.test.js diff --git a/tests/learning-domain.test.js b/tests/learning-domain.test.js new file mode 100644 index 0000000..1b06feb --- /dev/null +++ b/tests/learning-domain.test.js @@ -0,0 +1,70 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const domainPath = path.join(__dirname, '..', 'learning-domain.js'); +const domainExists = fs.existsSync(domainPath); + +let domain = null; +if (domainExists) domain = require(domainPath); + +test('learning domain module exists', () => { + assert.equal(domainExists, true, 'learning-domain.js must exist'); +}); + +test('scoreAssessment totals selected rubric scores by dimension', { skip: !domainExists }, () => { + const questions = [ + { id: 'q1', dimension: 'mechanism', options: [{ id: 'weak', score: 0 }, { id: 'strong', score: 3 }] }, + { id: 'q2', dimension: 'evidence', options: [{ id: 'partial', score: 2 }] }, + { id: 'q3', dimension: 'mechanism', options: [{ id: 'basic', score: 1 }] }, + ]; + const result = domain.scoreAssessment(questions, { q1: 'strong', q2: 'partial', q3: 'basic' }); + + assert.deepEqual(result, { + total: 6, + max: 9, + byDimension: { mechanism: 4, evidence: 2 }, + answered: 3, + }); +}); + +test('scoreAssessment gives no invented credit for unanswered questions', { skip: !domainExists }, () => { + const questions = [ + { id: 'q1', dimension: 'mechanism', options: [{ id: 'strong', score: 3 }] }, + { id: 'q2', dimension: 'evidence', options: [{ id: 'strong', score: 3 }] }, + ]; + const result = domain.scoreAssessment(questions, { q1: 'strong' }); + + assert.equal(result.total, 3); + assert.equal(result.max, 6); + assert.equal(result.answered, 1); + assert.deepEqual(result.byDimension, { mechanism: 3, evidence: 0 }); +}); + +test('promotionDecision promotes only when delta and dimension gates both pass', { skip: !domainExists }, () => { + const baseline = { total: 5, byDimension: { mechanism: 1, evidence: 1, tradeoffs: 1, intervention: 1, changeCondition: 1 } }; + const post = { total: 9, byDimension: { mechanism: 3, evidence: 2, tradeoffs: 1, intervention: 2, changeCondition: 1 } }; + const result = domain.promotionDecision(baseline, post); + + assert.equal(result.promoted, true); + assert.equal(result.delta, 4); + assert.deepEqual(result.improvedDimensions.sort(), ['evidence', 'intervention', 'mechanism']); +}); + +test('promotionDecision rejects a large delta concentrated in one dimension', { skip: !domainExists }, () => { + const baseline = { total: 2, byDimension: { mechanism: 0, evidence: 2 } }; + const post = { total: 5, byDimension: { mechanism: 3, evidence: 2 } }; + const result = domain.promotionDecision(baseline, post); + + assert.equal(result.delta, 3); + assert.equal(result.promoted, false); + assert.deepEqual(result.improvedDimensions, ['mechanism']); +}); + +test('deriveLearningState never assigns mastered from study or immediate application alone', { skip: !domainExists }, () => { + assert.equal(domain.deriveLearningState({}), 'unseen'); + assert.equal(domain.deriveLearningState({ studied: true }), 'studied'); + assert.equal(domain.deriveLearningState({ studied: true, fieldApplied: true }), 'applied'); + assert.equal(domain.deriveLearningState({ studied: true, fieldApplied: true, transferEvidence: true }), 'mastered'); +}); From 65b36d4e792bed1f1daff66e59eabcab0a5628ff Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:44:49 +0500 Subject: [PATCH 016/107] feat: add learning assessment domain --- learning-domain.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 learning-domain.js diff --git a/learning-domain.js b/learning-domain.js new file mode 100644 index 0000000..b85d86b --- /dev/null +++ b/learning-domain.js @@ -0,0 +1,81 @@ +(function (root, factory) { + const api = factory(); + if (typeof module === 'object' && module.exports) module.exports = api; + if (root) root.PM01Learning = api; +})(typeof globalThis !== 'undefined' ? globalThis : this, function () { + 'use strict'; + + function assertScore(score) { + if (!Number.isInteger(score) || score < 0 || score > 3) { + throw new RangeError(`Rubric score must be an integer from 0 to 3; received ${score}`); + } + } + + function scoreAssessment(questions, answers) { + const safeQuestions = Array.isArray(questions) ? questions : []; + const safeAnswers = answers && typeof answers === 'object' ? answers : {}; + const byDimension = {}; + let total = 0; + let answered = 0; + + safeQuestions.forEach((question) => { + const dimension = question.dimension; + if (!dimension) throw new Error(`Question ${question.id || ''} is missing a rubric dimension`); + if (!Object.prototype.hasOwnProperty.call(byDimension, dimension)) byDimension[dimension] = 0; + + const options = Array.isArray(question.options) ? question.options : []; + options.forEach((option) => assertScore(option.score)); + + const selectedId = safeAnswers[question.id]; + if (selectedId == null) return; + const selected = options.find((option) => option.id === selectedId); + if (!selected) throw new Error(`Unknown option ${selectedId} for question ${question.id}`); + + byDimension[dimension] += selected.score; + total += selected.score; + answered += 1; + }); + + return { + total, + max: safeQuestions.length * 3, + byDimension, + answered, + }; + } + + function promotionDecision(baseline, post, options) { + const config = { + minDelta: 3, + minDimensionsImproved: 2, + ...(options || {}), + }; + const baselineTotal = Number(baseline && baseline.total) || 0; + const postTotal = Number(post && post.total) || 0; + const before = (baseline && baseline.byDimension) || {}; + const after = (post && post.byDimension) || {}; + const dimensions = [...new Set([...Object.keys(before), ...Object.keys(after)])]; + const improvedDimensions = dimensions.filter((dimension) => (Number(after[dimension]) || 0) > (Number(before[dimension]) || 0)); + const delta = postTotal - baselineTotal; + + return { + promoted: delta >= config.minDelta && improvedDimensions.length >= config.minDimensionsImproved, + delta, + improvedDimensions, + }; + } + + function deriveLearningState(evidence) { + const safeEvidence = evidence || {}; + if (safeEvidence.studied && safeEvidence.fieldApplied && safeEvidence.transferEvidence) return 'mastered'; + if (safeEvidence.fieldApplied) return 'applied'; + if (safeEvidence.studied) return 'studied'; + return 'unseen'; + } + + return { + scoreAssessment, + promotionDecision, + deriveLearningState, + }; +}); From d563231cb27237039dbb13e624b927bdc764bcfc Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:45:40 +0500 Subject: [PATCH 017/107] test: define M01 validation content contract --- tests/m01-content.test.js | 65 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 tests/m01-content.test.js diff --git a/tests/m01-content.test.js b/tests/m01-content.test.js new file mode 100644 index 0000000..14d98b3 --- /dev/null +++ b/tests/m01-content.test.js @@ -0,0 +1,65 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const vm = require('node:vm'); + +const dataPath = path.join(__dirname, '..', 'm01-validation-data.js'); +const dataExists = fs.existsSync(dataPath); + +function loadValidation() { + const sandbox = { window: { PM01: {} } }; + vm.createContext(sandbox); + vm.runInContext(fs.readFileSync(dataPath, 'utf8'), sandbox, { filename: 'm01-validation-data.js' }); + return sandbox.window.PM01.m01Validation; +} + +test('M01 validation content module exists', () => { + assert.equal(dataExists, true, 'm01-validation-data.js must exist'); +}); + +test('M01 validation covers five stable rubric dimensions', { skip: !dataExists }, () => { + const validation = loadValidation(); + assert.deepEqual( + Array.from(validation.rubricDimensions, (item) => item.id), + ['mechanism', 'evidence', 'tradeoffs', 'intervention', 'changeCondition'] + ); +}); + +test('baseline and post-case are non-identical five-question assessments using the same dimensions', { skip: !dataExists }, () => { + const validation = loadValidation(); + assert.notEqual(validation.baseline.id, validation.postCase.id); + assert.notEqual(validation.baseline.scenario, validation.postCase.scenario); + + for (const assessment of [validation.baseline, validation.postCase]) { + assert.equal(assessment.questions.length, 5); + assert.deepEqual( + Array.from(assessment.questions, (question) => question.dimension).sort(), + ['changeCondition', 'evidence', 'intervention', 'mechanism', 'tradeoffs'] + ); + for (const question of assessment.questions) { + assert.equal(question.options.length, 4); + for (const option of question.options) { + assert.equal(Number.isInteger(option.score), true); + assert.equal(option.score >= 0 && option.score <= 3, true); + assert.equal(typeof option.feedback, 'string'); + assert.equal(option.feedback.length > 20, true); + } + } + } +}); + +test('M01 validation includes two lesson drills plus field application and reflection', { skip: !dataExists }, () => { + const validation = loadValidation(); + assert.equal(validation.decisionDrills.length >= 2, true); + assert.deepEqual( + Array.from(validation.decisionDrills, (drill) => drill.lessonId).sort(), + ['project-system', 'system-diagnostic'] + ); + + const fieldIds = Array.from(validation.fieldApplication.fields, (field) => field.id); + for (const required of ['project', 'symptom', 'mechanism', 'intervention', 'signal', 'evidence', 'nextDecision']) { + assert.equal(fieldIds.includes(required), true, `missing field ${required}`); + } + assert.equal(validation.reflection.prompts.length >= 3, true); +}); From 9e0211d6857e4fc7a43e9f1c1f5421726762f24b Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:47:26 +0500 Subject: [PATCH 018/107] feat: add M01 validation cases and drills --- m01-validation-data.js | 182 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 m01-validation-data.js diff --git a/m01-validation-data.js b/m01-validation-data.js new file mode 100644 index 0000000..2fcf172 --- /dev/null +++ b/m01-validation-data.js @@ -0,0 +1,182 @@ +(function () { + 'use strict'; + + const PM01 = window.PM01; + if (!PM01) throw new Error('PM01 course data must be loaded before M01 validation data'); + + const rubricDimensions = [ + { id: 'mechanism', label: 'Механизм', description: 'Отделяет симптом от воспроизводящей причины в системе.' }, + { id: 'evidence', label: 'Доказательства', description: 'Опирается на наблюдаемые факты и явно отмечает неизвестное.' }, + { id: 'tradeoffs', label: 'Компромиссы', description: 'Видит цену вмешательства и альтернативные объяснения.' }, + { id: 'intervention', label: 'Вмешательство', description: 'Выбирает действие, направленное на механизм, а не на внешний симптом.' }, + { id: 'changeCondition', label: 'Условие изменения', description: 'Заранее задает сигнал, по которому решение будет подтверждено или пересмотрено.' }, + ]; + + function option(id, score, label, feedback) { + return { id, score, label, feedback }; + } + + PM01.m01Validation = { + version: 1, + moduleId: 'm01', + title: 'M01 · Проверка системного мышления', + promise: 'Сравнить качество диагноза проекта до и после модуля, а затем перенести модель на реальную работу.', + rubricDimensions, + promotionRule: { minDelta: 3, minDimensionsImproved: 2 }, + + baseline: { + id: 'm01-baseline-release', + title: 'Baseline · Релиз снова сдвинулся', + scenario: 'Команда из восьми человек третий раз переносит релиз. Разработка говорит, что требования меняются слишком поздно. Аналитик говорит, что бизнес долго отвечает. Бизнес считает, что разработка завышает оценки. QA получает крупные изменения за два дня до релиза. На статусах все задачи почти всегда зеленые до последней недели.', + reasoningPrompt: 'Кратко запиши свой диагноз до выбора вариантов: где проблема, почему она повторяется и что ты бы сделал первым?', + questions: [ + { + id: 'baseline-mechanism', dimension: 'mechanism', prompt: 'Какой диагноз лучше всего объясняет повторяемость проблемы?', + options: [ + option('people', 0, 'Команда недостаточно дисциплинирована', 'Это объясняет проблему качествами людей, но не показывает воспроизводящий механизм системы.'), + option('estimates', 1, 'Оценки разработки систематически неточны', 'Неточность оценок может быть симптомом, но не объясняет поздние изменения и очереди решений.'), + option('handoff', 2, 'Поздние требования создают перегрузку перед QA', 'Это находит важный механизм, но пока оставляет без ответа источник поздних требований.'), + option('decision-flow', 3, 'Решения и уточнения долго ждут upstream, затем пакетно попадают в исполнение', 'Диагноз связывает задержку решений с накоплением работы и поздней перегрузкой downstream.'), + ], + }, + { + id: 'baseline-evidence', dimension: 'evidence', prompt: 'Какое доказательство стоит получить первым?', + options: [ + option('opinions', 0, 'Собрать мнения всех участников о виноватой стороне', 'Мнения полезны как гипотезы, но сами по себе не показывают движение работы и решений.'), + option('velocity', 1, 'Сравнить velocity последних спринтов', 'Velocity показывает объем завершенной работы, но слабо локализует ожидание и поздние изменения.'), + option('changes', 2, 'Посчитать изменения требований в последнюю неделю', 'Это полезный факт, но без времени ожидания решений не показывает всю причинную цепь.'), + option('timeline', 3, 'Восстановить timeline ключевых решений, изменений и передачи в QA', 'Timeline позволяет увидеть очереди, задержки и связь upstream-событий с downstream-перегрузкой.'), + ], + }, + { + id: 'baseline-tradeoffs', dimension: 'tradeoffs', prompt: 'Какой компромисс важно признать до вмешательства?', + options: [ + option('none', 0, 'Если процесс правильный, компромиссов быть не должно', 'Любое системное вмешательство меняет скорость, гибкость, загрузку или объем доступной информации.'), + option('overtime', 1, 'Нужно решить, сколько переработок допустимо', 'Это рассматривает компенсацию симптома, а не основной выбор дизайна потока.'), + option('freeze', 2, 'Ранний freeze снизит изменения, но может зафиксировать неверные решения', 'Компромисс реальный, однако freeze — только один из возможных механизмов управления неопределенностью.'), + option('batch-vs-learning', 3, 'Меньшие партии и ранние решения снижают поздний риск, но требуют чаще вовлекать бизнес', 'Ответ явно связывает улучшение потока с ценой более частых решений и обратной связи.'), + ], + }, + { + id: 'baseline-intervention', dimension: 'intervention', prompt: 'Что разумнее попробовать первым?', + options: [ + option('pressure', 0, 'Потребовать от команды точнее соблюдать исходный план', 'Давление на исполнение не устраняет ожидание решений и может лишь скрыть риск дольше.'), + option('more-qa', 1, 'Добавить QA на последнюю неделю перед релизом', 'Это увеличит локальную мощность, но сохранит пакетный поток поздних изменений.'), + option('freeze', 2, 'Запретить изменения за неделю до релиза', 'Это может стабилизировать конец цикла, но не уменьшает upstream-очередь решений.'), + option('decision-sla', 3, 'Сократить время ключевых решений и дробить передачу изменений небольшими партиями', 'Вмешательство направлено на найденный механизм: ожидание upstream и позднее пакетное поступление работы.'), + ], + }, + { + id: 'baseline-change', dimension: 'changeCondition', prompt: 'Какой ранний сигнал покажет, что вмешательство работает?', + options: [ + option('on-time', 0, 'Следующий релиз выйдет вовремя', 'Это слишком поздний итоговый результат и не помогает вовремя скорректировать вмешательство.'), + option('busy', 1, 'У всех будет стабильная загрузка', 'Высокая загрузка не равна хорошему потоку и может даже увеличивать очереди.'), + option('bugs', 2, 'Снизится число дефектов перед релизом', 'Это полезный downstream-сигнал, но он появляется позже причины и смешивает несколько механизмов.'), + option('lead-time', 3, 'Снизится время от вопроса/изменения до решения и передачи следующему этапу', 'Это ранний показатель именно того механизма, который должно изменить вмешательство.'), + ], + }, + ], + }, + + decisionDrills: [ + { + id: 'm01-drill-system', lessonId: 'project-system', title: 'Drill · Красная задача', + situation: 'Одна задача просрочена на пять дней. Разработчик три дня ждал API внешней команды, но в плане это ожидание не отображалось.', + prompt: 'Какое действие сильнее всего соответствует системному взгляду?', + options: [ + option('escalate-dev', 0, 'Эскалировать просрочку разработчика', 'Это реагирует на место проявления симптома, а не на источник ожидания.'), + option('extend', 1, 'Увеличить оценку похожих задач', 'Большая оценка может скрыть вариативность, но не делает зависимость управляемой.'), + option('track-dependency', 3, 'Сделать внешнюю зависимость явным узлом потока с владельцем и ранним сигналом', 'Так вмешательство меняет систему, которая воспроизводит ожидание, а не только текущую дату.'), + ], + }, + { + id: 'm01-drill-diagnostic', lessonId: 'system-diagnostic', title: 'Drill · Дефект в QA', + situation: 'QA три раза возвращает одну и ту же функцию из-за разных трактовок бизнес-правила. Требование было согласовано в переписке несколькими людьми.', + prompt: 'Где остановить причинную цепь для первого системного вмешательства?', + options: [ + option('qa', 0, 'На QA: усилить чек-лист тестирования', 'QA обнаруживает расхождение, но чек-лист не устраняет неоднозначное решение upstream.'), + option('developer', 1, 'На разработчике: обязать задавать больше вопросов', 'Это может помочь локально, но снова делает устойчивость зависимой от поведения конкретного человека.'), + option('decision-interface', 3, 'На интерфейсе решения: одно правило, владелец трактовки и проверяемый пример до разработки', 'Условие системы становится явным и снижает вероятность повторения с другим исполнителем.'), + ], + }, + ], + + postCase: { + id: 'm01-post-integration', + title: 'Post-case · Интеграция к запуску партнера', + scenario: 'Команда готовит интеграцию к публичному запуску партнера. Техническая часть почти завершена, но юридические ограничения и формат данных уточняются через двух менеджеров. Разработчики держат несколько вариантов реализации открытыми. За неделю до запуска партнер присылает окончательный формат, после чего приходится менять обработку данных, документацию и тесты одновременно.', + reasoningPrompt: 'Запиши диагноз заново без подсказок из baseline: какой механизм создает риск и какое вмешательство ты выберешь?', + questions: [ + { + id: 'post-mechanism', dimension: 'mechanism', prompt: 'Какой механизм наиболее вероятно создает повторяемый риск?', + options: [ + option('coding', 0, 'Разработка недостаточно быстро переписывает интеграцию', 'Скорость переписывания относится к реакции на позднюю информацию, а не к источнику риска.'), + option('partner', 1, 'Партнер просто ненадежен, это нельзя управлять', 'Внешняя неопределенность реальна, но систему можно спроектировать так, чтобы раньше получать решения и ограничивать последствия.'), + option('parallel', 2, 'Слишком много вариантов реализации поддерживаются параллельно', 'Это важная стоимость неопределенности, но нужно объяснить, почему развилка остается открытой так долго.'), + option('decision-interface', 3, 'Критичные внешние решения проходят длинный интерфейс и приходят большой партией перед необратимым сроком', 'Ответ связывает очередь решений, размер партии и позднее распространение изменений по системе.'), + ], + }, + { + id: 'post-evidence', dimension: 'evidence', prompt: 'Какой набор фактов лучше проверит этот диагноз?', + options: [ + option('hours', 0, 'Количество часов разработки по интеграции', 'Трудозатраты не показывают, сколько времени работа ожидала внешней информации или решения.'), + option('messages', 1, 'Количество сообщений с партнером', 'Объем коммуникации не равен скорости получения решения и может маскировать повторные уточнения.'), + option('rework', 2, 'Объем переделок после получения финального формата', 'Переделки показывают цену задержки, но без timeline не локализуют механизм.'), + option('decision-timeline', 3, 'Время каждого критичного вопроса: запрос → владелец → ответ → изменение downstream', 'Так можно проверить и очередь решений, и распространение последствий по системе.'), + ], + }, + { + id: 'post-tradeoffs', dimension: 'tradeoffs', prompt: 'Какой trade-off должен быть явным?', + options: [ + option('quality-speed', 0, 'Качество всегда приходится жертвовать ради скорости', 'Это слишком общий тезис и не связан с механизмом конкретной системы.'), + option('people', 1, 'Больше встреч означает меньше времени на разработку', 'Это реальная стоимость, но не формулирует управленческий выбор вокруг неопределенности.'), + option('flexibility', 2, 'Раннее решение уменьшит переделки, но может ограничить гибкость', 'Компромисс верный, но не учитывает возможность поэтапно закрывать только дорогие развилки.'), + option('optionality', 3, 'Нужно платить за опциональность только там, где стоимость позднего решения ниже стоимости преждевременной фиксации', 'Ответ связывает время решения, цену неопределенности и необратимость вместо универсального freeze.'), + ], + }, + { + id: 'post-intervention', dimension: 'intervention', prompt: 'Какое первое вмешательство наиболее системное?', + options: [ + option('heroics', 0, 'Заранее запланировать усиление команды перед запуском', 'Резерв мощности помогает пережить симптом, но не улучшает поток решений.'), + option('deadline', 1, 'Поставить партнеру более жесткий дедлайн ответа', 'Дедлайн без владельца, формата решения и последствий часто не меняет реальную очередь.'), + option('mock', 2, 'Сделать больше технических моков всех возможных форматов', 'Это снижает часть технического риска, но может дорого поддерживать ненужную опциональность.'), + option('decision-contract', 3, 'Выделить критичные развилки, владельцев и даты решений; закрывать их до того, как downstream-цена станет высокой', 'Вмешательство сокращает время открытых дорогих развилок и делает внешнюю зависимость управляемой.'), + ], + }, + { + id: 'post-change', dimension: 'changeCondition', prompt: 'Какой change condition лучше использовать?', + options: [ + option('launch', 0, 'Запуск состоится без переноса', 'Это поздний бинарный итог и слабый датчик качества потока решений.'), + option('meetings', 1, 'С партнерами станет меньше встреч', 'Количество встреч может измениться в любую сторону и не отражает скорость закрытия критичных развилок.'), + option('rework', 2, 'Снизится объем переделок перед запуском', 'Это хороший lagging signal, но он не позволяет рано увидеть возвращение очереди решений.'), + option('open-decisions', 3, 'Возраст и число открытых критичных решений снижаются до согласованных порогов', 'Это ранний и непосредственно связанный с механизмом сигнал, который позволяет пересмотреть способ взаимодействия заранее.'), + ], + }, + ], + }, + + fieldApplication: { + title: 'Перенос на реальный проект', + instructions: 'Выбери текущий или недавний проект. Не описывай идеальный процесс — зафиксируй факты, одно вмешательство и наблюдаемое доказательство.', + fields: [ + { id: 'project', label: 'Проект и контекст', prompt: 'Что за проект и какой наблюдаемый outcome важен?' }, + { id: 'symptom', label: 'Симптом', prompt: 'Где проблема становится видимой?' }, + { id: 'mechanism', label: 'Механизм', prompt: 'Какой повторяемый механизм или условие системы создает симптом?' }, + { id: 'intervention', label: 'Вмешательство', prompt: 'Какое минимальное изменение системы ты попробуешь?' }, + { id: 'signal', label: 'Ранний сигнал', prompt: 'Какой показатель должен измениться раньше итогового результата?' }, + { id: 'evidence', label: 'Наблюдаемое доказательство', prompt: 'Что фактически произошло после вмешательства? Если еще не проверено — так и укажи.' }, + { id: 'nextDecision', label: 'Следующее решение', prompt: 'Что ты продолжишь, изменишь или остановишь на основании доказательства?' }, + ], + }, + + reflection: { + title: 'Обновление модели', + prompts: [ + { id: 'changedDiagnosis', label: 'Что изменилось в диагнозе по сравнению с baseline?' }, + { id: 'changedAction', label: 'Как изменилось выбранное первое действие и почему?' }, + { id: 'newEvidence', label: 'Какие данные теперь нужны тебе раньше, чем раньше?' }, + { id: 'remainingUncertainty', label: 'В чем ты все еще не уверен и как это проверишь?' }, + ], + }, + }; +})(); From d8d81376f0e1bd175ae6ca4df15cd5d3e17836fe Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:48:38 +0500 Subject: [PATCH 019/107] test: define M01 validation UI contract --- tests/static-contract.test.js | 42 +++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/static-contract.test.js diff --git a/tests/static-contract.test.js b/tests/static-contract.test.js new file mode 100644 index 0000000..7d46638 --- /dev/null +++ b/tests/static-contract.test.js @@ -0,0 +1,42 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +function read(name) { + const file = path.join(__dirname, '..', name); + return fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : ''; +} + +const html = read('index.html'); +const validationApp = read('m01-validation-app.js'); +const styles = read('styles.css'); + +test('index loads validation data, domain, base app, then validation extension in order', () => { + const scripts = ['course-data.js', 'm01-validation-data.js', 'learning-domain.js', 'app.js', 'm01-validation-app.js']; + let lastIndex = -1; + for (const script of scripts) { + const index = html.indexOf(`src="${script}"`); + assert.notEqual(index, -1, `missing ${script}`); + assert.equal(index > lastIndex, true, `${script} must load after previous script`); + lastIndex = index; + } +}); + +test('validation extension owns the M01 validation route and course CTA', () => { + assert.equal(validationApp.includes("validation/m01"), true, 'missing validation route'); + assert.equal(validationApp.includes('data-validation-cta'), true, 'missing M01 validation CTA contract'); +}); + +test('validation UI uses semantic assessment controls and live result feedback', () => { + assert.equal(validationApp.includes(' { const scripts = ['course-data.js', 'm01-validation-data.js', 'learning-domain.js', 'app.js', 'm01-validation-app.js']; let lastIndex = -1; for (const script of scripts) { @@ -28,6 +30,10 @@ test('validation extension owns the M01 validation route and course CTA', () => assert.equal(validationApp.includes('data-validation-cta'), true, 'missing M01 validation CTA contract'); }); +test('validation uses an isolated storage key so legacy app saves cannot erase experiment work', () => { + assert.equal(validationApp.includes('pm01-validation-m01-v1'), true, 'missing isolated validation storage key'); +}); + test('validation UI uses semantic assessment controls and live result feedback', () => { assert.equal(validationApp.includes(' { - assert.equal(styles.includes('.validation-shell'), true); - assert.equal(styles.includes('.validation-score'), true); - assert.equal(styles.includes('.validation-evidence'), true); + assert.equal(validationStyles.includes('.validation-shell'), true); + assert.equal(validationStyles.includes('.validation-score'), true); + assert.equal(validationStyles.includes('.validation-evidence'), true); }); From 72b9d5b1423fa87ac63c71b2e046d25afcf5b391 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:51:43 +0500 Subject: [PATCH 021/107] feat: load M01 validation extension --- index.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.html b/index.html index 9dbb379..57a8347 100644 --- a/index.html +++ b/index.html @@ -16,6 +16,7 @@ rel="stylesheet" /> + @@ -49,6 +50,9 @@
+ + + From db389dafae1a6fe2c6c072eb41a4be2c0051e172 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:52:05 +0500 Subject: [PATCH 022/107] feat: style M01 validation experience --- m01-validation.css | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 m01-validation.css diff --git a/m01-validation.css b/m01-validation.css new file mode 100644 index 0000000..37df02a --- /dev/null +++ b/m01-validation.css @@ -0,0 +1,49 @@ +.validation-shell { max-width: 980px; margin: 0 auto; } +.validation-hero { margin-bottom: 38px; padding-bottom: 30px; border-bottom: 1px solid var(--line); } +.validation-hero h1 { max-width: 860px; } +.validation-note { max-width: 760px; padding: 14px 16px; border-left: 3px solid var(--accent); background: rgba(255,255,255,.035); color: #c4d0d7; line-height: 1.6; } +.validation-step { margin: 38px 0; padding: 26px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--ink-2); } +.validation-step.locked { opacity: .65; } +.validation-step > header { margin-bottom: 20px; } +.validation-step > header p { color: var(--muted); line-height: 1.6; } +.validation-scenario { padding: 20px; border-radius: 10px; background: #06101a; color: #d0dae0; line-height: 1.65; } +.validation-question { margin: 22px 0; padding: 0; border: 0; } +.validation-question legend { margin-bottom: 10px; font-weight: 800; line-height: 1.45; } +.validation-options { display: grid; gap: 8px; } +.validation-option { display: flex; gap: 10px; align-items: flex-start; padding: 12px 13px; border: 1px solid var(--line); border-radius: 8px; color: #c4cfd6; cursor: pointer; } +.validation-option:has(input:checked) { border-color: var(--accent); background: rgba(255,177,27,.08); color: white; } +.validation-option input { margin-top: 3px; accent-color: var(--accent); } +.validation-feedback { margin: 8px 0 0 27px; color: #91a2af; font-size: 13px; line-height: 1.55; } +.validation-reasoning, .validation-evidence textarea { width: 100%; min-height: 120px; margin-top: 8px; padding: 14px; border: 1px solid var(--line); border-radius: 8px; background: #06101a; color: white; resize: vertical; line-height: 1.55; } +.validation-reasoning:focus, .validation-evidence textarea:focus { outline: 3px solid rgba(255,177,27,.25); border-color: var(--accent); } +.validation-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 18px; } +.validation-score { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin: 20px 0; } +.validation-score > div { padding: 16px; border: 1px solid var(--line); border-radius: 9px; background: rgba(255,255,255,.025); } +.validation-score strong { display: block; font-size: 26px; } +.validation-score span { color: var(--muted); font-size: 12px; } +.validation-dimensions { display: grid; gap: 8px; margin-top: 14px; } +.validation-dimension { display: flex; justify-content: space-between; gap: 16px; padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 13px; } +.validation-dimension span:last-child { color: var(--muted); } +.validation-drills { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } +.validation-drill { padding: 20px; border: 1px solid var(--line); border-radius: 10px; background: rgba(255,255,255,.025); } +.validation-drill p { color: #bcc8cf; line-height: 1.55; } +.validation-lesson-links { display: flex; flex-wrap: wrap; gap: 9px; margin: 15px 0 22px; } +.validation-evidence { display: grid; gap: 18px; } +.validation-evidence label { display: block; font-weight: 700; } +.validation-evidence small { display: block; margin-top: 5px; color: var(--muted); font-weight: 400; line-height: 1.45; } +.validation-state { display: inline-flex; align-items: center; gap: 8px; margin-top: 12px; padding: 8px 11px; border: 1px solid var(--line); border-radius: 99px; color: #cbd5db; font: 12px "IBM Plex Mono", monospace; } +.validation-message { min-height: 24px; margin-top: 14px; color: var(--muted); line-height: 1.5; } +.validation-cta { margin: 28px 0; padding: 24px; border: 1px solid rgba(255,177,27,.35); border-radius: var(--radius); background: rgba(255,177,27,.055); } +.validation-cta h2 { margin-bottom: 9px; font-size: 25px; } +.validation-cta p { max-width: 760px; color: #b9c6ce; line-height: 1.55; } +.validation-cta .button { margin-top: 8px; } +.validation-result { margin-top: 24px; padding: 20px; border-radius: 10px; background: var(--paper); color: var(--ink); } +.validation-result p { color: #59666e; line-height: 1.55; } +.validation-result .validation-score > div { border-color: var(--paper-line); background: rgba(7,18,29,.035); } +.validation-result .validation-dimension { border-color: var(--paper-line); } +.validation-result .validation-dimension span:last-child { color: #65717a; } + +@media (max-width: 760px) { + .validation-step { padding: 20px; } + .validation-score, .validation-drills { grid-template-columns: 1fr; } +} From 34fdef766c8813715acc3668f937b57b6edb3292 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:54:10 +0500 Subject: [PATCH 023/107] feat: add M01 end-to-end validation flow --- m01-validation-app.js | 446 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 446 insertions(+) create mode 100644 m01-validation-app.js diff --git a/m01-validation-app.js b/m01-validation-app.js new file mode 100644 index 0000000..4a2bd9b --- /dev/null +++ b/m01-validation-app.js @@ -0,0 +1,446 @@ +(function () { + 'use strict'; + + const DATA = window.PM01 && window.PM01.m01Validation; + const DOMAIN = window.PM01Learning; + const validationStorageKey = 'pm01-validation-m01-v1'; + const legacyStorageKey = 'pm01-state-v1'; + const validationRoute = 'validation/m01'; + const m01LessonIds = ['project-system', 'system-diagnostic']; + + if (!DATA || !DOMAIN) return; + + function emptyAssessment() { + return { answers: {}, reasoning: '', submittedAt: null, score: null }; + } + + function emptyValidationState() { + return { + version: 1, + baseline: emptyAssessment(), + drills: {}, + postCase: emptyAssessment(), + field: { values: {}, submittedAt: null }, + reflection: { values: {}, delayedTransfer: '', submittedAt: null }, + }; + } + + function loadJson(key, fallback) { + try { + const parsed = JSON.parse(localStorage.getItem(key)); + return parsed && typeof parsed === 'object' ? parsed : fallback; + } catch (_) { + return fallback; + } + } + + function loadValidationState() { + const stored = loadJson(validationStorageKey, {}); + const defaults = emptyValidationState(); + return { + ...defaults, + ...stored, + baseline: { ...defaults.baseline, ...(stored.baseline || {}), answers: { ...(stored.baseline && stored.baseline.answers || {}) } }, + drills: { ...(stored.drills || {}) }, + postCase: { ...defaults.postCase, ...(stored.postCase || {}), answers: { ...(stored.postCase && stored.postCase.answers || {}) } }, + field: { ...defaults.field, ...(stored.field || {}), values: { ...(stored.field && stored.field.values || {}) } }, + reflection: { ...defaults.reflection, ...(stored.reflection || {}), values: { ...(stored.reflection && stored.reflection.values || {}) } }, + }; + } + + let state = loadValidationState(); + let storageHealthy = testStorage(); + + function testStorage() { + const key = `${validationStorageKey}-probe`; + try { + localStorage.setItem(key, '1'); + localStorage.removeItem(key); + return true; + } catch (_) { + return false; + } + } + + function saveValidationState(message) { + try { + localStorage.setItem(validationStorageKey, JSON.stringify(state)); + storageHealthy = true; + if (message) setMessage(message); + return true; + } catch (_) { + storageHealthy = false; + setMessage('Не удалось сохранить данные в браузере. Не переходи к следующему шагу: ответы могут быть потеряны.', true); + return false; + } + } + + function legacyState() { + return loadJson(legacyStorageKey, { completed: [] }); + } + + function isStudied() { + const completed = Array.isArray(legacyState().completed) ? legacyState().completed : []; + return m01LessonIds.every((id) => completed.includes(id)); + } + + function allDrillsAnswered() { + return DATA.decisionDrills.every((drill) => state.drills[drill.id] && state.drills[drill.id].choice); + } + + function escapeHtml(value) { + return String(value == null ? '' : value) + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); + } + + function currentRoute() { + return location.hash.replace(/^#\/?/, '').replace(/\/$/, ''); + } + + function setMessage(text, error) { + const target = document.querySelector('#validation-message'); + if (!target) return; + target.textContent = text || ''; + target.dataset.error = error ? 'true' : 'false'; + } + + function dimensionLabel(id) { + return DATA.rubricDimensions.find((item) => item.id === id)?.label || id; + } + + function assessmentComplete(assessment, draft) { + return assessment.questions.every((question) => draft.answers[question.id]) && draft.reasoning.trim().length >= 20; + } + + function assessmentBlock(assessment, key, heading, unlocked) { + const draft = state[key]; + const submitted = Boolean(draft.submittedAt); + if (!unlocked) { + return `
+

${escapeHtml(heading)}

${escapeHtml(assessment.title)}

Этот этап откроется после предыдущих шагов.

+
`; + } + + const questions = assessment.questions.map((question) => { + const selected = draft.answers[question.id]; + const options = question.options.map((item) => { + const checked = selected === item.id; + return `${submitted && checked ? `

${escapeHtml(item.feedback)}

` : ''}`; + }).join(''); + return `
+ ${escapeHtml(question.prompt)} +
${options}
+
`; + }).join(''); + + const score = submitted && draft.score ? `
+
${draft.score.total}/${draft.score.max}итог по rubric
+
${draft.score.answered}/${assessment.questions.length}зафиксировано ответов
+
зафиксированответ нельзя менять после submit
+
` : ''; + + return `
+

${escapeHtml(heading)}

${escapeHtml(assessment.title)}

${escapeHtml(assessment.scenario)}

+
${escapeHtml(assessment.reasoningPrompt)}
+ + + ${questions} + ${score} + ${submitted ? '

Ответ зафиксирован. Feedback показан только после фиксации выбора.

' : `
`} +
`; + } + + function drillBlock(drill) { + const stored = state.drills[drill.id]; + const selected = stored && stored.choice; + const options = drill.options.map((item) => { + const checked = selected === item.id; + return `${checked ? `

${escapeHtml(item.feedback)}

` : ''}`; + }).join(''); + return `
+

Decision Drill

+

${escapeHtml(drill.title)}

+

${escapeHtml(drill.situation)}

+
+ ${escapeHtml(drill.prompt)} +
${options}
+
+
`; + } + + function learningBlock() { + const studied = isStudied(); + const drillsDone = allDrillsAnswered(); + return `
+

02 · Learning

Изучи модель и прими два решения

Вернись сюда после двух уроков. Decision Drills фиксируют первое решение до показа feedback.

+ +

Уроки: ${studied ? 'изучены ✓' : 'нужно завершить оба'} · Drills: ${drillsDone ? '2/2 ✓' : `${Object.keys(state.drills).length}/2`}

+
${DATA.decisionDrills.map(drillBlock).join('')}
+
`; + } + + function resultBlock() { + if (!state.baseline.score || !state.postCase.score) return ''; + const decision = DOMAIN.promotionDecision(state.baseline.score, state.postCase.score, DATA.promotionRule); + const dimensions = DATA.rubricDimensions.map((dimension) => { + const before = state.baseline.score.byDimension[dimension.id] || 0; + const after = state.postCase.score.byDimension[dimension.id] || 0; + const diff = after - before; + return `
${escapeHtml(dimension.label)}${before} → ${after}${diff > 0 ? ` (+${diff})` : diff < 0 ? ` (${diff})` : ''}
`; + }).join(''); + const deltaLabel = decision.delta > 0 ? `+${decision.delta}` : String(decision.delta); + const explanation = decision.promoted + ? 'Есть прототипный сигнал улучшения: выполнены оба promotion gate. Это не означает mastery и требует проверки на реальном переносе.' + : `Promotion gate пока не пройден: требуется delta ≥ ${DATA.promotionRule.minDelta} и улучшение минимум по ${DATA.promotionRule.minDimensionsImproved} измерениям.`; + return `
+

Learning signal

Что изменилось в reasoning

+
+
${state.baseline.score.total}/15baseline
+
${state.postCase.score.total}/15post-case
+
${deltaLabel}delta
+
+

${escapeHtml(explanation)}

+
${dimensions}
+
`; + } + + function fieldBlock(unlocked) { + if (!unlocked) return `

04 · Transfer

Перенос на реальный проект

Откроется после post-case.

`; + const submitted = Boolean(state.field.submittedAt); + const fields = DATA.fieldApplication.fields.map((field) => `
+ + +
`).join(''); + return `
+

04 · Transfer

${escapeHtml(DATA.fieldApplication.title)}

${escapeHtml(DATA.fieldApplication.instructions)}

+
${fields}
+ ${submitted ? '

Field application зафиксирован ✓

' : '
'} +
`; + } + + function reflectionBlock(unlocked) { + if (!unlocked) return `

05 · Reflection

Обновление mental model

Откроется после field application.

`; + const submitted = Boolean(state.reflection.submittedAt); + const prompts = DATA.reflection.prompts.map((prompt) => `
+ + +
`).join(''); + return `
+

05 · Reflection

${escapeHtml(DATA.reflection.title)}

Не пересказывай урок. Зафиксируй, что именно изменилось в диагнозе, действии или требованиях к доказательствам.

+
${prompts} +
+ + +
+
+ ${submitted ? '

Reflection зафиксирован ✓

' : '
'} +
`; + } + + function evidenceStateLabel() { + const studied = isStudied(); + const fieldApplied = Boolean(state.field.submittedAt); + const learningState = DOMAIN.deriveLearningState({ studied, fieldApplied, transferEvidence: false }); + const labels = { unseen: 'не начато', studied: 'изучено', applied: 'применено', mastered: 'mastered' }; + return labels[learningState] || learningState; + } + + function validationView() { + const baselineDone = Boolean(state.baseline.submittedAt); + const postUnlocked = baselineDone && isStudied() && allDrillsAnswered(); + const postDone = Boolean(state.postCase.submittedAt); + const fieldDone = Boolean(state.field.submittedAt); + const storageWarning = storageHealthy ? '' : '

Браузер сейчас не дает сохранить localStorage. Не продолжай эксперимент до восстановления хранения: переход к урокам может привести к потере ответов.

'; + + return `
+
+

Phase 1 · Reference prototype

+

${escapeHtml(DATA.title)}

+

${escapeHtml(DATA.promise)}

+

Это эксперимент качества обучения, а не экзамен. Система измеряет изменение reasoning; статус mastered автоматически не присваивается.

+ ${storageWarning} + Evidence state: ${escapeHtml(evidenceStateLabel())} +
+ + ${assessmentBlock(DATA.baseline, 'baseline', '01 · Baseline', true)} + ${baselineDone ? learningBlock() : '

02 · Learning

Сначала зафиксируй baseline

Материал модуля намеренно не показывается в этом маршруте до baseline, чтобы не загрязнять исходное измерение.

'} + ${assessmentBlock(DATA.postCase, 'postCase', '03 · Integrative case', postUnlocked)} + ${postDone ? resultBlock() : ''} + ${fieldBlock(postDone)} + ${reflectionBlock(fieldDone)} + +
+ ← К программе + +
+
+
`; + } + + function fieldComplete() { + return DATA.fieldApplication.fields.every((field) => String(state.field.values[field.id] || '').trim().length >= 8); + } + + function reflectionComplete() { + return DATA.reflection.prompts.every((prompt) => String(state.reflection.values[prompt.id] || '').trim().length >= 12); + } + + function rerenderValidation(message) { + renderValidationRoute(); + if (message) setMessage(message); + } + + function bindAssessmentEvents() { + document.querySelectorAll('[data-assessment]').forEach((input) => input.addEventListener('change', () => { + const key = input.dataset.assessment; + if (state[key].submittedAt) return; + state[key].answers[input.dataset.question] = input.value; + saveValidationState('Черновик ответа сохранен.'); + })); + + document.querySelectorAll('[data-reasoning]').forEach((textarea) => textarea.addEventListener('change', () => { + const key = textarea.dataset.reasoning; + if (state[key].submittedAt) return; + state[key].reasoning = textarea.value; + saveValidationState('Черновик reasoning сохранен.'); + })); + + document.querySelectorAll('[data-submit-assessment]').forEach((button) => button.addEventListener('click', () => { + const key = button.dataset.submitAssessment; + const assessment = key === 'baseline' ? DATA.baseline : DATA.postCase; + const reasoning = document.querySelector(`[data-reasoning="${key}"]`); + if (reasoning) state[key].reasoning = reasoning.value; + if (!assessmentComplete(assessment, state[key])) { + saveValidationState(); + setMessage('Ответь на все пять вопросов и запиши диагноз минимум в 20 символах.', true); + return; + } + state[key].score = DOMAIN.scoreAssessment(assessment.questions, state[key].answers); + state[key].submittedAt = new Date().toISOString(); + if (saveValidationState()) rerenderValidation(`${key === 'baseline' ? 'Baseline' : 'Post-case'} зафиксирован.`); + })); + } + + function bindDrillEvents() { + document.querySelectorAll('[data-drill]').forEach((input) => input.addEventListener('change', () => { + const id = input.dataset.drill; + if (state.drills[id] && state.drills[id].choice) return; + state.drills[id] = { choice: input.value, selectedAt: new Date().toISOString() }; + if (saveValidationState()) rerenderValidation('Первый выбор Decision Drill зафиксирован; feedback открыт.'); + })); + } + + function bindFieldEvents() { + document.querySelectorAll('[data-field]').forEach((textarea) => textarea.addEventListener('change', () => { + if (state.field.submittedAt) return; + state.field.values[textarea.dataset.field] = textarea.value; + saveValidationState('Черновик field application сохранен.'); + })); + document.querySelector('#save-field')?.addEventListener('click', () => { + document.querySelectorAll('[data-field]').forEach((textarea) => { state.field.values[textarea.dataset.field] = textarea.value; }); + saveValidationState('Черновик field application сохранен.'); + }); + document.querySelector('#submit-field')?.addEventListener('click', () => { + document.querySelectorAll('[data-field]').forEach((textarea) => { state.field.values[textarea.dataset.field] = textarea.value; }); + if (!fieldComplete()) { + saveValidationState(); + setMessage('Заполни все поля field application содержательно (минимум 8 символов в каждом).', true); + return; + } + state.field.submittedAt = new Date().toISOString(); + if (saveValidationState()) rerenderValidation('Field application зафиксирован.'); + }); + } + + function bindReflectionEvents() { + document.querySelectorAll('[data-reflection]').forEach((textarea) => textarea.addEventListener('change', () => { + if (state.reflection.submittedAt) return; + state.reflection.values[textarea.dataset.reflection] = textarea.value; + saveValidationState('Черновик reflection сохранен.'); + })); + document.querySelector('[data-delayed-transfer]')?.addEventListener('change', (event) => { + if (state.reflection.submittedAt) return; + state.reflection.delayedTransfer = event.currentTarget.value; + saveValidationState('Черновик delayed transfer сохранен.'); + }); + const collect = () => { + document.querySelectorAll('[data-reflection]').forEach((textarea) => { state.reflection.values[textarea.dataset.reflection] = textarea.value; }); + const delayed = document.querySelector('[data-delayed-transfer]'); + if (delayed) state.reflection.delayedTransfer = delayed.value; + }; + document.querySelector('#save-reflection')?.addEventListener('click', () => { + collect(); + saveValidationState('Черновик reflection сохранен.'); + }); + document.querySelector('#submit-reflection')?.addEventListener('click', () => { + collect(); + if (!reflectionComplete()) { + saveValidationState(); + setMessage('Ответь на все обязательные reflection prompts содержательно (минимум 12 символов).', true); + return; + } + state.reflection.submittedAt = new Date().toISOString(); + if (saveValidationState()) rerenderValidation('Reflection зафиксирован. M01 validation готов к review.'); + }); + } + + function bindReset() { + document.querySelector('#reset-validation')?.addEventListener('click', () => { + if (!window.confirm('Удалить только данные M01 validation в этом браузере? Прогресс основного курса не изменится.')) return; + try { + localStorage.removeItem(validationStorageKey); + state = emptyValidationState(); + storageHealthy = testStorage(); + rerenderValidation('M01 validation data сброшены.'); + } catch (_) { + setMessage('Не удалось сбросить данные браузера.', true); + } + }); + } + + function renderValidationRoute() { + const main = document.querySelector('#main'); + if (!main) return; + main.innerHTML = validationView(); + document.querySelectorAll('.main-nav a').forEach((link) => link.classList.toggle('active', link.dataset.route === 'course')); + bindAssessmentEvents(); + bindDrillEvents(); + bindFieldEvents(); + bindReflectionEvents(); + bindReset(); + main.focus({ preventScroll: true }); + } + + function decorateCourse() { + if (document.querySelector('[data-validation-cta]')) return; + const moduleList = document.querySelector('.module-list'); + if (!moduleList) return; + moduleList.insertAdjacentHTML('beforebegin', `
+

Phase 1 · M01

+

Проверить, изменилось ли системное мышление

+

Отдельный маршрут фиксирует baseline до обучения, затем post-case, перенос на реальный проект и reflection. Результат — learning signal, не сертификат и не автоматический mastery.

+ Начать M01 validation → +
`); + } + + function renderExtension() { + if (currentRoute() === validationRoute) renderValidationRoute(); + else if (currentRoute() === 'course') decorateCourse(); + } + + window.addEventListener('hashchange', () => queueMicrotask(renderExtension)); + renderExtension(); +})(); From c4b636d5ce77acd60cc80cff99256eccdf853f8e Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:55:47 +0500 Subject: [PATCH 024/107] docs: add M01 learner validation protocol --- docs/validation/M01-VALIDATION-PROTOCOL.md | 238 +++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 docs/validation/M01-VALIDATION-PROTOCOL.md diff --git a/docs/validation/M01-VALIDATION-PROTOCOL.md b/docs/validation/M01-VALIDATION-PROTOCOL.md new file mode 100644 index 0000000..b237d34 --- /dev/null +++ b/docs/validation/M01-VALIDATION-PROTOCOL.md @@ -0,0 +1,238 @@ +# M01 Learning Validation Protocol + +## Purpose + +This protocol tests the first PMO01 learning slice end-to-end before any scalable v1 implementation. + +The question is not whether learners enjoy the module or finish every page. The primary question is: + +> After M01, can a learner diagnose a structurally similar project problem and choose a stronger intervention with better reasoning than before? + +The experiment is exploratory. Its thresholds are operational decision rules for product development, not evidence of a statistically generalizable educational effect. + +## What is being validated + +M01 tests five reasoning dimensions: + +1. **Mechanism** — separates a visible symptom from a reproducible system mechanism. +2. **Evidence** — chooses observable evidence rather than opinions or vanity metrics. +3. **Trade-offs** — makes the cost of an intervention and alternative explanations explicit. +4. **Intervention** — acts on the diagnosed mechanism rather than compensating for a symptom. +5. **Change condition** — defines an early signal that can confirm or invalidate the intervention. + +The prototype uses one scored item per dimension in both baseline and post-case. Each item scores `0..3`; total score is `0..15`. + +## Individual learning signal + +An individual session produces a **positive prototype learning signal** only when both conditions are true: + +- post-case total is at least `baseline + 3`; +- at least two rubric dimensions improve. + +This signal is not `mastery`. + +`mastered` must not be assigned from the immediate post-case. Delayed transfer evidence requires separate review and a later mastery decision. + +## Participant profile + +Use people who can reason about real delivery work, for example: + +- Project Manager; +- Delivery Manager; +- Process Manager; +- Team Lead with delivery ownership; +- Product/operations role that regularly coordinates dependencies and decisions. + +Record experience level rather than filtering to one exact seniority. For the first cycle, aim for at least **5 completed sessions** before making a module-level promotion decision. More sessions are useful when results conflict. + +Do not use the course author as evidence of learning effectiveness. + +## Environment + +The prototype stores experiment data only in browser `localStorage` under: + +`pm01-validation-m01-v1` + +The main course continues using its own `pm01-state-v1` key. The separation prevents legacy course saves from erasing validation work. + +For each new participant: + +1. open `#/validation/m01`; +2. use **Сбросить M01 validation data** before the session, or use a fresh browser profile; +3. verify the page does not show an existing baseline score; +4. do not show the M01 lesson content before baseline is submitted. + +## Session sequence + +### 1. Baseline + +Participant reads the baseline scenario without M01 material. + +Required evidence: + +- free-text diagnosis; +- answers to all five rubric questions; +- frozen baseline score after submission. + +Observe: + +- whether the participant blames people or follows a mechanism; +- what data they ask for first; +- whether they specify an early change condition; +- hesitation or ambiguity in question wording. + +Do not coach the participant toward the system model. + +### 2. Learning experience + +Participant completes both existing M01 lessons: + +- `project-system`; +- `system-diagnostic`. + +Then they complete both Decision Drills. The first drill choice is frozen before feedback is revealed. + +Observe: + +- whether feedback explains consequences rather than merely marking correct/incorrect; +- whether a drill exposes a misconception that the lesson text did not expose; +- whether the participant can explain why their choice changed after feedback. + +### 3. Integrative post-case + +Post-case uses a different scenario but the same five reasoning dimensions. + +Required evidence: + +- new free-text diagnosis; +- all five post-case answers; +- post score; +- total delta; +- per-dimension delta; +- individual promotion-signal result. + +Do not interpret a higher total alone as success if improvement is concentrated in one dimension. + +### 4. Real-project transfer + +Participant applies the model to a current or recent real project and records: + +- project/outcome; +- visible symptom; +- proposed system mechanism; +- one minimal intervention; +- early signal; +- observed evidence or an explicit statement that it has not yet been observed; +- next decision. + +A field application is useful only when it connects diagnosis → intervention → evidence. Rephrasing the lesson is not transfer. + +### 5. Reflection + +Participant records: + +- what changed in the diagnosis; +- what changed in the first action; +- what evidence they would now obtain earlier; +- remaining uncertainty and how they would test it. + +Optional delayed-transfer text can be captured later. It is evidence for human review only and does not automatically produce `mastered`. + +## Short interview + +After the workflow, ask the same questions in every session: + +1. What did you initially think the project problem was? +2. What, if anything, changed in your diagnosis after the module? +3. Which part of the module caused that change? +4. Which feedback felt obvious, misleading, or unconvincing? +5. Could you use the model tomorrow without opening the course? Where? +6. What information would you now request earlier on a real project? +7. Where did the interface or wording interfere with your reasoning? + +Capture examples and direct observations, not only satisfaction ratings. + +## Optional delayed check + +When practical, give a new structurally similar case after a delay rather than immediately after reading. + +Record: + +- whether the learner independently reconstructs symptom → mechanism → intervention → evidence; +- whether they need the course terminology as a prompt; +- whether the reasoning transfers to a different domain; +- whether the immediate post-case improvement persists. + +Do not introduce an automatic mastery rule until delayed evidence has been reviewed across multiple sessions. + +## Session record + +For each participant record outside the product, using anonymized IDs where possible: + +| Field | Record | +|---|---| +| Participant ID | Non-identifying ID | +| Role / experience | Context for interpretation | +| Baseline total | 0–15 | +| Post total | 0–15 | +| Delta | Post − baseline | +| Improved dimensions | IDs/names | +| Individual signal | pass / not pass | +| Field application credible | yes / partial / no | +| Drill misconception exposed | description | +| Major friction | description | +| Content ambiguity | description | +| Content-model exception | description | +| Delayed transfer | not run / evidence | + +Do not store sensitive project details when an anonymized description is sufficient. + +## Module-level promotion review + +Do not migrate M01 into v1 solely because CI is green or the UI works. + +After at least 5 completed learner sessions, review these five areas: + +### 1. Learning signal + +Look at individual `+3 / 2-dimension` results and the direction of change across the cohort. Treat the threshold as a development heuristic, not a significance test. + +### 2. Transfer + +Determine whether learners can produce a credible real-project chain: + +`mechanism → intervention → early signal → evidence → next decision` + +### 3. Interaction usefulness + +Promote a Decision Drill only if it does at least one of the following: + +- exposes a meaningful reasoning difference; +- changes diagnosis/action; +- reveals a misconception worth addressing; +- improves transfer. + +A drill that only creates clicks should be removed or redesigned. + +### 4. Friction and reliability + +Block promotion for: + +- silent loss of learner work; +- confusing sequence that contaminates baseline; +- inaccessible critical controls; +- wording ambiguity that materially changes score interpretation. + +### 5. Content-model fit + +Record every special case needed to represent M01. If the module requires one-off runtime behavior that the canonical content model cannot express cleanly, resolve the content contract before v1 migration. + +## Promotion decision + +Possible outcomes: + +- **Promote** — learning mechanism and transfer evidence are strong enough to become v1 requirements. +- **Revise and retest** — promising signal, but content, rubric, interaction, or friction is materially distorting results. +- **Reject mechanism** — interaction adds complexity without useful reasoning evidence. + +Document the decision and evidence before beginning the v1 vertical slice. From 7721d3b7091c1201257cd7b69bbfddeedc770ecc Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:56:13 +0500 Subject: [PATCH 025/107] docs: document M01 validation slice --- README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c1ee772..a1235b8 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,29 @@ Не следует расширять текущие `app.js` и `course-data.js` как постоянную платформенную архитектуру. +## Phase 1 — M01 Learning Validation + +Первый вертикальный validation slice проверяет модуль **M01 «Проект как система»** до начала v1 rewrite. + +Маршрут в прототипе: + +`#/validation/m01` + +Последовательность: + +`baseline → 2 урока + Decision Drills → integrative post-case → real-project transfer → reflection` + +Baseline и post-case оценивают пять измерений reasoning: механизм, доказательства, trade-offs, вмешательство и change condition. Положительный индивидуальный learning signal требует одновременно `post ≥ baseline + 3` и улучшения минимум по двум измерениям. Это development signal, а не автоматический `mastered`. + +Экспериментальный UI и данные намеренно изолированы от legacy-монолитов: + +- `learning-domain.js` — чистая scoring/state логика; +- `m01-validation-data.js` — cases/drills/field/reflection content; +- `m01-validation-app.js` — validation route и browser persistence; +- `m01-validation.css` — отдельные стили. + +Операционный протокол реальных learner sessions: [`docs/validation/M01-VALIDATION-PROTOCOL.md`](docs/validation/M01-VALIDATION-PROTOCOL.md). + ## Документация Начать с [`docs/README.md`](docs/README.md). @@ -54,7 +77,27 @@ ## Хранение данных -Прогресс, ответы диагностики и рабочие заметки хранятся только в `localStorage` браузера. Они не отправляются на сервер. +Все данные остаются в browser `localStorage` и не отправляются на сервер. + +- `pm01-state-v1` — legacy progress, diagnostic, notes и criteria основного курса; +- `pm01-validation-m01-v1` — изолированное состояние M01 validation experiment. + +Раздельные ключи нужны, чтобы legacy `app.js` не мог случайно перезаписать экспериментальные ответы своим in-memory state. + +## Проверка + +CI использует Node.js built-in test runner и проверяет: + +- scoring/promotion/learning-state domain contracts; +- структуру M01 validation content; +- script/style integration и базовые accessibility contracts; +- JavaScript syntax для prototype runtime files. + +Локально: + +```bash +node --test tests/*.test.js +``` ## Публикация From 47e3d94970a36491d5cc13f98d484b29ce7805db Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:56:23 +0500 Subject: [PATCH 026/107] test: add JavaScript syntax verification --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3be428d..3780af9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,5 +17,10 @@ jobs: uses: actions/setup-node@v4 with: node-version: 22 + - name: Check JavaScript syntax + run: | + for file in app.js course-data.js learning-domain.js m01-validation-data.js m01-validation-app.js; do + node --check "$file" + done - name: Run tests run: node --test tests/*.test.js From a38c13c39fd7d79ae83ddc7953ef6076bd7db64a Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:57:01 +0500 Subject: [PATCH 027/107] docs: reconcile M01 validation plan with implementation --- .../2026-09-06-m01-learning-validation.md | 102 +++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md index 16d39ee..ad37dab 100644 --- a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md +++ b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md @@ -4,7 +4,7 @@ **Goal:** Turn M01 `Проект как система` into the first measurable end-to-end learning-validation slice: baseline case → lessons/drills → integrative post-case → field application → reflection/evidence. -**Architecture:** Keep the existing static prototype. Add a small pure-JavaScript learning domain module for scoring and state semantics, structured M01 validation content in `course-data.js`, and one dedicated validation route in `app.js`. Persist validation work in the existing localStorage state without introducing backend/auth/framework migration. +**Architecture:** Keep the existing static prototype and leave legacy `app.js`, `course-data.js`, and `styles.css` unchanged. Add isolated M01 extension files: a pure learning-domain module, structured validation content, a post-router UI extension, and validation-specific CSS. Persist validation work under a separate `pm01-validation-m01-v1` localStorage key so the legacy app cannot erase experiment state when it writes `pm01-state-v1`. **Tech Stack:** Static HTML/CSS/JavaScript, browser `localStorage`, Node.js built-in test runner in GitHub Actions. @@ -19,6 +19,7 @@ - Promotion signal: post-case score improves by at least 3 points and at least 2 rubric dimensions improve. - Learner work must survive ordinary reload/navigation via localStorage. - New domain logic must be testable outside the DOM. +- M01 validation data must not share a storage key with the legacy in-memory course state. --- @@ -29,12 +30,12 @@ - Create: `tests/learning-domain.test.js` **Interfaces:** -- Consumes: future CommonJS exports from `learning-domain.js`. +- Consumes: CommonJS exports from `learning-domain.js`. - Produces: executable contract for `scoreAssessment`, `promotionDecision`, and `deriveLearningState`. -- [ ] Create a GitHub Actions workflow that runs `node --test tests/*.test.js` on pushes and pull requests. -- [ ] Add tests asserting rubric totals, per-dimension scores, promotion delta + dimension gate, and the rule that immediate assessment cannot produce `mastered`. -- [ ] Open/refresh the feature PR and verify CI fails specifically because `learning-domain.js` does not yet exist. +- [x] Add GitHub Actions CI running `node --test tests/*.test.js` on pushes and pull requests. +- [x] Add tests for rubric totals, unanswered questions, promotion delta + dimension gate, and non-automatic mastery. +- [x] Verify RED in Actions: tests fail specifically because `learning-domain.js` does not exist. ### Task 2: Implement the pure learning domain @@ -42,82 +43,87 @@ - Create: `learning-domain.js` **Interfaces:** -- Produces: `scoreAssessment(questions, answers) -> { total, byDimension, max }`. +- Produces: `scoreAssessment(questions, answers) -> { total, byDimension, max, answered }`. - Produces: `promotionDecision(baseline, post, options?) -> { promoted, delta, improvedDimensions }`. - Produces: `deriveLearningState({ studied, fieldApplied, transferEvidence }) -> 'unseen'|'studied'|'applied'|'mastered'`. -- [ ] Implement a browser + CommonJS compatible module with no DOM/storage dependencies. -- [ ] Validate answer scores are integers 0..3 and ignore unanswered questions rather than inventing credit. -- [ ] Implement the default promotion gate: `delta >= 3` and at least two dimensions improve. -- [ ] Require explicit `transferEvidence` for `mastered`; `fieldApplied` alone yields `applied`. -- [ ] Verify the Task 1 test suite passes. +- [x] Implement browser + CommonJS compatible module with no DOM/storage dependencies. +- [x] Validate option scores as integers `0..3`; unanswered questions receive no invented credit. +- [x] Implement default promotion gate: `delta >= 3` and at least two improved dimensions. +- [x] Require explicit transfer evidence for the domain function to return `mastered`; immediate application yields at most `applied`. +- [x] Verify GREEN in Actions. ### Task 3: Add structured M01 validation content and content tests **Files:** -- Modify: `course-data.js` +- Create: `m01-validation-data.js` - Create: `tests/m01-content.test.js` **Interfaces:** - Produces: `window.PM01.m01Validation` with `rubricDimensions`, `baseline`, `decisionDrills`, `postCase`, `fieldApplication`, `reflection`. -- [ ] First add a failing test that evaluates `course-data.js` in a VM sandbox and checks the M01 validation contract. -- [ ] Add five stable rubric dimensions: mechanism, evidence, tradeoffs, intervention, changeCondition. -- [ ] Add a baseline case with five scored questions; each answer option carries `score: 0..3` and inspectable feedback. -- [ ] Add two decision drills mapped to the existing M01 lessons. -- [ ] Add a non-identical integrative post-case using the same five dimensions. -- [ ] Add a field-application prompt requiring a real project, intervention, expected signal, observed evidence, and next decision. -- [ ] Add reflection prompts comparing baseline reasoning with post-case reasoning. -- [ ] Verify content-contract tests pass. +- [x] Add failing VM-based content-contract test before production content. +- [x] Add five stable rubric dimensions: mechanism, evidence, tradeoffs, intervention, changeCondition. +- [x] Add a five-question baseline with inspectable `0..3` options and feedback. +- [x] Add two Decision Drills mapped to `project-system` and `system-diagnostic`. +- [x] Add a non-identical five-question integrative post-case using the same dimensions. +- [x] Add real-project field application requiring project, symptom, mechanism, intervention, signal, evidence, and next decision. +- [x] Add reflection prompts comparing baseline reasoning with later reasoning. +- [x] Verify RED before content and GREEN after content in Actions. -### Task 4: Integrate the validation route and persisted learner state +### Task 4: Integrate the validation route and isolated learner state **Files:** - Modify: `index.html` -- Modify: `app.js` +- Create: `m01-validation-app.js` - Create: `tests/static-contract.test.js` **Interfaces:** -- `index.html` loads `learning-domain.js` after `course-data.js` and before `app.js`. -- `app.js` persists `state.validation.m01` under existing `pm01-state-v1` storage. +- `index.html` loads `course-data.js → m01-validation-data.js → learning-domain.js → app.js → m01-validation-app.js`. +- Validation state persists under `pm01-validation-m01-v1`. +- Existing course state remains under `pm01-state-v1` and is read only to determine whether both M01 lessons are completed. - Route: `#/validation/m01`. -- [ ] First add failing static-contract tests for script order, route support, and the M01 validation CTA. -- [ ] Extend default state with `validation: { m01: {} }` while preserving existing stored state. -- [ ] Add a `validation/m01` route with staged sections: baseline, lesson links/drills, post-case, field application, reflection/result. -- [ ] Save assessment answers and reasoning before revealing feedback. -- [ ] Show baseline score only after baseline submission; show post score and delta only after post submission. -- [ ] Use `promotionDecision` to show a neutral validation signal, not celebratory correctness UI. -- [ ] Use `deriveLearningState` to show `studied`/`applied`; never auto-show `mastered` unless explicit transfer evidence is recorded. -- [ ] Add a CTA from M01 in the course view to the validation route without changing other modules. -- [ ] Verify static-contract and domain tests pass. +- [x] Add failing static-contract tests for script order, route ownership, course CTA, isolated storage, semantic controls, and validation CSS. +- [x] Keep validation persistence separate from legacy storage after identifying stale-state overwrite risk. +- [x] Add staged route: baseline → lesson links/drills → post-case → field application → reflection/result. +- [x] Freeze submitted baseline/post answers and first drill choices before showing feedback. +- [x] Show baseline score only after baseline submission; show post score/delta only after post submission. +- [x] Use `promotionDecision` for a neutral learning signal, explicitly not mastery. +- [x] Use `deriveLearningState` only up to `applied` in the UI; delayed transfer evidence remains human-review evidence. +- [x] Add M01 validation CTA to the course view without modifying the legacy router implementation. +- [x] Verify RED before UI extension and GREEN after integration. -### Task 5: Add validation UI styling and accessibility checks +### Task 5: Add validation-specific styling and accessibility contracts **Files:** -- Modify: `styles.css` +- Create: `m01-validation.css` - Modify: `tests/static-contract.test.js` **Interfaces:** -- Reuse existing `.question-card`, `.option`, `.practice`, `.button` patterns where possible. +- Reuses existing typography/button tokens while keeping validation selectors isolated. -- [ ] Add only the styles required for the validation stepper, rubric result rows, reasoning textareas, and evidence summary. -- [ ] Ensure all inputs have visible labels/legends and keyboard-operable native controls. -- [ ] Ensure score/learning-state meaning is conveyed by text, not color alone. -- [ ] Add static checks for required fieldsets/labels/aria-live result region. -- [ ] Verify all tests pass. +- [x] Add validation step, score, drill, evidence, result, and CTA styles. +- [x] Use native radio/fieldset/legend/textarea/button controls. +- [x] Provide explicit labels for textareas and a polite live region for persistence/validation messages. +- [x] Convey score/state meaning with text rather than color alone. +- [x] Add narrow-screen layout for score/drill grids. +- [x] Verify static contracts pass. ### Task 6: Document the experiment and prepare review **Files:** - Create: `docs/validation/M01-VALIDATION-PROTOCOL.md` - Modify: `README.md` +- Modify: `.github/workflows/ci.yml` **Interfaces:** -- Produces an operational protocol for real learner sessions and a clear statement that scores are prototype evidence, not validated mastery. - -- [ ] Document who to test with, baseline/post protocol, what to observe, interview prompts, and how to record qualitative failures. -- [ ] Define the module promotion review: learning delta, transfer evidence, interaction usefulness, friction, and content-model exceptions. -- [ ] Add a README link to the M01 validation route/protocol. -- [ ] Run/inspect GitHub Actions and require green CI before marking the PR ready. -- [ ] Review the final diff for scope creep: no backend/auth/framework migration or unrelated refactor. +- Produces a reproducible learner-session protocol and makes runtime/test boundaries visible to future contributors. + +- [x] Document participant profile, baseline/post sequence, observations, interview prompts, field transfer, delayed follow-up, and anonymized session record. +- [x] Define module-level review across learning signal, transfer, interaction usefulness, friction/reliability, and content-model fit. +- [x] Document M01 route, file boundaries, storage keys, and validation protocol in README. +- [x] Add `node --check` for legacy and validation runtime JavaScript to CI. +- [ ] Inspect final Actions run after all documentation/CI changes and require GREEN. +- [ ] Review final diff for scope creep and architecture violations. +- [ ] Update PR #2 from draft only after verification and review gates pass. From 7192ba192d10a36a5ea865ef883f7b0ac25fe8b2 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:59:41 +0500 Subject: [PATCH 028/107] test: add M01 validation runtime smoke coverage --- tests/m01-app-smoke.test.js | 94 +++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/m01-app-smoke.test.js diff --git a/tests/m01-app-smoke.test.js b/tests/m01-app-smoke.test.js new file mode 100644 index 0000000..c7a7500 --- /dev/null +++ b/tests/m01-app-smoke.test.js @@ -0,0 +1,94 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const vm = require('node:vm'); + +const domain = require('../learning-domain.js'); +const validationDataCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-data.js'), 'utf8'); +const validationAppCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-app.js'), 'utf8'); + +function storageFrom(initial = {}) { + const values = new Map(Object.entries(initial)); + return { + getItem(key) { return values.has(key) ? values.get(key) : null; }, + setItem(key, value) { values.set(key, String(value)); }, + removeItem(key) { values.delete(key); }, + }; +} + +function runValidationApp(route, initialStorage = {}) { + const main = { innerHTML: '', focus() {} }; + const moduleList = { + inserted: '', + insertAdjacentHTML(_position, html) { this.inserted = html; }, + }; + const document = { + querySelector(selector) { + if (selector === '#main') return main; + if (selector === '.module-list') return moduleList; + return null; + }, + querySelectorAll() { return []; }, + }; + const localStorage = storageFrom(initialStorage); + const window = { + PM01: {}, + PM01Learning: domain, + addEventListener() {}, + confirm() { return true; }, + }; + const context = { + window, + document, + localStorage, + location: { hash: `#/${route}` }, + queueMicrotask(callback) { callback(); }, + console, + Date, + }; + vm.createContext(context); + vm.runInContext(validationDataCode, context, { filename: 'm01-validation-data.js' }); + vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); + return { main, moduleList, localStorage }; +} + +test('fresh validation route renders baseline without exposing learning drills or post-case controls', () => { + const { main } = runValidationApp('validation/m01'); + + assert.match(main.innerHTML, /01 · Baseline/); + assert.match(main.innerHTML, /data-submit-assessment="baseline"/); + assert.doesNotMatch(main.innerHTML, /Decision Drill/); + assert.doesNotMatch(main.innerHTML, /data-submit-assessment="postCase"/); +}); + +test('post-case unlocks only after baseline, both lessons, and both drills are recorded', () => { + const validationState = { + version: 1, + baseline: { + answers: {}, + reasoning: 'Исходный диагноз уже был зафиксирован ранее.', + submittedAt: '2026-09-06T12:00:00.000Z', + score: { total: 5, max: 15, byDimension: {}, answered: 5 }, + }, + drills: { + 'm01-drill-system': { choice: 'track-dependency', selectedAt: '2026-09-06T12:10:00.000Z' }, + 'm01-drill-diagnostic': { choice: 'decision-interface', selectedAt: '2026-09-06T12:11:00.000Z' }, + }, + }; + const legacyState = { completed: ['project-system', 'system-diagnostic'] }; + const { main } = runValidationApp('validation/m01', { + 'pm01-validation-m01-v1': JSON.stringify(validationState), + 'pm01-state-v1': JSON.stringify(legacyState), + }); + + assert.match(main.innerHTML, /Decision Drill/); + assert.match(main.innerHTML, /data-submit-assessment="postCase"/); +}); + +test('course route receives a single M01 validation CTA from the extension', () => { + const { moduleList } = runValidationApp('course'); + + assert.match(moduleList.inserted, /data-validation-cta/); + assert.match(moduleList.inserted, /#\/validation\/m01/); +}); From 7d2507d146bc0761672c836a6c64e9941f1f16ed Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 19:59:50 +0500 Subject: [PATCH 029/107] ci: update GitHub Actions runtime versions --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3780af9..2a3eb00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,9 +12,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@v7 with: node-version: 22 - name: Check JavaScript syntax From b8133d0f64be5a402f84b55ccc7cf349178cdc9a Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:01:21 +0500 Subject: [PATCH 030/107] test: keep baseline results blind until post-case --- tests/m01-app-smoke.test.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/m01-app-smoke.test.js b/tests/m01-app-smoke.test.js index c7a7500..0663415 100644 --- a/tests/m01-app-smoke.test.js +++ b/tests/m01-app-smoke.test.js @@ -86,6 +86,25 @@ test('post-case unlocks only after baseline, both lessons, and both drills are r assert.match(main.innerHTML, /data-submit-assessment="postCase"/); }); +test('submitted baseline stays blind: score and option feedback are hidden until post-case is complete', () => { + const validationState = { + version: 1, + baseline: { + answers: { 'baseline-mechanism': 'people' }, + reasoning: 'Я зафиксировал исходный диагноз до изучения материала.', + submittedAt: '2026-09-06T12:00:00.000Z', + score: { total: 0, max: 15, byDimension: { mechanism: 0 }, answered: 5 }, + }, + }; + const { main } = runValidationApp('validation/m01', { + 'pm01-validation-m01-v1': JSON.stringify(validationState), + }); + + assert.doesNotMatch(main.innerHTML, /итог по rubric/); + assert.doesNotMatch(main.innerHTML, /Это объясняет проблему качествами людей/); + assert.match(main.innerHTML, /результат скрыт до post-case/); +}); + test('course route receives a single M01 validation CTA from the extension', () => { const { moduleList } = runValidationApp('course'); From 52d5094f0751ef47e564ce90b410b5467352d6b7 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:04:48 +0500 Subject: [PATCH 031/107] fix: keep baseline results blind until post-case --- m01-validation-app.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/m01-validation-app.js b/m01-validation-app.js index 4a2bd9b..c6ff836 100644 --- a/m01-validation-app.js +++ b/m01-validation-app.js @@ -108,10 +108,6 @@ target.dataset.error = error ? 'true' : 'false'; } - function dimensionLabel(id) { - return DATA.rubricDimensions.find((item) => item.id === id)?.label || id; - } - function assessmentComplete(assessment, draft) { return assessment.questions.every((question) => draft.answers[question.id]) && draft.reasoning.trim().length >= 20; } @@ -119,6 +115,7 @@ function assessmentBlock(assessment, key, heading, unlocked) { const draft = state[key]; const submitted = Boolean(draft.submittedAt); + const revealResults = submitted && (key !== 'baseline' || Boolean(state.postCase.submittedAt)); if (!unlocked) { return `

${escapeHtml(heading)}

${escapeHtml(assessment.title)}

Этот этап откроется после предыдущих шагов.

@@ -132,7 +129,7 @@ return `${submitted && checked ? `

${escapeHtml(item.feedback)}

` : ''}`; + ${revealResults && checked ? `

${escapeHtml(item.feedback)}

` : ''}`; }).join(''); return `
${escapeHtml(question.prompt)} @@ -140,12 +137,16 @@
`; }).join(''); - const score = submitted && draft.score ? `
+ const score = revealResults && draft.score ? `
${draft.score.total}/${draft.score.max}итог по rubric
${draft.score.answered}/${assessment.questions.length}зафиксировано ответов
зафиксированответ нельзя менять после submit
` : ''; + const submittedMessage = key === 'baseline' && !state.postCase.submittedAt + ? 'Baseline зафиксирован; результат скрыт до post-case, чтобы не загрязнять измерение.' + : 'Ответ зафиксирован. Feedback открыт после завершения измерения.'; + return `

${escapeHtml(heading)}

${escapeHtml(assessment.title)}

${escapeHtml(assessment.scenario)}

${escapeHtml(assessment.reasoningPrompt)}
@@ -153,7 +154,7 @@ ${questions} ${score} - ${submitted ? '

Ответ зафиксирован. Feedback показан только после фиксации выбора.

' : `
`} + ${submitted ? `

${submittedMessage}

` : `
`}
`; } @@ -443,4 +444,4 @@ window.addEventListener('hashchange', () => queueMicrotask(renderExtension)); renderExtension(); -})(); +})(); \ No newline at end of file From aed3bc4458cedf0e4320a242068fe3278925ebb8 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:05:58 +0500 Subject: [PATCH 032/107] docs: keep baseline assessment blind until post-case --- docs/validation/M01-VALIDATION-PROTOCOL.md | 25 +++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/validation/M01-VALIDATION-PROTOCOL.md b/docs/validation/M01-VALIDATION-PROTOCOL.md index b237d34..d479b66 100644 --- a/docs/validation/M01-VALIDATION-PROTOCOL.md +++ b/docs/validation/M01-VALIDATION-PROTOCOL.md @@ -62,6 +62,22 @@ For each new participant: 3. verify the page does not show an existing baseline score; 4. do not show the M01 lesson content before baseline is submitted. +## Measurement blinding rule + +Baseline answers and the computed score are persisted and frozen at submission, but the learner must **not** see baseline score or option-level feedback before completing the post-case. + +This rule prevents the assessment itself from becoming an unplanned teaching intervention. The participant may still see their own frozen choices and free-text diagnosis; explanatory feedback and score remain blind until post-case submission. + +After post-case submission the prototype may reveal: + +- baseline score; +- post-case score; +- per-dimension changes; +- option feedback; +- the prototype promotion signal. + +Any future implementation of the same experiment must preserve this blinding rule unless a new validation design explicitly replaces it. + ## Session sequence ### 1. Baseline @@ -72,7 +88,8 @@ Required evidence: - free-text diagnosis; - answers to all five rubric questions; -- frozen baseline score after submission. +- frozen baseline score stored after submission; +- baseline score and option feedback hidden from the learner until post-case completion. Observe: @@ -81,7 +98,7 @@ Observe: - whether they specify an early change condition; - hesitation or ambiguity in question wording. -Do not coach the participant toward the system model. +Do not coach the participant toward the system model and do not disclose whether any baseline answer is strong or weak. ### 2. Learning experience @@ -107,6 +124,7 @@ Required evidence: - new free-text diagnosis; - all five post-case answers; - post score; +- baseline score revealed only now; - total delta; - per-dimension delta; - individual promotion-signal result. @@ -219,7 +237,8 @@ A drill that only creates clicks should be removed or redesigned. Block promotion for: - silent loss of learner work; -- confusing sequence that contaminates baseline; +- baseline score or explanatory feedback leaking before post-case completion; +- confusing sequence that otherwise contaminates baseline; - inaccessible critical controls; - wording ambiguity that materially changes score interpretation. From 050ea6d26197ba68f8e82d8dc34306f4c6678332 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:25:38 +0500 Subject: [PATCH 033/107] docs: finalize M01 validation verification checklist --- .../plans/2026-09-06-m01-learning-validation.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md index ad37dab..0ada375 100644 --- a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md +++ b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md @@ -20,6 +20,7 @@ - Learner work must survive ordinary reload/navigation via localStorage. - New domain logic must be testable outside the DOM. - M01 validation data must not share a storage key with the legacy in-memory course state. +- Baseline score and option-level explanatory feedback must remain blind until post-case submission so the assessment itself does not become an unplanned teaching intervention. --- @@ -77,6 +78,7 @@ - Modify: `index.html` - Create: `m01-validation-app.js` - Create: `tests/static-contract.test.js` +- Create: `tests/m01-app-smoke.test.js` **Interfaces:** - `index.html` loads `course-data.js → m01-validation-data.js → learning-domain.js → app.js → m01-validation-app.js`. @@ -87,8 +89,11 @@ - [x] Add failing static-contract tests for script order, route ownership, course CTA, isolated storage, semantic controls, and validation CSS. - [x] Keep validation persistence separate from legacy storage after identifying stale-state overwrite risk. - [x] Add staged route: baseline → lesson links/drills → post-case → field application → reflection/result. -- [x] Freeze submitted baseline/post answers and first drill choices before showing feedback. -- [x] Show baseline score only after baseline submission; show post score/delta only after post submission. +- [x] Freeze submitted baseline/post answers and first drill choices at submission. +- [x] Keep baseline score and option-level feedback blind after baseline submission; reveal comparison/results only after post-case submission. +- [x] Show post score, baseline/post comparison, dimension deltas, and promotion signal only after post-case submission. +- [x] Add a VM runtime smoke test for route rendering, post-case gates, course CTA injection, and baseline blinding. +- [x] Verify the baseline-blinding regression test fails before the fix and passes after the fix. - [x] Use `promotionDecision` for a neutral learning signal, explicitly not mastery. - [x] Use `deriveLearningState` only up to `applied` in the UI; delayed transfer evidence remains human-review evidence. - [x] Add M01 validation CTA to the course view without modifying the legacy router implementation. @@ -122,8 +127,10 @@ - [x] Document participant profile, baseline/post sequence, observations, interview prompts, field transfer, delayed follow-up, and anonymized session record. - [x] Define module-level review across learning signal, transfer, interaction usefulness, friction/reliability, and content-model fit. +- [x] Document the baseline measurement-blinding rule and its rationale. - [x] Document M01 route, file boundaries, storage keys, and validation protocol in README. - [x] Add `node --check` for legacy and validation runtime JavaScript to CI. -- [ ] Inspect final Actions run after all documentation/CI changes and require GREEN. -- [ ] Review final diff for scope creep and architecture violations. -- [ ] Update PR #2 from draft only after verification and review gates pass. +- [x] Upgrade GitHub Actions runtime dependencies to current major versions used by the workflow. +- [x] Inspect final Actions run after documentation/CI changes and require GREEN. +- [x] Review final diff for scope creep and architecture violations: Phase 1 only; no backend/auth/framework migration; legacy `app.js`, `course-data.js`, and `styles.css` unchanged. +- [ ] Update PR #2 from draft only after the verification run for this final plan-reconciliation commit is GREEN. From d5381848a392836a7e3b4185ffb1841370021cf1 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:32:01 +0500 Subject: [PATCH 034/107] docs: audit M01 curriculum and content-model readiness --- docs/validation/M01-READINESS-AUDIT.md | 350 +++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 docs/validation/M01-READINESS-AUDIT.md diff --git a/docs/validation/M01-READINESS-AUDIT.md b/docs/validation/M01-READINESS-AUDIT.md new file mode 100644 index 0000000..c88659e --- /dev/null +++ b/docs/validation/M01-READINESS-AUDIT.md @@ -0,0 +1,350 @@ +# M01 Validation Readiness Audit + +## Purpose + +This audit closes the non-human part of PMO01 Phase 1 before real learner sessions. + +It checks whether the current M01 validation slice: + +1. has an explicit curriculum/competency target; +2. exercises the learning model rather than page completion; +3. can later be represented by the canonical content model without turning experimental measurement mechanics into permanent product requirements; +4. has known gaps documented before learner evidence is collected. + +This document is a **readiness audit**, not a Phase 1 exit decision. Phase 1 cannot exit without real learner evidence. + +## Sources reviewed + +- `docs/product/CURRICULUM.md` +- `docs/product/LEARNING_MODEL.md` +- `docs/product/METRICS.md` +- `docs/content/CONTENT_MODEL.md` +- current M01 candidate lessons in `course-data.js` +- `m01-validation-data.js` +- `docs/validation/M01-VALIDATION-PROTOCOL.md` + +## Executive result + +**Status: READY FOR LEARNER TESTING / NOT READY FOR PHASE 2.** + +The M01 slice is coherent enough to run the first real validation cohort. Its core learning target is narrow enough to measure, its interactions test meaningful PM reasoning, and no current observation requires a new permanent content entity solely to support the baseline/post experiment. + +The remaining Phase 1 blocker is empirical: + +> Run real learner sessions and determine whether M01 changes diagnosis/intervention reasoning and transfers to real project work. + +Do not freeze v1 contracts or select a framework before that gate. + +--- + +## 1. Curriculum scope + +### Recommended module competency contract + +M01 should be treated as targeting: + +- **Primary: C1 — System diagnosis** +- **Secondary: C8 — Intervention design** + +M01 uses examples involving dependencies, decisions, information, and feedback, but it should **not** claim mastery of C3, C6, or C7 from this module alone. Those concepts appear as evidence inside system diagnosis; their full competency treatment belongs to later curriculum units. + +This keeps the measured construct narrow enough for a meaningful pre/post comparison. + +### Candidate module outcome + +Current prototype outcome: + +> Построить карту семи потоков реального проекта и найти главный системный разрыв. + +Recommended canonical outcome after validation: + +> Given evidence from a real or described project, the learner can distinguish a visible symptom from a reproducible system mechanism, identify the most relevant flow break, choose a first intervention aimed at that mechanism, and state an early condition for changing course. + +This outcome is observable and maps directly to C1 + C8 and the five validation rubric dimensions. + +--- + +## 2. Lesson outcome audit + +| Lesson | Current instructional role | Recommended observable outcome | Competencies | Status | +|---|---|---|---|---| +| `project-system` | Changes unit of analysis from task/date to system/flow | Given project evidence, map the seven flows, separate facts from interpretations, and identify one system break that plausibly explains downstream symptoms. | C1 | Ready to test; canonical metadata missing | +| `system-diagnostic` | Traces symptom to reproducible system condition before intervening | Given a recurring project symptom, trace symptom → mechanism → system condition, choose an intervention aimed at the mechanism, and define an early signal that could invalidate the intervention. | C1, C8 | Ready to test; canonical metadata missing | + +### Findings + +- The lessons have distinct instructional jobs; they are not duplicates. +- Both practices require observable learner work rather than recall. +- The second lesson extends the first from system framing into intervention design. +- Current prototype fields use `thesis`, `body`, `model`, `practice`, and `criteria`, not the future canonical lesson metadata. This is a **migration transformation**, not a reason to modify the prototype now. + +### Learner-test question + +Watch for whether participants actually distinguish the two lessons: + +- Lesson 1 should change **where they look**. +- Lesson 2 should change **how they explain cause and choose action**. + +If learners experience both as the same idea, revise or merge before migration. + +--- + +## 3. Rubric audit + +The M01 validation rubric uses five dimensions: + +1. mechanism; +2. evidence; +3. trade-offs; +4. intervention; +5. change condition. + +This is consistent with: + +- C1: symptom/mechanism/system diagnosis; +- C8: intervention, expected effect, risk/trade-off, early signal/change condition; +- the L1 diagnostic-reasoning metric in `METRICS.md`; +- the Assessment Rubric examples in `CONTENT_MODEL.md`. + +### Decision + +**Keep the five dimensions stable for the first learner cohort.** + +Do not tune weights or thresholds between participants unless a question is clearly invalid. Changing rubric semantics mid-cohort would make pre/post and participant comparisons harder to interpret. + +The current `+3 total / 2 improved dimensions` rule remains a prototype development signal, not a mastery threshold or statistical claim. + +--- + +## 4. Decision Drill audit + +### `m01-drill-system` + +What it tests: + +- symptom response vs system response; +- whether an external dependency becomes an explicit managed node rather than hidden waiting time. + +Why it is useful: + +- all choices are plausible management reactions; +- the preferred answer changes the system rather than blaming the late task; +- feedback explains why local schedule compensation is weaker. + +Risk to observe: + +- the preferred answer may be too obvious after immediately reading the lesson. + +Learner-session signal: + +- useful if it exposes a misconception, causes a rationale change, or helps later transfer; +- weak if nearly everyone selects the preferred choice instantly and cannot articulate a meaningful contrast. + +### `m01-drill-diagnostic` + +What it tests: + +- stopping the causal chain at a reproducible decision/interface condition rather than QA or an individual developer. + +Why it is useful: + +- it directly tests C1 and C8; +- it distinguishes downstream detection from upstream mechanism; +- feedback explains the system condition rather than only marking correctness. + +Risk to observe: + +- wording around “где остановить причинную цепь” may require facilitator observation for ambiguity. + +### Content-model fit + +Canonical Decision Drill requires structured fields such as competencies and analysis of mechanism/trade-offs/change conditions. + +The prototype currently stores most analysis as per-choice feedback. Before v1 migration this should be transformed into structured canonical analysis, but no new entity type is required. + +**Classification: representable with enrichment; no content-model exception.** + +--- + +## 5. Integrative post-case audit + +The post-case is structurally similar to baseline but uses a different project scenario. It exercises all five rubric dimensions and requires free-text diagnosis before the scored choices. + +It satisfies the learning-model intent to test: + +- diagnosis; +- missing/relevant evidence; +- trade-off reasoning; +- intervention choice; +- change condition. + +### Content-model fit + +The canonical Integrative Case expects: + +- case evidence; +- diagnosis prompt; +- intervention decision; +- rationale prompt; +- evidence/change-condition prompt; +- assessment rubric. + +All of these concepts exist in the current post-case, although some are encoded as separate scored questions rather than named canonical fields. + +Before migration, convert the case into canonical structured content and reference an explicit rubric ID/version. + +**Classification: representable by canonical Integrative Case + Assessment Rubric; no new entity required.** + +--- + +## 6. Field Application audit + +Current M01 field application collects: + +- project/context and outcome; +- symptom; +- mechanism; +- intervention; +- early signal; +- observed evidence; +- next decision. + +This is a strong match to the intended transfer chain: + +`diagnosis → intervention → evidence → updated decision` + +### Content-model gaps before migration + +The canonical Field Application should make these properties explicit rather than infer them from UI fields: + +- `context_requirement`; +- `action_steps`; +- `expected_artifact`; +- `evidence_criteria`; +- `reflection_prompts`; +- `privacy_guidance`. + +The prototype contains enough material to derive these properties, but they are not yet first-class metadata. + +Recommended expected artifact for M01: + +> A short intervention record containing the observed symptom, proposed system mechanism, one minimal intervention, an early signal, observed evidence, and the next decision. + +Recommended evidence criteria: + +- mechanism is not merely a person or symptom; +- intervention acts on the proposed mechanism; +- early signal is observable before the final outcome where possible; +- evidence distinguishes observed result from an untested expectation; +- next decision follows from evidence rather than from course completion. + +Recommended privacy guidance: + +> Use anonymized project descriptions and omit confidential names, customer data, credentials, commercial terms, or sensitive internal metrics when they are not necessary for reasoning. + +**Classification: representable with metadata enrichment; no content-model exception.** + +--- + +## 7. Reflection audit + +The current reflection prompts ask what changed in: + +- diagnosis; +- first action; +- evidence requirements; +- remaining uncertainty. + +This maps directly to L3 Reflection Quality in `METRICS.md` and the learning-model requirement to update the learner's mental model. + +Reflection should remain qualitative during Phase 1. Do not add an automatic “reflection score” before learner samples show a stable, useful classification rule. + +**Classification: representable as Field Application / module reflection prompts.** + +--- + +## 8. What is validation harness vs production content + +This boundary is critical for v1 quality. + +### Validation harness — do not automatically migrate as permanent content entities + +- baseline case used to establish pre-learning reasoning; +- pre/post blinding behavior; +- prototype promotion threshold (`+3` / 2 dimensions); +- cohort/session measurement protocol; +- facilitator interview; +- comparison display used to inspect learning delta. + +These exist to **measure the product while it is being validated**. They are not automatically part of the learner-facing production curriculum. + +### Candidate production learning content + +- M01 lesson outcomes/content; +- Decision Drills that prove useful; +- integrative case; +- field application; +- reflection prompts that support transfer; +- assessment rubric semantics that remain useful after learner testing. + +### Consequence + +Do **not** add a permanent `BaselineAssessment` entity to `CONTENT_MODEL.md` merely because the Phase 1 experiment uses one. + +If future product requirements later need diagnostic/pre-assessment content in production, specify that separately from the current validation harness. + +--- + +## 9. Learner-state boundary + +The current prototype stores validation work separately in `pm01-validation-m01-v1` and reads legacy lesson completion from `pm01-state-v1`. + +This is appropriate for the reference prototype but is not the future learner repository contract. + +For v1, learner records should eventually reference: + +- program version; +- immutable content ID/version; +- interaction/attempt identity where needed; +- learning-state transition evidence. + +Do not design those stable contracts until Phase 1 evidence confirms which interactions and evidence must survive migration. + +--- + +## 10. Phase 1 promotion-gate status + +| Gate | Current status | Evidence still needed | +|---|---|---| +| Explicit learning outcomes | **Provisionally pass** | Confirm wording after learner observations | +| Lesson → competency mapping | **Provisionally pass** | C1 primary, C8 secondary; validate scope | +| Useful Decision Drill | **Implemented / unproven** | Real learner misconception/decision evidence | +| Integrative case | **Implemented / unproven** | Pre/post reasoning delta with real learners | +| Field transfer possible | **Implemented / unproven** | Credible real-project applications | +| Rubric usable | **Implemented / unproven** | Check ambiguity and discrimination in sessions | +| Major learner friction known | **Not passed** | Must observe real sessions | +| No unresolved content-model special case | **Provisionally pass** | Re-check after learner-driven revisions | + +## Decision + +### Current decision: RUN LEARNER SESSIONS + +Do not start Phase 2 contract freeze yet. + +Run at least **5 completed M01 sessions** using `M01-VALIDATION-PROTOCOL.md` and the session record template. Then review: + +1. diagnostic reasoning delta; +2. improved rubric dimensions; +3. drill usefulness; +4. credible field transfer; +5. reflection quality; +6. interface/content friction; +7. any learner-driven content-model exception. + +Possible gate outcomes remain: + +- **Promote to Phase 2** — evidence supports the learning format and no unresolved content-domain ambiguity remains; +- **Revise and retest** — interaction/content/rubric/friction distorts the result; +- **Reject mechanism** — an interaction adds complexity without useful learning evidence. + +Until one of those decisions is documented, Phase 2 is intentionally blocked. From 9b68ef221eee2d5224bb31b2663b6271c92cb410 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:32:54 +0500 Subject: [PATCH 035/107] docs: add M01 learner session record template --- .../validation/M01-SESSION-RECORD-TEMPLATE.md | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 docs/validation/M01-SESSION-RECORD-TEMPLATE.md diff --git a/docs/validation/M01-SESSION-RECORD-TEMPLATE.md b/docs/validation/M01-SESSION-RECORD-TEMPLATE.md new file mode 100644 index 0000000..10a0c0a --- /dev/null +++ b/docs/validation/M01-SESSION-RECORD-TEMPLATE.md @@ -0,0 +1,178 @@ +# M01 Learner Session Record Template + +> Copy this file for each participant. Use a non-identifying participant ID. Do not record sensitive project details unless they are essential to the learning observation. + +## Session metadata + +- **Participant ID:** +- **Date:** +- **Role:** +- **Experience level:** +- **Facilitator:** +- **Browser/device:** +- **Fresh validation state confirmed:** yes / no + +## Baseline + +- **Baseline total (0–15):** +- **Mechanism:** +- **Evidence:** +- **Trade-offs:** +- **Intervention:** +- **Change condition:** +- **Free-text diagnosis summary:** +- **Observed reasoning pattern:** +- **Question ambiguity/friction:** + +> Baseline score and explanatory feedback must remain hidden from the participant until post-case submission. + +## Learning experience + +### Lesson 1 — `project-system` + +- What appeared to change in where the participant looked for the problem? +- Did they distinguish facts from interpretations? +- Did the seven-flow model help or add noise? +- Friction/ambiguity: + +### Decision Drill — `m01-drill-system` + +- **First choice:** +- **Why:** +- **Did feedback change the rationale?** yes / no / unclear +- **Misconception exposed:** +- **Interaction useful?** yes / partial / no +- **Reason:** + +### Lesson 2 — `system-diagnostic` + +- Could the participant distinguish symptom → mechanism → system condition? +- Did they move away from person-level explanations? +- Could they connect intervention to mechanism? +- Friction/ambiguity: + +### Decision Drill — `m01-drill-diagnostic` + +- **First choice:** +- **Why:** +- **Did feedback change the rationale?** yes / no / unclear +- **Misconception exposed:** +- **Interaction useful?** yes / partial / no +- **Reason:** + +## Integrative post-case + +- **Post total (0–15):** +- **Mechanism:** +- **Evidence:** +- **Trade-offs:** +- **Intervention:** +- **Change condition:** +- **Total delta:** +- **Improved dimensions:** +- **Prototype individual signal:** pass / not pass +- **Free-text diagnosis summary:** +- **What changed compared with baseline:** +- **Question ambiguity/friction:** + +## Field application + +- **Real/recent project used:** yes / no +- **Description sufficiently anonymized:** yes / no / n/a +- **Mechanism credible:** yes / partial / no +- **Intervention acts on mechanism:** yes / partial / no +- **Early signal observable:** yes / partial / no +- **Evidence actually observed:** yes / not yet / unclear +- **Next decision follows from evidence:** yes / partial / no +- **Overall transfer:** credible / partial / restatement only / not completed +- **Facilitator notes:** + +## Reflection + +Classify each item based on the participant's response. + +- **Changed diagnosis:** clear / partial / absent +- **Changed planned action:** clear / partial / absent +- **New evidence requirement:** clear / partial / absent +- **Remaining uncertainty made testable:** clear / partial / absent +- **Mostly restated lesson content:** yes / no / mixed + +Key quote or paraphrased observation: + +## Short interview + +### 1. What did you initially think the project problem was? + +Notes: + +### 2. What changed in your diagnosis after the module? + +Notes: + +### 3. Which part of the module caused that change? + +Notes: + +### 4. Which feedback felt obvious, misleading, or unconvincing? + +Notes: + +### 5. Could you use the model tomorrow without opening the course? Where? + +Notes: + +### 6. What information would you now request earlier on a real project? + +Notes: + +### 7. Where did the interface or wording interfere with your reasoning? + +Notes: + +## Reliability and UX observations + +- Any response/state loss: yes / no +- Reload/navigation recovery worked: yes / no / not tested +- Persistence error surfaced explicitly if applicable: yes / no / n/a +- Keyboard-critical path issue: yes / no / not tested +- Major abandonment/friction point: +- Other prototype defect: + +## Content-model observations + +Did the learner reveal any requirement that the current production content model cannot represent without a one-off exception? + +- yes / no / unclear + +If yes, describe the requirement without proposing architecture prematurely: + +## Delayed follow-up + +- **Run:** yes / no +- **Delay:** +- **New case/domain:** +- **Independent reconstruction of mechanism:** yes / partial / no +- **Intervention matched diagnosis:** yes / partial / no +- **Change condition/evidence stated:** yes / partial / no +- **Needed course terminology as prompt:** yes / no +- **Transfer persisted:** yes / partial / no / unclear + +## Session-level review + +- **Learning signal:** positive / mixed / absent / uninterpretable +- **Transfer evidence:** strong / partial / absent +- **Interaction usefulness:** strong / mixed / weak +- **Major friction:** none / manageable / blocking +- **Rubric ambiguity:** none / manageable / blocking +- **Recommended action for M01:** keep / revise / investigate / exclude session +- **Reason:** + +## Data handling + +Do not store: + +- credentials or access tokens; +- customer/person names when unnecessary; +- confidential contract/commercial terms; +- sensitive internal metrics when an abstract value or direction is sufficient; +- personal data not required to interpret the learning result. From 1130e229d6eb8e44960467fc7a1b18b07dac048b Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:33:26 +0500 Subject: [PATCH 036/107] docs: add M01 cohort review decision template --- docs/validation/M01-COHORT-REVIEW-TEMPLATE.md | 231 ++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 docs/validation/M01-COHORT-REVIEW-TEMPLATE.md diff --git a/docs/validation/M01-COHORT-REVIEW-TEMPLATE.md b/docs/validation/M01-COHORT-REVIEW-TEMPLATE.md new file mode 100644 index 0000000..9913f74 --- /dev/null +++ b/docs/validation/M01-COHORT-REVIEW-TEMPLATE.md @@ -0,0 +1,231 @@ +# M01 Cohort Review Template + +Use this document after the first learner cohort is complete. The first review should normally include at least **5 completed sessions**. + +This is a product/learning decision record, not a statistical significance report. + +## Cohort metadata + +- **Review date:** +- **Sessions included:** +- **Sessions excluded:** +- **Reason for exclusions:** +- **Experience mix:** +- **Facilitator(s):** + +## Per-session summary + +| Participant ID | Baseline | Post | Delta | Improved dimensions | Individual signal | Field transfer | Major friction | Delayed follow-up | +|---|---:|---:|---:|---:|---|---|---|---| +| | | | | | | | | | +| | | | | | | | | | +| | | | | | | | | | +| | | | | | | | | | +| | | | | | | | | | + +## L1 — Diagnostic reasoning delta + +Review the direction and distribution of change rather than only the average. + +- **Positive individual signals (`+3` and ≥2 improved dimensions):** +- **No-signal sessions:** +- **Negative-delta sessions:** +- **Median delta:** +- **Range:** + +### Dimension-level pattern + +| Dimension | Improved | Unchanged | Worse | Interpretation | +|---|---:|---:|---:|---| +| Mechanism | | | | | +| Evidence | | | | | +| Trade-offs | | | | | +| Intervention | | | | | +| Change condition | | | | | + +Questions: + +1. Is improvement broad across reasoning dimensions or concentrated in one item? +2. Is any item too easy/hard to discriminate before and after learning? +3. Is wording ambiguity a plausible alternative explanation for score movement? +4. Does free-text reasoning support the same conclusion as multiple-choice scores? + +## L2 — Transfer + +For participants who studied M01: + +- **Credible field applications:** +- **Partial applications:** +- **Restatement-only / no transfer:** +- **Not completed:** + +Prototype transfer rate: + +```text +credible or policy-defined completed field applications +------------------------------------------------------- +participants who studied the module +``` + +Do not interpret a filled form as transfer when intervention/evidence does not follow from the diagnosis. + +### Common transfer patterns + +- Mechanisms learners identified: +- Interventions learners attempted: +- Signals/evidence learners used: +- Where learners reverted to symptom/person-level reasoning: + +## L3 — Reflection quality + +Count/classify whether reflections show: + +- changed diagnosis; +- changed planned action; +- new evidence requirement; +- testable remaining uncertainty; +- lesson restatement without mental-model change. + +Key qualitative patterns: + +## L4 — Delayed retrieval / application + +- **Delayed checks run:** +- **Independent transfer retained:** +- **Partial:** +- **Absent:** + +Observations: + +- Did participants reconstruct the model without course vocabulary prompts? +- Did transfer survive a different project domain? +- Did immediate post-case gains persist? + +Do not introduce a `mastered` rule from a small cohort. Record evidence only. + +## Decision Drill review + +### `m01-drill-system` + +- Preferred choice selected immediately by most learners: yes / no / unclear +- Meaningful misconception exposed: yes / no / mixed +- Feedback changed reasoning: yes / no / mixed +- Transfer contribution observed: yes / no / unclear +- Recommendation: keep / revise / remove +- Reason: + +### `m01-drill-diagnostic` + +- Preferred choice selected immediately by most learners: yes / no / unclear +- Meaningful misconception exposed: yes / no / mixed +- Feedback changed reasoning: yes / no / mixed +- Transfer contribution observed: yes / no / unclear +- Recommendation: keep / revise / remove +- Reason: + +## Integrative case review + +- Case felt structurally similar but non-identical to baseline: yes / no / mixed +- Case tested multiple concepts rather than recall: yes / no / mixed +- Scoring dimensions remained interpretable: yes / no / mixed +- Free-text diagnosis added useful evidence beyond choices: yes / no / mixed +- Recommendation: keep / revise / replace + +## Rubric review + +For each dimension, note ambiguity, ceiling/floor effects, or mismatch between option score and observed reasoning. + +| Dimension | Keep | Revise | Evidence | +|---|---|---|---| +| Mechanism | | | | +| Evidence | | | | +| Trade-offs | | | | +| Intervention | | | | +| Change condition | | | | + +### Promotion threshold review + +The current `+3 total / ≥2 dimensions` rule is a development heuristic. + +- Did it classify sessions in a way consistent with qualitative reasoning evidence? +- Did it create obvious false positives? +- Did it create obvious false negatives? +- Recommendation: keep for next prototype cycle / revise / stop using + +Do not convert this threshold into mastery semantics. + +## Reliability / UX review + +Count and classify: + +- response/state loss; +- reload/navigation recovery failures; +- persistence errors; +- keyboard/accessibility blockers; +- route/sequence confusion; +- baseline contamination; +- abandonment points; +- wording/UI interfering with reasoning. + +### Blocking defects + +List defects that invalidate or materially distort learner evidence: + +## Content-contract fit review + +Start from `M01-READINESS-AUDIT.md` and record whether learner-driven revisions create a real new content-domain requirement. + +### Expected migration enrichments + +These are not special cases by themselves: + +- competency/outcome metadata; +- structured drill analysis; +- explicit rubric IDs/versioning; +- explicit field-application artifact/evidence/privacy metadata. + +### New exceptions discovered + +List only requirements that cannot be represented cleanly by the current canonical entities: + +## Phase 1 exit decision + +Choose exactly one: + +### A. Promote to Phase 2 + +Use only when: + +- learner evidence indicates meaningful reasoning improvement or useful discrimination; +- field transfer is credible enough to preserve as a requirement; +- major learner friction is known and non-blocking; +- no unresolved content-domain ambiguity remains. + +### B. Revise and retest + +Use when the learning mechanism looks promising but: + +- wording/rubric distorts measurement; +- an interaction is weak; +- transfer is incomplete; +- UX/reliability interferes with evidence; +- content-contract assumptions changed materially. + +### C. Reject mechanism + +Use when an interaction or module format adds complexity without producing useful reasoning or transfer evidence. + +## Recorded decision + +- **Decision:** A / B / C +- **Rationale:** +- **Evidence supporting the decision:** +- **Required changes before next gate:** +- **Owner:** +- **Date:** + +## Phase 2 authorization + +Phase 2 contract freeze is authorized only if the recorded decision is **A. Promote to Phase 2**. + +If the decision is B or C, do not begin v1 domain freeze or framework selection. From b8382942031b9f7b6b01657580d9ca812873a400 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:33:42 +0500 Subject: [PATCH 037/107] docs: add Phase 1 validation operating documents --- docs/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/README.md b/docs/README.md index 94b496b..0749751 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,6 +25,12 @@ The reference prototype is not the long-term architecture. ### Content - `content/CONTENT_MODEL.md` — canonical entities and rules for lessons, drills, cases, assessments, and artifacts. +### Validation +- `validation/M01-VALIDATION-PROTOCOL.md` — operational protocol for the first end-to-end learner validation cycle. +- `validation/M01-READINESS-AUDIT.md` — curriculum, learning-model, and content-contract audit before real learner sessions. +- `validation/M01-SESSION-RECORD-TEMPLATE.md` — per-participant evidence and facilitator observation template. +- `validation/M01-COHORT-REVIEW-TEMPLATE.md` — cohort synthesis and explicit Phase 1 exit decision record. + ### Architecture - `architecture/ARCHITECTURE.md` — current-state and target-state architecture. - `architecture/adr/0001-reference-prototype-to-v1.md` — decision record for the A → C strategy. @@ -36,6 +42,12 @@ The reference prototype is not the long-term architecture. - `superpowers/specs/2026-09-03-pmo01-v0-design.md` — historical V0 design. It remains useful context but is not the current architecture source of truth. - `superpowers/specs/2026-09-06-pmo01-platform-foundation-design.md` — current platform foundation design. +## Current gate + +The technical/reference-prototype work required to run the first M01 validation cohort is prepared. The project remains in **Phase 1 — Validate the learning model** until real learner evidence is reviewed. + +Do not begin Phase 2 contract freeze solely because the prototype and CI are ready. Phase 2 requires an explicit cohort decision recorded with `validation/M01-COHORT-REVIEW-TEMPLATE.md`. + ## Source-of-truth precedence When documents conflict, use this order: From 261f260d21208c3a66f4156acf363983b0ee4cdf Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:33:59 +0500 Subject: [PATCH 038/107] docs: update roadmap to current learner-evidence gate --- docs/ROADMAP.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 04b1614..6c78040 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -162,8 +162,20 @@ Only after core learning quality and state model are stable, evaluate separately Each is an independent product decision, not a default consequence of “scaling”. -## Immediate next decision +## Current gate — complete Phase 1 with real learner evidence -Before code migration begins, review and accept the platform foundation documents in `docs/`. +The platform foundation and the technical M01 validation slice are prepared for review. The remaining Phase 1 gate is empirical, not architectural. -After acceptance, write an implementation plan for **Phase 1 only** rather than a single plan spanning the entire roadmap. +Before Phase 2 begins: + +1. run at least 5 completed M01 learner sessions using `docs/validation/M01-VALIDATION-PROTOCOL.md`; +2. record each session with `docs/validation/M01-SESSION-RECORD-TEMPLATE.md`; +3. review diagnostic reasoning delta, field transfer, reflection quality, interaction usefulness, reliability/friction, and content-model fit; +4. record one explicit cohort decision with `docs/validation/M01-COHORT-REVIEW-TEMPLATE.md`: + - **Promote to Phase 2**; + - **Revise and retest**; + - **Reject mechanism**. + +`docs/validation/M01-READINESS-AUDIT.md` documents the current curriculum/content-contract fit before learner testing. + +Do not freeze v1 domain contracts, select a framework, or start a v1 migration until the cohort decision is **Promote to Phase 2**. From 82c4418de634ed2b824de5714bab34dec4e74ae9 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:37:56 +0500 Subject: [PATCH 039/107] docs: close M01 implementation checklist --- docs/superpowers/plans/2026-09-06-m01-learning-validation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md index 0ada375..e7ece3a 100644 --- a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md +++ b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md @@ -133,4 +133,4 @@ - [x] Upgrade GitHub Actions runtime dependencies to current major versions used by the workflow. - [x] Inspect final Actions run after documentation/CI changes and require GREEN. - [x] Review final diff for scope creep and architecture violations: Phase 1 only; no backend/auth/framework migration; legacy `app.js`, `course-data.js`, and `styles.css` unchanged. -- [ ] Update PR #2 from draft only after the verification run for this final plan-reconciliation commit is GREEN. +- [x] Update PR #2 from draft after verification; PR #2 is ready-for-review and mergeable. From 8d790222760ccc9f91bedb13b3d728ee50590ff5 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 20:43:26 +0500 Subject: [PATCH 040/107] docs: reconcile M01 implementation plan with verified gate --- .../2026-09-06-m01-learning-validation.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md index e7ece3a..2278773 100644 --- a/docs/superpowers/plans/2026-09-06-m01-learning-validation.md +++ b/docs/superpowers/plans/2026-09-06-m01-learning-validation.md @@ -134,3 +134,31 @@ - [x] Inspect final Actions run after documentation/CI changes and require GREEN. - [x] Review final diff for scope creep and architecture violations: Phase 1 only; no backend/auth/framework migration; legacy `app.js`, `course-data.js`, and `styles.css` unchanged. - [x] Update PR #2 from draft after verification; PR #2 is ready-for-review and mergeable. + +## Gate verification record — 2026-09-06 + +### VERIFIED + +- PR #2 head is `050ea6d26197ba68f8e82d8dc34306f4c6678332`; it remains open and is not merged into `main`. +- Stacked documentation head before this reconciliation was `82c4418de634ed2b824de5714bab34dec4e74ae9`. +- GitHub Actions run #49 on that head completed successfully. +- CI executed JavaScript syntax checks and `node --test tests/*.test.js`; all 19 tests passed, 0 failed, 0 skipped. +- Full scope review of PR #2 confirms Phase 1-only changes: validation runtime/data/styles/tests/CI/docs; no backend, auth, CMS, AI subsystem, framework migration, or v1 rewrite. +- Full scope review of the stacked docs layer confirms documentation/readiness-only changes; no runtime/application files changed. + +### UNEXECUTED + +- No merge of PR #2 into `main`. +- No Phase 2 domain-contract freeze. +- No framework selection or v1 migration. +- No real learner cohort execution in this code gate. + +### BLOCKED + +- Phase 2 remains blocked on the empirical learner-evidence gate in Issue #4: at least 5 completed M01 learner sessions and one explicit cohort decision (`Promote`, `Revise and retest`, or `Reject mechanism`). + +### Next step + +1. Require GREEN CI on the new plan-reconciliation HEAD created by this update. +2. If GREEN, treat the technical/scope gate as closed. +3. Execute Issue #4 learner cohort before any Phase 2 work. From ae769d5832011c7b28363df57bafde1728fc2486 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:18:27 +0500 Subject: [PATCH 041/107] docs: minimize M01 evidence collection gate --- docs/validation/M01-VALIDATION-PROTOCOL.md | 29 ++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/docs/validation/M01-VALIDATION-PROTOCOL.md b/docs/validation/M01-VALIDATION-PROTOCOL.md index d479b66..0805e38 100644 --- a/docs/validation/M01-VALIDATION-PROTOCOL.md +++ b/docs/validation/M01-VALIDATION-PROTOCOL.md @@ -43,7 +43,7 @@ Use people who can reason about real delivery work, for example: - Team Lead with delivery ownership; - Product/operations role that regularly coordinates dependencies and decisions. -Record experience level rather than filtering to one exact seniority. For the first cycle, aim for at least **5 completed sessions** before making a module-level promotion decision. More sessions are useful when results conflict. +Record experience level rather than filtering to one exact seniority. For the first cycle, require at least **5 completed sessions** before making a module-level promotion decision. More sessions are useful when results conflict. Do not use the course author as evidence of learning effectiveness. @@ -183,6 +183,21 @@ Record: Do not introduce an automatic mastery rule until delayed evidence has been reviewed across multiple sessions. +## Minimum evidence packet + +For each completed learner session, retain only the evidence needed for the Phase 1 decision: + +- anonymized participant ID and role/experience; +- baseline total, post total, delta, and improved dimensions; +- baseline and post free-text diagnoses; +- whether each Decision Drill exposed or changed reasoning; +- field transfer: credible / partial / no; +- reflection showing changed diagnosis/action/evidence requirement, or no meaningful change; +- major UX/reliability/content ambiguity that could distort the result; +- delayed-transfer evidence when available. + +Do not add new engineering telemetry or product instrumentation for this cohort. Use the existing session record template and manual evidence capture. + ## Session record For each participant record outside the product, using anonymized IDs where possible: @@ -209,7 +224,7 @@ Do not store sensitive project details when an anonymized description is suffici Do not migrate M01 into v1 solely because CI is green or the UI works. -After at least 5 completed learner sessions, review these five areas: +After at least **5 completed learner sessions**, review these five areas: ### 1. Learning signal @@ -248,10 +263,10 @@ Record every special case needed to represent M01. If the module requires one-of ## Promotion decision -Possible outcomes: +After the minimum cohort is complete, record exactly one outcome: -- **Promote** — learning mechanism and transfer evidence are strong enough to become v1 requirements. -- **Revise and retest** — promising signal, but content, rubric, interaction, or friction is materially distorting results. -- **Reject mechanism** — interaction adds complexity without useful reasoning evidence. +- **Promote to Phase 2** — learning mechanism and transfer evidence are strong enough to become v1 requirements. +- **Revise and retest** — promising signal, but content, rubric, interaction, or friction materially distorts results. +- **Reject mechanism** — interaction or module format adds complexity without useful reasoning/transfer evidence. -Document the decision and evidence before beginning the v1 vertical slice. +Until one of these outcomes is recorded after at least 5 completed sessions, Phase 2 and v1 work remain blocked. From ec4b33dc19061cb20d7f7ea41695a6b897c2dca3 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:25:16 +0500 Subject: [PATCH 042/107] chore: publish M01 validation via GitHub Pages --- .github/workflows/pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index be1eb25..f3d1cfa 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -2,7 +2,7 @@ name: Deploy to GitHub Pages on: push: - branches: [main] + branches: [main, docs/m01-validation-readiness-audit] workflow_dispatch: permissions: From 3ce50de5f7860fb5cd5f426269e4249d4cf21918 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:27:22 +0500 Subject: [PATCH 043/107] chore: restore Pages workflow to main-only --- .github/workflows/pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index f3d1cfa..be1eb25 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -2,7 +2,7 @@ name: Deploy to GitHub Pages on: push: - branches: [main, docs/m01-validation-readiness-audit] + branches: [main] workflow_dispatch: permissions: From d869f96b7eded8d41dadc39db53a4817567d95a5 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:34:42 +0500 Subject: [PATCH 044/107] chore: deploy M01 validation after Pages activation --- .github/workflows/pages.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index be1eb25..c0add50 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -2,7 +2,7 @@ name: Deploy to GitHub Pages on: push: - branches: [main] + branches: [main, docs/m01-validation-readiness-audit] workflow_dispatch: permissions: @@ -25,8 +25,6 @@ jobs: uses: actions/checkout@v4 - name: Configure Pages uses: actions/configure-pages@v5 - with: - enablement: true - name: Upload static site uses: actions/upload-pages-artifact@v3 with: From f589a9f1768812046f1deba845cd3d8b1c9da785 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 22:35:40 +0500 Subject: [PATCH 045/107] chore: use unrestricted environment for M01 Pages preview --- .github/workflows/pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index c0add50..6b54cd6 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -17,7 +17,7 @@ concurrency: jobs: deploy: environment: - name: github-pages + name: m01-pages-preview url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: From f7a749e85c8b03537b5cdeef71828ff8996bc5a9 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 23:16:20 +0500 Subject: [PATCH 046/107] fix: remove external font dependency from M01 preview --- index.html | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/index.html b/index.html index 57a8347..0846e17 100644 --- a/index.html +++ b/index.html @@ -9,12 +9,6 @@ content="Практическая программа по проектному управлению как инженерии систем исполнения." /> PM 0.1 — Инженерия исполнения - - - @@ -55,4 +49,4 @@ - + \ No newline at end of file From 158c404a4082ac1611a0d927b4a9a3648539c7eb Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Sun, 6 Sep 2026 23:51:20 +0500 Subject: [PATCH 047/107] chore: restore standard Pages workflow after preview publication --- .github/workflows/pages.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 6b54cd6..6a1d143 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -2,7 +2,7 @@ name: Deploy to GitHub Pages on: push: - branches: [main, docs/m01-validation-readiness-audit] + branches: [main] workflow_dispatch: permissions: @@ -17,7 +17,7 @@ concurrency: jobs: deploy: environment: - name: m01-pages-preview + name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: From 00cce1c779aac09b22aaf4cdde90f712a7f08ec6 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 00:10:05 +0500 Subject: [PATCH 048/107] chore: switch Pages to clean gh-pages source --- .../workflows/switch-pages-to-gh-pages.yml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/switch-pages-to-gh-pages.yml diff --git a/.github/workflows/switch-pages-to-gh-pages.yml b/.github/workflows/switch-pages-to-gh-pages.yml new file mode 100644 index 0000000..9920a3c --- /dev/null +++ b/.github/workflows/switch-pages-to-gh-pages.yml @@ -0,0 +1,30 @@ +name: Switch Pages to gh-pages + +on: + push: + branches: [docs/m01-validation-readiness-audit] + paths: [.github/workflows/switch-pages-to-gh-pages.yml] + +permissions: + contents: read + pages: write + +jobs: + switch: + runs-on: ubuntu-latest + steps: + - name: Switch Pages publishing source to gh-pages + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + code=$(curl -sS -L -o /tmp/pages-response.txt -w '%{http_code}' \ + -X PUT \ + -H 'Accept: application/vnd.github+json' \ + -H "Authorization: Bearer ${GH_TOKEN}" \ + -H 'X-GitHub-Api-Version: 2026-03-10' \ + https://api.github.com/repos/${GITHUB_REPOSITORY}/pages \ + -d '{"build_type":"legacy","source":{"branch":"gh-pages","path":"/"}}') + cat /tmp/pages-response.txt || true + echo "HTTP ${code}" + test "${code}" = '204' From d3001b35efe84a36a894927f03aa7e4e52a42d3c Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 00:10:37 +0500 Subject: [PATCH 049/107] chore: remove temporary Pages source switch workflow --- .../workflows/switch-pages-to-gh-pages.yml | 30 ------------------- 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/switch-pages-to-gh-pages.yml diff --git a/.github/workflows/switch-pages-to-gh-pages.yml b/.github/workflows/switch-pages-to-gh-pages.yml deleted file mode 100644 index 9920a3c..0000000 --- a/.github/workflows/switch-pages-to-gh-pages.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Switch Pages to gh-pages - -on: - push: - branches: [docs/m01-validation-readiness-audit] - paths: [.github/workflows/switch-pages-to-gh-pages.yml] - -permissions: - contents: read - pages: write - -jobs: - switch: - runs-on: ubuntu-latest - steps: - - name: Switch Pages publishing source to gh-pages - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -euo pipefail - code=$(curl -sS -L -o /tmp/pages-response.txt -w '%{http_code}' \ - -X PUT \ - -H 'Accept: application/vnd.github+json' \ - -H "Authorization: Bearer ${GH_TOKEN}" \ - -H 'X-GitHub-Api-Version: 2026-03-10' \ - https://api.github.com/repos/${GITHUB_REPOSITORY}/pages \ - -d '{"build_type":"legacy","source":{"branch":"gh-pages","path":"/"}}') - cat /tmp/pages-response.txt || true - echo "HTTP ${code}" - test "${code}" = '204' From 4c8a02fb23f69201e499fc70835fd3b2d2fe88dc Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 00:27:04 +0500 Subject: [PATCH 050/107] test: catch base-router conflict on M01 validation route --- tests/static-contract.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/static-contract.test.js b/tests/static-contract.test.js index 3e9c090..badac28 100644 --- a/tests/static-contract.test.js +++ b/tests/static-contract.test.js @@ -9,6 +9,7 @@ function read(name) { } const html = read('index.html'); +const baseApp = read('app.js'); const validationApp = read('m01-validation-app.js'); const validationStyles = read('m01-validation.css'); @@ -25,6 +26,11 @@ test('index loads validation styles and data, domain, base app, then validation } }); +test('base router reserves the M01 validation route instead of rendering not-found before the extension', () => { + assert.equal(baseApp.includes('route: "validation-m01"'), true, 'base router must recognize validation/m01'); + assert.equal(baseApp.includes('if (route === "validation-m01")'), true, 'base renderer must yield validation/m01 to the extension'); +}); + test('validation extension owns the M01 validation route and course CTA', () => { assert.equal(validationApp.includes("validation/m01"), true, 'missing validation route'); assert.equal(validationApp.includes('data-validation-cta'), true, 'missing M01 validation CTA contract'); From 2fdd515ab0645ebf5046cd5bd7795070c6cf3530 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 02:28:23 +0500 Subject: [PATCH 051/107] test: reproduce M01 hashchange route ownership conflict --- tests/m01-app-smoke.test.js | 63 +++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/m01-app-smoke.test.js b/tests/m01-app-smoke.test.js index 0663415..0fa9b29 100644 --- a/tests/m01-app-smoke.test.js +++ b/tests/m01-app-smoke.test.js @@ -5,6 +5,7 @@ const path = require('node:path'); const vm = require('node:vm'); const domain = require('../learning-domain.js'); +const baseAppCode = fs.readFileSync(path.join(__dirname, '..', 'app.js'), 'utf8'); const validationDataCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-data.js'), 'utf8'); const validationAppCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-app.js'), 'utf8'); @@ -53,6 +54,61 @@ function runValidationApp(route, initialStorage = {}) { return { main, moduleList, localStorage }; } +function runHashchangeOwnershipHandoff() { + const main = { innerHTML: '', focus() {} }; + const sidebarProgress = { innerHTML: '' }; + const mobileNav = { classList: { remove() {}, toggle() { return false; } } }; + const menuButton = { addEventListener() {}, setAttribute() {} }; + const listeners = { hashchange: [] }; + const microtasks = []; + const document = { + querySelector(selector) { + if (selector === '#main') return main; + if (selector === '#sidebar-progress') return sidebarProgress; + if (selector === '#mobile-nav') return mobileNav; + if (selector === '#menu-button') return menuButton; + return null; + }, + querySelectorAll() { return []; }, + }; + const localStorage = storageFrom(); + const window = { + PM01: { modules: [], flows: [], diagnostics: [], tools: [] }, + PM01Learning: domain, + addEventListener(type, callback) { + if (!listeners[type]) listeners[type] = []; + listeners[type].push(callback); + }, + scrollTo() {}, + confirm() { return true; }, + }; + const context = { + window, + document, + localStorage, + location: { hash: '#/unknown' }, + queueMicrotask(callback) { microtasks.push(callback); }, + console, + Date, + }; + vm.createContext(context); + vm.runInContext(validationDataCode, context, { filename: 'm01-validation-data.js' }); + vm.runInContext(baseAppCode, context, { filename: 'app.js' }); + vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); + + assert.equal(listeners.hashchange.length, 2, 'base and extension hashchange handlers must both be registered'); + main.innerHTML = 'sentinel-before-validation'; + context.location.hash = '#/validation/m01'; + + listeners.hashchange[0](); + const afterBaseRouter = main.innerHTML; + + listeners.hashchange[1](); + while (microtasks.length) microtasks.shift()(); + + return { afterBaseRouter, afterExtension: main.innerHTML }; +} + test('fresh validation route renders baseline without exposing learning drills or post-case controls', () => { const { main } = runValidationApp('validation/m01'); @@ -111,3 +167,10 @@ test('course route receives a single M01 validation CTA from the extension', () assert.match(moduleList.inserted, /data-validation-cta/); assert.match(moduleList.inserted, /#\/validation\/m01/); }); + +test('hashchange hands validation/m01 to the extension without base-router DOM overwrite', () => { + const { afterBaseRouter, afterExtension } = runHashchangeOwnershipHandoff(); + + assert.equal(afterBaseRouter, 'sentinel-before-validation', 'base router must yield without writing #main'); + assert.match(afterExtension, /01 · Baseline/, 'validation extension must own and render the route'); +}); From a49d3e8ae86e7227fcdb05ee13a03c533bbe7e24 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 02:31:01 +0500 Subject: [PATCH 052/107] fix: yield M01 validation route to extension --- app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index 3ef5930..f7835ac 100644 --- a/app.js +++ b/app.js @@ -300,12 +300,14 @@ const parts = location.hash.replace(/^#\/?/, "").split("/").filter(Boolean); if (!parts.length) return { route: "home" }; if (parts[0] === "lesson") return { route: "lesson", id: parts[1] }; + if (parts[0] === "validation" && parts[1] === "m01" && parts.length === 2) return { route: "validation-m01" }; if (["course", "diagnostic", "toolkit"].includes(parts[0])) return { route: parts[0] }; return { route: "not-found" }; } function render() { const { route, id } = parseRoute(); + if (route === "validation-m01") return; const views = { home: homeView, course: courseView, lesson: () => lessonView(id), diagnostic: diagnosticView, toolkit: toolkitView, "not-found": notFoundView }; document.querySelector("#main").innerHTML = views[route](); setActiveNav(route); @@ -324,4 +326,4 @@ }); window.addEventListener("hashchange", render); render(); -})(); +})(); \ No newline at end of file From ee983222c658b9e668975f4674cbc51d914c099d Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 04:04:57 +0500 Subject: [PATCH 053/107] test: define clear lesson completion and primary learning path --- tests/learning-flow.test.js | 106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/learning-flow.test.js diff --git a/tests/learning-flow.test.js b/tests/learning-flow.test.js new file mode 100644 index 0000000..2d363b6 --- /dev/null +++ b/tests/learning-flow.test.js @@ -0,0 +1,106 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const vm = require('node:vm'); + +const appCode = fs.readFileSync(path.join(__dirname, '..', 'app.js'), 'utf8'); + +function storageFrom(initial = {}) { + const values = new Map(Object.entries(initial)); + return { + getItem(key) { return values.has(key) ? values.get(key) : null; }, + setItem(key, value) { values.set(key, String(value)); }, + removeItem(key) { values.delete(key); }, + }; +} + +function classListStub() { + return { add() {}, remove() {}, toggle() { return false; } }; +} + +function runBaseApp(route, legacyState = {}) { + const main = { innerHTML: '', focus() {} }; + const sidebarProgress = { innerHTML: '' }; + const mobileNav = { classList: classListStub() }; + const menuButton = { classList: classListStub(), addEventListener() {}, setAttribute() {} }; + const notes = { value: '' }; + const criterion = { checked: false, dataset: { criterion: '0' }, addEventListener() {} }; + const completeButton = { + textContent: '', + disabled: false, + classList: classListStub(), + listeners: {}, + addEventListener(type, callback) { this.listeners[type] = callback; }, + }; + const saveNotes = { addEventListener() {} }; + const toast = { textContent: '', classList: classListStub() }; + + const document = { + querySelector(selector) { + if (selector === '#main') return main; + if (selector === '#sidebar-progress') return sidebarProgress; + if (selector === '#mobile-nav') return mobileNav; + if (selector === '#menu-button') return menuButton; + if (selector === '#lesson-notes') return notes; + if (selector === '#complete-lesson') return completeButton; + if (selector === '#save-notes') return saveNotes; + if (selector === '#toast') return toast; + return null; + }, + querySelectorAll(selector) { + if (selector === '[data-criterion]') return [criterion]; + if (selector === '[data-criterion]:checked') return criterion.checked ? [criterion] : []; + return []; + }, + }; + + const localStorage = storageFrom({ + 'pm01-state-v1': JSON.stringify({ + completed: [], notes: {}, criteria: {}, lastLesson: null, diagnostic: {}, ...legacyState, + }), + }); + + const window = { + PM01: { + flows: [], diagnostics: [], tools: [], + modules: [ + { id: 'm01', title: 'Модуль 1', duration: '1–2 ч', outcome: 'Результат 1', lessons: [ + { id: 'a', title: 'Урок A', thesis: 'Тезис A', minutes: 10, body: ['Текст'], model: 'Модель', practice: ['Шаг'], criteria: ['Есть доказательство'] }, + ] }, + { id: 'm02', title: 'Модуль 2', duration: '1–2 ч', outcome: 'Результат 2', lessons: [ + { id: 'b', title: 'Урок B', thesis: 'Тезис B', minutes: 10, body: ['Текст'], model: 'Модель', practice: ['Шаг'], criteria: ['Есть доказательство'] }, + ] }, + ], + }, + addEventListener() {}, + scrollTo() {}, + }; + const location = { hash: `#/${route}` }; + const context = { window, document, localStorage, location, console, Blob, URL, setTimeout() { return 1; }, clearTimeout() {} }; + vm.createContext(context); + vm.runInContext(appCode, context, { filename: 'app.js' }); + + return { main, completeButton, criterion, location, localStorage }; +} + +test('lesson completion clearly stays unavailable until every evidence criterion is checked', () => { + const { main } = runBaseApp('lesson/a'); + assert.match(main.innerHTML, /0\/1 выполнено/); + assert.match(main.innerHTML, /id="complete-lesson"[^>]*disabled/); +}); + +test('completing a ready lesson records progress and advances to the next lesson', () => { + const { completeButton, location, localStorage } = runBaseApp('lesson/a', { criteria: { a: [0] } }); + assert.equal(typeof completeButton.listeners.click, 'function'); + completeButton.listeners.click({ currentTarget: completeButton }); + const saved = JSON.parse(localStorage.getItem('pm01-state-v1')); + assert.deepEqual(saved.completed, ['a']); + assert.equal(location.hash, '#/lesson/b'); +}); + +test('course view presents one primary learning path', () => { + const { main } = runBaseApp('course'); + assert.match(main.innerHTML, /Основной путь/); + assert.doesNotMatch(main.innerHTML, /Не линейный курс/); +}); From 6aaf61cd4184370839af34445c257851653d73de Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 04:07:09 +0500 Subject: [PATCH 054/107] fix: make learning path and lesson completion explicit --- app.js | 143 +++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 89 insertions(+), 54 deletions(-) diff --git a/app.js b/app.js index f7835ac..7db5e3b 100644 --- a/app.js +++ b/app.js @@ -33,7 +33,7 @@ } function progress() { - return Math.round((state.completed.length / allLessons.length) * 100); + return allLessons.length ? Math.round((state.completed.length / allLessons.length) * 100) : 0; } function renderSidebarProgress() { @@ -55,6 +55,7 @@ function showToast(message) { const toast = document.querySelector("#toast"); + if (!toast) return; toast.textContent = message; toast.classList.add("show"); clearTimeout(showToast.timer); @@ -63,7 +64,8 @@ function moduleCompletion(module) { const done = module.lessons.filter((lesson) => state.completed.includes(lesson.id)).length; - return { done, total: module.lessons.length, percent: Math.round((done / module.lessons.length) * 100) }; + const total = module.lessons.length; + return { done, total, percent: total ? Math.round((done / total) * 100) : 0 }; } function nextLesson() { @@ -73,46 +75,51 @@ return allLessons.find((lesson) => !state.completed.includes(lesson.id)) || allLessons.at(-1); } + function moduleTargetLesson(module) { + return module.lessons.find((lesson) => !state.completed.includes(lesson.id)) || module.lessons.at(-1); + } + function homeView() { const next = nextLesson(); const nextIndex = allLessons.findIndex((lesson) => lesson.id === next.id) + 1; return `
-

Практическая программа · уровень senior+

-

Инженерия
исполнения

-

Научись управлять не задачами и статусами, а системой: потоком ценности, решений, информации, зависимостей и риска.

+

Практика управления проектами · senior+

+

Управляй системой,
а не списком задач

+

Курс учит находить причину проблем проекта, принимать решения и проверять их на реальной работе. Иди по урокам по порядку — практика встроена в каждый шаг.

-
10модулей
-
20полевых уроков
+
10модулей по порядку
+
20уроков с практикой
8рабочих шаблонов
-
2–4недели на capstone
+
1итоговая работа
-

Основная модель

Семь потоков проекта

Любая системная проблема проекта проявляется как задержка, разрыв или искажение одного из этих потоков.

+

Модель курса

Семь потоков проекта

Это семь мест, где чаще всего ломается работа проекта. В каждом модуле ты научишься замечать один из таких разрывов и исправлять его.

${DATA.flows.map((flow, i) => `
0${i + 1}${flow.name}
`).join("")}
-

Следующий шаг · урок ${String(nextIndex).padStart(2, "0")}

+

Твой следующий шаг · урок ${String(nextIndex).padStart(2, "0")}

${next.title}

${next.thesis}

${next.minutes} минут · ${next.moduleTitle} - Открыть урок → + Продолжить →
-

Маршрут

Не линейный курс, а система практики

Вся программа
+

Основной путь

Один маршрут: урок → практика → следующий урок

Все 10 модулей
${DATA.modules.slice(0, 4).map(moduleCard).join("")}
`; @@ -120,26 +127,35 @@ function moduleCard(module, index = DATA.modules.indexOf(module)) { const completion = moduleCompletion(module); + const target = moduleTargetLesson(module); return ``; } function courseView() { const hours = DATA.modules.reduce((sum, module) => { - const [min, max] = module.duration.match(/\d+/g).map(Number); + const values = module.duration.match(/\d+/g)?.map(Number) || [0, 0]; + const min = values[0] || 0; + const max = values[1] ?? min; return [sum[0] + min, sum[1] + max]; }, [0, 0]); + const currentModuleIndex = DATA.modules.findIndex((module) => moduleCompletion(module).done < module.lessons.length); return `
-

Учебный маршрут

От диспетчера задач
к архитектору системы

Каждый модуль заканчивается артефактом для реального проекта. Теория считается освоенной только после наблюдаемого изменения системы.

-
${hours[0]}–${hours[1]} ч

общая нагрузка

${progress()}%

пройдено

1

итоговое системное вмешательство

+

Основной путь

10 модулей.
Иди по порядку.

Начни с первого незавершённого урока. В конце каждого урока есть короткая практика. Когда она выполнена, кнопка завершения откроет следующий шаг. Проверки и диагностика дополняют путь, но не создают второй курс.

+
${hours[0]}–${hours[1]} ч

ориентир по времени

${progress()}%

пройдено

${state.completed.length}/${allLessons.length}

уроков завершено

+
Как двигаться:1. Открой текущий урок2. Сделай практику3. Заверши урок4. Перейди дальше
${DATA.modules.map((module, index) => { const completion = moduleCompletion(module); - return ``; + const target = moduleTargetLesson(module); + const finished = completion.done === completion.total; + const current = !finished && (currentModuleIndex === index || currentModuleIndex === -1); + const status = finished ? "Завершено" : current ? "Сейчас" : "Дальше"; + return ``; }).join("")}
`; } @@ -154,6 +170,8 @@ const next = allLessons[index + 1]; const checked = state.criteria[id] || []; const done = state.completed.includes(id); + const required = lesson.criteria.length; + const ready = checked.length >= required; return `
@@ -161,31 +179,35 @@

${lesson.moduleTitle} · урок ${String(index + 1).padStart(2, "0")}/${allLessons.length}

${lesson.title}

${lesson.thesis}

-
${lesson.minutes} минутпрактика обязательна
+
${lesson.minutes} минут${done ? "завершён ✓" : "теория + практика"}
-

Смена оптики

${lesson.body.map((paragraph) => `

${paragraph}

`).join("")}
+

Главная мысль

${lesson.body.map((paragraph) => `

${paragraph}

`).join("")}
${lesson.thesis}
-

Рабочая модель

${lesson.model}
+

Как это работает

${lesson.model}
-

Полевая работа

Применить на реальном проекте

+

Практика

Примени к своему проекту

    ${lesson.practice.map((step) => `
  1. ${step}
  2. `).join("")}
-

Доказательства освоения

+

Проверь результат

+

Отметь пункт только если действительно сделал его. Это не тест — чекбоксы просто помогают понять, готов ли ты идти дальше.

${lesson.criteria.map((criterion, criterionIndex) => ``).join("")}
- - +

${checked.length}/${required} выполнено${ready ? " · можно завершать урок" : " · выполни все пункты, чтобы открыть следующий шаг"}

+ +
- + ${done + ? `${next ? "Продолжить к следующему уроку" : "Вернуться к программе"} →` + : ``}
`; @@ -199,8 +221,8 @@ { score: 3, label: "Системно — есть правило, владелец и обратная связь" } ]; return `
-

Baseline · 7 потоков

Диагностика зрелости

-

Оцени не намерения команды, а воспроизводимое поведение системы за последние четыре недели. Результат покажет, с какого модуля начинать.

+

Необязательная самопроверка

Где проект теряет управляемость?

+

Оцени, как проект работал последние четыре недели. Результат покажет слабое место, на которое стоит обратить внимание. Порядок курса при этом не меняется.

${DATA.diagnostics.map((item, index) => `
${index + 1}. ${item.q}
${choices.map((choice) => ``).join("")}
`).join("")}
@@ -213,7 +235,7 @@ if (!target) return; const answers = Object.keys(state.diagnostic); if (answers.length < DATA.diagnostics.length) { - target.innerHTML = `

Результат

${answers.length}/${DATA.diagnostics.length} ответов

Ответь на все вопросы. Оценка сохранится в этом браузере автоматически.

`; + target.innerHTML = `

Результат

${answers.length}/${DATA.diagnostics.length} ответов

Ответь на все вопросы. Ответы сохраняются в этом браузере автоматически.

`; return; } const scores = Object.fromEntries(DATA.flows.map((flow) => [flow.id, []])); @@ -222,18 +244,33 @@ const weakest = [...results].sort((a, b) => a.score - b.score)[0]; const moduleMap = { value: 2, work: 4, information: 8, decisions: 7, dependencies: 3, uncertainty: 6, feedback: 10 }; const recommended = DATA.modules[moduleMap[weakest.id] - 1]; - target.innerHTML = `

Точка старта

${weakest.name}: ${weakest.score}%

Слабейший поток системы. Начни с модуля «${recommended.title}» и повтори диагностику после полевого вмешательства.

${results.map((result) => `
${result.name}${result.score}%
`).join("")}Открыть модуль ${String(moduleMap[weakest.id]).padStart(2, "0")}`; + target.innerHTML = `

Зона внимания

${weakest.name}: ${weakest.score}%

Слабее всего сейчас выглядит поток «${weakest.name}». Продолжай основной путь и обрати особое внимание на модуль «${recommended.title}».

${results.map((result) => `
${result.name}${result.score}%
`).join("")}Вернуться к основному пути`; } function toolkitView() { - return `

Рабочие артефакты

Инструменты,
которые меняют решения

Скачай Markdown-шаблон, заполни фактами реального проекта и принеси на следующую точку принятия решения.

+ return `

Шаблоны для работы

Инструменты,
которые помогают принять решение

Скачай нужный Markdown-шаблон, заполни его фактами проекта и используй в реальной рабочей ситуации.

${DATA.tools.map((tool, index) => `
TOOL ${String(index + 1).padStart(2, "0")}

${tool.name}

${tool.description}

`).join("")}
-

Правило применения

Артефакт существует ради решения

Есть потребитель

До заполнения ясно, кто и какое решение примет с его помощью.

Есть срок жизни

Устаревший документ удаляют или обновляют, а не хранят как декорацию.

Один источник истины

Информация не копируется вручную между несколькими статусами.

Минимум достаточного

Поле остается только если его отсутствие уже приводило к дорогой ошибке.

+

Правило

Шаблон нужен только тогда, когда помогает решить задачу

Понятно, кому нужен

До заполнения ясно, кто и какое решение примет с его помощью.

Понятно, когда устареет

Ненужный документ удаляют или обновляют, а не хранят ради процесса.

Один источник

Не копируй одну и ту же информацию вручную в несколько мест.

Только нужные поля

Оставляй поле, если без него уже возникали ошибки или плохие решения.

`; } function notFoundView() { - return `

404

Такого урока нет

Вернись к программе и выбери следующий шаг.

Открыть программу
`; + return `

404

Такой страницы нет

Вернись к учебному пути и продолжи с текущего шага.

Открыть учебный путь
`; + } + + function updateLessonCompletionGate(id) { + const criteria = document.querySelectorAll("[data-criterion]"); + const checked = document.querySelectorAll("[data-criterion]:checked").length; + const button = document.querySelector("#complete-lesson"); + const status = document.querySelector("#completion-status"); + const ready = checked >= criteria.length; + if (button) button.disabled = !ready; + if (status) { + status.textContent = `${checked}/${criteria.length} выполнено${ready ? " · можно завершать урок" : " · выполни все пункты, чтобы открыть следующий шаг"}`; + status.classList.toggle("ready", ready); + } + state.criteria[id] = [...document.querySelectorAll("[data-criterion]:checked")].map((item) => Number(item.dataset.criterion)); + saveState(); } function bindViewEvents(route, id) { @@ -245,29 +282,27 @@ }); }); document.querySelectorAll("[data-criterion]").forEach((checkbox) => { - checkbox.addEventListener("change", () => { - state.criteria[id] = [...document.querySelectorAll("[data-criterion]:checked")].map((item) => Number(item.dataset.criterion)); - saveState(); - }); + checkbox.addEventListener("change", () => updateLessonCompletionGate(id)); }); document.querySelector("#save-notes")?.addEventListener("click", () => { state.notes[id] = document.querySelector("#lesson-notes").value; saveState(); showToast("Заметки сохранены"); }); - document.querySelector("#complete-lesson")?.addEventListener("click", (event) => { + document.querySelector("#complete-lesson")?.addEventListener("click", () => { state.notes[id] = document.querySelector("#lesson-notes").value; - const completed = state.completed.includes(id); - if (!completed && (state.criteria[id] || []).length < document.querySelectorAll("[data-criterion]").length) { - saveState(); - showToast("Сначала отметь все доказательства освоения"); + const criteriaCount = document.querySelectorAll("[data-criterion]").length; + if ((state.criteria[id] || []).length < criteriaCount) { + updateLessonCompletionGate(id); + showToast("Сначала выполни все пункты практики"); return; } - state.completed = completed ? state.completed.filter((item) => item !== id) : [...state.completed, id]; + if (!state.completed.includes(id)) state.completed = [...state.completed, id]; saveState(); - event.currentTarget.textContent = completed ? "Отметить урок пройденным" : "Урок пройден ✓"; - event.currentTarget.classList.toggle("done", !completed); - showToast(completed ? "Отметка снята" : "Урок добавлен в прогресс"); + const index = allLessons.findIndex((lesson) => lesson.id === id); + const next = allLessons[index + 1]; + showToast("Урок завершён"); + location.hash = next ? `#/lesson/${next.id}` : "#/course"; }); } @@ -315,13 +350,13 @@ bindViewEvents(route, id); document.querySelector("#main").focus({ preventScroll: true }); window.scrollTo(0, 0); - document.querySelector("#mobile-nav").classList.remove("open"); - document.querySelector("#menu-button").setAttribute("aria-expanded", "false"); + document.querySelector("#mobile-nav")?.classList.remove("open"); + document.querySelector("#menu-button")?.setAttribute("aria-expanded", "false"); } - document.querySelector("#menu-button").addEventListener("click", (event) => { + document.querySelector("#menu-button")?.addEventListener("click", (event) => { const nav = document.querySelector("#mobile-nav"); - const open = nav.classList.toggle("open"); + const open = nav?.classList.toggle("open") || false; event.currentTarget.setAttribute("aria-expanded", String(open)); }); window.addEventListener("hashchange", render); From 6a93f545942de164de2ec55c1eba88f5e84a0e1b Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 04:09:23 +0500 Subject: [PATCH 055/107] style: add calm premium learning interface --- v1-1.css | 236 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 v1-1.css diff --git a/v1-1.css b/v1-1.css new file mode 100644 index 0000000..b30e51a --- /dev/null +++ b/v1-1.css @@ -0,0 +1,236 @@ +:root { + --ink: #1d1d1f; + --ink-2: #ffffff; + --panel: #f5f5f7; + --paper: #ffffff; + --paper-2: #f5f5f7; + --muted: #6e6e73; + --accent: #0071e3; + --accent-2: #0071e3; + --blue: #0071e3; + --green: #188038; + --red: #d70015; + --line: rgba(0, 0, 0, .08); + --paper-line: rgba(0, 0, 0, .12); + --sidebar: 248px; + --radius: 24px; + color: var(--ink); + background: #f5f5f7; + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif; +} + +html { background: #f5f5f7; } +body { background: #f5f5f7; color: var(--ink); } +a { color: inherit; } + +.sidebar { + width: var(--sidebar); + padding: 28px 22px; + border-right: 1px solid rgba(0,0,0,.08); + background: rgba(255,255,255,.86); + color: var(--ink); + -webkit-backdrop-filter: saturate(180%) blur(24px); + backdrop-filter: saturate(180%) blur(24px); +} +.brand { gap: 10px; } +.brand-mark { + width: 42px; + height: 42px; + border-radius: 12px; + background: var(--ink); + color: #fff; + font: 650 12px ui-monospace, SFMono-Regular, Menlo, monospace; +} +.brand strong { color: var(--ink); font-size: 20px; letter-spacing: -.035em; } +.brand small { color: var(--muted); font: 600 9px ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .06em; } +.main-nav { margin-top: 46px; gap: 4px; } +.main-nav a { + padding: 11px 12px; + color: #515154; + border-radius: 12px; + font-size: 14px; + font-weight: 600; +} +.main-nav a span { color: #a1a1a6; font: 600 11px ui-monospace, SFMono-Regular, Menlo, monospace; } +.main-nav a:hover { background: #f2f2f4; color: var(--ink); } +.main-nav a.active { background: #e8f1fb; color: #0066cc; } +.main-nav a.active span { color: #0066cc; } +.sidebar-progress { border-color: var(--line); background: #f7f7f9; } +.sidebar-progress .label-row { color: var(--muted); } +.progress-track { background: #e5e5ea; } +.progress-fill { background: var(--accent); } +.sidebar-note { color: #a1a1a6; font: 11px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; } + +main { margin-left: var(--sidebar); background: #f5f5f7; } +.page { max-width: 1180px; padding: 68px 64px 120px; animation: v11-enter .28s ease; } +@keyframes v11-enter { from { opacity: 0; transform: translateY(6px); } } + +.eyebrow { + color: #0066cc; + font: 650 12px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + letter-spacing: .01em; + text-transform: none; +} +h1, h2, h3 { color: var(--ink); font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", sans-serif; } +h1 { font-weight: 720; letter-spacing: -.055em; line-height: .98; } +h2 { font-weight: 700; letter-spacing: -.04em; } +h3 { font-weight: 680; } +.lead { color: #6e6e73; font-size: clamp(18px, 2vw, 22px); line-height: 1.5; } +.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.muted { color: var(--muted); } +.accent { color: var(--ink); } +.section { margin-top: 84px; } +.section-heading p { color: var(--muted); line-height: 1.55; } + +.hero { padding: 34px 0 20px; } +.hero::after { display: none; } +.hero h1 { max-width: 900px; font-size: clamp(48px, 7vw, 86px); } +.hero .lead { max-width: 720px; } +.hero-actions { gap: 10px; margin-top: 34px; } + +.button { + min-height: 44px; + padding: 0 18px; + border-color: rgba(0,0,0,.12); + border-radius: 999px; + background: rgba(255,255,255,.72); + color: var(--ink); + font-weight: 650; + box-shadow: 0 1px 1px rgba(0,0,0,.02); + transition: transform .16s ease, background .16s ease, border-color .16s ease, box-shadow .16s ease; +} +.button:hover { transform: translateY(-1px); border-color: rgba(0,0,0,.18); background: #fff; box-shadow: 0 4px 14px rgba(0,0,0,.06); } +.button.primary { border-color: #0071e3; background: #0071e3; color: white; } +.button.primary:hover { background: #0077ed; border-color: #0077ed; } +.button:disabled { opacity: .42; cursor: not-allowed; transform: none; box-shadow: none; } +.button.subtle { min-height: 38px; color: #0066cc; background: transparent; border-color: transparent; } +.button.subtle:hover { background: #e8f1fb; border-color: transparent; } + +.stat-grid { grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 54px; border: 0; background: transparent; } +.stat { min-height: 122px; padding: 24px; border: 1px solid var(--line); border-radius: 20px; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.03); } +.stat strong { color: var(--ink); font-size: 30px; } +.stat span { color: var(--muted); } + +.system-map { gap: 10px; } +.system-node { min-height: 120px; padding: 18px 14px; border: 1px solid var(--line); border-top: 1px solid var(--line); border-radius: 18px; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.03); } +.system-node::before { content: ""; display: block; width: 28px; height: 4px; border-radius: 99px; background: var(--node); } +.system-node span { margin-top: 14px; color: var(--muted); font: 600 11px ui-monospace, SFMono-Regular, Menlo, monospace; } +.system-node strong { color: var(--ink); } + +.next-card { border: 1px solid var(--line); border-radius: 28px; background: #fff; color: var(--ink); box-shadow: 0 8px 30px rgba(0,0,0,.05); } +.next-card .eyebrow { color: #0066cc !important; } +.next-card .next-meta { border-left-color: var(--line); } +.next-card .button { border-color: #0071e3; background: #0071e3; color: #fff; } + +.module-grid { gap: 18px; } +.module-card { min-height: 250px; padding: 30px; border-color: var(--line); border-radius: 24px; background: #fff; color: var(--ink); box-shadow: 0 2px 10px rgba(0,0,0,.035); } +.module-card::after { color: rgba(0,0,0,.025); } +.module-card .module-kicker { color: #0066cc; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.module-card h3 { color: var(--ink); } +.module-card p { color: var(--muted); } +.module-footer a { color: #0066cc; } +.module-footer a:hover { color: #004f9f; } + +.course-intro { gap: 42px; align-items: center; } +.course-intro > div:first-child { max-width: 740px; } +.course-metrics { padding: 26px; border-color: var(--line); border-radius: 22px; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.03); } +.course-metrics strong { color: var(--ink); } +.course-metrics p { color: var(--muted); } +.path-note { display: flex; flex-wrap: wrap; gap: 10px 20px; margin-top: 38px; padding: 18px 20px; border-radius: 18px; background: #e8f1fb; color: #1d1d1f; font-size: 13px; } +.path-note strong { width: 100%; } +.path-note span { color: #515154; } +.module-list { gap: 12px; margin-top: 28px; border: 0; } +.module-row { min-height: 118px; padding: 18px 20px; border: 1px solid var(--line); border-radius: 20px; background: #fff; box-shadow: 0 1px 2px rgba(0,0,0,.025); } +.module-row.current { border-color: rgba(0,113,227,.38); box-shadow: 0 0 0 3px rgba(0,113,227,.08); } +.module-row.completed { opacity: .76; } +.module-index { color: #a1a1a6; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.module-status { display: inline-flex; margin-bottom: 6px; color: #6e6e73; font-size: 11px; font-weight: 700; } +.module-row.current .module-status { color: #0066cc; } +.module-row h2 { color: var(--ink); } +.module-row p { color: var(--muted); } +.module-row a { color: #0066cc; } +.module-row a:hover { color: #004f9f; } + +.lesson-layout { grid-template-columns: minmax(0, 760px) 230px; gap: 48px; } +.lesson-layout > article { padding: clamp(28px, 5vw, 54px); border: 1px solid var(--line); border-radius: 30px; background: #fff; box-shadow: 0 8px 32px rgba(0,0,0,.04); } +.lesson-header { border-bottom-color: var(--line); } +.lesson-header .meta { color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.lesson-block p, .lesson-block li { color: #424245; font-size: 17px; line-height: 1.72; } +.insight { border: 0; border-radius: 18px; background: #f5f5f7; color: var(--ink); font-size: 21px; font-weight: 580; } +.model-card { border-color: var(--line); border-radius: 18px; background: #f5f5f7; color: #424245; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.practice { padding: 30px; border: 1px solid var(--line); border-radius: 22px; background: #f5f5f7; color: var(--ink); } +.practice .eyebrow { color: #0066cc; } +.practice-help, .field-help { color: var(--muted); font-size: 13px; line-height: 1.55; } +.field-help { display: block; margin-top: 4px; font-weight: 400; } +.criterion { padding: 13px 14px; border: 1px solid rgba(0,0,0,.06); border-radius: 13px; background: #fff; } +.criterion:has(input:checked) { border-color: rgba(0,113,227,.28); background: #f0f7ff; } +.criterion input { accent-color: #0071e3; } +.completion-status { margin: 14px 0 18px; color: #6e6e73; font-size: 13px; font-weight: 600; } +.completion-status.ready { color: #188038; } +.notes { border-color: var(--paper-line); border-radius: 14px; background: #fff; color: var(--ink); } +.notes:focus { outline: 3px solid rgba(0,113,227,.16); border-color: #0071e3; } +.practice .button { border-color: rgba(0,0,0,.12); color: var(--ink); background: #fff; } +.practice .button.primary { border-color: #0071e3; background: #0071e3; color: #fff; } +.lesson-aside .toc { border-color: var(--line); border-radius: 18px; background: rgba(255,255,255,.7); } +.toc small { color: #a1a1a6; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.toc a { color: #515154; } +.toc a:hover { color: #0066cc; } + +.question-card, .tool-card, .principle { border-color: var(--line); background: #fff; color: var(--ink); box-shadow: 0 1px 2px rgba(0,0,0,.025); } +.question-card legend, .tool-card h3, .principle strong { color: var(--ink); } +.option { border-color: var(--line); color: #515154; background: #fff; } +.option:has(input:checked) { border-color: rgba(0,113,227,.42); background: #f0f7ff; color: var(--ink); } +.option input { accent-color: #0071e3; } +.diagnostic-result { border: 1px solid var(--line); background: #fff; color: var(--ink); box-shadow: 0 8px 30px rgba(0,0,0,.045); } +.result-empty, .tool-card p, .principle p { color: var(--muted); } + +.validation-shell { max-width: 900px; } +.validation-hero { border-bottom-color: var(--line); } +.validation-note { border: 0; border-radius: 16px; background: #eef5fc; color: #515154; } +.validation-step { border-color: var(--line); border-radius: 24px; background: #fff; color: var(--ink); box-shadow: 0 2px 14px rgba(0,0,0,.035); } +.validation-step > header p { color: var(--muted); } +.validation-scenario { border-radius: 16px; background: #f5f5f7; color: #424245; } +.validation-option { border-color: var(--line); color: #515154; background: #fff; } +.validation-option:has(input:checked) { border-color: rgba(0,113,227,.4); background: #f0f7ff; color: var(--ink); } +.validation-option input { accent-color: #0071e3; } +.validation-feedback { color: var(--muted); } +.validation-reasoning, .validation-evidence textarea { border-color: var(--line); border-radius: 14px; background: #fff; color: var(--ink); } +.validation-reasoning:focus, .validation-evidence textarea:focus { outline: 3px solid rgba(0,113,227,.16); border-color: #0071e3; } +.validation-score > div, .validation-drill { border-color: var(--line); background: #f7f7f9; } +.validation-drill p, .validation-cta p { color: var(--muted); } +.validation-state { border-color: var(--line); color: #515154; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.validation-result { border: 1px solid var(--line); background: #fff; color: var(--ink); } +/* M01 validation is a research checkpoint, not a second learner-facing track. */ +.validation-cta { display: none; } + +.toast { border: 1px solid rgba(0,0,0,.08); background: rgba(29,29,31,.92); color: white; box-shadow: 0 12px 40px rgba(0,0,0,.2); } + +@media (max-width: 980px) { + :root { --sidebar: 220px; } + .page { padding: 54px 36px 96px; } + .system-map { grid-template-columns: repeat(4, 1fr); } + .module-row { grid-template-columns: 54px minmax(190px,.9fr) 1.2fr 100px; gap: 16px; } +} + +@media (max-width: 760px) { + .sidebar { display: none; } + main { margin-left: 0; padding-top: 64px; } + .mobile-header { display: flex; position: fixed; inset: 0 0 auto; z-index: 20; height: 64px; padding: 0 18px; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); background: rgba(255,255,255,.88); -webkit-backdrop-filter: blur(20px); backdrop-filter: blur(20px); } + .mobile-header .brand-mark { width: 34px; height: 34px; border-radius: 10px; } + .menu-button { border: 0; border-radius: 999px; padding: 8px 12px; background: #f2f2f4; color: var(--ink); font-weight: 650; } + .mobile-nav { top: 64px; border-bottom: 1px solid var(--line); background: rgba(255,255,255,.96); color: var(--ink); } + .mobile-nav a { color: #424245; } + .page { padding: 34px 18px 72px; } + .hero h1 { font-size: clamp(44px, 14vw, 66px); } + .stat-grid, .module-grid, .course-intro, .lesson-layout, .diagnostic-grid { grid-template-columns: 1fr; } + .system-map { grid-template-columns: repeat(2, 1fr); } + .next-card { grid-template-columns: 1fr; } + .next-card .next-meta { border-left: 0; border-top: 1px solid var(--line); gap: 20px; } + .module-row { grid-template-columns: 42px 1fr; gap: 10px 14px; padding: 18px; } + .module-row > p, .module-row > a { grid-column: 2; text-align: left; } + .lesson-layout > article { padding: 24px 20px; border-radius: 24px; } + .lesson-aside { position: static; } + .lesson-aside .toc { display: none; } + .practice { padding: 22px 18px; } + .path-note { display: grid; } +} From 9b79bb8757b53e8a7b9c6c3a31567fb9f78b6c47 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 04:09:40 +0500 Subject: [PATCH 056/107] content: simplify M01 language for learners --- content-overrides.js | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 content-overrides.js diff --git a/content-overrides.js b/content-overrides.js new file mode 100644 index 0000000..1e1e87d --- /dev/null +++ b/content-overrides.js @@ -0,0 +1,59 @@ +(function () { + "use strict"; + const DATA = window.PM01; + if (!DATA || !Array.isArray(DATA.modules)) return; + + const module = DATA.modules.find((item) => item.id === "m01"); + if (!module) return; + + module.subtitle = "Смотреть на проект целиком"; + module.outcome = "Разобраться, как устроен твой проект, найти главное слабое место и выбрать одно изменение с понятным эффектом."; + + const first = module.lessons.find((lesson) => lesson.id === "project-system"); + if (first) { + first.title = "Смотри на проект целиком"; + first.thesis = "Проект — это не список задач. Это система, которая должна привести к полезному результату."; + first.body = [ + "Срок, бюджет и список задач важны, но сами по себе не делают проект успешным. Можно всё закончить вовремя и при этом получить результат, который никому не нужен.", + "Поэтому сначала смотри на то, что должно измениться для пользователя или бизнеса. Затем проверь, что мешает команде стабильно прийти к этому результату.", + "Для этого мы используем семь потоков: ценность, работа, информация, решения, зависимости, неопределённость и обратная связь. Если один поток тормозит, проблема часто проявляется совсем в другом месте." + ]; + first.model = "Что есть на входе → как работает команда → что мы узнали → какой результат получили\n\nИщи: где поток тормозит → сколько стоит задержка → что изменить"; + first.practice = [ + "Возьми один текущий или недавний проект.", + "Одним предложением опиши, что должно измениться для пользователя или бизнеса.", + "По каждому из семи потоков запиши один наблюдаемый факт.", + "Выбери один разрыв, который сильнее всего мешает проекту двигаться дальше." + ]; + first.criteria = [ + "Понятно, какой результат должен измениться во внешнем мире", + "Есть по одному факту для всех семи потоков", + "Факты отделены от предположений", + "Выбран один главный разрыв" + ]; + } + + const second = module.lessons.find((lesson) => lesson.id === "system-diagnostic"); + if (second) { + second.title = "Сначала найди причину"; + second.thesis = "Не исправляй первый заметный симптом. Сначала пойми, почему проблема вообще возникла и почему она может повториться."; + second.body = [ + "Если тестировщик нашёл дефект, причина не обязательно в разработчике. Она могла появиться раньше: например, требование было двусмысленным или решение слишком долго ждали от другого человека.", + "Разбирай проблему по цепочке: что мы увидели → как это возникло → какое условие в системе это создало → что нужно изменить.", + "Хорошая причина не звучит как имя человека. Она описывает правило, ограничение, зависимость или способ работы, из-за которого проблема повторится даже с другой командой." + ]; + second.model = "СИМПТОМ\n↓ что мы видим\nМЕХАНИЗМ\n↓ как это возникло\nУСЛОВИЕ СИСТЕМЫ\n↓ почему повторится\nИЗМЕНЕНИЕ\n↓ что делаем иначе"; + second.practice = [ + "Возьми одну повторяющуюся проблему проекта.", + "Опиши минимум три шага от симптома к причине.", + "Найди условие, из-за которого проблема повторится даже с другим человеком.", + "Предложи одно изменение и ранний сигнал, по которому поймёшь, что оно помогает." + ]; + second.criteria = [ + "Причина описывает систему, а не конкретного человека", + "Понятно, как проблема возникает снова", + "Предлагаемое изменение влияет именно на найденную причину", + "Есть ранний сигнал, по которому можно проверить эффект" + ]; + } +})(); From 976f80c893764443fa2e986e799afdb935141f36 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 04:09:59 +0500 Subject: [PATCH 057/107] ux: connect v1.1 design and clearer navigation --- index.html | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/index.html b/index.html index 0846e17..d2bd6f3 100644 --- a/index.html +++ b/index.html @@ -3,14 +3,15 @@ - + - PM 0.1 — Инженерия исполнения + PM 0.1 — Практика управления проектами + @@ -18,16 +19,16 @@
@@ -35,8 +36,8 @@
@@ -44,6 +45,7 @@
+ From 5a8521a922ad9f7683f2f5d05700c7c38240d222 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 04:48:27 +0500 Subject: [PATCH 058/107] style: introduce editorial instrument art direction --- art-direction.css | 286 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 art-direction.css diff --git a/art-direction.css b/art-direction.css new file mode 100644 index 0000000..8c3450a --- /dev/null +++ b/art-direction.css @@ -0,0 +1,286 @@ +:root { + --ink: #f2efe7; + --ink-2: #111111; + --panel: #151515; + --paper: #f2efe7; + --paper-2: #dedbd2; + --muted: #9b9b94; + --accent: #dfff3f; + --accent-2: #dfff3f; + --blue: #dfff3f; + --green: #dfff3f; + --red: #ff5c47; + --line: rgba(242,239,231,.16); + --paper-line: rgba(10,10,10,.18); + --sidebar: 184px; + --radius: 0px; + color: var(--ink); + background: #0a0a0a; + font-family: Arial, Helvetica, sans-serif; +} + +html, body { background: #0a0a0a; color: var(--ink); } +body { overflow-x: hidden; } +a { color: inherit; } + +/* FRAME */ +.sidebar { + width: var(--sidebar); + padding: 26px 20px 22px; + border-right: 1px solid var(--line); + background: #0a0a0a; +} +.brand { align-items: flex-start; gap: 10px; } +.brand-mark { + width: 46px; + height: 46px; + border: 1px solid var(--ink); + border-radius: 0; + background: transparent; + color: var(--ink); + font: 700 11px/1 ui-monospace, SFMono-Regular, Menlo, monospace; +} +.brand strong { color: var(--ink); font-size: 20px; letter-spacing: -.06em; } +.brand small { color: var(--muted); font: 600 8px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .16em; } +.main-nav { margin-top: 76px; gap: 0; border-top: 1px solid var(--line); } +.main-nav a { + display: grid; + grid-template-columns: 30px 1fr; + gap: 8px; + padding: 14px 0; + border-bottom: 1px solid var(--line); + border-radius: 0; + background: transparent; + color: #b9b9b2; + font-size: 12px; + font-weight: 650; + letter-spacing: .02em; +} +.main-nav a span { margin: 0; color: #5f5f5a; font: 600 10px ui-monospace, SFMono-Regular, Menlo, monospace; } +.main-nav a:hover, .main-nav a.active { background: transparent; color: var(--ink); } +.main-nav a.active { box-shadow: inset 3px 0 0 var(--accent); padding-left: 10px; } +.main-nav a.active span { color: var(--accent); } +.sidebar-progress { margin-top: auto; padding: 14px 0 0; border: 0; border-top: 1px solid var(--line); border-radius: 0; background: transparent; } +.sidebar-progress .label-row { color: var(--muted); font: 10px ui-monospace, SFMono-Regular, Menlo, monospace; } +.progress-track { height: 2px; margin-top: 10px; border-radius: 0; background: #252525; } +.progress-fill { border-radius: 0; background: var(--accent); } +.sidebar-note { margin: 20px 0 0; color: #666660; font: 9px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .06em; text-transform: uppercase; } + +main { margin-left: var(--sidebar); background: #0a0a0a; } +.page { max-width: 1460px; padding: 46px 54px 120px; animation: editorial-enter .38s cubic-bezier(.2,.7,.2,1); } +@keyframes editorial-enter { from { opacity: 0; transform: translateY(14px); } } + +/* TYPE */ +.eyebrow { + margin-bottom: 14px; + color: var(--accent); + font: 650 10px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; + letter-spacing: .16em; + text-transform: uppercase; +} +h1, h2, h3 { color: var(--ink); font-family: Arial, Helvetica, sans-serif; } +h1 { font-weight: 800; letter-spacing: -.085em; line-height: .86; } +h2 { font-weight: 760; letter-spacing: -.065em; line-height: .95; } +h3 { font-weight: 720; letter-spacing: -.035em; } +.lead { max-width: 760px; color: #b7b7b0; font-size: clamp(17px, 1.6vw, 22px); line-height: 1.48; } +.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } +.muted { color: var(--muted); } +.accent { color: var(--accent); } +.section { margin-top: 108px; } +.section-heading { align-items: end; padding-top: 16px; border-top: 1px solid var(--line); } +.section-heading p { max-width: 500px; color: var(--muted); } + +/* HERO */ +.hero { position: relative; min-height: 72vh; padding: 18px 0 36px; display: flex; flex-direction: column; justify-content: space-between; } +.hero::after { + display: block; + content: "PM / 0.1"; + position: absolute; + top: 18px; + right: 0; + color: #2a2a27; + writing-mode: vertical-rl; + font: 700 10px ui-monospace, SFMono-Regular, Menlo, monospace; + letter-spacing: .2em; +} +.hero h1 { max-width: 1100px; margin: 0 0 24px; font-size: clamp(68px, 10.6vw, 158px); } +.hero .lead { margin-top: auto; } +.hero-actions { gap: 0; margin-top: 30px; } + +.button { + min-height: 46px; + padding: 0 17px; + border: 1px solid var(--line); + border-radius: 0; + background: transparent; + color: var(--ink); + box-shadow: none; + font: 700 11px/1 ui-monospace, SFMono-Regular, Menlo, monospace; + letter-spacing: .04em; + text-transform: uppercase; + transition: background .16s ease, color .16s ease, border-color .16s ease, transform .16s ease; +} +.button:hover { transform: none; border-color: var(--ink); background: var(--ink); color: #0a0a0a; box-shadow: none; } +.button.primary { border-color: var(--accent); background: var(--accent); color: #0a0a0a; } +.button.primary:hover { border-color: #f2efe7; background: #f2efe7; color: #0a0a0a; } +.button.subtle { min-height: 38px; padding: 0 12px; border-color: var(--line); background: transparent; color: var(--ink); } +.button.subtle:hover { border-color: var(--accent); background: transparent; color: var(--accent); } +.button:disabled { opacity: .3; cursor: not-allowed; } + +/* METRICS AS INSTRUMENT READOUT */ +.stat-grid { grid-template-columns: repeat(4, 1fr); gap: 0; margin-top: 66px; border: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); background: transparent; } +.stat { min-height: 120px; padding: 18px 18px 16px 0; border-right: 1px solid var(--line); background: transparent; } +.stat + .stat { padding-left: 18px; } +.stat:last-child { border-right: 0; } +.stat strong { color: var(--ink); font-size: clamp(30px, 4vw, 54px); letter-spacing: -.07em; } +.stat span { color: var(--muted); font: 10px ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; letter-spacing: .08em; } + +/* SEVEN FLOWS */ +.system-map { grid-template-columns: repeat(7, 1fr); gap: 0; border-top: 1px solid var(--line); } +.system-node { min-height: 148px; padding: 14px 12px 16px; border: 0; border-right: 1px solid var(--line); border-radius: 0; background: transparent; box-shadow: none; } +.system-node:last-child { border-right: 0; } +.system-node::before { content: ""; display: block; width: 100%; height: 2px; margin-bottom: 12px; background: var(--node); } +.system-node span { color: #666660; font: 600 9px ui-monospace, SFMono-Regular, Menlo, monospace; } +.system-node strong { margin-top: 46px; color: var(--ink); font-size: 12px; line-height: 1.25; } + +/* NEXT ACTION = VISUAL INTERRUPTION */ +.next-card { grid-template-columns: 1.45fr .55fr; border: 0; border-radius: 0; background: var(--accent); color: #0a0a0a; box-shadow: none; } +.next-card > div { padding: clamp(28px, 5vw, 64px); } +.next-card h2 { color: #0a0a0a; font-size: clamp(42px, 6vw, 82px); } +.next-card p { color: #1b1b19; } +.next-card .eyebrow { color: #0a0a0a !important; } +.next-card .next-meta { border-left: 1px solid rgba(10,10,10,.3); } +.next-card .button { align-self: stretch; border-color: #0a0a0a; color: #0a0a0a; } +.next-card .button:hover { background: #0a0a0a; color: var(--accent); } + +/* MODULES AS CHAPTERS, NOT CARDS */ +.module-grid { display: grid; grid-template-columns: 1fr; gap: 0; border-top: 1px solid var(--line); } +.module-card { min-height: 220px; padding: 28px 0 34px 170px; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; box-shadow: none; } +.module-card::after { right: auto; left: 0; bottom: 12px; color: #20201e; font-size: 118px; font-weight: 850; letter-spacing: -.1em; } +.module-card .module-kicker { color: var(--accent); font: 650 10px ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .08em; } +.module-card h3 { max-width: 720px; margin: 18px 0 10px; color: var(--ink); font-size: clamp(34px, 4vw, 60px); } +.module-card p { max-width: 700px; color: var(--muted); font-size: 15px; } +.module-footer { inset: auto 0 26px 170px; } +.module-footer a { color: var(--ink); font: 700 11px ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; letter-spacing: .05em; } +.module-footer a:hover { color: var(--accent); } + +/* COURSE */ +.course-intro { grid-template-columns: minmax(0, 1.45fr) minmax(220px, .55fr); gap: 56px; align-items: end; } +.course-intro h1 { font-size: clamp(62px, 8.6vw, 132px); } +.course-metrics { padding: 0 0 0 18px; border: 0; border-left: 1px solid var(--line); border-radius: 0; background: transparent; box-shadow: none; } +.course-metrics strong { color: var(--accent); font-size: 36px; } +.course-metrics p { color: var(--muted); font: 10px ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; } +.path-note { display: grid; grid-template-columns: 180px repeat(4, 1fr); gap: 0; margin-top: 56px; padding: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; color: var(--ink); font-size: 11px; } +.path-note strong, .path-note span { padding: 15px 12px; border-right: 1px solid var(--line); } +.path-note span:last-child { border-right: 0; } +.path-note strong { width: auto; color: var(--accent); font: 650 10px ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; } +.path-note span { color: #b5b5ae; } +.module-list { gap: 0; margin-top: 34px; border-top: 1px solid var(--line); } +.module-row { min-height: 138px; padding: 20px 0; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; box-shadow: none; grid-template-columns: 86px minmax(240px,.9fr) 1.2fr 130px; } +.module-row.current { border-color: var(--line); box-shadow: inset 4px 0 0 var(--accent); padding-left: 16px; } +.module-row.completed { opacity: .52; } +.module-index { color: #555550; font: 650 11px ui-monospace, SFMono-Regular, Menlo, monospace; } +.module-status { color: #777770; font: 650 9px ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .08em; text-transform: uppercase; } +.module-row.current .module-status { color: var(--accent); } +.module-row h2 { color: var(--ink); font-size: clamp(25px, 2.4vw, 38px); } +.module-row p { color: var(--muted); } +.module-row a { color: var(--ink); font: 700 10px ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; } +.module-row a:hover { color: var(--accent); } + +/* LESSON */ +.lesson-layout { grid-template-columns: minmax(0, 820px) 220px; gap: 64px; } +.lesson-layout > article { padding: 0; border: 0; border-radius: 0; background: transparent; box-shadow: none; } +.lesson-header { margin-bottom: 54px; padding: 10px 0 38px; border-bottom: 1px solid var(--line); } +.lesson-header h1 { font-size: clamp(56px, 7vw, 104px); } +.lesson-header .meta { color: #6f6f68; font: 10px ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; } +.lesson-block { margin: 60px 0; } +.lesson-block h2 { margin-bottom: 20px; font-size: clamp(32px, 3vw, 48px); } +.lesson-block p, .lesson-block li { color: #c2c2ba; font-size: 18px; line-height: 1.68; } +.insight { margin: 50px 0; padding: 24px 0 24px 24px; border: 0; border-left: 3px solid var(--accent); border-radius: 0; background: transparent; color: var(--ink); font-size: clamp(24px, 2.5vw, 36px); font-weight: 700; line-height: 1.25; } +.model-card { margin: 36px 0; padding: 24px 0; border: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; color: var(--accent); font: 13px/1.9 ui-monospace, SFMono-Regular, Menlo, monospace; } +.practice { margin-top: 70px; padding: 34px; border: 0; border-radius: 0; background: var(--paper); color: #0a0a0a; } +.practice h2, .practice h3 { color: #0a0a0a; } +.practice .eyebrow { color: #0a0a0a; } +.practice-help, .field-help { color: #5f5f5a; } +.criterion { padding: 13px 0; border: 0; border-bottom: 1px solid rgba(10,10,10,.16); border-radius: 0; background: transparent; } +.criterion:has(input:checked) { border-color: rgba(10,10,10,.16); background: transparent; } +.criterion input { accent-color: #0a0a0a; } +.completion-status { color: #666660; font: 650 10px ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; } +.completion-status.ready { color: #0a0a0a; } +.notes { border: 1px solid rgba(10,10,10,.24); border-radius: 0; background: transparent; color: #0a0a0a; } +.notes:focus { outline: 2px solid #0a0a0a; border-color: #0a0a0a; } +.practice .button { border-color: #0a0a0a; background: transparent; color: #0a0a0a; } +.practice .button.primary { border-color: #0a0a0a; background: #0a0a0a; color: var(--paper); } +.practice .button.primary:hover { background: var(--accent); color: #0a0a0a; } +.lesson-aside { top: 28px; } +.lesson-aside .toc { padding: 0; border: 0; border-top: 1px solid var(--line); border-radius: 0; background: transparent; } +.toc small { display: block; padding: 13px 0; border-bottom: 1px solid var(--line); color: #666660; font: 650 9px ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .08em; } +.toc a { padding: 11px 0; border-bottom: 1px solid var(--line); color: #9a9a93; font-size: 11px; } +.toc a:hover { color: var(--accent); } +.lesson-nav { gap: 0; margin-top: 24px; } +.lesson-nav .button { border-top: 0; } + +/* DIAGNOSTIC / TOOLS */ +.question-card, .tool-card, .principle { border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; color: var(--ink); box-shadow: none; } +.question-card { padding: 22px 0; } +.question-card legend, .tool-card h3, .principle strong { color: var(--ink); } +.option { border: 1px solid var(--line); border-radius: 0; color: #b8b8b1; background: transparent; } +.option:has(input:checked) { border-color: var(--accent); background: rgba(223,255,63,.08); color: var(--ink); } +.option input { accent-color: var(--accent); } +.diagnostic-result { border: 1px solid var(--line); border-radius: 0; background: #111; color: var(--ink); box-shadow: none; } +.result-empty, .tool-card p, .principle p { color: var(--muted); } +.tool-grid { gap: 0; border-top: 1px solid var(--line); } +.tool-card { min-height: 260px; padding: 26px 22px 26px 0; } + +/* VALIDATION KEEPS THE SAME SYSTEM */ +.validation-shell { max-width: 980px; } +.validation-hero { border-bottom-color: var(--line); } +.validation-note { border: 1px solid var(--line); border-left: 3px solid var(--accent); border-radius: 0; background: transparent; color: #b8b8b1; } +.validation-step { border: 0; border-top: 1px solid var(--line); border-radius: 0; background: transparent; color: var(--ink); box-shadow: none; padding: 30px 0; } +.validation-step > header p { color: var(--muted); } +.validation-scenario { border-radius: 0; background: #151515; color: #c0c0b8; } +.validation-option { border-color: var(--line); border-radius: 0; color: #b8b8b1; background: transparent; } +.validation-option:has(input:checked) { border-color: var(--accent); background: rgba(223,255,63,.08); color: var(--ink); } +.validation-option input { accent-color: var(--accent); } +.validation-feedback { color: var(--muted); } +.validation-reasoning, .validation-evidence textarea { border: 1px solid var(--line); border-radius: 0; background: #111; color: var(--ink); } +.validation-reasoning:focus, .validation-evidence textarea:focus { outline: 2px solid var(--accent); border-color: var(--accent); } +.validation-score > div, .validation-drill { border: 1px solid var(--line); border-radius: 0; background: transparent; } +.validation-state { border-color: var(--line); border-radius: 0; color: #b6b6af; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; } + +/* MOBILE */ +@media (max-width: 900px) { + :root { --sidebar: 0px; } + .sidebar { display: none; } + main { margin-left: 0; } + .mobile-header { display: flex; height: 62px; padding: 0 16px; border-bottom: 1px solid var(--line); background: #0a0a0a; } + .mobile-header .brand-mark { width: 34px; height: 34px; } + .menu-button { border: 1px solid var(--line); border-radius: 0; color: var(--ink); background: transparent; } + .mobile-nav { background: #0a0a0a; border-bottom: 1px solid var(--line); } + .mobile-nav a { border-bottom: 1px solid var(--line); color: var(--ink); } + .page { padding: 34px 18px 80px; } + .hero { min-height: auto; } + .hero h1 { font-size: clamp(58px, 17vw, 92px); } + .stat-grid { grid-template-columns: repeat(2,1fr); } + .stat:nth-child(2) { border-right: 0; } + .stat:nth-child(-n+2) { border-bottom: 1px solid var(--line); } + .system-map { grid-template-columns: repeat(2,1fr); } + .system-node { border-bottom: 1px solid var(--line); } + .next-card { grid-template-columns: 1fr; } + .next-card .next-meta { border-left: 0; border-top: 1px solid rgba(10,10,10,.3); } + .module-card { padding-left: 88px; } + .module-card::after { font-size: 70px; } + .module-footer { left: 88px; } + .course-intro { grid-template-columns: 1fr; } + .path-note { grid-template-columns: 1fr; } + .path-note strong, .path-note span { border-right: 0; border-bottom: 1px solid var(--line); } + .module-row { grid-template-columns: 48px 1fr; gap: 8px 14px; } + .module-row p, .module-row a { grid-column: 2; text-align: left; } + .lesson-layout { grid-template-columns: 1fr; } + .lesson-aside { position: static; } +} + +@media (prefers-reduced-motion: reduce) { + .page { animation: none; } + * { scroll-behavior: auto !important; transition: none !important; } +} From 56a4f1b76e48f5a92720a71991747b09cef5f7a1 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 04:48:44 +0500 Subject: [PATCH 059/107] style: switch PMO01 to editorial instrument theme --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index d2bd6f3..e9777e5 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - + PM 0.1 — Практика управления проектами - + From d18794f7b58100b73edf0f63c32b8316caf8be84 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:05:06 +0500 Subject: [PATCH 060/107] docs: define M01 learning lab design --- .../2026-09-07-m01-learning-lab-design.md | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-07-m01-learning-lab-design.md diff --git a/docs/superpowers/specs/2026-09-07-m01-learning-lab-design.md b/docs/superpowers/specs/2026-09-07-m01-learning-lab-design.md new file mode 100644 index 0000000..9e8366b --- /dev/null +++ b/docs/superpowers/specs/2026-09-07-m01-learning-lab-design.md @@ -0,0 +1,121 @@ +# M01 Learning Lab Design + +## Goal +Turn M01 from a reading page with checkbox self-attestation into a compact decision-training lab that teaches, checks, and transfers project-system diagnosis skills. + +## Scope +- Redesign only M01 (`project-system`, `system-diagnostic`) as the reference vertical slice. +- Preserve the existing sequential learning path and lesson URLs. +- Reuse existing M01 validation drills and feedback where they fit. +- Add in-lesson interactive workbook state stored locally in the browser. +- Fix readability/contrast problems across the current art direction. +- Do not change `main`, telemetry, or M02-M10 content in this iteration. + +## Learning loop +Each M01 lesson follows one visible sequence: + +1. **Cold case** — learner sees a realistic situation before explanation. +2. **Decision** — learner chooses or writes an initial diagnosis. +3. **Feedback** — immediate explanation of why the choice is strong or weak. +4. **Worked example** — concise expert reasoning, not a long lecture. +5. **Technique** — one reusable method with an explicit procedure. +6. **Guided drill** — another case with feedback. +7. **Workbook** — learner applies the technique in structured fields. +8. **Transfer** — learner uses the same structure on a real project. +9. **Exit check** — lesson completion requires a substantive drill plus workbook evidence, not checkbox self-attestation. + +## M01 lesson design + +### Lesson 1 — Смотри на проект целиком +Skill: distinguish task symptoms from the execution system and identify the highest-leverage broken flow. + +Technique: **Seven-flow scan** +- Outcome +- Value +- Work +- Information +- Decisions +- Dependencies +- Uncertainty +- Feedback + +Interactive workbook: +- Observable outcome +- Facts by flow +- Main break +- Why this break matters +- Earliest useful signal + +Cold/guided drill: reuse the existing `m01-drill-system` scenario and feedback. + +### Lesson 2 — Сначала найди причину +Skill: build a causal chain and choose an intervention that changes a reproducible mechanism rather than treating the visible symptom. + +Technique: **Symptom → Mechanism → System condition → Intervention → Early signal** + +Interactive workbook: +- Symptom +- Evidence +- Mechanism +- System condition +- Intervention +- Early signal + +Cold/guided drill: reuse `m01-drill-diagnostic` and relevant rubric language from M01 validation. + +## Completion contract +For M01 lab lessons, the completion button is enabled only when: +- at least one decision drill has been answered; +- all required workbook fields for that lesson contain non-whitespace content. + +Existing checkbox criteria remain hidden for M01 lab lessons and continue to work unchanged for M02-M10. + +Completion still writes to the existing `pm01-state-v1` `completed` array and advances to the next lesson. + +## State +Extend the existing `pm01-state-v1` object with: + +```js +lab: { + [lessonId]: { + drillAnswers: { [drillId]: optionId }, + workbook: { [fieldId]: string } + } +} +``` + +Older localStorage remains compatible because missing `lab` state defaults to `{}`. + +## Data boundary +Create `m01-learning-lab-data.js` that attaches `learningLab` metadata only to the two M01 lessons. It may read `PM01.m01Validation.decisionDrills` but must not duplicate the validation runtime. + +The normal learner route remains owned by `app.js`. `m01-validation-app.js` continues to own only `#/validation/m01`. + +## UI boundary +`app.js` gets generic conditional rendering/binding for optional `lesson.learningLab` data: +- render lab sections; +- persist drill choices and workbook fields; +- calculate readiness; +- render feedback; +- reuse existing completion/navigation behavior. + +No new router is introduced. + +## Readability requirements +Keep the Editorial Instrument identity but prioritize long-form usability: +- normal learner-facing text contrast must target WCAG AA 4.5:1; +- secondary text must not use the current near-invisible `#5f5f5a` / `#666660` on `#0a0a0a` for essential content; +- body copy remains at least 16px on desktop and mobile; +- metadata may remain smaller only when non-essential; +- light workbook surfaces use dark text with AA contrast; +- focus states remain clearly visible. + +## Success criteria +- M01 lessons visibly contain case → decision → feedback → technique → workbook → transfer. +- Decision drills give immediate option-specific feedback. +- Workbook state survives reload. +- M01 completion is gated by drill + required workbook evidence. +- M02-M10 completion behavior is unchanged. +- No additional learner-facing learning route is introduced. +- Existing CI plus new lab regression tests pass. +- Pages can deploy as a clean static site with no build dependency. From c27ad4670e965612204406631c12d6d744ef4b28 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:05:34 +0500 Subject: [PATCH 061/107] docs: plan M01 learning lab implementation --- .../plans/2026-09-07-m01-learning-lab.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-07-m01-learning-lab.md diff --git a/docs/superpowers/plans/2026-09-07-m01-learning-lab.md b/docs/superpowers/plans/2026-09-07-m01-learning-lab.md new file mode 100644 index 0000000..2086dc3 --- /dev/null +++ b/docs/superpowers/plans/2026-09-07-m01-learning-lab.md @@ -0,0 +1,100 @@ +# M01 Learning Lab Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Turn M01 into a decision-training lab with immediate feedback, an in-lesson workbook, substantive completion gates, and readable Editorial Instrument styling. + +**Architecture:** Keep `app.js` as the sole owner of normal lesson routes. Add optional `learningLab` metadata to only M01 lessons from a dedicated data file. Extend generic lesson rendering/binding so M01 gets drills/workbook while M02-M10 retain existing behavior. + +**Tech Stack:** Static HTML/CSS/JavaScript, browser localStorage, Node `node:test` CI. + +**Spec:** `docs/superpowers/specs/2026-09-07-m01-learning-lab-design.md` + +## Global Constraints +- Do not change `main`. +- Do not add telemetry. +- Do not add a new learner-facing route or router owner. +- Preserve `pm01-state-v1` backward compatibility. +- M02-M10 completion behavior must remain unchanged. +- Target WCAG AA 4.5:1 for normal learner-facing text. + +--- + +### Task 1: Define lab data contract and load order + +**Files:** +- Create: `m01-learning-lab-data.js` +- Modify: `index.html` +- Test: `tests/static-contract.test.js` + +**Interfaces:** +- Consumes: `window.PM01`, `window.PM01.m01Validation.decisionDrills`. +- Produces: `lesson.learningLab = { technique, workedExample, drills, workbookFields, transferPrompt }` for the two M01 lessons. + +- [ ] Add a failing static-contract test requiring `m01-learning-lab-data.js` after `m01-validation-data.js` and before `app.js`, and requiring both M01 lesson IDs in the lab data file. +- [ ] Run `node --test tests/*.test.js`; verify the new contract fails because the file/load order does not exist. +- [ ] Add `m01-learning-lab-data.js` with one reusable technique/workbook contract per M01 lesson and attach the existing validation decision drill by `lessonId`. +- [ ] Add the script to `index.html` in the required order. +- [ ] Run the full test suite and commit. + +### Task 2: Render decision lab and workbook + +**Files:** +- Modify: `app.js` +- Test: `tests/m01-app-smoke.test.js` + +**Interfaces:** +- Consumes: optional `lesson.learningLab`. +- Produces: DOM classes `learning-lab`, `lab-drill`, `lab-feedback`, `lab-workbook`, `lab-transfer`, plus persisted `state.lab[lessonId]`. + +- [ ] Add a failing VM/static smoke test asserting an M01 lesson includes a decision drill, feedback region, technique, workbook fields, and transfer section while a non-M01 lesson does not include `learning-lab`. +- [ ] Verify RED with `node --test tests/*.test.js`. +- [ ] Extend `defaultState` with `lab: {}` and normalize missing nested lab state when rendering. +- [ ] Render the optional lab sequence before the final completion action: cold/guided decision drill → feedback → worked example → technique → workbook → transfer. +- [ ] Bind drill radio changes to persist the selected option and render option-specific feedback immediately. +- [ ] Bind workbook input/textarea changes to persist field values. +- [ ] Run full tests and commit. + +### Task 3: Gate M01 completion on evidence instead of checkbox self-attestation + +**Files:** +- Modify: `app.js` +- Test: `tests/m01-app-smoke.test.js` + +**Interfaces:** +- Produces: `labReady(lesson, state)` behavior: at least one answered drill and all required workbook fields non-empty. + +- [ ] Add a failing test that M01 completion is disabled until drill + required workbook fields are present, then enabled; verify a representative M02 lesson still uses the existing criteria gate. +- [ ] Verify RED. +- [ ] Add a small readiness helper and M01-specific completion status text. +- [ ] Hide legacy checkbox criteria for lessons with `learningLab`; preserve them for all other lessons. +- [ ] Update completion handler so M01 uses lab readiness and still writes to the existing `completed` array and advances to the next lesson. +- [ ] Run full tests and commit. + +### Task 4: Fix readability without losing art direction + +**Files:** +- Modify: `art-direction.css` +- Test: `tests/static-contract.test.js` + +**Interfaces:** +- Produces: readable essential text tokens and styled lab surfaces/classes. + +- [ ] Add failing CSS contract tests that essential secondary learner text uses a named readable token rather than `#5f5f5a`, `#666660`, or `#777770`, and that lab classes have explicit focus/feedback styles. +- [ ] Verify RED. +- [ ] Introduce `--text-secondary` and `--text-tertiary` values with visibly stronger contrast on `#0a0a0a`. +- [ ] Replace low-contrast essential labels/statuses with the new tokens while leaving decorative numbering subdued. +- [ ] Style `learning-lab`, drill options, feedback, technique, workbook, transfer, and focus states in the Editorial Instrument language. +- [ ] Ensure body/workbook text remains at least 16px where substantive. +- [ ] Run full tests and commit. + +### Task 5: Integration verification and clean Pages preview + +**Files:** +- Production assets only on `gh-pages` after feature CI is GREEN. + +- [ ] Run/confirm `Prototype CI` on the final feature head and require success. +- [ ] Build a clean `gh-pages` tree from current production assets plus the new lab data/app/style/index files; do not copy tests/docs into Pages. +- [ ] Verify Pages deployment success for the exact deploy SHA. +- [ ] Externally fetch root and first M01 lesson to confirm the new scripts/content are publicly served. +- [ ] Open a PR from `feature/m01-learning-lab` to `feature/art-direction-editorial-instrument`; do not merge. From 8775d523d417e6898998b1c1d561d3cd93833cbd Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:06:04 +0500 Subject: [PATCH 062/107] test: define M01 learning lab data and readability contracts --- tests/static-contract.test.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/static-contract.test.js b/tests/static-contract.test.js index badac28..c534de4 100644 --- a/tests/static-contract.test.js +++ b/tests/static-contract.test.js @@ -12,6 +12,8 @@ const html = read('index.html'); const baseApp = read('app.js'); const validationApp = read('m01-validation-app.js'); const validationStyles = read('m01-validation.css'); +const labData = read('m01-learning-lab-data.js'); +const artDirection = read('art-direction.css'); test('index loads validation styles and data, domain, base app, then validation extension in order', () => { assert.notEqual(html.indexOf('href="m01-validation.css"'), -1, 'missing isolated validation stylesheet'); @@ -52,3 +54,29 @@ test('validation styles are isolated under validation-specific classes', () => { assert.equal(validationStyles.includes('.validation-score'), true); assert.equal(validationStyles.includes('.validation-evidence'), true); }); + +test('M01 learning lab data loads after validation data and before the base app', () => { + const validationIndex = html.indexOf('src="m01-validation-data.js"'); + const labIndex = html.indexOf('src="m01-learning-lab-data.js"'); + const appIndex = html.indexOf('src="app.js"'); + assert.notEqual(labIndex, -1, 'missing m01-learning-lab-data.js'); + assert.equal(labIndex > validationIndex, true, 'lab data must load after validation data'); + assert.equal(labIndex < appIndex, true, 'lab data must load before app.js'); +}); + +test('M01 learning lab data targets exactly the two M01 lesson ids and reuses validation drills', () => { + assert.equal(labData.includes('project-system'), true, 'project-system lab contract missing'); + assert.equal(labData.includes('system-diagnostic'), true, 'system-diagnostic lab contract missing'); + assert.equal(labData.includes('decisionDrills'), true, 'lab must reuse existing decision drill source'); + assert.equal(labData.includes('workbookFields'), true, 'lab workbook field contract missing'); + assert.equal(labData.includes('transferPrompt'), true, 'lab transfer contract missing'); +}); + +test('Editorial Instrument exposes readable learner text tokens and dedicated learning-lab styles', () => { + assert.equal(artDirection.includes('--text-secondary:'), true, 'readable secondary text token missing'); + assert.equal(artDirection.includes('--text-tertiary:'), true, 'readable tertiary text token missing'); + assert.equal(artDirection.includes('.learning-lab'), true, 'learning-lab visual contract missing'); + assert.equal(artDirection.includes('.lab-feedback'), true, 'lab feedback visual contract missing'); + assert.equal(artDirection.includes('.lab-workbook'), true, 'lab workbook visual contract missing'); + assert.equal(artDirection.includes(':focus-visible'), true, 'visible keyboard focus contract missing'); +}); From 04597bf0fc1fb9c547873fb7c50daa700df73f4d Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:07:12 +0500 Subject: [PATCH 063/107] feat: define M01 learning lab exercises and workbooks --- m01-learning-lab-data.js | 137 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 m01-learning-lab-data.js diff --git a/m01-learning-lab-data.js b/m01-learning-lab-data.js new file mode 100644 index 0000000..b3c40c1 --- /dev/null +++ b/m01-learning-lab-data.js @@ -0,0 +1,137 @@ +(function () { + 'use strict'; + + const PM01 = window.PM01; + if (!PM01 || !PM01.m01Validation) return; + + const lessons = PM01.modules.flatMap((module) => module.lessons || []); + const decisionDrills = PM01.m01Validation.decisionDrills || []; + + function lesson(id) { + return lessons.find((item) => item.id === id); + } + + function validationDrill(lessonId) { + return decisionDrills.find((item) => item.lessonId === lessonId); + } + + function option(id, label, feedback, score) { + return { id, label, feedback, score }; + } + + const projectSystem = lesson('project-system'); + if (projectSystem) { + projectSystem.learningLab = { + skill: 'Отделять видимый симптом от устройства проекта и находить поток, который сильнее всего ограничивает результат.', + technique: { + name: 'Seven-flow scan', + purpose: 'Быстро проверить проект как систему, а не как список задач.', + steps: [ + 'Сначала сформулируй наблюдаемый результат проекта — что должно измениться вне команды.', + 'По каждому потоку запиши один проверяемый факт, а не оценку.', + 'Ищи не самый громкий симптом, а разрыв, который блокирует несколько следующих действий.', + 'Выбери один ранний сигнал: что изменится первым, если диагноз верный.' + ], + model: 'OUTCOME → 7 FLOWS → MAIN BREAK → INTERVENTION → EARLY SIGNAL' + }, + workedExample: { + title: 'Пример разбора · задача просрочена на пять дней', + steps: [ + 'Симптом: одна задача просрочена.', + 'Факт: разработчик три дня ждал API внешней команды.', + 'Разрыв: внешняя зависимость отсутствовала в плане как управляемый узел.', + 'Системное действие: сделать зависимость явной, назначить владельца и срок реакции.', + 'Ранний сигнал: время ожидания внешнего ответа сокращается до того, как сдвигается дата задачи.' + ] + }, + drills: [ + { ...validationDrill('project-system'), stage: 'cold', required: true }, + { + id: 'm01-exit-system', + lessonId: 'project-system', + stage: 'exit', + required: true, + title: 'Exit check · Зеленый статус, красный результат', + situation: 'У проекта 92% задач закрываются в срок, но запуск дважды переносился: решения по юридическим ограничениям приходят за 1–2 дня до контрольной даты.', + prompt: 'Какой следующий шаг лучше всего проверяет системный диагноз?', + options: [ + option('more-status', 'Добавить ежедневный статус по просроченным задачам', 'Просрочка задач здесь не является главным механизмом: статус усилит наблюдение за симптомом.', 0), + option('buffer', 'Добавить запас к срокам всех задач', 'Общий запас может скрыть задержку решения, но не меняет поток решений.', 1), + option('decision-flow', 'Измерить путь юридического вопроса до решения, владельца и время ожидания', 'Это проверяет поток решений и локализует место, где система теряет время до того, как страдает план.', 3) + ] + } + ], + workbookTitle: 'Карта проекта · семь потоков', + workbookFields: [ + { id: 'outcome', label: 'Наблюдаемый результат', prompt: 'Что должно реально измениться для клиента, пользователя или бизнеса?', required: true }, + { id: 'value', label: 'Ценность', prompt: 'Какой факт показывает, что команда решает нужную проблему?', required: true }, + { id: 'work', label: 'Работа', prompt: 'Где работа ждёт, накапливается или возвращается назад?', required: true }, + { id: 'information', label: 'Информация', prompt: 'Какая важная информация приходит слишком поздно?', required: true }, + { id: 'decisions', label: 'Решения', prompt: 'Какое решение чаще всего ждут и кто его владелец?', required: true }, + { id: 'dependencies', label: 'Зависимости', prompt: 'Какая внешняя или внутренняя зависимость блокирует следующие действия?', required: true }, + { id: 'uncertainty', label: 'Неопределённость', prompt: 'Что команда считает фактом, хотя это ещё только допущение?', required: true }, + { id: 'feedback', label: 'Обратная связь', prompt: 'Как проект узнаёт достаточно рано, что решение было ошибочным?', required: true }, + { id: 'main-break', label: 'Главный разрыв', prompt: 'Какой один разрыв создаёт наибольший downstream-эффект?', required: true }, + { id: 'signal', label: 'Ранний сигнал', prompt: 'Что изменится первым, если ты выбрал правильный разрыв?', required: true } + ], + transferPrompt: 'Возьми текущий проект. Заполни карту только фактами за последние 2–4 недели. Затем выбери один разрыв, который способен изменить несколько потоков сразу.' + }; + } + + const systemDiagnostic = lesson('system-diagnostic'); + if (systemDiagnostic) { + systemDiagnostic.learningLab = { + skill: 'Строить причинную цепь до воспроизводимого условия системы и выбирать вмешательство, которое меняет механизм.', + technique: { + name: 'Causal chain', + purpose: 'Не лечить проблему там, где она стала заметной.', + steps: [ + 'Назови симптом без объяснений и обвинений.', + 'Добавь наблюдаемое evidence: что именно происходило и когда.', + 'Опиши механизм: какая последовательность событий создаёт симптом.', + 'Найди системное условие, из-за которого механизм повторится с другим человеком.', + 'Выбери вмешательство в условие и ранний сигнал его эффекта.' + ], + model: 'SYMPTOM → EVIDENCE → MECHANISM → SYSTEM CONDITION → INTERVENTION → EARLY SIGNAL' + }, + workedExample: { + title: 'Пример разбора · функция трижды возвращается из QA', + steps: [ + 'Симптом: QA трижды возвращает функцию.', + 'Evidence: каждый возврат связан с новой трактовкой одного бизнес-правила.', + 'Механизм: несколько людей согласуют правило в переписке без единого финального решения.', + 'Условие системы: нет владельца трактовки и проверяемого примера до разработки.', + 'Вмешательство: один owner правила + пример ожидаемого поведения до старта реализации.', + 'Ранний сигнал: количество уточнений после начала разработки снижается.' + ] + }, + drills: [ + { ...validationDrill('system-diagnostic'), stage: 'cold', required: true }, + { + id: 'm01-exit-diagnostic', + lessonId: 'system-diagnostic', + stage: 'exit', + required: true, + title: 'Exit check · Команда постоянно «не успевает»', + situation: 'Каждую вторую неделю разработчики не заканчивают запланированное. За последние четыре недели 11 из 18 задач хотя бы раз блокировались ожиданием ответа, доступа или согласования.', + prompt: 'Какой диагноз наиболее полезен для первого вмешательства?', + options: [ + option('estimate', 'Команда плохо оценивает задачи — увеличить оценки на 30%', 'Увеличение оценок может скрыть ожидание внутри срока, но не устраняет воспроизводимый механизм.', 0), + option('discipline', 'Усилить контроль выполнения плана на daily', 'Контроль поведения людей не объясняет, почему большинство задач системно ждёт внешних условий.', 0), + option('waiting-system', 'Ожидание внешних решений и доступов встроено в поток, но не управляется как отдельная работа', 'Диагноз объясняет повторяемость и подсказывает проверяемое вмешательство: сделать ожидания явными, измерять их и назначать владельцев.', 3) + ] + } + ], + workbookTitle: 'Причинная цепь', + workbookFields: [ + { id: 'symptom', label: 'Симптом', prompt: 'Что стало видимым? Опиши без объяснений и имён.', required: true }, + { id: 'evidence', label: 'Evidence', prompt: 'Какие 2–3 наблюдаемых факта подтверждают, что проблема существует?', required: true }, + { id: 'mechanism', label: 'Механизм', prompt: 'Какая последовательность событий создаёт симптом?', required: true }, + { id: 'condition', label: 'Условие системы', prompt: 'Почему это повторится с другим человеком?', required: true }, + { id: 'intervention', label: 'Вмешательство', prompt: 'Что изменить в системе, а не в поведении конкретного человека?', required: true }, + { id: 'signal', label: 'Ранний сигнал', prompt: 'Какой показатель изменится до итогового результата?', required: true } + ], + transferPrompt: 'Выбери одну повторяющуюся проблему своего проекта и собери цепь только из фактов. Если в конце цепи стоит имя человека — продолжи анализ ещё на один уровень.' + }; + } +})(); From 7cae68fd4bbaf44424449db6bcb0f1053f09800a Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:07:25 +0500 Subject: [PATCH 064/107] feat: load M01 learning lab data before app --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index e9777e5..0d0f801 100644 --- a/index.html +++ b/index.html @@ -47,6 +47,7 @@ + From 908c5d3c44c10c5020f7418419ca9b8b4eede5b9 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:08:02 +0500 Subject: [PATCH 065/107] test: define M01 learning lab runtime contract --- tests/m01-learning-lab.test.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/m01-learning-lab.test.js diff --git a/tests/m01-learning-lab.test.js b/tests/m01-learning-lab.test.js new file mode 100644 index 0000000..36b4283 --- /dev/null +++ b/tests/m01-learning-lab.test.js @@ -0,0 +1,33 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const app = fs.readFileSync(path.join(__dirname, '..', 'app.js'), 'utf8'); + +test('base state remains backward compatible while adding isolated lab state', () => { + assert.equal(app.includes('lab: {}'), true, 'default state must include an empty lab object'); + assert.equal(app.includes('pm01-state-v1'), true, 'existing storage key must remain unchanged'); +}); + +test('M01 lesson renderer exposes a decision-training sequence and workbook', () => { + assert.equal(app.includes('lesson.learningLab'), true, 'lesson renderer must branch on optional learningLab metadata'); + assert.equal(app.includes('class="learning-lab"'), true, 'learning lab wrapper missing'); + assert.equal(app.includes('data-lab-drill'), true, 'decision drill controls missing'); + assert.equal(app.includes('class="lab-feedback"'), true, 'immediate feedback region missing'); + assert.equal(app.includes('data-lab-field'), true, 'workbook persistence controls missing'); + assert.equal(app.includes('class="lab-transfer"'), true, 'real-project transfer section missing'); +}); + +test('M01 completion uses substantive lab readiness while legacy lessons keep criteria gate', () => { + assert.equal(app.includes('function labReady'), true, 'lab readiness helper missing'); + assert.equal(app.includes('requiredDrills'), true, 'lab readiness must account for required drills'); + assert.equal(app.includes('requiredFields'), true, 'lab readiness must account for required workbook fields'); + assert.equal(app.includes('data-criterion'), true, 'legacy criteria gate must remain available for M02-M10'); +}); + +test('lab interactions persist drill answers and workbook values in the existing lesson state', () => { + assert.equal(app.includes('drillAnswers'), true, 'drill answers must be persisted'); + assert.equal(app.includes('workbook'), true, 'workbook values must be persisted'); + assert.equal(app.includes('updateLabCompletionGate'), true, 'lab completion gate must refresh after interaction'); +}); From 3abfb41125fba000947e26f3a9de63d48f1aac27 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 05:10:53 +0500 Subject: [PATCH 066/107] feat: integrate M01 learning lab into lesson flow --- app.js | 233 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 196 insertions(+), 37 deletions(-) diff --git a/app.js b/app.js index 7db5e3b..4f59d06 100644 --- a/app.js +++ b/app.js @@ -6,13 +6,13 @@ module.lessons.map((lesson) => ({ ...lesson, moduleId: module.id, moduleTitle: module.title })) ); const storageKey = "pm01-state-v1"; - const defaultState = { completed: [], notes: {}, criteria: {}, lastLesson: null, diagnostic: {} }; + const defaultState = { completed: [], notes: {}, criteria: {}, lastLesson: null, diagnostic: {}, lab: {} }; let state = loadState(); function loadState() { try { - const stored = JSON.parse(localStorage.getItem(storageKey)); - return { ...defaultState, ...stored }; + const stored = JSON.parse(localStorage.getItem(storageKey)) || {}; + return { ...defaultState, ...stored, lab: stored.lab || {} }; } catch (_) { return { ...defaultState }; } @@ -79,6 +79,30 @@ return module.lessons.find((lesson) => !state.completed.includes(lesson.id)) || module.lessons.at(-1); } + function ensureLabState(id) { + state.lab ||= {}; + state.lab[id] ||= { drillAnswers: {}, workbook: {} }; + state.lab[id].drillAnswers ||= {}; + state.lab[id].workbook ||= {}; + return state.lab[id]; + } + + function labReady(lesson) { + if (!lesson?.learningLab) return { ready: false, answeredDrills: 0, requiredDrills: 0, completedFields: 0, requiredFields: 0 }; + const lessonState = ensureLabState(lesson.id); + const requiredDrills = lesson.learningLab.drills.filter((drill) => drill.required !== false); + const requiredFields = lesson.learningLab.workbookFields.filter((field) => field.required !== false); + const answeredDrills = requiredDrills.filter((drill) => Boolean(lessonState.drillAnswers[drill.id])).length; + const completedFields = requiredFields.filter((field) => String(lessonState.workbook[field.id] || "").trim().length > 0).length; + return { + ready: answeredDrills === requiredDrills.length && completedFields === requiredFields.length, + answeredDrills, + requiredDrills: requiredDrills.length, + completedFields, + requiredFields: requiredFields.length, + }; + } + function homeView() { const next = nextLesson(); const nextIndex = allLessons.findIndex((lesson) => lesson.id === next.id) + 1; @@ -145,10 +169,10 @@ const currentModuleIndex = DATA.modules.findIndex((module) => moduleCompletion(module).done < module.lessons.length); return `
-

Основной путь

10 модулей.
Иди по порядку.

Начни с первого незавершённого урока. В конце каждого урока есть короткая практика. Когда она выполнена, кнопка завершения откроет следующий шаг. Проверки и диагностика дополняют путь, но не создают второй курс.

+

Основной путь

10 модулей.
Иди по порядку.

Начни с первого незавершённого урока. В M01 решения и рабочая карта проверяются прямо внутри урока. Проверки и диагностика дополняют путь, но не создают второй курс.

${hours[0]}–${hours[1]} ч

ориентир по времени

${progress()}%

пройдено

${state.completed.length}/${allLessons.length}

уроков завершено

-
Как двигаться:1. Открой текущий урок2. Сделай практику3. Заверши урок4. Перейди дальше
+
Как двигаться:1. Разбери кейс2. Примени технику3. Заполни рабочий инструмент4. Проверь перенос на проект
${DATA.modules.map((module, index) => { const completion = moduleCompletion(module); const target = moduleTargetLesson(module); @@ -160,18 +184,118 @@
`; } + function renderLabFeedback(drill, answerId) { + const option = drill.options?.find((item) => item.id === answerId); + if (!option) return `
Выбери вариант, чтобы получить разбор.
`; + const label = Number(option.score) >= 3 ? "Сильный ход" : Number(option.score) >= 2 ? "Неполный диагноз" : "Слабый ход"; + return `
${label}

${escapeHtml(option.feedback)}

`; + } + + function renderLabDrill(drill, lessonState, isCold = false) { + if (!drill) return ""; + const answer = lessonState.drillAnswers[drill.id]; + return `
+

${isCold ? "01 · Сначала реши" : "04 · Exit check"}

+

${escapeHtml(drill.title || "Decision drill")}

+

${escapeHtml(drill.situation || "")}

+
+ ${escapeHtml(drill.prompt || "Что сделаешь?")} +
${(drill.options || []).map((option) => ``).join("")}
+
+ ${renderLabFeedback(drill, answer)} +
`; + } + + function renderLearningLab(lesson, next, done) { + const lab = lesson.learningLab; + const lessonState = ensureLabState(lesson.id); + const readiness = labReady(lesson); + const coldDrill = lab.drills.find((drill) => drill.stage === "cold") || lab.drills[0]; + const laterDrills = lab.drills.filter((drill) => drill !== coldDrill); + return `
+
+

Learning Lab

+

Навык урока

+

${escapeHtml(lab.skill)}

+
+ + ${renderLabDrill(coldDrill, lessonState, true)} + +
+

02 · Сверь мышление

+

${escapeHtml(lab.workedExample.title)}

+
    ${lab.workedExample.steps.map((step) => `
  1. ${escapeHtml(step)}
  2. `).join("")}
+
+ +
+

03 · Техника

+

${escapeHtml(lab.technique.name)}

+

${escapeHtml(lab.technique.purpose)}

+
    ${lab.technique.steps.map((step) => `
  1. ${escapeHtml(step)}
  2. `).join("")}
+
${escapeHtml(lab.technique.model)}
+
+ + ${laterDrills.map((drill) => renderLabDrill(drill, lessonState)).join("")} + +
+

05 · Рабочий инструмент

+

${escapeHtml(lab.workbookTitle)}

+

Заполни поля фактами. Ответы сохраняются в этом браузере автоматически.

+
${lab.workbookFields.map((field) => ``).join("")}
+
+ +
+

06 · Перенос

+

Примени к реальному проекту

+

${escapeHtml(lab.transferPrompt)}

+
+ +
+

${readiness.answeredDrills}/${readiness.requiredDrills} решений · ${readiness.completedFields}/${readiness.requiredFields} полей${readiness.ready ? " · можно завершать урок" : " · заверши обязательные решения и рабочую карту"}

+ + +
+ ${done + ? `${next ? "Продолжить к следующему уроку" : "Вернуться к программе"} →` + : ``} + +
+
+
`; + } + + function renderLegacyPractice(lesson, next, done, checked, ready) { + const required = lesson.criteria.length; + return `
+

Практика

Примени к своему проекту

+
    ${lesson.practice.map((step) => `
  1. ${step}
  2. `).join("")}
+

Проверь результат

+

Отметь пункт только если действительно сделал его. Это не тест — чекбоксы просто помогают понять, готов ли ты идти дальше.

+
${lesson.criteria.map((criterion, criterionIndex) => ``).join("")}
+

${checked.length}/${required} выполнено${ready ? " · можно завершать урок" : " · выполни все пункты, чтобы открыть следующий шаг"}

+ + +
+ ${done + ? `${next ? "Продолжить к следующему уроку" : "Вернуться к программе"} →` + : ``} + +
+
`; + } + function lessonView(id) { const lesson = allLessons.find((item) => item.id === id); if (!lesson) return notFoundView(); state.lastLesson = id; + if (lesson.learningLab) ensureLabState(id); saveState(); const index = allLessons.findIndex((item) => item.id === id); const previous = allLessons[index - 1]; const next = allLessons[index + 1]; const checked = state.criteria[id] || []; const done = state.completed.includes(id); - const required = lesson.criteria.length; - const ready = checked.length >= required; + const ready = lesson.learningLab ? labReady(lesson).ready : checked.length >= lesson.criteria.length; return `
@@ -179,32 +303,20 @@

${lesson.moduleTitle} · урок ${String(index + 1).padStart(2, "0")}/${allLessons.length}

${lesson.title}

${lesson.thesis}

-
${lesson.minutes} минут${done ? "завершён ✓" : "теория + практика"}
+
${lesson.minutes} минут${done ? "завершён ✓" : lesson.learningLab ? "кейс + техника + инструмент" : "теория + практика"}
-

Главная мысль

${lesson.body.map((paragraph) => `

${paragraph}

`).join("")}
-
${lesson.thesis}
-

Как это работает

${lesson.model}
-
-

Практика

Примени к своему проекту

-
    ${lesson.practice.map((step) => `
  1. ${step}
  2. `).join("")}
-

Проверь результат

-

Отметь пункт только если действительно сделал его. Это не тест — чекбоксы просто помогают понять, готов ли ты идти дальше.

-
${lesson.criteria.map((criterion, criterionIndex) => ``).join("")}
-

${checked.length}/${required} выполнено${ready ? " · можно завершать урок" : " · выполни все пункты, чтобы открыть следующий шаг"}

- - -
- ${done - ? `${next ? "Продолжить к следующему уроку" : "Вернуться к программе"} →` - : ``} - -
-
+ ${lesson.learningLab ? "" : `

Главная мысль

${lesson.body.map((paragraph) => `

${paragraph}

`).join("")}
`} + ${lesson.learningLab ? renderLearningLab(lesson, next, done) : ` +
${lesson.thesis}
+

Как это работает

${lesson.model}
+ ${renderLegacyPractice(lesson, next, done, checked, ready)}`}
`; } - function drillBlock(drill) { - const stored = state.drills[drill.id]; - const selected = stored && stored.choice; - const options = drill.options.map((item) => { - const checked = selected === item.id; - return `${checked ? `

${escapeHtml(item.feedback)}

` : ''}`; - }).join(''); - return `
-

Decision Drill

-

${escapeHtml(drill.title)}

-

${escapeHtml(drill.situation)}

-
- ${escapeHtml(drill.prompt)} -
${options}
-
-
`; - } - function learningBlock() { const studied = isStudied(); - const drillsDone = allDrillsAnswered(); return `
-

02 · Learning

Изучи модель и прими два решения

Вернись сюда после двух уроков. Decision Drills фиксируют первое решение до показа feedback.

+

02 · Learning

Пройди два урока M01

Первый выбор и feedback уже встроены в Learning Lab каждого урока. Возвращайся сюда после завершения обоих уроков.

-

Уроки: ${studied ? 'изучены ✓' : 'нужно завершить оба'} · Drills: ${drillsDone ? '2/2 ✓' : `${Object.keys(state.drills).length}/2`}

-
${DATA.decisionDrills.map(drillBlock).join('')}
+

Уроки: ${studied ? 'изучены ✓' : 'нужно завершить оба'}

`; } @@ -279,7 +252,7 @@ function validationView() { const baselineDone = Boolean(state.baseline.submittedAt); - const postUnlocked = baselineDone && isStudied() && allDrillsAnswered(); + const postUnlocked = baselineDone && isStudied(); const postDone = Boolean(state.postCase.submittedAt); const fieldDone = Boolean(state.field.submittedAt); const storageWarning = storageHealthy ? '' : '

Браузер сейчас не дает сохранить localStorage. Не продолжай эксперимент до восстановления хранения: переход к урокам может привести к потере ответов.

'; @@ -353,15 +326,6 @@ })); } - function bindDrillEvents() { - document.querySelectorAll('[data-drill]').forEach((input) => input.addEventListener('change', () => { - const id = input.dataset.drill; - if (state.drills[id] && state.drills[id].choice) return; - state.drills[id] = { choice: input.value, selectedAt: new Date().toISOString() }; - if (saveValidationState()) rerenderValidation('Первый выбор Decision Drill зафиксирован; feedback открыт.'); - })); - } - function bindFieldEvents() { document.querySelectorAll('[data-field]').forEach((textarea) => textarea.addEventListener('change', () => { if (state.field.submittedAt) return; @@ -436,7 +400,6 @@ main.innerHTML = validationView(); document.querySelectorAll('.main-nav a').forEach((link) => link.classList.toggle('active', link.dataset.route === 'course')); bindAssessmentEvents(); - bindDrillEvents(); bindFieldEvents(); bindReflectionEvents(); bindReset(); From 35641ebb509d07102645ca4328ec42868a04bd0b Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 21:45:31 +0500 Subject: [PATCH 076/107] test: exercise cold drill lock through M01 extension --- tests/m01-cold-drill-lock.test.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/tests/m01-cold-drill-lock.test.js b/tests/m01-cold-drill-lock.test.js index 20fe7a6..da2d652 100644 --- a/tests/m01-cold-drill-lock.test.js +++ b/tests/m01-cold-drill-lock.test.js @@ -8,6 +8,8 @@ const courseDataCode = fs.readFileSync(path.join(__dirname, '..', 'course-data.j const validationDataCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-data.js'), 'utf8'); const labDataCode = fs.readFileSync(path.join(__dirname, '..', 'm01-learning-lab-data.js'), 'utf8'); const appCode = fs.readFileSync(path.join(__dirname, '..', 'app.js'), 'utf8'); +const validationAppCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-app.js'), 'utf8'); +const domain = require('../learning-domain.js'); function storageFrom(initial = {}) { const values = new Map(Object.entries(initial)); @@ -23,12 +25,18 @@ function drillInput(value) { value, disabled: false, dataset: { labDrill: 'm01-drill-system' }, + listeners: [], addEventListener(type, callback) { - if (type === 'change') this.change = callback; + if (type === 'change') this.listeners.push(callback); }, }; } +function dispatchChange(input) { + if (input.disabled) return; + for (const callback of input.listeners) callback({ target: input, currentTarget: input }); +} + test('M01 cold drill freezes the first choice after feedback instead of allowing answer replacement', () => { const first = drillInput('escalate-dev'); const second = drillInput('track-dependency'); @@ -53,14 +61,17 @@ test('M01 cold drill freezes the first choice after feedback instead of allowing }; const localStorage = storageFrom(); const window = { + PM01Learning: domain, addEventListener() {}, scrollTo() {}, + confirm() { return true; }, }; const context = { window, document, localStorage, location: { hash: '#/lesson/project-system' }, + queueMicrotask(callback) { callback(); }, console, Blob, URL, @@ -72,12 +83,14 @@ test('M01 cold drill freezes the first choice after feedback instead of allowing vm.runInContext(validationDataCode, context, { filename: 'm01-validation-data.js' }); vm.runInContext(labDataCode, context, { filename: 'm01-learning-lab-data.js' }); vm.runInContext(appCode, context, { filename: 'app.js' }); + vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); - first.change({ target: first }); - second.change({ target: second }); + dispatchChange(first); + assert.equal(first.disabled, true); + assert.equal(second.disabled, true); + + dispatchChange(second); const stored = JSON.parse(localStorage.getItem('pm01-state-v1')); assert.equal(stored.lab['project-system'].drillAnswers['m01-drill-system'], 'escalate-dev'); - assert.equal(first.disabled, true); - assert.equal(second.disabled, true); }); From 1f90deab56959a03e2b3906f0b14612919f6c203 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 21:46:42 +0500 Subject: [PATCH 077/107] fix: freeze M01 cold drill first choice --- m01-validation-app.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/m01-validation-app.js b/m01-validation-app.js index e838c61..3c9a8ab 100644 --- a/m01-validation-app.js +++ b/m01-validation-app.js @@ -115,6 +115,33 @@ return location.hash.replace(/^#\/?/, '').replace(/\/$/, ''); } + function bindColdLabDrillLock() { + const route = currentRoute(); + const lessonId = route.startsWith('lesson/') ? route.slice('lesson/'.length) : null; + if (!lessonId || !m01LessonIds.includes(lessonId)) return; + + const currentLesson = lesson(lessonId); + const coldDrill = currentLesson && currentLesson.learningLab + ? currentLesson.learningLab.drills.find((drill) => drill.stage === 'cold') + : null; + if (!coldDrill) return; + + const inputs = [...document.querySelectorAll(`[data-lab-drill="${coldDrill.id}"]`)]; + if (!inputs.length) return; + + const lock = () => inputs.forEach((input) => { input.disabled = true; }); + const stored = legacyState(); + const answer = stored.lab && stored.lab[lessonId] + && stored.lab[lessonId].drillAnswers && stored.lab[lessonId].drillAnswers[coldDrill.id]; + + if (answer) { + lock(); + return; + } + + inputs.forEach((input) => input.addEventListener('change', lock)); + } + function setMessage(text, error) { const target = document.querySelector('#validation-message'); if (!target) return; @@ -421,6 +448,7 @@ function renderExtension() { if (currentRoute() === validationRoute) renderValidationRoute(); else if (currentRoute() === 'course') decorateCourse(); + else bindColdLabDrillLock(); } window.addEventListener('hashchange', () => queueMicrotask(renderExtension)); From eeba971b8d7dbb427e01e458c234c774d8ef7e8f Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Mon, 7 Sep 2026 21:47:39 +0500 Subject: [PATCH 078/107] docs: decontaminate M01 cohort setup --- docs/validation/M01-VALIDATION-PROTOCOL.md | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/validation/M01-VALIDATION-PROTOCOL.md b/docs/validation/M01-VALIDATION-PROTOCOL.md index 0805e38..f5d6720 100644 --- a/docs/validation/M01-VALIDATION-PROTOCOL.md +++ b/docs/validation/M01-VALIDATION-PROTOCOL.md @@ -53,15 +53,23 @@ The prototype stores experiment data only in browser `localStorage` under: `pm01-validation-m01-v1` -The main course continues using its own `pm01-state-v1` key. The separation prevents legacy course saves from erasing validation work. +The main course uses a separate key: + +`pm01-state-v1` + +For **each new participant**, start from a fresh browser context where both keys are absent. Use a fresh browser profile/private session or clear all PMO01 site data before opening the validation route. + +Do **not** prepare a different participant by using only **Сбросить M01 validation data**. That control intentionally clears `pm01-validation-m01-v1` but preserves the main-course progress in `pm01-state-v1`, so reused course progress could contaminate the next participant's learning sequence. For each new participant: -1. open `#/validation/m01`; -2. use **Сбросить M01 validation data** before the session, or use a fresh browser profile; -3. verify the page does not show an existing baseline score; +1. start a fresh browser context or clear all PMO01 site data; +2. open `#/validation/m01`; +3. verify the page does not show an existing baseline score or completed M01 state; 4. do not show the M01 lesson content before baseline is submitted. +The in-product validation reset remains useful for restarting the **same participant's** validation answers when the facilitator intentionally wants to preserve their course state. It is not a cohort participant reset. + ## Measurement blinding rule Baseline answers and the computed score are persisted and frozen at submission, but the learner must **not** see baseline score or option-level feedback before completing the post-case. @@ -102,18 +110,20 @@ Do not coach the participant toward the system model and do not disclose whether ### 2. Learning experience -Participant completes both existing M01 lessons: +Participant completes both existing M01 Learning Lab lessons: - `project-system`; - `system-diagnostic`. -Then they complete both Decision Drills. The first drill choice is frozen before feedback is revealed. +Each lesson contains its own cold Decision Drill, immediate feedback, an exit drill, and required workbook evidence. The cold drill records the learner's first choice and becomes read-only after feedback. + +The validation route must **not** repeat these same drills. Post-case becomes available after both M01 lessons satisfy their current evidence-aware completion contract. Observe: - whether feedback explains consequences rather than merely marking correct/incorrect; -- whether a drill exposes a misconception that the lesson text did not expose; -- whether the participant can explain why their choice changed after feedback. +- whether a drill exposes a misconception that the lesson material did not expose; +- whether the participant can explain why the feedback changed or did not change their reasoning. ### 3. Integrative post-case @@ -190,7 +200,7 @@ For each completed learner session, retain only the evidence needed for the Phas - anonymized participant ID and role/experience; - baseline total, post total, delta, and improved dimensions; - baseline and post free-text diagnoses; -- whether each Decision Drill exposed or changed reasoning; +- first choice from each M01 cold drill and whether its feedback exposed or changed reasoning; - field transfer: credible / partial / no; - reflection showing changed diagnosis/action/evidence requirement, or no meaningful change; - major UX/reliability/content ambiguity that could distort the result; @@ -269,4 +279,4 @@ After the minimum cohort is complete, record exactly one outcome: - **Revise and retest** — promising signal, but content, rubric, interaction, or friction materially distorts results. - **Reject mechanism** — interaction or module format adds complexity without useful reasoning/transfer evidence. -Until one of these outcomes is recorded after at least 5 completed sessions, Phase 2 and v1 work remain blocked. +Until one of these outcomes is recorded after at least 5 completed sessions, Phase 2 and v1 work remain blocked. \ No newline at end of file From c47436329af2644a8c58532dff95d695dd965107 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:49:20 +0500 Subject: [PATCH 079/107] test: define M01.1 content quality contract --- tests/m01-content-quality.test.js | 63 +++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/m01-content-quality.test.js diff --git a/tests/m01-content-quality.test.js b/tests/m01-content-quality.test.js new file mode 100644 index 0000000..40ba817 --- /dev/null +++ b/tests/m01-content-quality.test.js @@ -0,0 +1,63 @@ +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const vm = require('node:vm'); + +const courseDataCode = fs.readFileSync(path.join(__dirname, '..', 'course-data.js'), 'utf8'); +const validationDataCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-data.js'), 'utf8'); +const labDataCode = fs.readFileSync(path.join(__dirname, '..', 'm01-learning-lab-data.js'), 'utf8'); + +function loadM01() { + const context = { window: {}, console }; + vm.createContext(context); + vm.runInContext(courseDataCode, context, { filename: 'course-data.js' }); + vm.runInContext(validationDataCode, context, { filename: 'm01-validation-data.js' }); + vm.runInContext(labDataCode, context, { filename: 'm01-learning-lab-data.js' }); + const pm01 = context.window.PM01; + const lessons = pm01.modules.flatMap((module) => module.lessons || []); + return { + validation: pm01.m01Validation, + lessons: ['project-system', 'system-diagnostic'].map((id) => lessons.find((lesson) => lesson.id === id)), + }; +} + +test('M01.1 preserves the five validation dimensions', () => { + const { validation } = loadM01(); + assert.deepEqual( + Array.from(validation.rubricDimensions, (dimension) => dimension.id), + ['mechanism', 'evidence', 'tradeoffs', 'intervention', 'changeCondition'] + ); +}); + +test('every required M01 drill uses 3-4 plausible choices with a scored near-miss', () => { + const { lessons } = loadM01(); + for (const lesson of lessons) { + for (const drill of lesson.learningLab.drills.filter((item) => item.required !== false)) { + assert.ok(drill.options.length >= 3 && drill.options.length <= 4, `${drill.id} must have 3-4 options`); + assert.ok(drill.options.some((option) => Number(option.score) === 3), `${drill.id} must have a strongest option`); + assert.ok(drill.options.some((option) => Number(option.score) === 2), `${drill.id} must include a credible near-miss`); + } + } +}); + +test('each M01 workbook has six required fields including alternative hypothesis and falsifier', () => { + const { lessons } = loadM01(); + for (const lesson of lessons) { + const required = lesson.learningLab.workbookFields.filter((field) => field.required !== false); + assert.equal(required.length, 6, `${lesson.id} must have exactly six required workbook fields`); + const ids = new Set(required.map((field) => field.id)); + assert.ok(ids.has('alternative'), `${lesson.id} must require an alternative hypothesis`); + assert.ok(ids.has('falsifier'), `${lesson.id} must require a falsifying fact`); + } +}); + +test('M01 transfer requires a real decision, evidence and a condition for revising it', () => { + const { lessons } = loadM01(); + for (const lesson of lessons) { + const prompt = lesson.learningLab.transferPrompt.toLowerCase(); + assert.match(prompt, /решени/, `${lesson.id} transfer must require a real decision`); + assert.match(prompt, /(evidence|доказ|факт)/, `${lesson.id} transfer must require evidence`); + assert.match(prompt, /(пересмотр|изменить|отменить|опроверг)/, `${lesson.id} transfer must define when to revise the decision`); + } +}); From 54df481fbffea0de44c8ed4a6f78d2876e220633 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:52:03 +0500 Subject: [PATCH 080/107] feat: strengthen M01 learning content --- m01-learning-lab-data.js | 155 ++++++++++++++++++++++----------------- 1 file changed, 89 insertions(+), 66 deletions(-) diff --git a/m01-learning-lab-data.js b/m01-learning-lab-data.js index b3c40c1..33b328d 100644 --- a/m01-learning-lab-data.js +++ b/m01-learning-lab-data.js @@ -5,16 +5,11 @@ if (!PM01 || !PM01.m01Validation) return; const lessons = PM01.modules.flatMap((module) => module.lessons || []); - const decisionDrills = PM01.m01Validation.decisionDrills || []; function lesson(id) { return lessons.find((item) => item.id === id); } - function validationDrill(lessonId) { - return decisionDrills.find((item) => item.lessonId === lessonId); - } - function option(id, label, feedback, score) { return { id, label, feedback, score }; } @@ -22,116 +17,144 @@ const projectSystem = lesson('project-system'); if (projectSystem) { projectSystem.learningLab = { - skill: 'Отделять видимый симптом от устройства проекта и находить поток, который сильнее всего ограничивает результат.', + skill: 'Сравнивать несколько правдоподобных объяснений проблемы проекта и выбирать системный диагноз по различающим их фактам.', technique: { - name: 'Seven-flow scan', - purpose: 'Быстро проверить проект как систему, а не как список задач.', + name: 'Seven-flow hypothesis scan', + purpose: 'Быстро перейти от симптома к проверяемой гипотезе о том, какой поток ограничивает результат.', steps: [ - 'Сначала сформулируй наблюдаемый результат проекта — что должно измениться вне команды.', - 'По каждому потоку запиши один проверяемый факт, а не оценку.', - 'Ищи не самый громкий симптом, а разрыв, который блокирует несколько следующих действий.', - 'Выбери один ранний сигнал: что изменится первым, если диагноз верный.' + 'Сформулируй наблюдаемый outcome и 2–4 факта за последние недели — без объяснений и оценок людей.', + 'Просканируй семь потоков и отметь только 2–3, которые реально могут объяснить эти факты.', + 'Сформулируй главный системный диагноз и одну сильную альтернативу, которая тоже согласуется с наблюдениями.', + 'Найди различающий факт: какое наблюдение повысит вероятность одного объяснения и ослабит другое.', + 'Выбери минимальное вмешательство и ранний сигнал. Заранее запиши, при каком факте диагноз нужно пересмотреть.' ], - model: 'OUTCOME → 7 FLOWS → MAIN BREAK → INTERVENTION → EARLY SIGNAL' + model: 'OUTCOME → FACTS → 2–3 SUSPECT FLOWS → HYPOTHESIS ↔ ALTERNATIVE → DISCRIMINATING EVIDENCE → DECISION → EARLY SIGNAL' }, workedExample: { - title: 'Пример разбора · задача просрочена на пять дней', + title: 'Пример разбора · внешняя зависимость задержала задачу', steps: [ - 'Симптом: одна задача просрочена.', - 'Факт: разработчик три дня ждал API внешней команды.', - 'Разрыв: внешняя зависимость отсутствовала в плане как управляемый узел.', - 'Системное действие: сделать зависимость явной, назначить владельца и срок реакции.', - 'Ранний сигнал: время ожидания внешнего ответа сокращается до того, как сдвигается дата задачи.' + 'Outcome: интеграция должна быть готова к партнерскому тесту в пятницу.', + 'Факты: работа остановилась на три дня; API был внешним; ожидание не отображалось в плане; две другие задачи той же команды не блокировались.', + 'Гипотеза: внешние зависимости не управляются как отдельный поток — нет явного владельца, следующего обязательства и возраста ожидания.', + 'Сильная альтернатива: проблема локальна для этой интеграции — команда поздно обнаружила конкретную техническую несовместимость.', + 'Различающий факт: если у нескольких зависимостей есть длинные периоды ожидания без владельца/следующего шага, системная гипотеза усиливается; если нет — ослабевает.', + 'Решение: сделать активные внешние зависимости явными и измерять возраст ожидания. Ранний сигнал — старые зависимости начинают закрываться раньше сдвига задач.' ] }, drills: [ - { ...validationDrill('project-system'), stage: 'cold', required: true }, + { + id: 'm01-drill-system', + lessonId: 'project-system', + stage: 'cold', + required: true, + title: 'Cold decision · Красная задача', + situation: 'Интеграционная задача просрочена на пять дней. Разработчик три дня ждал API внешней команды. Внешняя команда говорит, что базовый endpoint был известен заранее, но нужный scope доступа согласовали только после запроса разработчика. В плане ожидание отдельно не отображалось.', + prompt: 'Какой первый ход даст самый сильный системный диагноз, а не просто уменьшит риск следующей просрочки?', + options: [ + option('historical-buffer', 'Добавить к похожим интеграциям резерв на основе исторического времени ожидания внешних команд', 'Это разумно улучшает прогноз, но может лишь встроить неизвестный механизм ожидания в оценку, не объясняя его.', 1), + option('dependency-owner', 'Для каждой внешней зависимости сразу назначать владельца, дату следующего обязательства и правило эскалации', 'Это сильное управленческое действие и может сократить ожидание, но оно уже предполагает, что главный механизм найден.', 2), + option('blocker-discipline', 'Требовать фиксировать внешний blocker в день обнаружения и поднимать его на следующем daily', 'Это улучшит видимость и скорость реакции, но оставляет открытым вопрос, где именно возникает системное ожидание: в обнаружении, решении, доступе или передаче.', 2), + option('dependency-timeline', 'Восстановить timeline нескольких внешних зависимостей: когда возник запрос, кто владел следующим шагом, сколько ждали и что разблокировало работу; затем выбрать общий механизм', 'Такой ход различает конкурирующие объяснения и позволяет вмешиваться в повторяемый механизм, а не в единичный симптом.', 3) + ] + }, { id: 'm01-exit-system', lessonId: 'project-system', stage: 'exit', required: true, title: 'Exit check · Зеленый статус, красный результат', - situation: 'У проекта 92% задач закрываются в срок, но запуск дважды переносился: решения по юридическим ограничениям приходят за 1–2 дня до контрольной даты.', - prompt: 'Какой следующий шаг лучше всего проверяет системный диагноз?', + situation: 'У проекта 92% задач закрываются в срок, но запуск дважды переносился. Юридические вопросы обычно появляются за 10–14 дней до контрольной даты, проходят через продуктового менеджера и юриста, а финальные ограничения часто фиксируются за 1–2 дня до даты. Иногда задержка вызвана отсутствием ответа бизнеса, иногда — новой трактовкой юриста.', + prompt: 'Что лучше сделать первым, если гипотеза — проект ограничен потоком решений, а не исполнением задач?', options: [ - option('more-status', 'Добавить ежедневный статус по просроченным задачам', 'Просрочка задач здесь не является главным механизмом: статус усилит наблюдение за симптомом.', 0), - option('buffer', 'Добавить запас к срокам всех задач', 'Общий запас может скрыть задержку решения, но не меняет поток решений.', 1), - option('decision-flow', 'Измерить путь юридического вопроса до решения, владельца и время ожидания', 'Это проверяет поток решений и локализует место, где система теряет время до того, как страдает план.', 3) + option('schedule-contingency', 'Добавить общий contingency к плану запуска на основе двух прошлых переносов', 'Это делает прогноз реалистичнее, но не проверяет, почему критичные решения закрываются поздно.', 1), + option('legal-sla', 'Сразу ввести SLA: юридический ответ не позднее чем через два рабочих дня', 'SLA может стать полезным вмешательством, но пока смешивает разные причины задержки и предполагает, что именно скорость юриста является узким местом.', 2), + option('decision-review', 'Проводить еженедельный review всех открытых юридических вопросов с бизнесом и юристом', 'Review повышает частоту внимания и может сократить возраст решений, но не покажет, какой участок пути реально создаёт задержку.', 2), + option('decision-timeline', 'Взять последние 8–10 критичных вопросов и восстановить путь вопрос → владелец следующего шага → ожидание → решение → downstream-переделка; затем поставить порог на найденное узкое место', 'Это сначала проверяет механизм и только потом задаёт интервенцию там, где накапливается возраст решения.', 3) ] } ], - workbookTitle: 'Карта проекта · семь потоков', + workbookTitle: 'Карта системной гипотезы', workbookFields: [ - { id: 'outcome', label: 'Наблюдаемый результат', prompt: 'Что должно реально измениться для клиента, пользователя или бизнеса?', required: true }, - { id: 'value', label: 'Ценность', prompt: 'Какой факт показывает, что команда решает нужную проблему?', required: true }, - { id: 'work', label: 'Работа', prompt: 'Где работа ждёт, накапливается или возвращается назад?', required: true }, - { id: 'information', label: 'Информация', prompt: 'Какая важная информация приходит слишком поздно?', required: true }, - { id: 'decisions', label: 'Решения', prompt: 'Какое решение чаще всего ждут и кто его владелец?', required: true }, - { id: 'dependencies', label: 'Зависимости', prompt: 'Какая внешняя или внутренняя зависимость блокирует следующие действия?', required: true }, - { id: 'uncertainty', label: 'Неопределённость', prompt: 'Что команда считает фактом, хотя это ещё только допущение?', required: true }, - { id: 'feedback', label: 'Обратная связь', prompt: 'Как проект узнаёт достаточно рано, что решение было ошибочным?', required: true }, - { id: 'main-break', label: 'Главный разрыв', prompt: 'Какой один разрыв создаёт наибольший downstream-эффект?', required: true }, - { id: 'signal', label: 'Ранний сигнал', prompt: 'Что изменится первым, если ты выбрал правильный разрыв?', required: true } + { id: 'outcome', label: 'Outcome', prompt: 'Какой наблюдаемый результат проекта должен измениться и для кого?', required: true }, + { id: 'evidence', label: 'Факты и подозрительные потоки', prompt: 'Запиши 2–4 наблюдаемых факта и назови только 2–3 потока, которые реально могут их объяснить.', required: true }, + { id: 'hypothesis', label: 'Главная гипотеза', prompt: 'Какой один системный механизм лучше всего связывает эти факты?', required: true }, + { id: 'alternative', label: 'Сильная альтернатива', prompt: 'Какое другое правдоподобное объяснение подходит тем же фактам?', required: true }, + { id: 'falsifier', label: 'Факт-опровержение', prompt: 'Какой конкретный факт заставит ослабить или отбросить главную гипотезу в пользу альтернативы?', required: true }, + { id: 'decision', label: 'Решение и ранний сигнал', prompt: 'Какое минимальное вмешательство ты выберешь сейчас и какой ранний сигнал должен измениться, если диагноз верный?', required: true } ], - transferPrompt: 'Возьми текущий проект. Заполни карту только фактами за последние 2–4 недели. Затем выбери один разрыв, который способен изменить несколько потоков сразу.' + transferPrompt: 'Возьми одно реальное решение по текущему проекту. Запиши главную гипотезу и сильную альтернативу, собери evidence, который различает их, и прими одно минимальное действие. Заранее укажи факт, при котором ты изменишь или отменишь это решение.' }; } const systemDiagnostic = lesson('system-diagnostic'); if (systemDiagnostic) { systemDiagnostic.learningLab = { - skill: 'Строить причинную цепь до воспроизводимого условия системы и выбирать вмешательство, которое меняет механизм.', + skill: 'Строить причинную цепь как проверяемую гипотезу: объяснять повторяемость, сравнивать альтернативы и заранее искать факт, который способен опровергнуть диагноз.', technique: { - name: 'Causal chain', - purpose: 'Не лечить проблему там, где она стала заметной.', + name: 'Falsifiable causal chain', + purpose: 'Не останавливаться на убедительной истории — проверять, действительно ли выбранный механизм воспроизводит симптом.', steps: [ - 'Назови симптом без объяснений и обвинений.', - 'Добавь наблюдаемое evidence: что именно происходило и когда.', - 'Опиши механизм: какая последовательность событий создаёт симптом.', - 'Найди системное условие, из-за которого механизм повторится с другим человеком.', - 'Выбери вмешательство в условие и ранний сигнал его эффекта.' + 'Отдели симптом от объяснения и собери 2–4 наблюдаемых факта: что, когда и при каких условиях происходило.', + 'Построй цепь механизм → системное условие: почему тот же эффект повторится с другим человеком или в следующем цикле.', + 'Сформулируй сильную альтернативную цепь, которая тоже объясняет факты, но ведёт к другому вмешательству.', + 'Назови факт-опровержение: что должно быть правдой, чтобы твой основной диагноз оказался слабее альтернативы.', + 'Выбери минимальное вмешательство в системное условие, ранний сигнал и момент пересмотра решения.' ], - model: 'SYMPTOM → EVIDENCE → MECHANISM → SYSTEM CONDITION → INTERVENTION → EARLY SIGNAL' + model: 'SYMPTOM + FACTS → CAUSAL HYPOTHESIS ↔ ALTERNATIVE → FALSIFIER → SYSTEM CONDITION → INTERVENTION → EARLY SIGNAL' }, workedExample: { title: 'Пример разбора · функция трижды возвращается из QA', steps: [ - 'Симптом: QA трижды возвращает функцию.', - 'Evidence: каждый возврат связан с новой трактовкой одного бизнес-правила.', - 'Механизм: несколько людей согласуют правило в переписке без единого финального решения.', - 'Условие системы: нет владельца трактовки и проверяемого примера до разработки.', - 'Вмешательство: один owner правила + пример ожидаемого поведения до старта реализации.', - 'Ранний сигнал: количество уточнений после начала разработки снижается.' + 'Симптом и факты: три возврата; каждый связан с иной трактовкой одного бизнес-правила; дефекты реализации между возвратами не повторяются.', + 'Главная гипотеза: правило не имеет единственного проверяемого решения до старта разработки, поэтому разные участники легитимно интерпретируют его по-разному.', + 'Сильная альтернатива: правило достаточно определено, а разработчик системно пропускает явные ограничения в спецификации.', + 'Факт-опровержение: если до разработки уже существовали однозначное правило, владелец и примеры, а возвраты вызваны несоблюдением этих примеров, основной диагноз ослабевает.', + 'Вмешательство при подтверждении гипотезы: один owner трактовки + исполнимый пример ожидаемого поведения до разработки.', + 'Ранний сигнал: число новых трактовок и уточнений после старта реализации падает раньше, чем общий defect rate.' ] }, drills: [ - { ...validationDrill('system-diagnostic'), stage: 'cold', required: true }, + { + id: 'm01-drill-diagnostic', + lessonId: 'system-diagnostic', + stage: 'cold', + required: true, + title: 'Cold decision · Дефект в QA', + situation: 'QA три раза возвращает одну и ту же функцию из-за разных трактовок бизнес-правила. Требование согласовывали в переписке продуктовый менеджер, аналитик и бизнес. В задаче есть текстовое описание, но нет примеров пограничного поведения. Разработчик утверждает, что каждый раз реализовывал последнюю известную трактовку.', + prompt: 'Какой первый ход лучше различит несколько правдоподобных причин повторных возвратов?', + options: [ + option('acceptance-example', 'До следующей реализации зафиксировать один набор acceptance examples и назначить владельца финальной трактовки', 'Это сильное профилактическое действие, но оно сразу принимает гипотезу о неоднозначном upstream-решении как основную.', 2), + option('joint-refinement', 'Проводить обязательный refinement разработчика и QA для всех правил с пограничными условиями', 'Это может снизить расхождения за счёт общей картины, но не различает отсутствие решения и плохое применение уже существующего решения.', 2), + option('regression-checklist', 'Расширить QA checklist всеми тремя обнаруженными трактовками и проверять их в следующем цикле', 'Это уменьшит шанс повторить известные варианты, но лечит downstream-обнаружение и почти не объясняет происхождение новых трактовок.', 1), + option('return-timeline', 'Разобрать три возврата: какая трактовка считалась действующей до разработки, кто имел право её менять, где это было зафиксировано и что именно изменилось; затем выбрать механизм', 'Так можно отличить неоднозначный decision interface от ошибок применения уже определённого правила и выбрать разное вмешательство.', 3) + ] + }, { id: 'm01-exit-diagnostic', lessonId: 'system-diagnostic', stage: 'exit', required: true, title: 'Exit check · Команда постоянно «не успевает»', - situation: 'Каждую вторую неделю разработчики не заканчивают запланированное. За последние четыре недели 11 из 18 задач хотя бы раз блокировались ожиданием ответа, доступа или согласования.', - prompt: 'Какой диагноз наиболее полезен для первого вмешательства?', + situation: 'Каждую вторую неделю разработчики не заканчивают запланированное. За последние четыре недели 11 из 18 задач хотя бы раз блокировались ожиданием ответа, доступа или согласования. Часть blockers закрывалась за часы после эскалации, часть жила несколько дней; оценки задач при этом в среднем отличаются от факта примерно на 15%.', + prompt: 'Какой следующий шаг сильнее всего проверит причинный диагноз до изменения процесса?', options: [ - option('estimate', 'Команда плохо оценивает задачи — увеличить оценки на 30%', 'Увеличение оценок может скрыть ожидание внутри срока, но не устраняет воспроизводимый механизм.', 0), - option('discipline', 'Усилить контроль выполнения плана на daily', 'Контроль поведения людей не объясняет, почему большинство задач системно ждёт внешних условий.', 0), - option('waiting-system', 'Ожидание внешних решений и доступов встроено в поток, но не управляется как отдельная работа', 'Диагноз объясняет повторяемость и подсказывает проверяемое вмешательство: сделать ожидания явными, измерять их и назначать владельцев.', 3) + option('estimate-factor', 'Добавить к оценкам коэффициент на историческую долю blocker-time', 'Это может улучшить планирование срока, но превращает ожидание в резерв и не объясняет, какие blockers являются системным механизмом.', 1), + option('wip-cap', 'Снизить WIP и брать следующую работу, когда задача блокируется внешним ожиданием', 'Это уменьшает потерю мощности и является разумным near-miss, но может замаскировать накопление внешних решений вместо устранения причины.', 2), + option('blocker-owner', 'Назначить дежурного владельца, который эскалирует любой blocker старше четырёх часов', 'Такой механизм может быстро сократить часть ожиданий, но одинаково обрабатывает доступ, решение и согласование без проверки их разных причин.', 2), + option('blocker-sample', 'Разобрать 11 заблокированных задач: тип ожидания, источник, возраст до/после эскалации и что реально сняло blocker; проверить, создаёт ли небольшое число повторяемых условий большую часть задержки', 'Это проверяет повторяемость механизма и даёт основание выбрать точечное системное вмешательство вместо универсального правила.', 3) ] } ], - workbookTitle: 'Причинная цепь', + workbookTitle: 'Проверяемая причинная цепь', workbookFields: [ - { id: 'symptom', label: 'Симптом', prompt: 'Что стало видимым? Опиши без объяснений и имён.', required: true }, - { id: 'evidence', label: 'Evidence', prompt: 'Какие 2–3 наблюдаемых факта подтверждают, что проблема существует?', required: true }, - { id: 'mechanism', label: 'Механизм', prompt: 'Какая последовательность событий создаёт симптом?', required: true }, - { id: 'condition', label: 'Условие системы', prompt: 'Почему это повторится с другим человеком?', required: true }, - { id: 'intervention', label: 'Вмешательство', prompt: 'Что изменить в системе, а не в поведении конкретного человека?', required: true }, - { id: 'signal', label: 'Ранний сигнал', prompt: 'Какой показатель изменится до итогового результата?', required: true } + { id: 'evidence', label: 'Симптом и факты', prompt: 'Опиши симптом без имён и оценок, затем добавь 2–4 наблюдаемых факта: что происходило и при каких условиях.', required: true }, + { id: 'mechanism', label: 'Причинный механизм', prompt: 'Какая последовательность событий связывает факты с симптомом?', required: true }, + { id: 'condition', label: 'Системное условие', prompt: 'Какое условие делает механизм повторяемым с другим человеком или в следующем цикле?', required: true }, + { id: 'alternative', label: 'Сильная альтернатива', prompt: 'Какая другая причинная цепь объясняет те же факты и потребовала бы другого вмешательства?', required: true }, + { id: 'falsifier', label: 'Факт-опровержение', prompt: 'Какой факт покажет, что основная цепь слабее альтернативной?', required: true }, + { id: 'intervention', label: 'Вмешательство и ранний сигнал', prompt: 'Какое минимальное изменение системного условия ты проверишь и какой ранний сигнал должен измениться до итогового результата?', required: true } ], - transferPrompt: 'Выбери одну повторяющуюся проблему своего проекта и собери цепь только из фактов. Если в конце цепи стоит имя человека — продолжи анализ ещё на один уровень.' + transferPrompt: 'Выбери одно реальное решение по повторяющейся проблеме проекта. Построй причинную гипотезу и сильную альтернативу, найди evidence и факт, способный опровергнуть основной диагноз, затем выбери минимальное вмешательство. Заранее запиши, при каком наблюдении ты изменишь или отменишь решение.' }; } })(); From 8f8bff57451e9457d81f66a2e27ac0275eb9b925 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:53:52 +0500 Subject: [PATCH 081/107] test: accept explicit M01 decision revision language --- tests/m01-content-quality.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/m01-content-quality.test.js b/tests/m01-content-quality.test.js index 40ba817..d290730 100644 --- a/tests/m01-content-quality.test.js +++ b/tests/m01-content-quality.test.js @@ -58,6 +58,6 @@ test('M01 transfer requires a real decision, evidence and a condition for revisi const prompt = lesson.learningLab.transferPrompt.toLowerCase(); assert.match(prompt, /решени/, `${lesson.id} transfer must require a real decision`); assert.match(prompt, /(evidence|доказ|факт)/, `${lesson.id} transfer must require evidence`); - assert.match(prompt, /(пересмотр|изменить|отменить|опроверг)/, `${lesson.id} transfer must define when to revise the decision`); + assert.match(prompt, /(пересмотр|измен|отмен|опроверг)/, `${lesson.id} transfer must define when to revise the decision`); } }); From de03a400bfe2ffb4aba6c947461d6d8d9884d78f Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:54:13 +0500 Subject: [PATCH 082/107] test: align M01 static contract with strengthened cold drills --- tests/static-contract.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/static-contract.test.js b/tests/static-contract.test.js index c534de4..dbaf52e 100644 --- a/tests/static-contract.test.js +++ b/tests/static-contract.test.js @@ -64,10 +64,11 @@ test('M01 learning lab data loads after validation data and before the base app' assert.equal(labIndex < appIndex, true, 'lab data must load before app.js'); }); -test('M01 learning lab data targets exactly the two M01 lesson ids and reuses validation drills', () => { +test('M01 learning lab data targets the two M01 lessons and keeps stable cold drill ids', () => { assert.equal(labData.includes('project-system'), true, 'project-system lab contract missing'); assert.equal(labData.includes('system-diagnostic'), true, 'system-diagnostic lab contract missing'); - assert.equal(labData.includes('decisionDrills'), true, 'lab must reuse existing decision drill source'); + assert.equal(labData.includes('m01-drill-system'), true, 'project-system cold drill id must stay stable'); + assert.equal(labData.includes('m01-drill-diagnostic'), true, 'system-diagnostic cold drill id must stay stable'); assert.equal(labData.includes('workbookFields'), true, 'lab workbook field contract missing'); assert.equal(labData.includes('transferPrompt'), true, 'lab transfer contract missing'); }); From 1024b87028beaecad2662d543e244521f5801517 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 13:25:58 +0300 Subject: [PATCH 083/107] docs: specify M01 playable simulator vertical slice --- ...9-09-m01-playable-vertical-slice-design.md | 1038 +++++++++++++++++ 1 file changed, 1038 insertions(+) create mode 100644 docs/superpowers/specs/2026-09-09-m01-playable-vertical-slice-design.md diff --git a/docs/superpowers/specs/2026-09-09-m01-playable-vertical-slice-design.md b/docs/superpowers/specs/2026-09-09-m01-playable-vertical-slice-design.md new file mode 100644 index 0000000..3c300b6 --- /dev/null +++ b/docs/superpowers/specs/2026-09-09-m01-playable-vertical-slice-design.md @@ -0,0 +1,1038 @@ +# PMO01 M01 Playable Vertical Slice — Design Spec + +**Status:** reviewable design; no implementation authorized by this document +**Date:** 2026-09-09 +**Scope:** M01 only +**Target:** one 7–10 minute deterministic project-management mission +**Base:** current M01.1 learning content and validation model + +## 1. Decision summary + +PMO01 should move from a mostly linear lesson/lab experience to a compact deterministic project-management simulator for the first playable vertical slice. + +The slice is **one authored mission with exactly four decision moments**. The learner sees a project state, makes a choice, sees consequences, can open optional checklists/tools, and finishes with a trajectory review. The simulation is not an optimization game and does not reduce project management to maximizing one score. It is designed to expose trade-offs, hypothesis quality, evidence use, and willingness to revise a decision when new facts weaken the original explanation. + +The recommended implementation model is a **deterministic state machine with authored narrative consequences**. This gives PMO01 a game-like interaction while preserving reproducibility, accessibility, testability, and the P01–P05 evidence contract. + +This spec does **not** authorize code changes, production publication, M02 work, telemetry changes, or changes to `main`. + +--- + +## 2. Current M01.1 flow being redesigned + +Current M01.1 is structurally: + +`cold decision → worked example → technique → exit decision → six-field workbook → transfer prompt` + +across two lessons: + +- `project-system` +- `system-diagnostic` + +The current content already teaches the reasoning PMO01 should preserve: + +1. separate observations from interpretations; +2. compare more than one plausible systemic explanation; +3. identify discriminating evidence; +4. state a falsifier that could weaken the preferred diagnosis; +5. choose a minimal intervention aimed at the mechanism; +6. identify an early signal; +7. state a condition for revising or cancelling the decision. + +Current completion is evidence-aware: required drill answers and required workbook fields must exist before a Learning Lab counts as complete. The redesign must not regress that property into “clicked through the screens.” + +The validation route is also a separate measurement system with blind baseline, learning intervention, post-case, field transfer, and reflection. That separation is valuable and should remain. + +--- + +## 3. Goals + +### Product goal + +Make the first M01 learning experience feel like managing a live project rather than reading a lesson and completing forms. + +### Learning goal + +After one mission, the learner should be more likely to: + +- diagnose a repeatable system mechanism rather than blame a person or local symptom; +- compare a preferred diagnosis with a credible alternative; +- seek evidence that distinguishes the two; +- choose an intervention appropriate to the mechanism and its trade-offs; +- revise a decision when new evidence undermines the initial hypothesis. + +### Validation goal + +Preserve a clean causal interpretation for P01–P05: + +`blind baseline → simulator treatment → blind post-case → real-project transfer` + +with a pinned treatment version and no mixing of old M01.1 lesson-treatment data with simulator-treatment data in one five-person cohort. + +### UX goal + +A motivated learner should be able to complete the mission in roughly **7–10 minutes** without a facilitator explaining the interface. + +This is a target, not a countdown. PMO01 must not impose a visible timer or penalize slower readers. + +--- + +## 4. Non-goals for the first vertical slice + +The first slice deliberately does **not** include: + +- AI-generated scenarios or LLM-driven NPCs; +- randomized consequences; +- avatars, character customization, XP, badges, streaks, currencies, leaderboards; +- multiplayer or facilitator dashboards; +- free-form chat with simulated stakeholders; +- branching into dozens of unique story paths; +- adaptive difficulty; +- telemetry or server-side analytics; +- M02 content; +- migration of the full PMO01 course; +- a new framework decision; +- automatic mastery inference; +- a single aggregate “PM score.” + +The slice exists to validate the **decision-simulation learning loop**, not to build a game platform. + +--- + +## 5. Approaches considered + +### A. Fully authored branch tree + +Every option sends the learner to a different branch with unique later decisions. + +**Advantages** + +- high narrative specificity; +- visibly different paths. + +**Problems** + +- combinatorial authoring and QA cost; +- hard to compare learners because they may receive materially different later tasks; +- encourages content duplication; +- difficult to keep a 7–10 minute slice bounded. + +**Decision:** not selected. + +### B. Deterministic state machine with conditional narrative — recommended + +Every learner encounters the same four decision moments. Choices change project meters and flags. Later narrative and consequence text can vary based on those flags, but the decision structure remains stable. + +**Advantages** + +- game-like consequences without a branch explosion; +- deterministic and replayable; +- comparable P01–P05 evidence; +- straightforward unit testing; +- content remains authored and reviewable; +- supports later mission growth without choosing a full framework now. + +**Decision:** selected. + +### C. LLM-driven simulation + +An AI model generates stakeholder responses and consequences dynamically. + +**Advantages** + +- potentially realistic and open-ended. + +**Problems** + +- poor reproducibility for a five-person validation cohort; +- harder safety/content review; +- difficult to distinguish learning effect from model variance; +- requires API/backend/product decisions outside the vertical-slice goal. + +**Decision:** explicitly deferred. + +--- + +## 6. Mission concept + +### Mission ID + +`m01-mission-partner-launch-v1` + +### Working title + +**“Пять дней до партнёрского запуска”** + +### Premise + +The learner is acting as the project lead for a partner integration scheduled to launch in five working days. + +The implementation is mostly complete and the status dashboard is green, but several weak signals are emerging: + +- the final external data format is not frozen; +- a legal restriction is still moving through several owners; +- developers are preserving multiple implementation variants; +- QA expects a stable build tomorrow; +- the partner believes the launch date is committed. + +The point is not to discover a hidden “correct answer.” The learner must decide what evidence to request, which uncertainty to close, what trade-off to accept, and when to revise the original diagnosis. + +### Initial visible state + +All meters use `0..100` internally and display both number and plain-language label. + +| State | Initial | Meaning | +|---|---:|---| +| **Deadline confidence** | 58 | likelihood that the currently promised scope can launch on the committed date | +| **Stakeholder trust** | 64 | confidence between team, partner, business and decision owners | +| **Team capacity** | 72 | usable delivery capacity without unsustainable overload | +| **Launch risk** | 63 | unresolved exposure that can still create late rework or launch failure; lower is better | + +`Team capacity` means practical delivery capacity, not budget or headcount. + +The four meters are intentionally not collapsed into one total score. + +--- + +## 7. Core learner loop + +Each decision uses the same interaction rhythm: + +1. **Situation update** — what changed and what is known. +2. **Project state** — four visible meters plus notable open facts. +3. **Optional tools** — learner may open a checklist before deciding. +4. **Decision** — choose one of 3–4 plausible options. +5. **Commit** — first choice becomes immutable for evidence purposes. +6. **Consequence** — immediate project-state change plus narrative outcome. +7. **New evidence** — the mission advances and introduces the next fact. + +The learner cannot undo a committed choice within the cohort run. Replay can exist later but must be clearly separated from first-run evidence. + +--- + +## 8. Four decision moments + +## D1 — Diagnose before intervening + +### Situation + +Five days remain. QA asks whether tomorrow’s candidate build is actually stable. The partner says the “final” data format may still change because legal is reviewing one field. The team asks for a decision. + +### Learner question + +**“What do you do first?”** + +### Options + +1. **Add contingency to the schedule.** + A reasonable forecasting move, but it prices uncertainty before identifying the mechanism. + +2. **Tell QA to prepare for both formats.** + Reduces a local execution risk but spends capacity while the upstream decision interface remains unclear. + +3. **Set a hard deadline for the partner’s final answer.** + Creates urgency, but can fail if ownership and the actual decision path are still ambiguous. + +4. **Reconstruct the critical decision timeline and identify who owns the next irreversible decision.** + Strongest diagnostic move because it distinguishes delay in information, ownership, approval and downstream handoff. + +### State effects + +Effects are deterministic and authored. Example values: + +| Option | Deadline | Trust | Capacity | Risk | Flags | +|---|---:|---:|---:|---:|---| +| contingency | +5 | 0 | 0 | -2 | `buffer_added` | +| prepare both | +2 | +1 | -12 | -4 | `parallel_variants` | +| hard deadline | +4 | -6 | 0 | -3 | `external_pressure` | +| decision timeline | +3 | +2 | -2 | -8 | `timeline_reconstructed`, `evidence_requested` | + +The strongest reasoning option does not necessarily produce the largest immediate deadline gain. This prevents the mission from becoming a simple meter-maximization puzzle. + +### Required evidence + +Before commit, learner writes one short sentence: + +**“Why this first?”** + +This is stored as `D1.rationale` and used in the final trajectory review. + +--- + +## D2 — Act under uncertainty + +### New evidence + +The mission reveals that legal is not the only source of uncertainty: the partner’s technical owner had already chosen a preferred format, but that decision was never marked final because a product manager was waiting for business confirmation. + +If `timeline_reconstructed` is set, the learner sees this as a direct result of the investigation. Otherwise the same fact arrives as a late escalation from the partner. The evidence is comparable; only the narrative context differs. + +### Learner question + +**“What intervention do you make now?”** + +### Options + +1. **Add another developer to protect the launch.** + Increases local capacity but does not close the decision interface. + +2. **Freeze the currently preferred format immediately.** + Reduces variation but risks locking the wrong business/legal choice. + +3. **Create one explicit decision owner, expiry time and acceptance example for the format decision.** + Changes the decision interface and reduces ambiguity before downstream work grows. + +4. **Keep both technical variants alive until all stakeholders agree.** + Preserves flexibility at a direct capacity cost. + +### State effects + +Representative deterministic effects: + +| Option | Deadline | Trust | Capacity | Risk | Flags | +|---|---:|---:|---:|---:|---| +| add developer | +5 | 0 | +6 | +1 | `extra_capacity`, `coordination_cost` | +| freeze now | +9 | -5 | +2 | +5 | `premature_freeze` | +| decision contract | +7 | +6 | -2 | -12 | `decision_contract` | +| keep variants | +3 | +2 | -14 | -3 | `optionality_preserved` | + +--- + +## D3 — Make the trade-off explicit + +### New evidence + +Three days remain. Business now asks to include one additional partner field in the launch because it would avoid a manual workaround after go-live. QA warns that adding it now expands the test surface. + +### Learner question + +**“Which trade-off do you choose?”** + +### Options + +1. **Accept the extra field and ask the team to absorb it.** + Maximizes short-term scope but consumes capacity and raises late risk. + +2. **Reject the field because the date is fixed.** + Protects the launch but may damage trust if the business cost is ignored. + +3. **Separate the irreversible launch requirement from the reversible enhancement; launch the required contract and schedule the extra field behind an explicit follow-up decision.** + Makes optionality and downstream cost explicit. + +4. **Move the whole launch so all requirements can be included together.** + Reduces immediate quality risk but spends the commitment without testing whether the new field is truly launch-critical. + +### State effects + +| Option | Deadline | Trust | Capacity | Risk | Flags | +|---|---:|---:|---:|---:|---| +| absorb field | -8 | +3 | -14 | +11 | `late_scope_added` | +| reject field | +7 | -8 | +3 | -3 | `scope_rejected` | +| split decision | +6 | +5 | -2 | -8 | `reversible_scope_split` | +| move launch | -18 | -3 | +8 | -10 | `launch_moved` | + +--- + +## D4 — Revise when the hypothesis is challenged + +### New evidence + +One day remains. A new fact contradicts the simplest version of the learner’s earlier story: + +- the format decision is now clear; +- the remaining blocker is a security rule discovered in an already-approved dependency; +- the team can either keep pushing the existing plan or revise the diagnosis and isolate the blocker. + +The exact wording can reference prior flags, but every learner receives the same underlying falsifying fact. + +### Learner question + +**“What do you do with your current diagnosis?”** + +### Options + +1. **Stay with the original plan; changing diagnosis this late will create more confusion.** +2. **Escalate the team for not discovering the issue earlier.** +3. **Revise the diagnosis, isolate the security dependency, and redefine the smallest safe launch decision.** +4. **Add capacity and keep both the original diagnosis and original scope unchanged.** + +### Required evidence + +Before commit, learner writes one short sentence: + +**“What fact made you keep or revise your diagnosis?”** + +Stored as `D4.revisionRationale`. + +### State effects + +The strongest response improves risk control and trust but may reduce deadline confidence if the smallest safe launch is narrower than the original promise. That trade-off is intentional. + +Example: + +| Option | Deadline | Trust | Capacity | Risk | Flags | +|---|---:|---:|---:|---:|---| +| stay course | +2 | -4 | -5 | +12 | `ignored_falsifier` | +| blame/escalate | 0 | -10 | -3 | +5 | `person_blame` | +| revise diagnosis | -2 | +8 | +1 | -18 | `hypothesis_revised`, `safe_scope` | +| add capacity | +4 | -2 | +4 | +7 | `capacity_compensation` | + +--- + +## 9. Project-state rules + +### Meter mechanics + +- each meter is an integer `0..100`; +- every consequence applies a fixed delta; +- values are clamped to `0..100`; +- no random effects; +- no hidden global score; +- no single “winning meter”; the final review interprets trajectory and reasoning together. + +### Flags + +Flags capture durable consequences and allow later copy to reflect the learner’s path without changing the four-decision structure. + +Example flags: + +- `timeline_reconstructed` +- `evidence_requested` +- `parallel_variants` +- `external_pressure` +- `decision_contract` +- `premature_freeze` +- `optionality_preserved` +- `late_scope_added` +- `reversible_scope_split` +- `launch_moved` +- `ignored_falsifier` +- `hypothesis_revised` +- `safe_scope` + +### Conditional consequences + +Conditional narrative is permitted only when: + +1. it refers to a flag created by an earlier choice; +2. it does not change which later decision nodes exist; +3. it does not change the assessment difficulty materially; +4. it is deterministic and covered by tests. + +This keeps trajectories personal without creating incomparable cohorts. + +--- + +## 10. Optional tools / checklists + +Tools are learner-controlled support, not answer revealers. + +Opening a tool: + +- has **no negative state consequence**; +- does not reduce a score; +- is logged as evidence of information-seeking behavior; +- never marks an option as “correct.” + +### Tool A — Decision Timeline + +Prompt checklist: + +- What decision is actually open? +- When did it become necessary? +- Who owns the next commitment? +- What is waiting for that commitment? +- How old is the wait? +- What becomes expensive if the decision arrives later? + +Maps current `Seven-flow hypothesis scan` evidence-seeking behavior into an interactive aid. + +### Tool B — Hypothesis Comparator + +Two-column checklist: + +- preferred explanation; +- strong alternative; +- facts both explain; +- observation that would favor one over the other; +- fact that would weaken the preferred explanation. + +Maps current `alternative` + `falsifier` workbook work into a just-in-time tool. + +### Tool C — Change Condition + +Checklist: + +- what should change first if the decision is right? +- by when? +- what observation means “continue”? +- what observation means “revise/stop”? + +Maps current early-signal and revision-condition content into an action aid. + +### Availability + +All three tools are available from a persistent **“Tools”** control at every decision. Context may recommend a tool by name, but the simulator must not automatically open it or imply that using it is required for the preferred answer. + +--- + +## 11. Screens and UI states + +The vertical slice needs only six reusable screen states. + +### S0 — Mission briefing + +Contents: + +- role and mission objective; +- project context; +- initial state meters; +- explanation that consequences persist; +- “Start mission” primary action. + +No tutorial wall of text. Tool behavior can be explained with one sentence. + +### S1 — Decision + +Contents: + +- short situation update; +- project meters; +- 3–4 radio-card options; +- optional “Tools” control; +- short rationale input only for D1 and D4; +- explicit “Commit decision” action. + +The learner can change selection before commit. After commit, the first committed choice is immutable for cohort evidence. + +### S2 — Consequence + +Contents: + +- concise narrative consequence; +- meter deltas shown as text, e.g. `Deadline confidence 58 → 61 (+3)`; +- one sentence linking the consequence to the project mechanism, without calling the decision correct/incorrect; +- “Continue” action. + +### S3 — Tool drawer/dialog + +Contents: + +- checklist only; +- close/return control; +- no answer highlighting; +- no meter changes. + +### S4 — Trajectory review (before post-case) + +This is the required final mission debrief, but it must remain **non-evaluative before post-case** to avoid contaminating the measurement. + +Show: + +- four committed decisions in order; +- state trajectory across four meters; +- D1 rationale; +- D4 revision rationale; +- tools opened; +- explicit flags such as “diagnosis revised” only when derived from learner behavior; +- reflection prompts: + - “Where did your model of the problem change?” + - “Which trade-off did you knowingly accept?” + - “What evidence would make you act differently next time?” + +Do **not** show: + +- preferred option labels; +- option scores; +- expert path; +- “you should have…” feedback; +- aggregate mission grade. + +### S5 — Expert debrief (only after post-case) + +After the validation post-case is submitted, the product may reveal a deeper analysis: + +- what each decision optimized; +- where near-miss reasoning was plausible but incomplete; +- which choices attacked symptoms vs mechanisms; +- how the falsifying fact should change a diagnosis; +- alternative trajectories worth replaying later. + +This screen is optional for the first implementation if keeping it would delay the playable slice. The pre-post trajectory review is mandatory. + +--- + +## 12. Proposed data model + +The implementation should keep mission content declarative and the transition engine pure. + +### Mission definition + +Conceptual shape: + +```text +MissionDefinition + id + version + title + estimatedMinutes + initialState + deadlineConfidence + stakeholderTrust + teamCapacity + launchRisk + tools[] + nodes[] + id + situation + prompt + rationaleRequirement + options[] + id + label + consequence + effects + setFlags[] + conditionalCopy[] + finalReview +``` + +### Runtime state + +```text +MissionRun + missionId + missionVersion + runId + startedAt + completedAt + currentNodeId + projectState + flags[] + decisions[] + nodeId + optionId + rationale + committedAt + stateBefore + stateAfter + toolEvents[] + toolId + nodeId + openedAt +``` + +### Important rule + +`decisions[]` is append-only for the first cohort run. UI selection can change before commit; after commit, the record is immutable. + +--- + +## 13. Storage and evidence isolation + +For the simulator treatment, use an isolated browser storage record such as: + +`pm01-sim-m01-v1` + +Do not overload `pm01-validation-m01-v1` with simulator runtime state and do not make simulator state depend on telemetry. + +The existing course state can still store a simple completion reference later, but the full mission run should remain versioned and inspectable independently. + +### Fresh-start implication + +If this simulator becomes the P01–P05 treatment, the facilitator clean-start contract must require absence of all relevant state: + +- `pm01-validation-m01-v1` +- `pm01-state-v1` +- `pm01-sim-m01-v1` + +This is a protocol change that belongs to the simulator implementation/release package, not to the current production M01.1 cohort before the redesign ships. + +--- + +## 14. P01–P05 evidence preservation + +### Treatment version pinning + +Every valid simulator participant record must include: + +`treatment = m01-mission-partner-launch-v1` + +and the exact production release SHA used for that session. + +### Do not mix treatments + +Evidence from the existing text/Learning-Lab M01.1 treatment and evidence from the simulator treatment must **not** be pooled into the same five-person cohort decision. + +Because no valid P01–P05 participant data currently exists, the project may choose the simulator as the treatment before P01 starts. Once the first valid participant starts a treatment, all remaining participants in that cohort must use the same pinned treatment version unless the cohort is explicitly restarted. + +### Baseline and post-case + +Keep the existing five validation dimensions unchanged for the first simulator cohort: + +- mechanism; +- evidence; +- trade-offs; +- intervention; +- change condition. + +Keep blind baseline and integrative post-case structurally unchanged unless a separate validation-design review explicitly changes them. + +### Additional simulator evidence + +For each participant capture: + +- committed option at D1–D4; +- D1 rationale; +- D4 revision rationale; +- tool openings by node; +- state trajectory after each decision; +- whether the learner revised the diagnosis after the falsifying fact; +- trajectory-review reflection; +- facilitator-observed confusion or gaming of meters; +- completion/abandonment point. + +These are **interaction and reasoning evidence**, not an automatic learning score. + +### Existing cohort decision rule + +The simulator does not replace the existing primary cohort gate: + +- ≥5 valid sessions; +- ≥3/5 positive individual baseline→post signal; +- ≥3/5 credible real-project transfer; +- qualitative evidence must not contradict the score gains; +- no repeated blocking UX/rubric ambiguity that plausibly explains the result. + +Simulator-specific evidence helps explain *why* the treatment did or did not work. + +--- + +## 15. Migration of current M01.1 content + +The redesign should reuse learning substance while changing presentation. + +| Current M01.1 element | Simulator destination | +|---|---| +| `m01-drill-system` cold decision | D1 diagnosis decision | +| Seven-flow hypothesis scan | Decision Timeline tool + D1/D2 consequence reasoning | +| worked example: external dependency | consequence/debrief content; no pre-reading block | +| `m01-exit-system` | D2 intervention decision | +| `m01-drill-diagnostic` | D3 trade-off / mechanism pressure test | +| Falsifiable causal chain | Hypothesis Comparator tool + D4 falsifying event | +| worked example: repeated QA interpretation | expert debrief example or later mission; not required in first mission body | +| `m01-exit-diagnostic` | D4 revise-or-persist decision | +| six-field workbook | distributed reasoning evidence + optional tools + final trajectory reflection | +| `alternative` field | Hypothesis Comparator and final review | +| `falsifier` field | D4 event and revision rationale | +| `decision` + early signal | D2/D3 consequences and Change Condition tool | +| real-project transfer prompt | preserved after post-case/mission as current field transfer | + +### Content removed from the critical path + +Long explanatory blocks should not appear before decisions. The simulator should teach through: + +- consequence; +- optional tool; +- later evidence; +- post-mission reflection. + +Existing prose is source material, not a requirement to preserve paragraphs verbatim. + +--- + +## 16. Completion semantics + +A simulator mission is complete only when: + +1. D1–D4 all have committed first-choice evidence; +2. D1 rationale is non-empty; +3. D4 revision rationale is non-empty; +4. the pre-post trajectory review has been reached; +5. the mission run persisted successfully. + +Opening tools is never required for completion. + +No minimum meter threshold is required. A learner can complete a poor trajectory and still produce valid evidence. + +The validation route should unlock post-case based on this mission completion evidence when the simulator treatment is active. + +--- + +## 17. Error and recovery behavior + +### Persistence failure + +If browser storage fails: + +- show an explicit blocking warning; +- do not silently advance; +- tell the participant not to continue because evidence may be lost. + +This matches the existing validation integrity principle. + +### Reload + +A reload should restore: + +- current node; +- committed decisions; +- project state; +- tool history; +- entered rationale. + +A committed decision must never become editable after reload. + +### Version mismatch + +If stored mission version differs from the loaded definition: + +- do not silently migrate an in-progress cohort run; +- show a version mismatch message; +- facilitator should exclude/restart the session according to cohort protocol. + +--- + +## 18. Accessibility requirements + +Accessibility is part of the vertical slice, not later polish. + +### Keyboard + +- every decision can be selected and committed without a mouse; +- tool drawer is keyboard reachable; +- focus returns to the triggering control when a tool closes; +- consequence/continue flow has predictable focus order. + +### Semantic controls + +- decision choices use native radio inputs or buttons with equivalent semantics; +- project state is readable as text, not canvas-only visualization; +- dialogs use correct dialog semantics and accessible names. + +### Meter communication + +Never communicate state change by color alone. + +Example: + +`Launch risk: 63 → 55 (−8, improved)` + +Icons/colors may supplement this text but cannot replace it. + +### Motion + +- no required animation; +- respect `prefers-reduced-motion`; +- meter animation, if used, is decorative and skippable. + +### Time + +- no countdown timer; +- 7–10 minutes is an observed usability target, not a time limit. + +### Readability + +- scenario blocks stay short; +- consequence text should normally fit in 2–4 short paragraphs; +- tools use checklist structure rather than dense prose. + +--- + +## 19. Test strategy for implementation + +No implementation should be considered complete without tests across four layers. + +### A. Mission schema/content tests + +Verify: + +- exactly one M01 mission in the first slice; +- exactly four decision nodes; +- every node has 3–4 options; +- every option has deterministic effects and consequence copy; +- all meter effects target valid state keys; +- all referenced flags/tools exist; +- no option lacks a next state; +- no forbidden M02 dependency. + +### B. Pure transition-engine tests + +For every option: + +- correct meter deltas apply; +- values clamp to `0..100`; +- correct flags are set; +- input state is not mutated unexpectedly; +- identical state + identical decision produces identical result; +- tool openings never change project meters; +- first committed decision is immutable. + +### C. Trajectory tests + +Exercise all option combinations or a generated matrix sufficient to prove: + +- D1 always reaches D2; +- D2 always reaches D3; +- D3 always reaches D4; +- D4 always reaches trajectory review; +- no dead ends; +- conditional narrative never changes assessment structure; +- no path requires a hidden tool action. + +### D. Persistence/validation integration tests + +Verify: + +- fresh simulator state does not contaminate existing baseline; +- simulator completion is evidence-aware; +- reload restores in-progress mission; +- completed first choices stay locked after reload; +- storage failure blocks advancement visibly; +- post-case remains unavailable before simulator completion; +- pre-post trajectory review does not reveal preferred options or scores; +- treatment version is stored and exportable for facilitator evidence; +- simulator cohort state can be cleared independently. + +### E. Accessibility tests + +At minimum: + +- semantic role/name checks; +- keyboard completion of a representative trajectory; +- focus management for tool dialog and consequence transitions; +- text equivalents for every meter and delta; +- reduced-motion path; +- no critical color-only information. + +--- + +## 20. Success criteria + +Success has separate engineering, UX, and learning/evidence gates. + +### Engineering gate + +- deterministic transition tests pass; +- all four decisions are reachable and persist correctly; +- no `main`, telemetry or M02 dependency is required for the prototype branch; +- simulator can be version-pinned for cohort use; +- baseline blinding is preserved. + +### UX gate + +From the first valid simulator cohort: + +- at least 4/5 learners complete the mission without facilitator explanation of navigation or controls; +- no repeated blocking state-loss/navigation/accessibility issue affects more than one valid session; +- median observed completion should be in or near the 7–10 minute target; slower completion is diagnostic evidence, not automatic failure; +- learners do not consistently interpret the meters as a single score to maximize. + +### Learning/product gate + +Retain the existing cohort decision thresholds: + +- ≥3/5 positive baseline→post prototype signals; +- ≥3/5 credible real-project transfers; +- qualitative reasoning supports rather than contradicts score improvement. + +Simulator-specific supporting evidence should show that, for at least 3/5 valid learners: + +- consequences caused them to articulate or revise a trade-off, hypothesis, evidence need, or decision condition; +- D4 is understood as a legitimate reason to revise diagnosis rather than as a trick question. + +These supporting criteria explain the mechanism; they do not replace the primary cohort gate. + +--- + +## 21. Implementation boundaries + +When implementation is separately authorized, the first plan should remain a vertical slice rather than a platform rewrite. + +Expected implementation units: + +1. declarative `m01` mission content; +2. small pure mission transition engine; +3. mission renderer for briefing/decision/consequence/review; +4. tool drawer/checklist renderer; +5. isolated simulator persistence; +6. validation handoff/completion adapter; +7. tests. + +Do not start by rewriting global course navigation or choosing a new frontend framework. The simulator should first prove the interaction and evidence model in isolation. + +--- + +## 22. Self-review: ambiguity and scope resolutions + +The following ambiguities were resolved explicitly in this spec. + +### “Resources” + +Means **team capacity**, not money or headcount. This avoids introducing a finance simulation into M01. + +### “7–10 minutes” + +A UX target, never a timer or exclusion rule. + +### Number of decisions + +Exactly **four** for the first slice. Conditional narrative is allowed; conditional extra decisions are not. + +### Consequences + +Fully deterministic and authored. No random rolls and no LLM generation. + +### Project state + +Four independent visible meters. No aggregate hidden or visible score. + +### Tools + +Optional and penalty-free. Tool use is evidence, not a success criterion. + +### Final trajectory review + +Must exist immediately after D4, but before post-case it is descriptive/reflection-oriented and does not reveal preferred answers. Deeper expert comparison waits until after post-case. + +### Evidence and cohort comparability + +Simulator treatment must be pinned. Existing Learning-Lab M01.1 sessions and simulator sessions cannot be combined in the same five-person cohort decision. + +### Completion + +Requires committed decision evidence and required rationales, not healthy meters or “good” decisions. + +### Branching + +Personalized consequence copy is allowed, but all learners receive the same four decision problems and the same falsifying event structure. + +### Migration scope + +M01 learning presentation changes; baseline/post-case rubric, real-project transfer and broader course architecture remain unchanged for the first slice unless separately reviewed. + +### Accessibility + +Required in the first playable slice, not deferred. + +### Scope verdict + +The design is intentionally narrow enough for one implementation plan: + +**one mission + four decisions + four project meters + three optional tools + trajectory review + persistence/evidence integration.** + +Anything beyond that — additional missions, M02, AI stakeholders, adaptive difficulty, new framework, server telemetry — requires a separate design decision. + +--- + +## 23. Review questions for the owner + +User review should focus on these product choices before implementation: + +1. Is “Пять дней до партнёрского запуска” the right first mission context? +2. Are the four state meters the right mental model, especially `Deadline confidence` rather than raw days remaining? +3. Is four decision moments enough to feel like a simulator while remaining within 7–10 minutes? +4. Is the deliberate separation between non-evaluative trajectory review and post-case expert debrief acceptable? +5. Should the simulator become the treatment for P01–P05 before the first real participant, or should the existing published M01.1 cohort be completed first? + +No implementation should start until this design is reviewed and explicitly approved. From ca0a7374ab98deb71817ccb85689a50726c5f754 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:35:08 +0300 Subject: [PATCH 084/107] docs: plan M01 playable simulator implementation --- ...-playable-vertical-slice-implementation.md | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-09-m01-playable-vertical-slice-implementation.md diff --git a/docs/superpowers/plans/2026-09-09-m01-playable-vertical-slice-implementation.md b/docs/superpowers/plans/2026-09-09-m01-playable-vertical-slice-implementation.md new file mode 100644 index 0000000..71e159d --- /dev/null +++ b/docs/superpowers/plans/2026-09-09-m01-playable-vertical-slice-implementation.md @@ -0,0 +1,291 @@ +# M01 Playable Vertical Slice Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Implement one deterministic 7–10 minute M01 project-management simulator mission with four decisions, visible project state, optional tools, authored consequences, trajectory review, and cohort-safe evidence persistence. + +**Architecture:** Keep the existing static-app architecture. Add one authored mission data module, one pure deterministic simulator domain module, and one simulator UI extension that owns `#/mission/m01`. Integrate minimally with `index.html`, the base router reservation, M01 course entry point, and M01 validation treatment gate; preserve baseline/post-case isolation and keep all simulator evidence in a dedicated localStorage key. + +**Tech Stack:** Vanilla HTML/CSS/JavaScript, browser localStorage, Node 22 built-in test runner (`node --test`). + +**Spec:** `docs/superpowers/specs/2026-09-09-m01-playable-vertical-slice-design.md` + +## Global Constraints + +- M01 only; no M02 changes. +- No telemetry changes. +- Do not modify `main`; work only on the isolated feature branch. +- No production publication or merge. +- Mission ID is `m01-mission-partner-launch-v1`. +- Exactly four deterministic decision moments; no randomness and no hidden aggregate score. +- Visible project state: deadline confidence, stakeholder trust, team capacity, launch risk, each clamped to `0..100`. +- Optional tools are penalty-free and never reveal the preferred answer. +- First committed choice at each decision is immutable for cohort evidence. +- Baseline/post-case remain separate and blind; simulator completion unlocks post-case only for the pinned simulator treatment. +- Do not mix old textual M01.1 treatment evidence with simulator-treatment evidence inside one cohort. +- No automatic mastery inference. +- Accessibility is part of the slice: semantic controls, keyboard path, focus management, text equivalents for meters, reduced-motion compatibility. + +--- + +## File Structure + +- Create `m01-simulator-data.js` — authored mission content, initial state, four decisions, tools, copy, deterministic deltas and flags. +- Create `m01-simulator-domain.js` — pure state/evidence functions: initial state, transition, clamping, completion, trajectory summary. +- Create `m01-simulator-app.js` — route renderer, persistence, tool interaction, decision commit, consequence screen, trajectory review. +- Create `m01-simulator.css` — simulator-only styles and accessibility states. +- Modify `index.html` — load simulator CSS/data/domain/app in deterministic order. +- Modify `app.js` — reserve `mission/m01` from base-router overwrite and route the M01 course entry point to the mission while leaving non-M01 modules unchanged. +- Modify `m01-validation-app.js` — replace the two-lesson learning gate with the pinned simulator mission completion gate on this feature branch; leave baseline/post/field/reflection logic intact. +- Create `tests/m01-simulator-domain.test.js` — pure deterministic transition/evidence tests. +- Create `tests/m01-simulator-integration.test.js` — static/VM contract tests for route ownership, load order, treatment isolation, first-choice lock and accessible markup. + +--- + +### Task 1: Define simulator domain contract with RED tests + +**Files:** +- Create: `tests/m01-simulator-domain.test.js` +- Create: `tests/m01-simulator-integration.test.js` + +**Interfaces:** +- Consumes: existing Node test conventions and browser-global module pattern. +- Produces required interfaces for later tasks: + - `window.PM01SimulatorData.mission` + - `window.PM01SimulatorDomain.initialRun(mission)` + - `window.PM01SimulatorDomain.commitDecision(run, mission, decisionId, optionId, rationale)` + - `window.PM01SimulatorDomain.openTool(run, toolId, decisionId)` + - `window.PM01SimulatorDomain.isComplete(run, mission)` + - `window.PM01SimulatorDomain.trajectory(run, mission)` + +- [ ] **Step 1: Write failing pure-domain tests** + +Test that the mission has exactly four decisions, every decision has 3–4 options, all effects are deterministic integers, initial meters are `0..100`, transitions clamp meter values, committed decisions cannot be replaced, tool opening records evidence without changing meters, and completion requires exactly four committed decisions plus required rationales for D1/D4. + +- [ ] **Step 2: Write failing integration/static tests** + +Test that `index.html` loads simulator data → domain → app after base learning data but before validation extension; the base router reserves `mission/m01`; the simulator app owns `#/mission/m01`; validation uses the simulator completion contract instead of legacy lesson completion on this branch; simulator storage uses a dedicated key and does not write telemetry. + +- [ ] **Step 3: Run tests and confirm RED** + +Run: `node --test tests/m01-simulator-domain.test.js tests/m01-simulator-integration.test.js` + +Expected: FAIL because simulator files/interfaces do not yet exist. + +- [ ] **Step 4: Commit RED tests** + +Commit message: `test: define M01 simulator vertical-slice contract` + +--- + +### Task 2: Implement authored mission data + pure transition engine + +**Files:** +- Create: `m01-simulator-data.js` +- Create: `m01-simulator-domain.js` +- Test: `tests/m01-simulator-domain.test.js` + +**Interfaces:** +- `PM01SimulatorData.mission` includes `id`, `version`, `title`, `initialState`, `meters`, `tools`, `decisions`. +- `initialRun(mission)` returns `{ treatmentId, status, decisionIndex, meters, flags, decisions, toolsOpened, events }`. +- `commitDecision(...)` returns a new run object and throws/returns an explicit error for invalid node/option or a second commit to the same node. +- `openTool(...)` returns a new run with one evidence event and unchanged meters. +- `trajectory(...)` returns a deterministic review model from the run; no hidden aggregate score. + +- [ ] **Step 1: Implement mission data exactly from the approved spec** + +Use initial meters: deadline 58, trust 64, capacity 72, risk 63. Implement D1–D4 options, deterministic meter deltas and flags from the spec, plus the three optional tools: Decision Timeline, Hypothesis Comparator, Change Condition. + +- [ ] **Step 2: Implement pure immutable domain functions** + +Clamp meters to `0..100`. Record `before`, `after`, `delta`, selected option, flags added, rationale and event sequence. Do not use time or randomness in domain decisions; timestamps are UI persistence metadata only. + +- [ ] **Step 3: Run pure-domain tests** + +Run: `node --test tests/m01-simulator-domain.test.js` + +Expected: PASS. + +- [ ] **Step 4: Run full existing test suite** + +Run: `node --test tests/*.test.js` + +Expected: existing tests plus simulator-domain tests pass except integration tests that intentionally wait for Task 3/4 wiring. + +- [ ] **Step 5: Commit** + +Commit message: `feat: add deterministic M01 simulator mission engine` + +--- + +### Task 3: Build accessible playable mission UI and persistence + +**Files:** +- Create: `m01-simulator-app.js` +- Create: `m01-simulator.css` +- Modify: `index.html` +- Test: `tests/m01-simulator-integration.test.js` + +**Interfaces:** +- Route: `#/mission/m01`. +- Dedicated storage key: `pm01-simulator-m01-v1`. +- Stored envelope: `{ treatmentId, missionVersion, run, startedAt, completedAt }`. +- Completion evidence must expose the pinned treatment ID `m01-mission-partner-launch-v1`. + +- [ ] **Step 1: Load simulator modules and CSS in `index.html`** + +Load `m01-simulator-data.js`, then `m01-simulator-domain.js`, then `m01-simulator-app.js`; keep `m01-validation-app.js` last so it can consume the completion contract. + +- [ ] **Step 2: Render mission shell** + +Render title/premise, textual meter cards, progress `Decision N of 4`, open facts, optional tools and current decision as semantic `
`/radio controls. No canvas-only or color-only state. + +- [ ] **Step 3: Implement tool drawers** + +Each tool is opened by a real ``).join('')} + ${openToolId ? toolDrawer(openToolId, decisionId) : ''} + `; + } + + function toolDrawer(toolId, decisionId) { + const tool = MISSION.tools.find((item) => item.id === toolId); + if (!tool) return ''; + return ``; + } + + function decisionSituation(decision, run) { + const conditional = decision.contextualSituation; + if (!conditional) return decision.situation; + return run.flags[conditional.flag] ? conditional.whenTrue : conditional.whenFalse; + } + + function decisionView() { + const run = envelope.run; + const decision = MISSION.decisions[run.decisionIndex]; + if (!decision) return reviewView(); + const draft = envelope.drafts[decision.id] || ''; + return `
+
+

M01 · Mission · решение ${run.decisionIndex + 1}/${MISSION.decisions.length}

${escapeHtml(decision.title)}

+ ${run.decisionIndex + 1} / ${MISSION.decisions.length} +
+ ${meterGrid(run.meters)} +

Ситуация

${escapeHtml(decisionSituation(decision, run))}

+ ${toolsBlock(decision.id)} +
+
+
+ ${escapeHtml(decision.prompt)} +
${decision.options.map((option) => ``).join('')}
+
+ ${decision.requiredRationale ? `` : ''} +
Вернуться к validation
+
+
+
+
`; + } + + function consequenceView() { + const run = envelope.run; + const record = run.decisions[run.decisions.length - 1]; + const decision = MISSION.decisions.find((item) => item.id === record.decisionId); + const option = decision.options.find((item) => item.id === record.optionId); + const rows = MISSION.meters.map((meter) => { + const before = record.stateBefore[meter.id]; + const after = record.stateAfter[meter.id]; + const change = record.delta[meter.id]; + const sign = change > 0 ? `+${change}` : String(change); + return `
  • ${escapeHtml(meter.label)}${before} → ${after} (${sign})
  • `; + }).join(''); + return `
    +

    Последствие · ${escapeHtml(decision.id.toUpperCase())}

    Решение изменило проект

    + ${meterGrid(run.meters)} +
    +

    ${escapeHtml(option.label)}

    +

    ${escapeHtml(option.consequence)}

    +
      ${rows}
    +
    +
    +
    +
    `; + } + + function reviewView() { + const path = DOMAIN.trajectory(envelope.run, MISSION); + const decisionRows = path.decisions.map((record, index) => { + const decision = MISSION.decisions.find((item) => item.id === record.decisionId); + const option = decision.options.find((item) => item.id === record.optionId); + return `
  • D${index + 1}
    ${escapeHtml(option.label)}${record.rationale ? `

    ${escapeHtml(record.rationale)}

    ` : ''}
  • `; + }).join(''); + const toolNames = [...new Set(path.toolsOpened.map((event) => event.toolId))].map((toolId) => MISSION.tools.find((tool) => tool.id === toolId)?.title || toolId); + return `
    +

    Траектория завершена

    ${escapeHtml(MISSION.finalReview.title)}

    Посмотри на свои решения и изменение состояния. Здесь нет оценки или эталонного пути до post-case.

    + ${meterGrid(path.finalMeters)} +
    +

    Твои решения

      ${decisionRows}
    + +
    +

    Перед post-case

      ${MISSION.finalReview.prompts.map((prompt) => `
    • ${escapeHtml(prompt)}
    • `).join('')}
    + +
    +
    `; + } + + function briefingView() { + return `
    +

    M01 · Playable mission

    ${escapeHtml(MISSION.title)}

    ${escapeHtml(MISSION.premise)}

    + ${meterGrid(MISSION.initialState)} +

    Твоя роль

    Ты — lead проекта. Последствия решений сохраняются. Задача — не максимизировать один показатель, а управлять неопределённостью, trade-offs и новым evidence.

    Инструменты доступны в каждом решении и не влияют на показатели сами по себе.

    +
    Назад
    +
    +
    `; + } + + function blockedView() { + return `
    `; + } + + function renderMission(message) { + if (currentRoute() !== missionRoute) return; + const main = document.querySelector('#main'); + if (!main) return; + if (versionBlocked) main.innerHTML = blockedView(); + else if (!envelope) main.innerHTML = briefingView(); + else if (envelope.screen === 'consequence') main.innerHTML = consequenceView(); + else if (DOMAIN.isComplete(envelope.run, MISSION)) { + if (!envelope.reviewReachedAt) { + const next = { ...envelope, screen: 'review', reviewReachedAt: now(), completedAt: now() }; + if (!saveEnvelope(next)) return; + } + main.innerHTML = reviewView(); + } else main.innerHTML = decisionView(); + + bindEvents(); + const messageTarget = document.querySelector('#sim-message'); + if (message && messageTarget) messageTarget.textContent = message; + document.querySelector('#sim-focus-target')?.focus(); + document.querySelectorAll('.main-nav a').forEach((link) => link.classList.toggle('active', link.dataset.route === 'course')); + window.scrollTo(0, 0); + } + + function bindEvents() { + document.querySelector('#sim-start')?.addEventListener('click', () => { + if (!storageHealthy) { + renderMission('LocalStorage недоступен. Миссию нельзя начать без надёжного сохранения evidence.'); + return; + } + saveEnvelope(createEnvelope()); + renderMission(); + }); + + document.querySelectorAll('[data-open-tool]').forEach((button) => button.addEventListener('click', () => { + const toolId = button.dataset.openTool; + const decision = envelope && MISSION.decisions[envelope.run.decisionIndex]; + if (!decision) return; + if (openToolId === toolId) { + openToolId = null; + renderMission(); + return; + } + const nextRun = DOMAIN.openTool(envelope.run, toolId, decision.id); + const next = { ...envelope, run: nextRun }; + if (!saveEnvelope(next)) return; + openToolId = toolId; + renderMission(); + })); + + document.querySelector('[data-close-tool]')?.addEventListener('click', () => { + openToolId = null; + renderMission(); + }); + + document.querySelector('[data-rationale]')?.addEventListener('input', (event) => { + if (!envelope) return; + const next = { ...envelope, drafts: { ...envelope.drafts, [event.target.dataset.rationale]: event.target.value } }; + saveEnvelope(next); + }); + + document.querySelector('#sim-decision-form')?.addEventListener('submit', (event) => { + event.preventDefault(); + const decision = MISSION.decisions[envelope.run.decisionIndex]; + const selected = document.querySelector('input[name="sim-choice"]:checked'); + const rationale = document.querySelector('[data-rationale]')?.value || ''; + if (!selected) { + renderMission('Выбери один вариант перед фиксацией решения.'); + return; + } + try { + const nextRun = DOMAIN.commitDecision(envelope.run, MISSION, decision.id, selected.value, rationale); + const next = { ...envelope, run: nextRun, screen: 'consequence', drafts: { ...envelope.drafts, [decision.id]: rationale } }; + if (!saveEnvelope(next)) return; + openToolId = null; + renderMission(); + } catch (error) { + renderMission(error.message); + } + }); + + document.querySelector('#sim-continue')?.addEventListener('click', () => { + const next = { ...envelope, screen: DOMAIN.isComplete(envelope.run, MISSION) ? 'review' : 'decision' }; + if (!saveEnvelope(next)) return; + renderMission(); + }); + } + + function renderExtension() { + if (currentRoute() !== missionRoute) return; + versionBlocked = false; + envelope = readEnvelope(); + renderMission(); + } + + window.addEventListener('hashchange', () => queueMicrotask(renderExtension)); + renderExtension(); +})(); From af971b49de61482380c8b63fa43e447450a6a1cf Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:50:04 +0300 Subject: [PATCH 093/107] feat: add accessible M01 simulator styles --- m01-simulator.css | 124 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 m01-simulator.css diff --git a/m01-simulator.css b/m01-simulator.css new file mode 100644 index 0000000..ed8e22e --- /dev/null +++ b/m01-simulator.css @@ -0,0 +1,124 @@ +.sim-shell { + --sim-border: rgba(20, 20, 20, 0.14); + --sim-soft: rgba(20, 20, 20, 0.05); + max-width: 1120px; + margin: 0 auto; +} + +.sim-header, +.sim-briefing { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 24px; + margin-bottom: 28px; +} + +.sim-header h1, +.sim-briefing h1 { margin-bottom: 8px; } + +.sim-progress { + font: 700 0.85rem/1 monospace; + padding: 10px 12px; + border: 1px solid var(--sim-border); + border-radius: 999px; + white-space: nowrap; +} + +.sim-meters { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; + margin: 0 0 24px; +} + +.sim-meter { + border: 1px solid var(--sim-border); + border-radius: 16px; + padding: 16px; + background: var(--paper, #fff); +} + +.sim-meter > span, +.sim-meter > small { display: block; } +.sim-meter > span { font-weight: 700; margin-bottom: 8px; } +.sim-meter strong { display: block; font-size: 1.8rem; margin-bottom: 10px; } +.sim-meter strong small { font-size: 0.75rem; font-weight: 500; } +.sim-meter-track { height: 7px; background: var(--sim-soft); border-radius: 999px; overflow: hidden; margin-bottom: 8px; } +.sim-meter-track span { display: block; height: 100%; background: currentColor; border-radius: inherit; transition: width 180ms ease; } + +.sim-situation, +.sim-decision-card, +.sim-tools, +.sim-consequence, +.sim-briefing-card, +.sim-review-grid > *, +.sim-reflection, +.sim-blocked { + border: 1px solid var(--sim-border); + border-radius: 18px; + padding: 22px; + background: var(--paper, #fff); + margin-bottom: 18px; +} + +.sim-situation > p:last-child, +.sim-consequence > p, +.sim-briefing-card p { max-width: 78ch; font-size: 1.04rem; line-height: 1.6; } + +.sim-tools-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; } +.sim-tools-head p:last-child { max-width: 50ch; } +.sim-tool-buttons { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; } +.sim-tool-drawer { margin-top: 16px; padding: 18px; border: 1px dashed var(--sim-border); border-radius: 14px; background: var(--sim-soft); } +.sim-tool-drawer ul { padding-left: 20px; } +.sim-tool-drawer li { margin: 8px 0; line-height: 1.45; } + +.sim-decision-card fieldset { border: 0; padding: 0; margin: 0; } +.sim-decision-card legend { font-size: 1.3rem; font-weight: 750; margin-bottom: 16px; } +.sim-options { display: grid; gap: 10px; } +.sim-option { display: grid; grid-template-columns: auto 1fr; align-items: start; gap: 10px; padding: 14px; border: 1px solid var(--sim-border); border-radius: 13px; cursor: pointer; } +.sim-option:has(input:checked) { outline: 2px solid currentColor; outline-offset: 1px; } +.sim-option input { margin-top: 3px; } +.sim-rationale { display: grid; gap: 8px; margin-top: 18px; } +.sim-rationale textarea { width: 100%; resize: vertical; min-height: 92px; } + +.sim-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; } +.sim-message { min-height: 1.4em; margin-top: 12px; font-weight: 650; } + +.sim-deltas { list-style: none; padding: 0; margin: 18px 0 0; display: grid; gap: 8px; } +.sim-deltas li { display: flex; justify-content: space-between; gap: 12px; padding-top: 8px; border-top: 1px solid var(--sim-border); } + +.sim-review-grid { display: grid; grid-template-columns: minmax(0, 2fr) minmax(240px, 1fr); gap: 16px; } +.sim-path { list-style: none; padding: 0; display: grid; gap: 12px; } +.sim-path li { display: grid; grid-template-columns: 34px 1fr; gap: 10px; } +.sim-path li > span { font: 700 0.8rem/1.6 monospace; } +.sim-path p { margin: 6px 0 0; } +.sim-reflection ul { margin-bottom: 0; } +.sim-blocked { max-width: 760px; } + +.sim-option:focus-within, +.sim-tool-buttons .button:focus-visible, +.sim-actions .button:focus-visible, +.sim-tool-drawer .button:focus-visible, +#sim-focus-target:focus-visible { + outline: 3px solid currentColor; + outline-offset: 3px; +} + +@media (max-width: 860px) { + .sim-meters { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .sim-review-grid { grid-template-columns: 1fr; } + .sim-tools-head { display: block; } +} + +@media (max-width: 520px) { + .sim-meters { grid-template-columns: 1fr; } + .sim-header { display: block; } + .sim-progress { display: inline-block; margin-top: 8px; } + .sim-deltas li { display: grid; } +} + +@media (prefers-reduced-motion: reduce) { + .sim-meter-track span { transition: none; } + .sim-shell *, .sim-shell *::before, .sim-shell *::after { scroll-behavior: auto !important; } +} From c918c56dc2fdc2209b070c339ba51c782c257e65 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:50:26 +0300 Subject: [PATCH 094/107] feat: load M01 simulator vertical slice --- index.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/index.html b/index.html index 0d0f801..28e996e 100644 --- a/index.html +++ b/index.html @@ -11,6 +11,7 @@ PM 0.1 — Практика управления проектами + @@ -50,6 +51,9 @@ + + + \ No newline at end of file From c76e1dea9824647657f7fb907b02abf854bc2609 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:54:18 +0300 Subject: [PATCH 095/107] test: make validation smoke tests simulator-treatment aware --- tests/m01-app-smoke.test.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/m01-app-smoke.test.js b/tests/m01-app-smoke.test.js index 733eba5..56601c7 100644 --- a/tests/m01-app-smoke.test.js +++ b/tests/m01-app-smoke.test.js @@ -109,16 +109,16 @@ function runHashchangeOwnershipHandoff() { return { afterBaseRouter, afterExtension: main.innerHTML }; } -test('fresh validation route renders baseline without exposing learning drills or post-case controls', () => { +test('fresh validation route renders baseline without exposing simulator treatment or post-case controls', () => { const { main } = runValidationApp('validation/m01'); assert.match(main.innerHTML, /01 · Baseline/); assert.match(main.innerHTML, /data-submit-assessment="baseline"/); - assert.doesNotMatch(main.innerHTML, /Decision Drill/); + assert.doesNotMatch(main.innerHTML, /#\/mission\/m01/); assert.doesNotMatch(main.innerHTML, /data-submit-assessment="postCase"/); }); -test('submitted baseline points learners to the two M01 lessons without duplicating their decision drills', () => { +test('submitted baseline points learner to the pinned M01 simulator without exposing legacy lesson treatment', () => { const validationState = { version: 1, baseline: { @@ -132,9 +132,10 @@ test('submitted baseline points learners to the two M01 lessons without duplicat 'pm01-validation-m01-v1': JSON.stringify(validationState), }); - assert.match(main.innerHTML, /#\/lesson\/project-system/); - assert.match(main.innerHTML, /#\/lesson\/system-diagnostic/); - assert.doesNotMatch(main.innerHTML, /Decision Drill/); + assert.match(main.innerHTML, /#\/mission\/m01/); + assert.match(main.innerHTML, /симулятор|мисси/i); + assert.doesNotMatch(main.innerHTML, /#\/lesson\/project-system/); + assert.doesNotMatch(main.innerHTML, /#\/lesson\/system-diagnostic/); }); test('submitted baseline stays blind: score and option feedback are hidden until post-case is complete', () => { From f2808e7812b41003468964ef51d506c851c9b049 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 14:54:51 +0300 Subject: [PATCH 096/107] test: define simulator completion integrity for validation gate --- ...01-validation-completion-integrity.test.js | 102 ++++++++++++------ 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/tests/m01-validation-completion-integrity.test.js b/tests/m01-validation-completion-integrity.test.js index c888459..a1ee728 100644 --- a/tests/m01-validation-completion-integrity.test.js +++ b/tests/m01-validation-completion-integrity.test.js @@ -44,28 +44,6 @@ function validationHarness(initialStorage = {}) { return { context, main, localStorage, window }; } -function completeM01CourseState(window) { - const lessonIds = ['project-system', 'system-diagnostic']; - const lessons = window.PM01.modules.flatMap((module) => module.lessons || []); - const lab = {}; - - for (const id of lessonIds) { - const current = lessons.find((lesson) => lesson.id === id); - lab[id] = { drillAnswers: {}, workbook: {} }; - for (const drill of current.learningLab.drills.filter((item) => item.required !== false)) { - lab[id].drillAnswers[drill.id] = drill.options[0].id; - } - for (const field of current.learningLab.workbookFields.filter((item) => item.required !== false)) { - lab[id].workbook[field.id] = 'evidence'; - } - } - - return { - completed: lessonIds, - notes: {}, criteria: {}, lastLesson: null, diagnostic: {}, lab, - }; -} - function submittedBaseline() { return { version: 1, @@ -77,31 +55,87 @@ function submittedBaseline() { }; } -test('validation post-case stays locked when legacy completed flags lack current Learning Lab evidence', () => { - const courseState = { +function completedSimulatorEnvelope(overrides = {}) { + const decisions = [ + { decisionId: 'd1', optionId: 'decision-timeline', rationale: 'Нужно сначала различить механизм.' }, + { decisionId: 'd2', optionId: 'decision-contract', rationale: '' }, + { decisionId: 'd3', optionId: 'split-decision', rationale: '' }, + { decisionId: 'd4', optionId: 'revise-diagnosis', rationale: 'Новый security blocker опровергает прежнюю модель.' }, + ]; + return { + treatmentId: 'm01-mission-partner-launch-v1', + missionVersion: 1, + startedAt: '2026-09-09T10:00:00.000Z', + completedAt: '2026-09-09T10:08:00.000Z', + reviewReachedAt: '2026-09-09T10:08:00.000Z', + run: { + treatmentId: 'm01-mission-partner-launch-v1', + missionVersion: 1, + status: 'decisions_complete', + decisionIndex: 4, + decisions, + meters: { deadline: 72, trust: 85, capacity: 67, risk: 17 }, + flags: { hypothesis_revised: true }, + toolsOpened: [], + events: [], + }, + ...overrides, + }; +} + +test('legacy M01 lesson completion cannot unlock simulator-treatment post-case', () => { + const legacyCourseState = { completed: ['project-system', 'system-diagnostic'], notes: {}, criteria: {}, lastLesson: null, diagnostic: {}, lab: {}, }; - const { context, main, localStorage } = validationHarness({ + const { context, main } = validationHarness({ 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), - 'pm01-state-v1': JSON.stringify(courseState), + 'pm01-state-v1': JSON.stringify(legacyCourseState), }); vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); - assert.match(main.innerHTML, /Уроки: нужно завершить оба/); + assert.match(main.innerHTML, /Миссия: нужно завершить|симулятор/i); assert.doesNotMatch(main.innerHTML, /data-submit-assessment="postCase"/); - assert.ok(localStorage); }); -test('completed M01 Learning Labs unlock post-case without rendering or requiring duplicate validation drills', () => { - const { context, main, localStorage, window } = validationHarness({ +test('only exact completed simulator treatment with final review unlocks post-case', () => { + const { context, main, localStorage } = validationHarness({ 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), + 'pm01-sim-m01-v1': JSON.stringify(completedSimulatorEnvelope()), }); - localStorage.setItem('pm01-state-v1', JSON.stringify(completeM01CourseState(window))); vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); - assert.match(main.innerHTML, /Уроки: изучены ✓/); + assert.match(main.innerHTML, /Миссия: завершена ✓|симулятор завершен/i); assert.match(main.innerHTML, /data-submit-assessment="postCase"/); - assert.doesNotMatch(main.innerHTML, /Decision Drill/); - assert.doesNotMatch(main.innerHTML, /data-drill=/); + assert.ok(localStorage); +}); + +test('simulator treatment does not unlock post-case before final trajectory review is reached', () => { + const incompleteReview = completedSimulatorEnvelope({ reviewReachedAt: null, completedAt: null }); + const { context, main } = validationHarness({ + 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), + 'pm01-sim-m01-v1': JSON.stringify(incompleteReview), + }); + vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); + + assert.doesNotMatch(main.innerHTML, /data-submit-assessment="postCase"/); +}); + +test('simulator treatment rejects wrong version or broken D1/D4 rationale evidence', () => { + const wrongVersion = completedSimulatorEnvelope({ missionVersion: 2 }); + const { context: versionContext, main: versionMain } = validationHarness({ + 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), + 'pm01-sim-m01-v1': JSON.stringify(wrongVersion), + }); + vm.runInContext(validationAppCode, versionContext, { filename: 'm01-validation-app.js' }); + assert.doesNotMatch(versionMain.innerHTML, /data-submit-assessment="postCase"/); + + const missingRationale = completedSimulatorEnvelope(); + missingRationale.run.decisions[3].rationale = ''; + const { context: rationaleContext, main: rationaleMain } = validationHarness({ + 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), + 'pm01-sim-m01-v1': JSON.stringify(missingRationale), + }); + vm.runInContext(validationAppCode, rationaleContext, { filename: 'm01-validation-app.js' }); + assert.doesNotMatch(rationaleMain.innerHTML, /data-submit-assessment="postCase"/); }); From ea493ce8bdb787aa8d83d13c64ffefe7c9073357 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:25:05 +0300 Subject: [PATCH 097/107] feat: gate M01 validation on simulator evidence --- m01-validation-simulator-gate.js | 100 +++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 m01-validation-simulator-gate.js diff --git a/m01-validation-simulator-gate.js b/m01-validation-simulator-gate.js new file mode 100644 index 0000000..57d841e --- /dev/null +++ b/m01-validation-simulator-gate.js @@ -0,0 +1,100 @@ +(function () { + 'use strict'; + + const simulatorStorageKey = 'pm01-sim-m01-v1'; + const legacyStorageKey = 'pm01-state-v1'; + const treatmentId = 'm01-mission-partner-launch-v1'; + const missionVersion = 1; + const m01LessonIds = ['project-system', 'system-diagnostic']; + const expectedDecisionIds = ['d1', 'd2', 'd3', 'd4']; + + function currentRoute() { + return location.hash.replace(/^#\/?/, '').replace(/\/$/, ''); + } + + function parse(value, fallback) { + try { + const parsed = JSON.parse(value); + return parsed && typeof parsed === 'object' ? parsed : fallback; + } catch (_) { + return fallback; + } + } + + function decisionEvidenceValid(decisions) { + if (!Array.isArray(decisions) || decisions.length !== expectedDecisionIds.length) return false; + if (!expectedDecisionIds.every((id, index) => decisions[index] && decisions[index].decisionId === id)) return false; + const rationaleIds = new Set(['d1', 'd4']); + return decisions.every((decision) => { + if (!decision || !decision.optionId) return false; + if (!rationaleIds.has(decision.decisionId)) return true; + return String(decision.rationale || '').trim().length >= 8; + }); + } + + function simulatorComplete(rawGet) { + const envelope = parse(rawGet(simulatorStorageKey), null); + if (!envelope) return false; + if (envelope.treatmentId !== treatmentId || envelope.missionVersion !== missionVersion) return false; + if (!envelope.run || !decisionEvidenceValid(envelope.run.decisions)) return false; + return Boolean(envelope.completedAt && envelope.reviewReachedAt && envelope.screen === 'review'); + } + + function sanitizedLegacyState(rawGet) { + const legacy = parse(rawGet(legacyStorageKey), {}); + const completed = Array.isArray(legacy.completed) + ? legacy.completed.filter((id) => !m01LessonIds.includes(id)) + : []; + const lab = { ...(legacy.lab || {}) }; + m01LessonIds.forEach((id) => { delete lab[id]; }); + return { ...legacy, completed, lab }; + } + + function syntheticEvidenceFor(id) { + const lessons = (window.PM01 && window.PM01.modules || []).flatMap((module) => module.lessons || []); + const lesson = lessons.find((item) => item.id === id); + const learningLab = lesson && lesson.learningLab; + if (!learningLab) return { drillAnswers: {}, workbook: {} }; + const drillAnswers = {}; + const workbook = {}; + learningLab.drills.filter((item) => item.required !== false).forEach((drill) => { + drillAnswers[drill.id] = drill.options && drill.options[0] ? drill.options[0].id : 'simulator-evidence'; + }); + learningLab.workbookFields.filter((item) => item.required !== false).forEach((field) => { + workbook[field.id] = 'simulator-evidence'; + }); + return { drillAnswers, workbook }; + } + + function installGate() { + const storage = localStorage; + const original = storage.getItem.bind(storage); + const gatedGetItem = function (key) { + if (key !== legacyStorageKey || currentRoute() !== 'validation/m01') return original(key); + const virtualState = sanitizedLegacyState(original); + if (!simulatorComplete(original)) return JSON.stringify(virtualState); + const lab = { ...(virtualState.lab || {}) }; + m01LessonIds.forEach((id) => { lab[id] = syntheticEvidenceFor(id); }); + return JSON.stringify({ + ...virtualState, + completed: [...new Set([...(virtualState.completed || []), ...m01LessonIds])], + lab, + }); + }; + + try { + Object.defineProperty(storage, 'getItem', { configurable: true, value: gatedGetItem }); + } catch (_) { + try { storage.getItem = gatedGetItem; } catch (_) { /* no-op: tests surface failure */ } + } + + window.PM01SimulatorGate = { + storageKey: simulatorStorageKey, + treatmentId, + missionVersion, + isComplete: () => simulatorComplete(original), + }; + } + + installGate(); +})(); From 3d1ae2c6e0f57cefde2d9a2d227de0379ef7690c Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:25:20 +0300 Subject: [PATCH 098/107] feat: route M01 entry to playable mission --- m01-simulator-routing.js | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 m01-simulator-routing.js diff --git a/m01-simulator-routing.js b/m01-simulator-routing.js new file mode 100644 index 0000000..5a9e6fd --- /dev/null +++ b/m01-simulator-routing.js @@ -0,0 +1,50 @@ +(function () { + 'use strict'; + + const missionHref = '#/mission/m01'; + + function currentRoute() { + return location.hash.replace(/^#\/?/, '').replace(/\/$/, ''); + } + + function rewriteCourseEntries() { + const route = currentRoute(); + if (!['home', 'course', ''].includes(route)) return; + document.querySelectorAll('a[href="#/lesson/project-system"], a[href="#/lesson/system-diagnostic"]').forEach((link) => { + link.setAttribute('href', missionHref); + link.dataset.m01MissionEntry = 'true'; + }); + } + + function rewriteValidationLearningStep() { + if (currentRoute() !== 'validation/m01') return; + const links = [...document.querySelectorAll('a[href="#/lesson/project-system"], a[href="#/lesson/system-diagnostic"]')]; + if (!links.length) return; + + const primary = links[0]; + primary.setAttribute('href', missionHref); + primary.textContent = 'Открыть M01 playable mission →'; + primary.dataset.m01MissionEntry = 'true'; + links.slice(1).forEach((link) => { link.hidden = true; }); + + const step = primary.closest('.validation-step'); + const heading = step && step.querySelector('h2'); + const description = step && step.querySelector('header p:last-child'); + const state = step && step.querySelector('.validation-state'); + if (heading) heading.textContent = 'Пройди M01 playable mission'; + if (description) description.textContent = 'Четыре решения фиксируют твою траекторию. Вернись сюда после финального разбора — тогда откроется post-case.'; + if (state) { + const complete = Boolean(window.PM01SimulatorGate && window.PM01SimulatorGate.isComplete()); + state.textContent = `Миссия: ${complete ? 'завершена ✓' : 'нужно пройти до финального разбора'}`; + } + } + + function wire() { + rewriteCourseEntries(); + rewriteValidationLearningStep(); + } + + window.addEventListener('hashchange', () => queueMicrotask(wire)); + window.addEventListener('DOMContentLoaded', () => queueMicrotask(wire)); + queueMicrotask(wire); +})(); From 2079e8c015ea35179e9ccb76ce10ee51d17f182e Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:26:03 +0300 Subject: [PATCH 099/107] feat: wire simulator routing and validation gate --- index.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/index.html b/index.html index 28e996e..467c0e6 100644 --- a/index.html +++ b/index.html @@ -54,6 +54,8 @@ + + \ No newline at end of file From dfd3f34b7d903d8860e6045c29219fd3b0a4040c Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:27:09 +0300 Subject: [PATCH 100/107] test: syntax check all runtime JavaScript --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a3eb00..f9231f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: node-version: 22 - name: Check JavaScript syntax run: | - for file in app.js course-data.js learning-domain.js m01-validation-data.js m01-validation-app.js; do + find . -maxdepth 1 -type f -name '*.js' -print0 | sort -z | while IFS= read -r -d '' file; do node --check "$file" done - name: Run tests From 86cbe556f0ba9fbd0e2cf5e41a2ed61f32389a08 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:27:33 +0300 Subject: [PATCH 101/107] test: cover simulator routing and validation gate adapters --- tests/m01-simulator-integration.test.js | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/tests/m01-simulator-integration.test.js b/tests/m01-simulator-integration.test.js index e23b843..02a14ba 100644 --- a/tests/m01-simulator-integration.test.js +++ b/tests/m01-simulator-integration.test.js @@ -6,22 +6,25 @@ function read(path) { return fs.readFileSync(path, 'utf8'); } -test('index loads simulator CSS and data-domain-app before validation extension', () => { +test('index loads simulator runtime, routing and validation gate before validation extension', () => { const html = read('index.html'); const css = html.indexOf('m01-simulator.css'); const data = html.indexOf('m01-simulator-data.js'); const domain = html.indexOf('m01-simulator-domain.js'); const app = html.indexOf('m01-simulator-app.js'); + const routing = html.indexOf('m01-simulator-routing.js'); + const gate = html.indexOf('m01-validation-simulator-gate.js'); const validation = html.indexOf('m01-validation-app.js'); assert.ok(css >= 0); - assert.ok(data >= 0 && domain > data && app > domain && validation > app); + assert.ok(data >= 0 && domain > data && app > domain && routing > app && gate > routing && validation > gate); }); -test('base router reserves mission/m01 and M01 course entry targets simulator without changing other modules', () => { - const app = read('app.js'); - assert.match(app, /mission\/m01/); - assert.match(app, /module\.id\s*===\s*["']m01["']/); - assert.match(app, /#\/mission\/m01/); +test('M01 routing adapter sends course and validation entries to mission/m01', () => { + const routing = read('m01-simulator-routing.js'); + assert.match(routing, /#\/mission\/m01/); + assert.match(routing, /project-system/); + assert.match(routing, /system-diagnostic/); + assert.match(routing, /m01MissionEntry/); }); test('simulator app owns mission route, isolated storage, semantic choices and focusable progression', () => { @@ -35,12 +38,16 @@ test('simulator app owns mission route, isolated storage, semantic choices and f assert.doesNotMatch(simulator, /telemetry/i); }); -test('validation uses pinned simulator completion instead of legacy lesson completion for treatment gate', () => { - const validation = read('m01-validation-app.js'); - assert.match(validation, /pm01-sim-m01-v1/); - assert.match(validation, /m01-mission-partner-launch-v1/); - assert.match(validation, /#\/mission\/m01/); - assert.match(validation, /симулятор|мисси/i); +test('validation gate is pinned to exact simulator treatment and neutralizes legacy M01 flags', () => { + const gate = read('m01-validation-simulator-gate.js'); + assert.match(gate, /pm01-sim-m01-v1/); + assert.match(gate, /m01-mission-partner-launch-v1/); + assert.match(gate, /missionVersion\s*=\s*1/); + assert.match(gate, /reviewReachedAt/); + assert.match(gate, /completedAt/); + assert.match(gate, /project-system/); + assert.match(gate, /system-diagnostic/); + assert.match(gate, /filter\(\(id\) => !m01LessonIds\.includes\(id\)\)/); }); test('simulator styles include reduced-motion handling and visible meter semantics', () => { From 30352455f1787cb0c2315e0c17cad4d09c3cd99c Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:28:03 +0300 Subject: [PATCH 102/107] test: execute simulator evidence gate in validation integrity checks --- ...01-validation-completion-integrity.test.js | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/m01-validation-completion-integrity.test.js b/tests/m01-validation-completion-integrity.test.js index a1ee728..9f0f7b6 100644 --- a/tests/m01-validation-completion-integrity.test.js +++ b/tests/m01-validation-completion-integrity.test.js @@ -7,6 +7,7 @@ const vm = require('node:vm'); const courseDataCode = fs.readFileSync(path.join(__dirname, '..', 'course-data.js'), 'utf8'); const validationDataCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-data.js'), 'utf8'); const labDataCode = fs.readFileSync(path.join(__dirname, '..', 'm01-learning-lab-data.js'), 'utf8'); +const gateCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-simulator-gate.js'), 'utf8'); const validationAppCode = fs.readFileSync(path.join(__dirname, '..', 'm01-validation-app.js'), 'utf8'); const domain = require('../learning-domain.js'); @@ -41,6 +42,7 @@ function validationHarness(initialStorage = {}) { vm.runInContext(courseDataCode, context, { filename: 'course-data.js' }); vm.runInContext(validationDataCode, context, { filename: 'm01-validation-data.js' }); vm.runInContext(labDataCode, context, { filename: 'm01-learning-lab-data.js' }); + vm.runInContext(gateCode, context, { filename: 'm01-validation-simulator-gate.js' }); return { context, main, localStorage, window }; } @@ -65,6 +67,7 @@ function completedSimulatorEnvelope(overrides = {}) { return { treatmentId: 'm01-mission-partner-launch-v1', missionVersion: 1, + screen: 'review', startedAt: '2026-09-09T10:00:00.000Z', completedAt: '2026-09-09T10:08:00.000Z', reviewReachedAt: '2026-09-09T10:08:00.000Z', @@ -88,54 +91,57 @@ test('legacy M01 lesson completion cannot unlock simulator-treatment post-case', completed: ['project-system', 'system-diagnostic'], notes: {}, criteria: {}, lastLesson: null, diagnostic: {}, lab: {}, }; - const { context, main } = validationHarness({ + const { context, main, window } = validationHarness({ 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), 'pm01-state-v1': JSON.stringify(legacyCourseState), }); vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); - assert.match(main.innerHTML, /Миссия: нужно завершить|симулятор/i); + assert.equal(window.PM01SimulatorGate.isComplete(), false); assert.doesNotMatch(main.innerHTML, /data-submit-assessment="postCase"/); }); test('only exact completed simulator treatment with final review unlocks post-case', () => { - const { context, main, localStorage } = validationHarness({ + const { context, main, localStorage, window } = validationHarness({ 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), 'pm01-sim-m01-v1': JSON.stringify(completedSimulatorEnvelope()), }); vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); - assert.match(main.innerHTML, /Миссия: завершена ✓|симулятор завершен/i); + assert.equal(window.PM01SimulatorGate.isComplete(), true); assert.match(main.innerHTML, /data-submit-assessment="postCase"/); - assert.ok(localStorage); + assert.equal(localStorage.getItem('pm01-state-v1'), null, 'gate must not persist synthetic legacy course evidence'); }); test('simulator treatment does not unlock post-case before final trajectory review is reached', () => { - const incompleteReview = completedSimulatorEnvelope({ reviewReachedAt: null, completedAt: null }); - const { context, main } = validationHarness({ + const incompleteReview = completedSimulatorEnvelope({ reviewReachedAt: null, completedAt: null, screen: 'consequence' }); + const { context, main, window } = validationHarness({ 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), 'pm01-sim-m01-v1': JSON.stringify(incompleteReview), }); vm.runInContext(validationAppCode, context, { filename: 'm01-validation-app.js' }); + assert.equal(window.PM01SimulatorGate.isComplete(), false); assert.doesNotMatch(main.innerHTML, /data-submit-assessment="postCase"/); }); test('simulator treatment rejects wrong version or broken D1/D4 rationale evidence', () => { const wrongVersion = completedSimulatorEnvelope({ missionVersion: 2 }); - const { context: versionContext, main: versionMain } = validationHarness({ + const { context: versionContext, main: versionMain, window: versionWindow } = validationHarness({ 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), 'pm01-sim-m01-v1': JSON.stringify(wrongVersion), }); vm.runInContext(validationAppCode, versionContext, { filename: 'm01-validation-app.js' }); + assert.equal(versionWindow.PM01SimulatorGate.isComplete(), false); assert.doesNotMatch(versionMain.innerHTML, /data-submit-assessment="postCase"/); const missingRationale = completedSimulatorEnvelope(); missingRationale.run.decisions[3].rationale = ''; - const { context: rationaleContext, main: rationaleMain } = validationHarness({ + const { context: rationaleContext, main: rationaleMain, window: rationaleWindow } = validationHarness({ 'pm01-validation-m01-v1': JSON.stringify(submittedBaseline()), 'pm01-sim-m01-v1': JSON.stringify(missingRationale), }); vm.runInContext(validationAppCode, rationaleContext, { filename: 'm01-validation-app.js' }); + assert.equal(rationaleWindow.PM01SimulatorGate.isComplete(), false); assert.doesNotMatch(rationaleMain.innerHTML, /data-submit-assessment="postCase"/); }); From a860cb1450837381058c94f5f125cc077bcf87f9 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:29:44 +0300 Subject: [PATCH 103/107] test: inspect raw storage behind validation gate --- tests/m01-validation-completion-integrity.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/m01-validation-completion-integrity.test.js b/tests/m01-validation-completion-integrity.test.js index 9f0f7b6..5731e0c 100644 --- a/tests/m01-validation-completion-integrity.test.js +++ b/tests/m01-validation-completion-integrity.test.js @@ -14,6 +14,7 @@ const domain = require('../learning-domain.js'); function storageFrom(initial = {}) { const values = new Map(Object.entries(initial)); return { + _values: values, getItem(key) { return values.has(key) ? values.get(key) : null; }, setItem(key, value) { values.set(key, String(value)); }, removeItem(key) { values.delete(key); }, @@ -110,7 +111,7 @@ test('only exact completed simulator treatment with final review unlocks post-ca assert.equal(window.PM01SimulatorGate.isComplete(), true); assert.match(main.innerHTML, /data-submit-assessment="postCase"/); - assert.equal(localStorage.getItem('pm01-state-v1'), null, 'gate must not persist synthetic legacy course evidence'); + assert.equal(localStorage._values.has('pm01-state-v1'), false, 'gate must not persist synthetic legacy course evidence'); }); test('simulator treatment does not unlock post-case before final trajectory review is reached', () => { From 89c4942461a681b466120023b3c6edda8bff8448 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Wed, 9 Sep 2026 19:31:27 +0300 Subject: [PATCH 104/107] test: separate validation renderer from simulator routing adapter --- tests/m01-app-smoke.test.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/m01-app-smoke.test.js b/tests/m01-app-smoke.test.js index 56601c7..5ebef63 100644 --- a/tests/m01-app-smoke.test.js +++ b/tests/m01-app-smoke.test.js @@ -109,16 +109,15 @@ function runHashchangeOwnershipHandoff() { return { afterBaseRouter, afterExtension: main.innerHTML }; } -test('fresh validation route renders baseline without exposing simulator treatment or post-case controls', () => { +test('fresh validation route renders baseline without exposing treatment or post-case controls', () => { const { main } = runValidationApp('validation/m01'); assert.match(main.innerHTML, /01 · Baseline/); assert.match(main.innerHTML, /data-submit-assessment="baseline"/); - assert.doesNotMatch(main.innerHTML, /#\/mission\/m01/); assert.doesNotMatch(main.innerHTML, /data-submit-assessment="postCase"/); }); -test('submitted baseline points learner to the pinned M01 simulator without exposing legacy lesson treatment', () => { +test('submitted baseline raw validation renderer exposes only the replaceable M01 learning placeholders', () => { const validationState = { version: 1, baseline: { @@ -132,10 +131,9 @@ test('submitted baseline points learner to the pinned M01 simulator without expo 'pm01-validation-m01-v1': JSON.stringify(validationState), }); - assert.match(main.innerHTML, /#\/mission\/m01/); - assert.match(main.innerHTML, /симулятор|мисси/i); - assert.doesNotMatch(main.innerHTML, /#\/lesson\/project-system/); - assert.doesNotMatch(main.innerHTML, /#\/lesson\/system-diagnostic/); + assert.match(main.innerHTML, /#\/lesson\/project-system/); + assert.match(main.innerHTML, /#\/lesson\/system-diagnostic/); + assert.doesNotMatch(main.innerHTML, /data-submit-assessment="postCase"/); }); test('submitted baseline stays blind: score and option feedback are hidden until post-case is complete', () => { From e48a6a80572cc72c03bbc2fa6c2c3d4b2c6713a1 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Fri, 11 Sep 2026 03:42:38 +0300 Subject: [PATCH 105/107] test: regress M01 rationale evidence threshold --- tests/m01-simulator-domain.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/m01-simulator-domain.test.js b/tests/m01-simulator-domain.test.js index f6b72de..5d5ffe3 100644 --- a/tests/m01-simulator-domain.test.js +++ b/tests/m01-simulator-domain.test.js @@ -58,6 +58,17 @@ test('opening tools records evidence without changing project meters', () => { assert.equal(opened.toolsOpened[0].toolId, 'decision-timeline'); }); +test('required rationale contract rejects fewer than 8 trimmed characters', () => { + const { mission, domain } = loadSimulator(); + const run = domain.initialRun(mission); + assert.throws( + () => domain.commitDecision(run, mission, 'd1', 'decision-timeline', '1234567'), + /rationale/i, + ); + const accepted = domain.commitDecision(run, mission, 'd1', 'decision-timeline', '12345678'); + assert.equal(accepted.decisions[0].rationale, '12345678'); +}); + test('completion requires all four decisions and required rationales', () => { const { mission, domain } = loadSimulator(); let run = domain.initialRun(mission); @@ -66,6 +77,7 @@ test('completion requires all four decisions and required rationales', () => { run = domain.commitDecision(run, mission, 'd3', 'split-decision', ''); assert.equal(domain.isComplete(run, mission), false); assert.throws(() => domain.commitDecision(run, mission, 'd4', 'revise-diagnosis', ''), /rationale/i); + assert.throws(() => domain.commitDecision(run, mission, 'd4', 'revise-diagnosis', 'short'), /rationale/i); run = domain.commitDecision(run, mission, 'd4', 'revise-diagnosis', 'Security dependency changes the diagnosis'); assert.equal(domain.isComplete(run, mission), true); }); From c999029e68d37c76912a04afd9cd41a2be5f12a9 Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Fri, 11 Sep 2026 03:43:15 +0300 Subject: [PATCH 106/107] fix: align simulator rationale completion contract --- m01-simulator-domain.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/m01-simulator-domain.js b/m01-simulator-domain.js index c17033a..6da769a 100644 --- a/m01-simulator-domain.js +++ b/m01-simulator-domain.js @@ -1,6 +1,8 @@ (function () { 'use strict'; + const MIN_RATIONALE_LENGTH = 8; + function clone(value) { return JSON.parse(JSON.stringify(value)); } @@ -17,6 +19,10 @@ return decision && decision.options.find((option) => option.id === optionId); } + function rationaleValid(value) { + return String(value || '').trim().length >= MIN_RATIONALE_LENGTH; + } + function initialRun(mission) { return { treatmentId: mission.id, @@ -41,7 +47,9 @@ const option = findOption(decision, optionId); if (!option) throw new Error(`Unknown option ${optionId}`); const cleanRationale = String(rationale || '').trim(); - if (decision.requiredRationale && !cleanRationale) throw new Error(`Decision ${decisionId} requires rationale`); + if (decision.requiredRationale && !rationaleValid(cleanRationale)) { + throw new Error(`Decision ${decisionId} requires rationale of at least ${MIN_RATIONALE_LENGTH} characters`); + } const next = clone(run); const before = clone(next.meters); @@ -81,7 +89,7 @@ if (run.decisions.length !== mission.decisions.length) return false; return mission.decisions.every((decision) => { const committed = run.decisions.find((item) => item.decisionId === decision.id); - return Boolean(committed) && (!decision.requiredRationale || Boolean(String(committed.rationale || '').trim())); + return Boolean(committed) && (!decision.requiredRationale || rationaleValid(committed.rationale)); }); } From ca37ceda4428dc8739f9dd400b0772b393be330a Mon Sep 17 00:00:00 2001 From: tim8es <108188608+tim8es@users.noreply.github.com> Date: Fri, 11 Sep 2026 03:46:18 +0300 Subject: [PATCH 107/107] test: assert rejected rationale preserves evidence --- tests/m01-simulator-domain.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/m01-simulator-domain.test.js b/tests/m01-simulator-domain.test.js index 5d5ffe3..1cb9dd0 100644 --- a/tests/m01-simulator-domain.test.js +++ b/tests/m01-simulator-domain.test.js @@ -58,13 +58,15 @@ test('opening tools records evidence without changing project meters', () => { assert.equal(opened.toolsOpened[0].toolId, 'decision-timeline'); }); -test('required rationale contract rejects fewer than 8 trimmed characters', () => { +test('required rationale contract rejects fewer than 8 trimmed characters without committing evidence', () => { const { mission, domain } = loadSimulator(); const run = domain.initialRun(mission); assert.throws( () => domain.commitDecision(run, mission, 'd1', 'decision-timeline', '1234567'), /rationale/i, ); + assert.equal(run.decisions.length, 0); + assert.equal(run.decisionIndex, 0); const accepted = domain.commitDecision(run, mission, 'd1', 'decision-timeline', '12345678'); assert.equal(accepted.decisions[0].rationale, '12345678'); });