Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f29b1b7e50 | |||
| 0d2c64aa8c | |||
| 256acf8781 | |||
| a0b1cfdcae | |||
| 2b04bbd4f8 | |||
| 888b7563cd | |||
| 3a58090db9 | |||
| 23579dd9be | |||
| 7c12b7419c | |||
| f05bb4dde2 | |||
| 703f101c11 | |||
| 30eec9fb7d | |||
| 83a831c46d | |||
| b72780c54e | |||
| 8c9a91be1c | |||
| f892c94feb | |||
| 21d84a77a9 | |||
| 2172d2ba45 | |||
| 915335aea6 | |||
| 9f791f9f93 | |||
| c31e199e45 | |||
| 42409ddec0 | |||
| d667feda0f | |||
| 6987c8a172 | |||
| aeda3f6df1 | |||
| 5cc8511990 | |||
| 3f91afd8d7 | |||
| 8bedf21c08 | |||
| 5d5eab70fe | |||
| b7a2412e88 | |||
| dd9e37ea3f | |||
| c09b9ab7fd | |||
| 3e73c0e68f |
@@ -0,0 +1,146 @@
|
||||
<!-- adr-kit-guide v0.13.0 -->
|
||||
<!-- Canonical project-side ADR guide. Copied from the plugin's templates/adr-kit-guide.md to .claude/adr-kit-guide.md by /adr-kit:init, /adr-kit:upgrade, and /adr-kit:setup. -->
|
||||
<!-- This file is plain markdown — readable by Claude Code, headless `claude -p`, shell scripts in pre-commit hooks, evaluator scripts, and any agent that doesn't process @-imports. Do not embed Claude-Code-specific syntax inside this file. -->
|
||||
|
||||
# ADR Kit Guide
|
||||
|
||||
This project uses [adr-kit](https://github.com/rvdbreemen/adr-kit) to manage Architecture Decision Records. The kit ships:
|
||||
|
||||
- a project-side guide (this file) referenced from `CLAUDE.md`,
|
||||
- a library of slash commands and a subagent for ADR authorship,
|
||||
- a pre-commit hook that catches code changes drifting outside accepted ADRs.
|
||||
|
||||
ADR files live at `docs/adr/ADR-NNN-kebab-case-title.md`. They are versioned, immutable once accepted, and the durable record of *why* the codebase looks the way it does.
|
||||
|
||||
## Three operating modes
|
||||
|
||||
| Mode | When | Entry point |
|
||||
|---|---|---|
|
||||
| **Init / bootstrap** | Once per project: scan source + docs, propose a starter ADR set, hook the kit into `CLAUDE.md`, install the pre-commit hook | `/adr-kit:init` |
|
||||
| **Per-commit verification** | Every `git commit`: declarative-rule check **plus** Claude Sonnet LLM judge for `llm_judge: true` ADRs in one batched call. Default-on as of v0.13.0. Falls back to declarative-only when the `claude` CLI is unavailable | `.githooks/pre-commit` (auto) |
|
||||
| **On-demand invocation** | Mid-session: write a new ADR, judge a staged diff, supersede an existing decision | `/adr-kit:adr`, `/adr-kit:judge`, `adr-generator` subagent |
|
||||
|
||||
## Slash commands
|
||||
|
||||
| Command | Purpose | User-only? |
|
||||
|---|---|---|
|
||||
| `/adr-kit:init` | One-shot project bootstrap (audit codebase, generate ADRs, install hook). Combines `setup` + audit + `install-hooks`. | yes |
|
||||
| `/adr-kit:adr` | Author a single ADR (delegates to `adr-generator` subagent; runs four verification gates). | no — model can self-call |
|
||||
| `/adr-kit:judge` | Interactive judge against a staged diff. Runs declarative checks + in-session LLM check for `llm_judge: true` ADRs. Walks resolution paths on violation. | no — model can self-call |
|
||||
| `/adr-kit:lint` | Validate existing ADRs against the four verification gates. | yes |
|
||||
| `/adr-kit:migrate` | Rewrite legacy ADRs into canonical format. | yes |
|
||||
| `/adr-kit:setup` | Append `## ADR Kit` block to `CLAUDE.md` (idempotent). | yes |
|
||||
| `/adr-kit:upgrade` | Migrate v0.11 → v0.12 footprint without re-running the heavy audit. | yes |
|
||||
| `/adr-kit:install-hooks` | Install or uninstall the pre-commit hook. | yes |
|
||||
|
||||
## The four verification gates
|
||||
|
||||
An ADR cannot move from `Proposed` to `Accepted` until all four pass.
|
||||
|
||||
1. **Completeness** — every required section is present and non-empty: Status, Context, Decision, Alternatives Considered (≥ 2), Consequences (positive + negative), Related Decisions, References. Plus filename matches `ADR-NNN-kebab-case.md` and the heading number agrees.
|
||||
2. **Evidence** — Context or References cites at least one concrete external/internal artefact (incident, profiling data, code path, RFC, vendor doc). No hand-waving justifications.
|
||||
3. **Clarity** — Decision section names a single concrete choice (not a survey), uses imperative voice, no hedging language ("perhaps", "we should consider"). Identifiers (file paths, function names, config keys) are traceable.
|
||||
4. **Consistency** — filename, heading number, and any cross-references resolve. No duplicate ADR numbers in the directory.
|
||||
|
||||
`bin/adr-lint` enforces Completeness and Consistency deterministically. Evidence and Clarity are heuristic; opt in via `--gates evidence,clarity` or run `/adr-kit:lint` to use the LLM-aware skill.
|
||||
|
||||
## Authoring workflow (`/adr-kit:adr` or `adr-generator`)
|
||||
|
||||
1. Identify the architecturally significant change (architecture, NFRs, interfaces, dependencies, build/CI tooling). Refactors and bug fixes within existing patterns do NOT need an ADR.
|
||||
2. Invoke `/adr-kit:adr` (or the `adr-generator` subagent). Provide: title, context with concrete forces, ≥ 2 alternatives with rejection reasons, consequences (both directions), related ADRs.
|
||||
3. The agent applies the four gates and writes `docs/adr/ADR-NNN-…md` with `Status: Proposed`.
|
||||
4. Human review. Iterate until all gates pass.
|
||||
5. Flip Status to `Accepted, YYYY-MM-DD` after explicit human approval. **Never self-approve.**
|
||||
6. If the decision touches code in a mechanically expressible way, add an `Enforcement` block (see below) so the pre-commit hook can guard the boundary.
|
||||
|
||||
## Enforcement block (v0.12+)
|
||||
|
||||
Optional `## Enforcement` section at the end of an ADR. Fenced JSON code block, parsed by `bin/adr-judge`. Schema: plugin's `schemas/adr-enforcement.schema.json`.
|
||||
|
||||
```json
|
||||
{
|
||||
"forbid_pattern": [
|
||||
{ "pattern": "\\bArduinoJson\\b", "path_glob": "src/**/*.{ino,cpp,h}",
|
||||
"message": "Use snprintf_P + sendJsonMapEntry; ArduinoJson fragments the heap (ADR-042)." }
|
||||
],
|
||||
"forbid_import": [
|
||||
{ "pattern": "^#include\\s+<ArduinoJson\\.h>", "path_glob": "src/**" }
|
||||
],
|
||||
"require_pattern": [],
|
||||
"llm_judge": false
|
||||
}
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
|
||||
- `forbid_pattern` — regex must NOT match any added line in the diff (lines starting with `+`, excluding `+++` markers).
|
||||
- `forbid_import` — same engine as `forbid_pattern`; the separate name documents intent.
|
||||
- `require_pattern` — regex must match at least once in the post-diff content of any file matching `path_glob`.
|
||||
- `llm_judge: true` — Claude Sonnet evaluates the diff against this ADR's `## Decision` text at commit time (default-on as of v0.13.0). The pre-commit hook batches all `llm_judge: true` ADRs into one Sonnet call and blocks the commit on `VIOLATION`. Falls back gracefully (advisory only, exit 0) when the `claude` CLI is missing.
|
||||
- ADRs with no Enforcement block are skipped silently by the judge.
|
||||
|
||||
**Path globs** support `**` (recursive). Examples: `src/**/*.py`, `tests/**`, `**/Makefile`.
|
||||
|
||||
## Pre-commit hook
|
||||
|
||||
After `/adr-kit:init` (or `/adr-kit:install-hooks`), every `git commit` runs `bin/adr-judge` on the staged diff with two passes:
|
||||
|
||||
- **Declarative pass** — fast, regex-only, no LLM. A violation exits non-zero and blocks the commit.
|
||||
- **LLM pass (Sonnet, default-on as of v0.13.0)** — all `llm_judge: true` ADRs are batched into one `claude -p --model claude-sonnet-4-6` call. Sonnet returns a per-ADR JSON verdict; any `VIOLATION` blocks the commit with the model's one-sentence reason. Falls back gracefully when the `claude` CLI is missing or unauthenticated — never blocks a legitimate commit due to tooling drift.
|
||||
|
||||
**Cost shape** (typical project, 50 `llm_judge` ADRs, small diff): roughly $0.10–0.30 per commit on Sonnet 4.6 with prompt caching. Latency 5–10s. Configurable via `judge.llm_model` / `judge.llm_timeout_seconds` / `judge.llm_cmd` in `docs/adr/.adr-kit.json`.
|
||||
|
||||
**Knobs:**
|
||||
|
||||
- Disable LLM pass per commit: `ADR_KIT_NO_LLM=1 git commit -m "…"`
|
||||
- Disable hook entirely per commit: `ADR_KIT_HOOK_DISABLE=1 git commit -m "…"`
|
||||
- Switch model: set `judge.llm_model: "claude-haiku-4-5"` in `.adr-kit.json` for higher throughput at lower cost.
|
||||
- Remove permanently: `/adr-kit:install-hooks --uninstall`
|
||||
|
||||
## Supersession (changing a decision)
|
||||
|
||||
Accepted ADRs are immutable. To change a decision:
|
||||
|
||||
1. Author a new ADR with the next number. Status `Proposed`. The Decision should explain what changes and why now.
|
||||
2. In its Related Decisions: `Supersedes ADR-OLD`.
|
||||
3. After the new ADR is `Accepted`: edit ONLY the old ADR's Status line to `Superseded by ADR-NEW, YYYY-MM-DD.` Leave every other section untouched — the old decision's content is the historical record.
|
||||
|
||||
Never edit Decision, Context, Consequences, or Alternatives of an Accepted/Deprecated ADR. The Status line is the only permitted change.
|
||||
|
||||
## Code review checks
|
||||
|
||||
When reviewing a PR, apply these seven checks (Check 7 added in v0.12):
|
||||
|
||||
1. **ADR exists** for any architecturally significant change in the PR (new dep, interface change, NFR shift, build tooling change). Missing → request the author to invoke `/adr-kit:adr` or `adr-generator`.
|
||||
2. **ADR is linked** in the PR description (path or relative URL).
|
||||
3. **No violation** of Accepted ADRs in the diff. Cross-reference against `docs/adr/README.md` and the Enforcement blocks. The pre-commit hook should have caught this; if it didn't, the ADR is missing rules or wasn't installed.
|
||||
4. **Supersession chain is correct** — old ADR's Status updated, new ADR cross-references, content immutability preserved.
|
||||
5. **All four gates pass** on any new/modified ADR. Cite the failing gate when blocking ("fails Evidence gate — no concrete reference in Context").
|
||||
6. **Legacy non-compliance has a remediation plan** — pre-existing violations that this PR doesn't fix should at least carry a `// TODO(ADR-NNN): align` or a backlog entry, not be silently ignored.
|
||||
7. **Enforcement block is set appropriately** on any new Accepted ADR with a code surface. Either declarative rules, OR `llm_judge: true`, OR an explicit "manual review only" note in the ADR body explaining why the rule cannot be expressed mechanically. Missing block on a code-touching ADR is a smell.
|
||||
|
||||
## Anti-rationalisation guards
|
||||
|
||||
When `/adr-kit:adr` is asked to write or accept an ADR, it actively pushes back on these nine common excuses (see plugin's `skills/adr/SKILL.md` for the full text):
|
||||
|
||||
- "It's just a small change" — the rule is "architecturally significant", not "large".
|
||||
- "We can decide later" — later is now; defer = decide.
|
||||
- "Everyone knows this" — undocumented tacit knowledge is the problem ADRs solve.
|
||||
- "It's documented in the code" — code shows what, not why.
|
||||
- "We'll do it the same as last time" — name "last time" with an ADR reference.
|
||||
- "There's only one option" — there are always alternatives; "do nothing" is one.
|
||||
- "It's reversible" — most architecture is partially reversible; the ADR captures the *current* commitment.
|
||||
- "It's a refactor" — pure refactors don't need ADRs; *new patterns* introduced during refactoring do.
|
||||
- "We don't have time" — opportunity cost of skipping is a future maintainer hunting for the why.
|
||||
|
||||
## Plugin-side deep dives
|
||||
|
||||
This guide is the project's own copy. For agents inside Claude Code, the plugin auto-loads richer instructions:
|
||||
|
||||
- Plugin path (locale-dependent): `~/.claude/plugins/cache/rvdbreemen-adr-kit/adr-kit/<version>/`
|
||||
- `instructions/adr.coding.md` — per-developer rules (when to invoke the agent, supersession workflow, Definition of Done).
|
||||
- `instructions/adr.review.md` — the seven review checks with citation templates.
|
||||
- `skills/adr/SKILL.md` — full anti-rationalisation guard list, gate definitions, code examples.
|
||||
- `agents/adr-generator.md` — the subagent prompt.
|
||||
|
||||
If you're working outside Claude Code (in a hook, a CI job, or a different agent), this file (`.claude/adr-kit-guide.md`) is your one-stop reference. Keep it in version control with the rest of the project.
|
||||
@@ -0,0 +1,239 @@
|
||||
---
|
||||
allowed-tools: Bash(git diff:*), Bash(git status:*), Bash(git log:*), Bash(git show:*), Bash(git remote show:*), Read, Glob, Grep, LS, Task
|
||||
description: Complete a security review of the pending changes on the current branch
|
||||
---
|
||||
|
||||
You are a senior security engineer conducting a focused security review of the changes on this branch.
|
||||
|
||||
GIT STATUS:
|
||||
|
||||
```
|
||||
!`git status`
|
||||
```
|
||||
|
||||
FILES MODIFIED:
|
||||
|
||||
```
|
||||
!`git diff --name-only origin/HEAD...`
|
||||
```
|
||||
|
||||
COMMITS:
|
||||
|
||||
```
|
||||
!`git log --no-decorate origin/HEAD...`
|
||||
```
|
||||
|
||||
DIFF CONTENT:
|
||||
|
||||
```
|
||||
!`git diff --merge-base origin/HEAD`
|
||||
```
|
||||
|
||||
Review the complete diff above. This contains all code changes in the PR.
|
||||
|
||||
OBJECTIVE:
|
||||
Perform a security-focused code review to identify HIGH-CONFIDENCE security vulnerabilities that could have real exploitation potential. This is not a general code review - focus ONLY on security implications newly added by this PR. Do not comment on existing security concerns.
|
||||
|
||||
CRITICAL INSTRUCTIONS:
|
||||
|
||||
1. MINIMIZE FALSE POSITIVES: Only flag issues where you're >80% confident of actual exploitability
|
||||
2. AVOID NOISE: Skip theoretical issues, style concerns, or low-impact findings
|
||||
3. FOCUS ON IMPACT: Prioritize vulnerabilities that could lead to unauthorized access, data breaches, or system compromise
|
||||
4. EXCLUSIONS: Do NOT report the following issue types:
|
||||
- Denial of Service (DOS) vulnerabilities, even if they allow service disruption
|
||||
- Secrets or sensitive data stored on disk (these are handled by other processes)
|
||||
- Rate limiting or resource exhaustion issues
|
||||
|
||||
SECURITY CATEGORIES TO EXAMINE:
|
||||
|
||||
**Input Validation Vulnerabilities:**
|
||||
|
||||
- SQL injection via unsanitized user input
|
||||
- Command injection in system calls or subprocesses
|
||||
- XXE injection in XML parsing
|
||||
- Template injection in templating engines
|
||||
- NoSQL injection in database queries
|
||||
- Path traversal in file operations
|
||||
|
||||
**Authentication & Authorization Issues:**
|
||||
|
||||
- Authentication bypass logic
|
||||
- Privilege escalation paths
|
||||
- Session management flaws
|
||||
- JWT token vulnerabilities
|
||||
- Authorization logic bypasses
|
||||
|
||||
**Crypto & Secrets Management:**
|
||||
|
||||
- Hardcoded API keys, passwords, or tokens
|
||||
- Weak cryptographic algorithms or implementations
|
||||
- Improper key storage or management
|
||||
- Cryptographic randomness issues
|
||||
- Certificate validation bypasses
|
||||
|
||||
**Injection & Code Execution:**
|
||||
|
||||
- Remote code execution via deseralization
|
||||
- Pickle injection in Python
|
||||
- YAML deserialization vulnerabilities
|
||||
- Eval injection in dynamic code execution
|
||||
- XSS vulnerabilities in web applications (reflected, stored, DOM-based)
|
||||
|
||||
**Data Exposure:**
|
||||
|
||||
- Sensitive data logging or storage
|
||||
- PII handling violations
|
||||
- API endpoint data leakage
|
||||
- Debug information exposure
|
||||
|
||||
Additional notes:
|
||||
|
||||
- Even if something is only exploitable from the local network, it can still be a HIGH severity issue
|
||||
|
||||
ANALYSIS METHODOLOGY:
|
||||
|
||||
Phase 1 - Repository Context Research (Use file search tools):
|
||||
|
||||
- Identify existing security frameworks and libraries in use
|
||||
- Look for established secure coding patterns in the codebase
|
||||
- Examine existing sanitization and validation patterns
|
||||
- Understand the project's security model and threat model
|
||||
|
||||
Phase 2 - Comparative Analysis:
|
||||
|
||||
- Compare new code changes against existing security patterns
|
||||
- Identify deviations from established secure practices
|
||||
- Look for inconsistent security implementations
|
||||
- Flag code that introduces new attack surfaces
|
||||
|
||||
Phase 3 - Vulnerability Assessment:
|
||||
|
||||
- Examine each modified file for security implications
|
||||
- Trace data flow from user inputs to sensitive operations
|
||||
- Look for privilege boundaries being crossed unsafely
|
||||
- Identify injection points and unsafe deserialization
|
||||
|
||||
REQUIRED OUTPUT FORMAT:
|
||||
|
||||
You MUST output your findings in markdown. The markdown output should contain the file, line number, severity, category (e.g. `sql_injection` or `xss`), description, exploit scenario, and fix recommendation.
|
||||
|
||||
For example:
|
||||
|
||||
# Vuln 1: XSS: `foo.py:42`
|
||||
|
||||
- Severity: High
|
||||
- Description: User input from `username` parameter is directly interpolated into HTML without escaping, allowing reflected XSS attacks
|
||||
- Exploit Scenario: Attacker crafts URL like `/bar?q=<script>alert(document.cookie)</script>` to execute JavaScript in victim's browser, enabling session hijacking or data theft
|
||||
- Recommendation: Use Flask's escape() function or Jinja2 templates with auto-escaping enabled for all user inputs rendered in HTML
|
||||
|
||||
SEVERITY GUIDELINES:
|
||||
|
||||
- **HIGH**: Directly exploitable vulnerabilities leading to RCE, data breach, or authentication bypass
|
||||
- **MEDIUM**: Vulnerabilities requiring specific conditions but with significant impact
|
||||
- **LOW**: Defense-in-depth issues or lower-impact vulnerabilities
|
||||
|
||||
CONFIDENCE SCORING:
|
||||
|
||||
- 0.9-1.0: Certain exploit path identified, tested if possible
|
||||
- 0.8-0.9: Clear vulnerability pattern with known exploitation methods
|
||||
- 0.7-0.8: Suspicious pattern requiring specific conditions to exploit
|
||||
- Below 0.7: Don't report (too speculative)
|
||||
|
||||
FINAL REMINDER:
|
||||
Focus on HIGH and MEDIUM findings only. Better to miss some theoretical issues than flood the report with false positives. Each finding should be something a security engineer would confidently raise in a PR review.
|
||||
|
||||
FALSE POSITIVE FILTERING:
|
||||
|
||||
> You do not need to run commands to reproduce the vulnerability, just read the code to determine if it is a real vulnerability. Do not use the bash tool or write to any files.
|
||||
>
|
||||
> HARD EXCLUSIONS - Automatically exclude findings matching these patterns:
|
||||
>
|
||||
> 1. Denial of Service (DOS) vulnerabilities or resource exhaustion attacks.
|
||||
> 2. Secrets or credentials stored on disk if they are otherwise secured.
|
||||
> 3. Rate limiting concerns or service overload scenarios.
|
||||
> 4. Memory consumption or CPU exhaustion issues.
|
||||
> 5. Lack of input validation on non-security-critical fields without proven security impact.
|
||||
> 6. Input sanitization concerns for GitHub Action workflows unless they are clearly triggerable via untrusted input.
|
||||
> 7. A lack of hardening measures. Code is not expected to implement all security best practices, only flag concrete vulnerabilities.
|
||||
> 8. Race conditions or timing attacks that are theoretical rather than practical issues. Only report a race condition if it is concretely problematic.
|
||||
> 9. Vulnerabilities related to outdated third-party libraries. These are managed separately and should not be reported here.
|
||||
> 10. Memory safety issues such as buffer overflows or use-after-free-vulnerabilities are impossible in rust. Do not report memory safety issues in rust or any other memory safe languages.
|
||||
> 11. Files that are only unit tests or only used as part of running tests.
|
||||
> 12. Log spoofing concerns. Outputting un-sanitized user input to logs is not a vulnerability.
|
||||
> 13. SSRF vulnerabilities that only control the path. SSRF is only a concern if it can control the host or protocol.
|
||||
> 14. Including user-controlled content in AI system prompts is not a vulnerability.
|
||||
> 15. Regex injection. Injecting untrusted content into a regex is not a vulnerability.
|
||||
> 16. Regex DOS concerns.
|
||||
> 17. Insecure documentation. Do not report any findings in documentation files such as markdown files.
|
||||
> 18. A lack of audit logs is not a vulnerability.
|
||||
>
|
||||
> PRECEDENTS -
|
||||
>
|
||||
> 1. Logging high value secrets in plaintext is a vulnerability. Logging URLs is assumed to be safe.
|
||||
> 2. UUIDs can be assumed to be unguessable and do not need to be validated.
|
||||
> 3. Environment variables and CLI flags are trusted values. Attackers are generally not able to modify them in a secure environment. Any attack that relies on controlling an environment variable is invalid.
|
||||
> 4. Resource management issues such as memory or file descriptor leaks are not valid.
|
||||
> 5. Subtle or low impact web vulnerabilities such as tabnabbing, XS-Leaks, prototype pollution, and open redirects should not be reported unless they are extremely high confidence.
|
||||
> 6. React and Angular are generally secure against XSS. These frameworks do not need to sanitize or escape user input unless it is using dangerouslySetInnerHTML, bypassSecurityTrustHtml, or similar methods. Do not report XSS vulnerabilities in React or Angular components or tsx files unless they are using unsafe methods.
|
||||
> 7. Most vulnerabilities in github action workflows are not exploitable in practice. Before validating a github action workflow vulnerability ensure it is concrete and has a very specific attack path.
|
||||
> 8. A lack of permission checking or authentication in client-side JS/TS code is not a vulnerability. Client-side code is not trusted and does not need to implement these checks, they are handled on the server-side. The same applies to all flows that send untrusted data to the backend, the backend is responsible for validating and sanitizing all inputs.
|
||||
> 9. Only include MEDIUM findings if they are obvious and concrete issues.
|
||||
> 10. Most vulnerabilities in ipython notebooks (*.ipynb files) are not exploitable in practice. Before validating a notebook vulnerability ensure it is concrete and has a very specific attack path where untrusted input can trigger the vulnerability.
|
||||
> 11. Logging non-PII data is not a vulnerability even if the data may be sensitive. Only report logging vulnerabilities if they expose sensitive information such as secrets, passwords, or personally identifiable information (PII).
|
||||
> 12. Command injection vulnerabilities in shell scripts are generally not exploitable in practice since shell scripts generally do not run with untrusted user input. Only report command injection vulnerabilities in shell scripts if they are concrete and have a very specific attack path for untrusted input.
|
||||
>
|
||||
> SIGNAL QUALITY CRITERIA - For remaining findings, assess:
|
||||
>
|
||||
> 1. Is there a concrete, exploitable vulnerability with a clear attack path?
|
||||
> 2. Does this represent a real security risk vs theoretical best practice?
|
||||
> 3. Are there specific code locations and reproduction steps?
|
||||
> 4. Would this finding be actionable for a security team?
|
||||
>
|
||||
> For each finding, assign a confidence score from 1-10:
|
||||
>
|
||||
> - 1-3: Low confidence, likely false positive or noise
|
||||
> - 4-6: Medium confidence, needs investigation
|
||||
> - 7-10: High confidence, likely true vulnerability
|
||||
|
||||
PROJECT FALSE-POSITIVE GUIDANCE (Лидерра):
|
||||
|
||||
> This section is project-specific (Лидерра CRM — Laravel 13 + Vue 3 multi-tenant SaaS).
|
||||
> Apply it alongside the HARD EXCLUSIONS and PRECEDENTS above when filtering findings.
|
||||
>
|
||||
> EXPECTED — treat as NOT a finding:
|
||||
>
|
||||
> 1. Missing application-layer tenant checks where the table has PostgreSQL Row-Level
|
||||
> Security. Tenant isolation is enforced at the DB layer (`SET LOCAL
|
||||
> app.current_tenant_id` via the `SetTenantContext` middleware; 5 DB roles; 39 RLS
|
||||
> policies — see `docs/adr/ADR-002-multitenancy-postgres-rls.md`). DO still flag
|
||||
> queued jobs or code running as the `crm_supplier_worker` role (which is BYPASSRLS)
|
||||
> that read/write tenant-scoped tables WITHOUT an explicit `where('tenant_id', ...)`.
|
||||
> 2. The `tools/*.mjs` economy / ruflo hook scripts using `child_process.spawnSync`
|
||||
> or `process.env`. These are intentional local CLI hooks, not user-facing or
|
||||
> network-reachable code paths.
|
||||
> 3. Hardcoded-secret findings already covered by gitleaks (pre-commit + pre-push).
|
||||
> Do NOT re-report unless a NEW hardcoded credential is introduced by this diff.
|
||||
> 4. Test factories / seeders (`*Factory.php`, `*Seeder.php`) using `Faker` or
|
||||
> predictable values — test-only, per HARD EXCLUSION 11.
|
||||
>
|
||||
> PRIORITISE for this project:
|
||||
>
|
||||
> 1. HMAC / signature verification gaps on inbound webhooks (supplier lead intake).
|
||||
> 2. Signed-URL generation and validation (report file downloads, e.g. the reports
|
||||
> `/api/reports/jobs/{id}/file` endpoint).
|
||||
> 3. `auth:sanctum` + tenant middleware coverage on `/api/*` routes — a missing guard
|
||||
> is a cross-tenant data-leak vector (cf. the J1 / CTO-18 fix).
|
||||
> 4. Personal-data (ПДн) handling under 152-ФЗ — exposure of subject data in
|
||||
> responses, logs, or exports.
|
||||
> 5. Mass-assignment on Eloquent models (`$fillable` / `$guarded` gaps) reachable
|
||||
> from a request.
|
||||
|
||||
START ANALYSIS:
|
||||
|
||||
Begin your analysis now. Do this in 3 steps:
|
||||
|
||||
1. Use a sub-task to identify vulnerabilities. Use the repository exploration tools to understand the codebase context, then analyze the PR changes for security implications. In the prompt for this sub-task, include all of the above.
|
||||
2. Then for each vulnerability identified by the above sub-task, create a new sub-task to filter out false-positives. Launch these sub-tasks as parallel sub-tasks. In the prompt for these sub-tasks, include everything in the "FALSE POSITIVE FILTERING" instructions (including the "PROJECT FALSE-POSITIVE GUIDANCE (Лидерра)" block).
|
||||
3. Filter out any vulnerabilities where the sub-task reported a confidence less than 8.
|
||||
|
||||
Your final reply must contain the markdown report and nothing else.
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
name: audit-portal
|
||||
description: Запускать при полном аудите портала Лидерры — периодической сквозной проверке качества и безопасности (статанализ, тесты, схема БД, security, UI-smoke, a11y, coverage, bundle, pre-prod). Триггеры — «провести аудит портала», «полный аудит», «portal audit», подготовка к pre-prod или релизу.
|
||||
---
|
||||
|
||||
# Audit Portal — 14-фазный аудит портала
|
||||
|
||||
## Когда использовать
|
||||
|
||||
Периодический сквозной аудит всего портала Лидерры. Прецеденты — аудиты #1
|
||||
(2026-05-12), #2 (2026-05-13), #3 (2026-05-14). НЕ для точечной проверки одного
|
||||
файла или фичи — для этого прямой инструмент (`/regression`, `/security-review`,
|
||||
Pest).
|
||||
|
||||
## 14 фаз
|
||||
|
||||
Фазы последовательны; фаза 2 — 4 параллельных субагента. Каждая фаза пишет
|
||||
находки в `docs/superpowers/audits/<дата>-portal-full-audit-findings.md`, секция
|
||||
`## Phase N`. BLOCKED-пункты — в `<дата>-portal-full-audit-blocked.md`.
|
||||
|
||||
| # | Фаза | Инструмент |
|
||||
|---|---|---|
|
||||
| 1 | Pre-flight — ветка/HEAD, delta-коммиты, `composer`/`npm install`, skeleton-файлы аудита | git, composer, npm |
|
||||
| 2 | Статанализ — ×4 параллельных субагента | A backend: pint+stan+composer audit · B frontend: eslint+vue-tsc+prettier+knip · C docs: markdownlint+cspell+lychee · D SQL: squawk+pgFormatter |
|
||||
| 3 | Тестовые своды | Pest --parallel + sequential, Vitest, Histoire build, Vite build |
|
||||
| 4 | Целостность схемы — root tables, RLS-политики (инвариант 39), 5 user-функций поимённо, orphan-FK, header drift | Laravel Boost MCP (`database-query`) |
|
||||
| 5 | Security — перечислить CI-workflows ПЕРВЫМ, gitleaks delta + полная история + no-git | gitleaks, `ls .github/workflows/`, `/security-review` + Trail of Bits плагины |
|
||||
| 6 | UI-smoke — обход 24 маршрутов: рендер, 0 JS-ошибок, иконки | Playwright MCP |
|
||||
| 7 | Кросс-док целостность — версии нормативки, schema-маркер, `routes/web.php`, `.mcp.json` | Read, Grep, Select-String |
|
||||
| 8 | A11y — Pa11y на 4 guest-URL + axe-core на auth-views | Pa11y, axe-core через Playwright |
|
||||
| 9 | Coverage — Vitest --coverage, сверка с baseline | `@vitest/coverage-v8` |
|
||||
| 10 | Bundle — Vite build + анализ чанков vs baseline | `parse-bundle-analyze.mjs` |
|
||||
| 11 | Pre-prod + TODO-sweep — schedule, RUNBOOK, `.env.example` diff, Sentry SDK, TODO/FIXME | `artisan schedule:list`, `composer show`, Select-String |
|
||||
| 12 | Категоризация + fix-loop — rollup P0–P3; P0/P1 чинятся через TDD (failing test → fix → `test:parallel`) | Pest, Vitest, git |
|
||||
| 13 | Финальная регрессия | Pest --parallel, Vitest, Vite build, gitleaks, lychee |
|
||||
| 14 | Report + memory + push | Write, `git push` (pre-push: gitleaks-full-history + lychee) |
|
||||
|
||||
Нумерация — Audit #3 (самый свежий). Audit #2 использовал Phase 0–14 с иным
|
||||
порядком a11y / coverage / bundle; при расхождении — версия выше.
|
||||
|
||||
## Рубрика серьёзности
|
||||
|
||||
- **P0** — блокирует production / data corruption / security incident.
|
||||
- **P1** — нарушение функциональности / failing test / type error / a11y violation.
|
||||
- **P2** — warning / style / dead code / stale doc.
|
||||
- **P3** — cosmetic / nice-to-have.
|
||||
|
||||
Fix-eligibility: `[FIX-NOW]` — P0/P1, ≤30 мин, atomic-коммит на находку;
|
||||
`[FIX-DEFER]` — P2/P3, только запись в findings, без кода; `[BLOCKED]` — нужно
|
||||
явное «закрываем» от заказчика → `blocked.md` (категории Q.HARD / Q.PRODUCT /
|
||||
Q.DEFER / Q.INFO).
|
||||
|
||||
## Методология
|
||||
|
||||
- Каждая фаза завершается `git commit` находок. После каждых 3 коммитов —
|
||||
self-review §8 (метрики схемы, версии нормативки).
|
||||
- Регрессия в фазе 12/13 → `systematic-debugging` (≥3 гипотезы) → rollback или
|
||||
forward-fix → перепрогон фазы.
|
||||
- Hard-stop'ы decision-tree: не менять `db/schema.sql`, не закрывать
|
||||
Б-/CTO-/Ю-/Диз-/DO-/OPEN- без явного «закрываем», не ставить пакеты, не
|
||||
править корневой `CLAUDE.md` напрямую, не делать force-push.
|
||||
- BLOCKED-находка, требующая решения владельца → в реестр `Открытые_вопросы`
|
||||
через скил `q-item-add`.
|
||||
|
||||
## Не использовать когда
|
||||
|
||||
- Нужна одна проверка (тест / lint / security одного диффа) — прямой инструмент
|
||||
или `/regression quick`.
|
||||
- Точечный security-review диффа ветки — `/security-review` напрямую.
|
||||
@@ -0,0 +1,7 @@
|
||||
Copyright 2026 WH-2099
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
name: mermaid
|
||||
description: Generate Mermaid diagrams from user requirements. Supports flowcharts, sequence diagrams, class diagrams, ER diagrams, Gantt charts, and 18 more diagram types.
|
||||
allowed-tools: Read Write Edit
|
||||
metadata:
|
||||
argument-hint: "[diagram description or requirements]"
|
||||
---
|
||||
|
||||
# Mermaid Diagram Generator
|
||||
|
||||
Generate high-quality Mermaid diagram code based on user requirements.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Understand Requirements**: Analyze user description to determine the most suitable diagram type
|
||||
2. **Read Documentation**: Read the corresponding syntax reference for the diagram type
|
||||
3. **Generate Code**: Generate Mermaid code following the specification
|
||||
4. **Apply Styling**: Apply appropriate themes and style configurations
|
||||
|
||||
## Diagram Type Reference
|
||||
|
||||
Select the appropriate diagram type and read the corresponding documentation:
|
||||
|
||||
| Type | Documentation | Use Cases |
|
||||
| ---- | ------------- | --------- |
|
||||
| Flowchart | [flowchart.md](references/flowchart.md) | Processes, decisions, steps |
|
||||
| Sequence Diagram | [sequenceDiagram.md](references/sequenceDiagram.md) | Interactions, messaging, API calls |
|
||||
| Class Diagram | [classDiagram.md](references/classDiagram.md) | Class structure, inheritance, associations |
|
||||
| State Diagram | [stateDiagram.md](references/stateDiagram.md) | State machines, state transitions |
|
||||
| ER Diagram | [entityRelationshipDiagram.md](references/entityRelationshipDiagram.md) | Database design, entity relationships |
|
||||
| Gantt Chart | [gantt.md](references/gantt.md) | Project planning, timelines |
|
||||
| Pie Chart | [pie.md](references/pie.md) | Proportions, distributions |
|
||||
| Mindmap | [mindmap.md](references/mindmap.md) | Hierarchical structures, knowledge graphs |
|
||||
| Timeline | [timeline.md](references/timeline.md) | Historical events, milestones |
|
||||
| Git Graph | [gitgraph.md](references/gitgraph.md) | Branches, merges, versions |
|
||||
| Quadrant Chart | [quadrantChart.md](references/quadrantChart.md) | Four-quadrant analysis |
|
||||
| Requirement Diagram | [requirementDiagram.md](references/requirementDiagram.md) | Requirements traceability |
|
||||
| C4 Diagram | [c4.md](references/c4.md) | System architecture (C4 model) |
|
||||
| Sankey Diagram | [sankey.md](references/sankey.md) | Flow, conversions |
|
||||
| XY Chart | [xyChart.md](references/xyChart.md) | Line charts, bar charts |
|
||||
| Block Diagram | [block.md](references/block.md) | System components, modules |
|
||||
| Packet Diagram | [packet.md](references/packet.md) | Network protocols, data structures |
|
||||
| Kanban | [kanban.md](references/kanban.md) | Task management, workflows |
|
||||
| Architecture Diagram | [architecture.md](references/architecture.md) | System architecture |
|
||||
| Radar Chart | [radar.md](references/radar.md) | Multi-dimensional comparison |
|
||||
| Treemap | [treemap.md](references/treemap.md) | Hierarchical data visualization |
|
||||
| User Journey | [userJourney.md](references/userJourney.md) | User experience flows |
|
||||
| ZenUML | [zenuml.md](references/zenuml.md) | Sequence diagrams (code style) |
|
||||
|
||||
## Configuration & Themes
|
||||
|
||||
- [Theming](references/config-theming.md) - Custom colors and styles
|
||||
- [Directives](references/config-directives.md) - Diagram-level configuration
|
||||
- [Layouts](references/config-layouts.md) - Layout direction and spacing
|
||||
- [Configuration](references/config-configuration.md) - Global settings
|
||||
- [Math](references/config-math.md) - LaTeX math support
|
||||
|
||||
## Output Specification
|
||||
|
||||
Generated Mermaid code should:
|
||||
|
||||
1. Be wrapped in ```mermaid code blocks
|
||||
2. Have correct syntax that renders directly
|
||||
3. Have clear structure with proper line breaks and indentation
|
||||
4. Use semantic node naming
|
||||
5. Include styling when needed to improve visual appearance
|
||||
|
||||
## Example Output
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Start] --> B{Condition}
|
||||
B -->|Yes| C[Execute]
|
||||
B -->|No| D[End]
|
||||
C --> D
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
User requirements: $ARGUMENTS
|
||||
@@ -0,0 +1,227 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/architecture.md](../../packages/mermaid/src/docs/syntax/architecture.md).
|
||||
|
||||
# Architecture Diagrams Documentation (v11.1.0+)
|
||||
|
||||
> In the context of mermaid-js, the architecture diagram is used to show the relationship between services and resources commonly found within the Cloud or CI/CD deployments. In an architecture diagram, services (nodes) are connected by edges. Related services can be placed within groups to better illustrate how they are organized.
|
||||
|
||||
## Example
|
||||
|
||||
```mermaid-example
|
||||
architecture-beta
|
||||
group api(cloud)[API]
|
||||
|
||||
service db(database)[Database] in api
|
||||
service disk1(disk)[Storage] in api
|
||||
service disk2(disk)[Storage] in api
|
||||
service server(server)[Server] in api
|
||||
|
||||
db:L -- R:server
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db
|
||||
```
|
||||
|
||||
```mermaid
|
||||
architecture-beta
|
||||
group api(cloud)[API]
|
||||
|
||||
service db(database)[Database] in api
|
||||
service disk1(disk)[Storage] in api
|
||||
service disk2(disk)[Storage] in api
|
||||
service server(server)[Server] in api
|
||||
|
||||
db:L -- R:server
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
The building blocks of an architecture are `groups`, `services`, `edges`, and `junctions`.
|
||||
|
||||
For supporting components, icons are declared by surrounding the icon name with `()`, while labels are declared by surrounding the text with `[]`.
|
||||
|
||||
To begin an architecture diagram, use the keyword `architecture-beta`, followed by your groups, services, edges, and junctions. While each of the 3 building blocks can be declared in any order, care must be taken to ensure the identifier was previously declared by another component.
|
||||
|
||||
### Groups
|
||||
|
||||
The syntax for declaring a group is:
|
||||
|
||||
```
|
||||
group {group id}({icon name})[{title}] (in {parent id})?
|
||||
```
|
||||
|
||||
Put together:
|
||||
|
||||
```
|
||||
group public_api(cloud)[Public API]
|
||||
```
|
||||
|
||||
creates a group identified as `public_api`, uses the icon `cloud`, and has the label `Public API`.
|
||||
|
||||
Additionally, groups can be placed within a group using the optional `in` keyword
|
||||
|
||||
```
|
||||
group private_api(cloud)[Private API] in public_api
|
||||
```
|
||||
|
||||
### Services
|
||||
|
||||
The syntax for declaring a service is:
|
||||
|
||||
```
|
||||
service {service id}({icon name})[{title}] (in {parent id})?
|
||||
```
|
||||
|
||||
Put together:
|
||||
|
||||
```
|
||||
service database1(database)[My Database]
|
||||
```
|
||||
|
||||
creates the service identified as `database1`, using the icon `database`, with the label `My Database`.
|
||||
|
||||
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
|
||||
|
||||
```
|
||||
service database1(database)[My Database] in private_api
|
||||
```
|
||||
|
||||
### Edges
|
||||
|
||||
The syntax for declaring an edge is:
|
||||
|
||||
```
|
||||
{serviceId}{{group}}?:{T|B|L|R} {<}?--{>}? {T|B|L|R}:{serviceId}{{group}}?
|
||||
```
|
||||
|
||||
#### Edge Direction
|
||||
|
||||
The side of the service the edge comes out of is specified by adding a colon (`:`) to the side of the service connecting to the arrow and adding `L|R|T|B`
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
db:R -- L:server
|
||||
```
|
||||
|
||||
creates an edge between the services `db` and `server`, with the edge coming out of the right of `db` and the left of `server`.
|
||||
|
||||
```
|
||||
db:T -- L:server
|
||||
```
|
||||
|
||||
creates a 90 degree edge between the services `db` and `server`, with the edge coming out of the top of `db` and the left of `server`.
|
||||
|
||||
#### Arrows
|
||||
|
||||
Arrows can be added to each side of an edge by adding `<` before the direction on the left, and/or `>` after the direction on the right.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
subnet:R --> L:gateway
|
||||
```
|
||||
|
||||
creates an edge with the arrow going into the `gateway` service
|
||||
|
||||
#### Edges out of Groups
|
||||
|
||||
To have an edge go from a group to another group or service within another group, the `{group}` modifier can be added after the `serviceId`.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
service server[Server] in groupOne
|
||||
service subnet[Subnet] in groupTwo
|
||||
|
||||
server{group}:B --> T:subnet{group}
|
||||
```
|
||||
|
||||
creates an edge going out of `groupOne`, adjacent to `server`, and into `groupTwo`, adjacent to `subnet`.
|
||||
|
||||
It's important to note that `groupId`s cannot be used for specifying edges and the `{group}` modifier can only be used for services within a group.
|
||||
|
||||
### Junctions
|
||||
|
||||
Junctions are a special type of node which acts as a potential 4-way split between edges.
|
||||
|
||||
The syntax for declaring a junction is:
|
||||
|
||||
```
|
||||
junction {junction id} (in {parent id})?
|
||||
```
|
||||
|
||||
```mermaid-example
|
||||
architecture-beta
|
||||
service left_disk(disk)[Disk]
|
||||
service top_disk(disk)[Disk]
|
||||
service bottom_disk(disk)[Disk]
|
||||
service top_gateway(internet)[Gateway]
|
||||
service bottom_gateway(internet)[Gateway]
|
||||
junction junctionCenter
|
||||
junction junctionRight
|
||||
|
||||
left_disk:R -- L:junctionCenter
|
||||
top_disk:B -- T:junctionCenter
|
||||
bottom_disk:T -- B:junctionCenter
|
||||
junctionCenter:R -- L:junctionRight
|
||||
top_gateway:B -- T:junctionRight
|
||||
bottom_gateway:T -- B:junctionRight
|
||||
```
|
||||
|
||||
```mermaid
|
||||
architecture-beta
|
||||
service left_disk(disk)[Disk]
|
||||
service top_disk(disk)[Disk]
|
||||
service bottom_disk(disk)[Disk]
|
||||
service top_gateway(internet)[Gateway]
|
||||
service bottom_gateway(internet)[Gateway]
|
||||
junction junctionCenter
|
||||
junction junctionRight
|
||||
|
||||
left_disk:R -- L:junctionCenter
|
||||
top_disk:B -- T:junctionCenter
|
||||
bottom_disk:T -- B:junctionCenter
|
||||
junctionCenter:R -- L:junctionRight
|
||||
top_gateway:B -- T:junctionRight
|
||||
bottom_gateway:T -- B:junctionRight
|
||||
```
|
||||
|
||||
## Icons
|
||||
|
||||
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
|
||||
Users can use any of the 200,000+ icons available in iconify.design, or add other custom icons, by [registering an icon pack](../config/icons.md).
|
||||
|
||||
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.
|
||||
|
||||
```mermaid-example
|
||||
architecture-beta
|
||||
group api(logos:aws-lambda)[API]
|
||||
|
||||
service db(logos:aws-aurora)[Database] in api
|
||||
service disk1(logos:aws-glacier)[Storage] in api
|
||||
service disk2(logos:aws-s3)[Storage] in api
|
||||
service server(logos:aws-ec2)[Server] in api
|
||||
|
||||
db:L -- R:server
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db
|
||||
```
|
||||
|
||||
```mermaid
|
||||
architecture-beta
|
||||
group api(logos:aws-lambda)[API]
|
||||
|
||||
service db(logos:aws-aurora)[Database] in api
|
||||
service disk1(logos:aws-glacier)[Storage] in api
|
||||
service disk2(logos:aws-s3)[Storage] in api
|
||||
service server(logos:aws-ec2)[Server] in api
|
||||
|
||||
db:L -- R:server
|
||||
disk1:T -- B:server
|
||||
disk2:T -- B:db
|
||||
```
|
||||
@@ -0,0 +1,753 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/block.md](../../packages/mermaid/src/docs/syntax/block.md).
|
||||
|
||||
# Block Diagrams Documentation
|
||||
|
||||
## Introduction to Block Diagrams
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
block:ID
|
||||
A
|
||||
B["A wide one in the middle"]
|
||||
C
|
||||
end
|
||||
space
|
||||
D
|
||||
ID --> D
|
||||
C --> D
|
||||
style B fill:#969,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
block:ID
|
||||
A
|
||||
B["A wide one in the middle"]
|
||||
C
|
||||
end
|
||||
space
|
||||
D
|
||||
ID --> D
|
||||
C --> D
|
||||
style B fill:#969,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
### Definition and Purpose
|
||||
|
||||
Block diagrams are an intuitive and efficient way to represent complex systems, processes, or architectures visually. They are composed of blocks and connectors, where blocks represent the fundamental components or functions, and connectors show the relationship or flow between these components. This method of diagramming is essential in various fields such as engineering, software development, and process management.
|
||||
|
||||
The primary purpose of block diagrams is to provide a high-level view of a system, allowing for easy understanding and analysis without delving into the intricate details of each component. This makes them particularly useful for simplifying complex systems and for explaining the overall structure and interaction of components within a system.
|
||||
|
||||
Many people use mermaid flowcharts for this purpose. A side-effect of this is that the automatic layout sometimes move shapes to positions that the diagram maker does not want. Block diagrams use a different approach. In this diagram we give the author full control over where the shapes are positioned.
|
||||
|
||||
### General Use Cases
|
||||
|
||||
Block diagrams have a wide range of applications across various industries and disciplines. Some of the key use cases include:
|
||||
|
||||
- **Software Architecture**: In software development, block diagrams can be used to illustrate the architecture of a software application. This includes showing how different modules or services interact, data flow, and high-level component interaction.
|
||||
|
||||
- **Network Diagrams**: Block diagrams are ideal for representing network architectures in IT and telecommunications. They can depict how different network devices and services are interconnected, including routers, switches, firewalls, and the flow of data across the network.
|
||||
|
||||
- **Process Flowcharts**: In business and manufacturing, block diagrams can be employed to create process flowcharts. These flowcharts represent various stages of a business or manufacturing process, helping to visualize the sequence of steps, decision points, and the flow of control.
|
||||
|
||||
- **Electrical Systems**: Engineers use block diagrams to represent electrical systems and circuitry. They can illustrate the high-level structure of an electrical system, the interaction between different electrical components, and the flow of electrical currents.
|
||||
|
||||
- **Educational Purposes**: Block diagrams are also extensively used in educational materials to explain complex concepts and systems in a simplified manner. They help in breaking down and visualizing scientific theories, engineering principles, and technological systems.
|
||||
|
||||
These examples demonstrate the versatility of block diagrams in providing clear and concise representations of complex systems. Their simplicity and clarity make them a valuable tool for professionals across various fields to communicate complex ideas effectively.
|
||||
|
||||
In the following sections, we will delve into the specifics of creating and manipulating block diagrams using Mermaid, covering everything from basic syntax to advanced configurations and styling.
|
||||
|
||||
Creating block diagrams with Mermaid is straightforward and accessible. This section introduces the basic syntax and structure needed to start building simple diagrams. Understanding these foundational concepts is key to efficiently utilizing Mermaid for more complex diagramming tasks.
|
||||
|
||||
### Simple Block Diagrams
|
||||
|
||||
#### Basic Structure
|
||||
|
||||
At its core, a block diagram consists of blocks representing different entities or components. In Mermaid, these blocks are easily created using simple text labels. The most basic form of a block diagram can be a series of blocks without any connectors.
|
||||
|
||||
**Example - Simple Block Diagram**:
|
||||
To create a simple block diagram with three blocks labeled 'a', 'b', and 'c', the syntax is as follows:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
a b c
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
a b c
|
||||
```
|
||||
|
||||
This example will produce a horizontal sequence of three blocks. Each block is automatically spaced and aligned for optimal readability.
|
||||
|
||||
### Defining the number of columns to use
|
||||
|
||||
#### Column Usage
|
||||
|
||||
While simple block diagrams are linear and straightforward, more complex systems may require a structured layout. Mermaid allows for the organization of blocks into multiple columns, facilitating the creation of more intricate and detailed diagrams.
|
||||
|
||||
**Example - Multi-Column Diagram:**
|
||||
In scenarios where you need to distribute blocks across multiple columns, you can specify the number of columns and arrange the blocks accordingly. Here's how to create a block diagram with three columns and four blocks, where the fourth block appears in a second row:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
columns 3
|
||||
a b c d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
columns 3
|
||||
a b c d
|
||||
```
|
||||
|
||||
This syntax instructs Mermaid to arrange the blocks 'a', 'b', 'c', and 'd' across three columns, wrapping to the next row as needed. This feature is particularly useful for representing layered or multi-tiered systems, such as network layers or hierarchical structures.
|
||||
|
||||
These basic building blocks of Mermaid's block diagrams provide a foundation for more complex diagramming. The simplicity of the syntax allows for quick creation and iteration of diagrams, making it an efficient tool for visualizing ideas and concepts. In the next section, we'll explore advanced block configuration options, including setting block widths and creating composite blocks.
|
||||
|
||||
## 3. Advanced Block Configuration
|
||||
|
||||
Building upon the basics, this section delves into more advanced features of block diagramming in Mermaid. These features allow for greater flexibility and complexity in diagram design, accommodating a wider range of use cases and scenarios.
|
||||
|
||||
### Setting Block Width
|
||||
|
||||
#### Spanning Multiple Columns
|
||||
|
||||
In more complex diagrams, you may need blocks that span multiple columns to emphasize certain components or to represent larger entities. Mermaid allows for the adjustment of block widths to cover multiple columns, enhancing the diagram's readability and structure.
|
||||
|
||||
**Example - Block Spanning Multiple Columns**:
|
||||
To create a block diagram where one block spans across two columns, you can specify the desired width for each block:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
columns 3
|
||||
a["A label"] b:2 c:2 d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
columns 3
|
||||
a["A label"] b:2 c:2 d
|
||||
```
|
||||
|
||||
In this example, the block labeled "A labels" spans one column, while blocks 'b', 'c' span 2 columns, and 'd' is again allocated its own column. This flexibility in block sizing is crucial for accurately representing systems with components of varying significance or size.
|
||||
|
||||
### Creating Composite Blocks
|
||||
|
||||
#### Nested Blocks
|
||||
|
||||
Composite blocks, or blocks within blocks, are an advanced feature in Mermaid's block diagram syntax. They allow for the representation of nested or hierarchical systems, where one component encompasses several subcomponents.
|
||||
|
||||
**Example - Composite Blocks:**
|
||||
Creating a composite block involves defining a parent block and then nesting other blocks within it. Here's how to define a composite block with nested elements:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block
|
||||
D
|
||||
end
|
||||
A["A: I am a wide one"]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block
|
||||
D
|
||||
end
|
||||
A["A: I am a wide one"]
|
||||
```
|
||||
|
||||
In this syntax, 'D' is a nested block within a larger parent block. This feature is particularly useful for depicting complex structures, such as a server with multiple services or a department within a larger organizational framework.
|
||||
|
||||
### Column Width Dynamics
|
||||
|
||||
#### Adjusting Widths
|
||||
|
||||
Mermaid also allows for dynamic adjustment of column widths based on the content of the blocks. The width of the columns is determined by the widest block in the column, ensuring that the diagram remains balanced and readable.
|
||||
|
||||
**Example - Dynamic Column Widths:**
|
||||
In diagrams with varying block sizes, Mermaid automatically adjusts the column widths to fit the largest block in each column. Here's an example:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
columns 3
|
||||
a:3
|
||||
block:group1:2
|
||||
columns 2
|
||||
h i j k
|
||||
end
|
||||
g
|
||||
block:group2:3
|
||||
%% columns auto (default)
|
||||
l m n o p q r
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
columns 3
|
||||
a:3
|
||||
block:group1:2
|
||||
columns 2
|
||||
h i j k
|
||||
end
|
||||
g
|
||||
block:group2:3
|
||||
%% columns auto (default)
|
||||
l m n o p q r
|
||||
end
|
||||
```
|
||||
|
||||
This example demonstrates how Mermaid dynamically adjusts the width of the columns to accommodate the widest block, in this case, 'a' and the composite block 'e'. This dynamic adjustment is essential for creating visually balanced and easy-to-understand diagrams.
|
||||
|
||||
**Merging Blocks Horizontally:**
|
||||
In scenarios where you need to stack blocks horizontally, you can use column width to accomplish the task. Blocks can be arranged vertically by putting them in a single column. Here is how you can create a block diagram in which 4 blocks are stacked on top of each other:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
block
|
||||
columns 1
|
||||
a["A label"] b c d
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
block
|
||||
columns 1
|
||||
a["A label"] b c d
|
||||
end
|
||||
```
|
||||
|
||||
In this example, the width of the merged block dynamically adjusts to the width of the largest child block.
|
||||
|
||||
With these advanced configuration options, Mermaid's block diagrams can be tailored to represent a wide array of complex systems and structures. The flexibility offered by these features enables users to create diagrams that are both informative and visually appealing. In the following sections, we will explore further capabilities, including different block shapes and linking options.
|
||||
|
||||
## 4. Block Varieties and Shapes
|
||||
|
||||
Mermaid's block diagrams are not limited to standard rectangular shapes. A variety of block shapes are available, allowing for a more nuanced and tailored representation of different types of information or entities. This section outlines the different block shapes you can use in Mermaid and their specific applications.
|
||||
|
||||
### Standard and Special Block Shapes
|
||||
|
||||
Mermaid supports a range of block shapes to suit different diagramming needs, from basic geometric shapes to more specialized forms.
|
||||
|
||||
#### Example - Round Edged Block
|
||||
|
||||
To create a block with round edges, which can be used to represent a softer or more flexible component:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1("This is the text in the box")
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1("This is the text in the box")
|
||||
```
|
||||
|
||||
#### Example - Stadium-Shaped Block
|
||||
|
||||
A stadium-shaped block, resembling an elongated circle, can be used for components that are process-oriented:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1(["This is the text in the box"])
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1(["This is the text in the box"])
|
||||
```
|
||||
|
||||
#### Example - Subroutine Shape
|
||||
|
||||
For representing subroutines or contained processes, a block with double vertical lines is useful:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1[["This is the text in the box"]]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1[["This is the text in the box"]]
|
||||
```
|
||||
|
||||
#### Example - Cylindrical Shape
|
||||
|
||||
The cylindrical shape is ideal for representing databases or storage components:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1[("Database")]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1[("Database")]
|
||||
```
|
||||
|
||||
#### Example - Circle Shape
|
||||
|
||||
A circle can be used for centralized or pivotal components:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1(("This is the text in the circle"))
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1(("This is the text in the circle"))
|
||||
```
|
||||
|
||||
#### Example - Asymmetric, Rhombus, and Hexagon Shapes
|
||||
|
||||
For decision points, use a rhombus, and for unique or specialized processes, asymmetric and hexagon shapes can be utilized:
|
||||
|
||||
**Asymmetric**
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1>"This is the text in the box"]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1>"This is the text in the box"]
|
||||
```
|
||||
|
||||
**Rhombus**
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1{"This is the text in the box"}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1{"This is the text in the box"}
|
||||
```
|
||||
|
||||
**Hexagon**
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1{{"This is the text in the box"}}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1{{"This is the text in the box"}}
|
||||
```
|
||||
|
||||
#### Example - Parallelogram and Trapezoid Shapes
|
||||
|
||||
Parallelogram and trapezoid shapes are perfect for inputs/outputs and transitional processes:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1[/"This is the text in the box"/]
|
||||
id2[\"This is the text in the box"\]
|
||||
A[/"Christmas"\]
|
||||
B[\"Go shopping"/]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1[/"This is the text in the box"/]
|
||||
id2[\"This is the text in the box"\]
|
||||
A[/"Christmas"\]
|
||||
B[\"Go shopping"/]
|
||||
```
|
||||
|
||||
#### Example - Double Circle
|
||||
|
||||
For highlighting critical or high-priority components, a double circle can be effective:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1((("This is the text in the circle")))
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1((("This is the text in the circle")))
|
||||
```
|
||||
|
||||
### Block Arrows and Space Blocks
|
||||
|
||||
Mermaid also offers unique shapes like block arrows and space blocks for directional flow and spacing.
|
||||
|
||||
#### Example - Block Arrows
|
||||
|
||||
Block arrows can visually indicate direction or flow within a process:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
blockArrowId<["Label"]>(right)
|
||||
blockArrowId2<["Label"]>(left)
|
||||
blockArrowId3<["Label"]>(up)
|
||||
blockArrowId4<["Label"]>(down)
|
||||
blockArrowId5<["Label"]>(x)
|
||||
blockArrowId6<["Label"]>(y)
|
||||
blockArrowId7<["Label"]>(x, down)
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
blockArrowId<["Label"]>(right)
|
||||
blockArrowId2<["Label"]>(left)
|
||||
blockArrowId3<["Label"]>(up)
|
||||
blockArrowId4<["Label"]>(down)
|
||||
blockArrowId5<["Label"]>(x)
|
||||
blockArrowId6<["Label"]>(y)
|
||||
blockArrowId7<["Label"]>(x, down)
|
||||
```
|
||||
|
||||
#### Example - Space Blocks
|
||||
|
||||
Space blocks can be used to create intentional empty spaces in the diagram, which is useful for layout and readability:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
columns 3
|
||||
a space b
|
||||
c d e
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
columns 3
|
||||
a space b
|
||||
c d e
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
ida space:3 idb idc
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
ida space:3 idb idc
|
||||
```
|
||||
|
||||
Note that you can set how many columns the space block occupied using the number notation `space:num` where num is a number indicating the num columns width. You can also use `space` which defaults to one column.
|
||||
|
||||
The variety of shapes and special blocks in Mermaid enhances the expressive power of block diagrams, allowing for more accurate and context-specific representations. These options give users the flexibility to create diagrams that are both informative and visually appealing. In the next sections, we will explore the ways to connect these blocks and customize their appearance.
|
||||
|
||||
### Standard and Special Block Shapes
|
||||
|
||||
Discuss the various shapes available for blocks, including standard shapes and special forms like block arrows and space blocks.
|
||||
|
||||
## 5. Connecting Blocks with Edges
|
||||
|
||||
One of the key features of block diagrams in Mermaid is the ability to connect blocks using various types of edges or links. This section explores the different ways blocks can be interconnected to represent relationships and flows between components.
|
||||
|
||||
### Basic Linking and Arrow Types
|
||||
|
||||
The most fundamental aspect of connecting blocks is the use of arrows or links. These connectors depict the relationships or the flow of information between the blocks. Mermaid offers a range of arrow types to suit different diagramming needs.
|
||||
|
||||
**Example - Basic Links**
|
||||
|
||||
A simple link with an arrow can be created to show direction or flow from one block to another:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
A space B
|
||||
A-->B
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
A space B
|
||||
A-->B
|
||||
```
|
||||
|
||||
This example illustrates a direct connection from block 'A' to block 'B', using a straightforward arrow.
|
||||
|
||||
This syntax creates a line connecting 'A' and 'B', implying a relationship or connection without indicating a specific direction.
|
||||
|
||||
### Text on Links
|
||||
|
||||
In addition to connecting blocks, it's often necessary to describe or label the relationship. Mermaid allows for the inclusion of text on links, providing context to the connections.
|
||||
|
||||
Example - Text with Links
|
||||
To add text to a link, the syntax includes the text within the link definition:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
A space:2 B
|
||||
A-- "X" -->B
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
A space:2 B
|
||||
A-- "X" -->B
|
||||
```
|
||||
|
||||
This example show how to add descriptive text to the links, enhancing the information conveyed by the diagram.
|
||||
|
||||
Example - Edges and Styles:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
block:ID
|
||||
A
|
||||
B["A wide one in the middle"]
|
||||
C
|
||||
end
|
||||
space
|
||||
D
|
||||
ID --> D
|
||||
C --> D
|
||||
style B fill:#939,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
columns 1
|
||||
db(("DB"))
|
||||
blockArrowId6<[" "]>(down)
|
||||
block:ID
|
||||
A
|
||||
B["A wide one in the middle"]
|
||||
C
|
||||
end
|
||||
space
|
||||
D
|
||||
ID --> D
|
||||
C --> D
|
||||
style B fill:#939,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
## 6. Styling and Customization
|
||||
|
||||
Beyond the structure and layout of block diagrams, Mermaid offers extensive styling options. These customization features allow for the creation of more visually distinctive and informative diagrams. This section covers how to apply individual styles to blocks and how to use classes for consistent styling across multiple elements.
|
||||
|
||||
### Individual Block Styling
|
||||
|
||||
Mermaid enables detailed styling of individual blocks, allowing you to apply various CSS properties such as color, stroke, and border thickness. This feature is especially useful for highlighting specific parts of a diagram or for adhering to certain visual themes.
|
||||
|
||||
#### Example - Styling a Single Block
|
||||
|
||||
To apply custom styles to a block, you can use the `style` keyword followed by the block identifier and the desired CSS properties:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
id1 space id2
|
||||
id1("Start")-->id2("Stop")
|
||||
style id1 fill:#636,stroke:#333,stroke-width:4px
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
id1 space id2
|
||||
id1("Start")-->id2("Stop")
|
||||
style id1 fill:#636,stroke:#333,stroke-width:4px
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
```
|
||||
|
||||
### Class Styling
|
||||
|
||||
Mermaid enables applying styling to classes, which could make styling easier if you want to apply a certain set of styles to multiple elements, as you could just link those elements to a class.
|
||||
|
||||
#### Example - Styling a Single Class
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
A space B
|
||||
A-->B
|
||||
classDef blue fill:#6e6ce6,stroke:#333,stroke-width:4px;
|
||||
class A blue
|
||||
style B fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
A space B
|
||||
A-->B
|
||||
classDef blue fill:#6e6ce6,stroke:#333,stroke-width:4px;
|
||||
class A blue
|
||||
style B fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
```
|
||||
|
||||
In this example, a class named 'blue' is defined and applied to block 'A', while block 'B' receives individual styling. This demonstrates the flexibility of Mermaid in applying both shared and unique styles within the same diagram.
|
||||
|
||||
The ability to style blocks individually or through classes provides a powerful tool for enhancing the visual impact and clarity of block diagrams. Whether emphasizing certain elements or maintaining a cohesive design across the diagram, these styling capabilities are central to effective diagramming. The next sections will present practical examples and use cases, followed by tips for troubleshooting common issues.
|
||||
|
||||
### 7. Practical Examples and Use Cases
|
||||
|
||||
The versatility of Mermaid's block diagrams becomes evident when applied to real-world scenarios. This section provides practical examples demonstrating the application of various features discussed in previous sections. These examples showcase how block diagrams can be used to represent complex systems and processes in an accessible and informative manner.
|
||||
|
||||
### Detailed Examples Illustrating Various Features
|
||||
|
||||
Combining the elements of structure, linking, and styling, we can create comprehensive diagrams that serve specific purposes in different contexts.
|
||||
|
||||
#### Example - System Architecture
|
||||
|
||||
Illustrating a simple software system architecture with interconnected components:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
columns 3
|
||||
Frontend blockArrowId6<[" "]>(right) Backend
|
||||
space:2 down<[" "]>(down)
|
||||
Disk left<[" "]>(left) Database[("Database")]
|
||||
|
||||
classDef front fill:#696,stroke:#333;
|
||||
classDef back fill:#969,stroke:#333;
|
||||
class Frontend front
|
||||
class Backend,Database back
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
columns 3
|
||||
Frontend blockArrowId6<[" "]>(right) Backend
|
||||
space:2 down<[" "]>(down)
|
||||
Disk left<[" "]>(left) Database[("Database")]
|
||||
|
||||
classDef front fill:#696,stroke:#333;
|
||||
classDef back fill:#969,stroke:#333;
|
||||
class Frontend front
|
||||
class Backend,Database back
|
||||
```
|
||||
|
||||
This example shows a basic architecture with a frontend, backend, and database. The blocks are styled to differentiate between types of components.
|
||||
|
||||
#### Example - Business Process Flow
|
||||
|
||||
Representing a business process flow with decision points and multiple stages:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
columns 3
|
||||
Start(("Start")) space:2
|
||||
down<[" "]>(down) space:2
|
||||
Decision{{"Make Decision"}} right<["Yes"]>(right) Process1["Process A"]
|
||||
downAgain<["No"]>(down) space r3<["Done"]>(down)
|
||||
Process2["Process B"] r2<["Done"]>(right) End(("End"))
|
||||
|
||||
style Start fill:#969;
|
||||
style End fill:#696;
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
columns 3
|
||||
Start(("Start")) space:2
|
||||
down<[" "]>(down) space:2
|
||||
Decision{{"Make Decision"}} right<["Yes"]>(right) Process1["Process A"]
|
||||
downAgain<["No"]>(down) space r3<["Done"]>(down)
|
||||
Process2["Process B"] r2<["Done"]>(right) End(("End"))
|
||||
|
||||
style Start fill:#969;
|
||||
style End fill:#696;
|
||||
```
|
||||
|
||||
These practical examples and scenarios underscore the utility of Mermaid block diagrams in simplifying and effectively communicating complex information across various domains.
|
||||
|
||||
The next section, 'Troubleshooting and Common Issues', will provide insights into resolving common challenges encountered when working with Mermaid block diagrams, ensuring a smooth diagramming experience.
|
||||
|
||||
## 8. Troubleshooting and Common Issues
|
||||
|
||||
Working with Mermaid block diagrams can sometimes present challenges, especially as the complexity of the diagrams increases. This section aims to provide guidance on resolving common issues and offers tips for managing more intricate diagram structures.
|
||||
|
||||
### Common Syntax Errors
|
||||
|
||||
Understanding and avoiding common syntax errors is key to a smooth experience with Mermaid diagrams.
|
||||
|
||||
#### Example - Incorrect Linking
|
||||
|
||||
A common mistake is incorrect linking syntax, which can lead to unexpected results or broken diagrams:
|
||||
|
||||
```
|
||||
block
|
||||
A - B
|
||||
```
|
||||
|
||||
**Correction**:
|
||||
Ensure that links between blocks are correctly specified with arrows (--> or ---) to define the direction and type of connection. Also remember that one of the fundamentals for block diagram is to give the author full control of where the boxes are positioned so in the example you need to add a space between the boxes:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
A space B
|
||||
A --> B
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
A space B
|
||||
A --> B
|
||||
```
|
||||
|
||||
#### Example - Misplaced Styling
|
||||
|
||||
Applying styles in the wrong context or with incorrect syntax can lead to blocks not being styled as intended:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
A
|
||||
style A fill#969;
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
A
|
||||
style A fill#969;
|
||||
```
|
||||
|
||||
**Correction:**
|
||||
Correct the syntax by ensuring proper separation of style properties with commas and using the correct CSS property format:
|
||||
|
||||
```mermaid-example
|
||||
block
|
||||
A
|
||||
style A fill:#969,stroke:#333;
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
block
|
||||
A
|
||||
style A fill:#969,stroke:#333;
|
||||
|
||||
```
|
||||
|
||||
### Tips for Complex Diagram Structures
|
||||
|
||||
Managing complexity in Mermaid diagrams involves planning and employing best practices.
|
||||
|
||||
#### Modular Design
|
||||
|
||||
Break down complex diagrams into smaller, more manageable components. This approach not only makes the diagram easier to understand but also simplifies the creation and maintenance process.
|
||||
|
||||
#### Consistent Styling
|
||||
|
||||
Use classes to maintain consistent styling across similar elements. This not only saves time but also ensures a cohesive and professional appearance.
|
||||
|
||||
#### Comments and Documentation
|
||||
|
||||
Use comments with `%%` within the Mermaid syntax to document the purpose of various parts of the diagram. This practice is invaluable for maintaining clarity, especially when working in teams or returning to a diagram after some time.
|
||||
|
||||
With these troubleshooting tips and best practices, you can effectively manage and resolve common issues in Mermaid block diagrams. The final section, 'Conclusion', will summarize the key points covered in this documentation and invite user feedback for continuous improvement.
|
||||
@@ -0,0 +1,619 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/c4.md](../../packages/mermaid/src/docs/syntax/c4.md).
|
||||
|
||||
# C4 Diagrams
|
||||
|
||||
> C4 Diagram: This is an experimental diagram for now. The syntax and properties can change in future releases. Proper documentation will be provided when the syntax is stable.
|
||||
|
||||
Mermaid's C4 diagram syntax is compatible with plantUML. See example below:
|
||||
|
||||
```mermaid-example
|
||||
C4Context
|
||||
title System Context diagram for Internet Banking System
|
||||
Enterprise_Boundary(b0, "BankBoundary0") {
|
||||
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
|
||||
Person(customerB, "Banking Customer B")
|
||||
Person_Ext(customerC, "Banking Customer C", "desc")
|
||||
|
||||
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
|
||||
|
||||
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
|
||||
|
||||
Enterprise_Boundary(b1, "BankBoundary") {
|
||||
|
||||
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
System_Boundary(b2, "BankBoundary2") {
|
||||
System(SystemA, "Banking System A")
|
||||
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
|
||||
}
|
||||
|
||||
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
|
||||
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
|
||||
|
||||
Boundary(b3, "BankBoundary3", "boundary") {
|
||||
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
|
||||
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BiRel(customerA, SystemAA, "Uses")
|
||||
BiRel(SystemAA, SystemE, "Uses")
|
||||
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
|
||||
Rel(SystemC, customerA, "Sends e-mails to")
|
||||
|
||||
UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red")
|
||||
UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5")
|
||||
UpdateRelStyle(SystemAA, SystemE, $textColor="blue", $lineColor="blue", $offsetY="-10")
|
||||
UpdateRelStyle(SystemAA, SystemC, $textColor="blue", $lineColor="blue", $offsetY="-40", $offsetX="-50")
|
||||
UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20")
|
||||
|
||||
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
|
||||
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context diagram for Internet Banking System
|
||||
Enterprise_Boundary(b0, "BankBoundary0") {
|
||||
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
|
||||
Person(customerB, "Banking Customer B")
|
||||
Person_Ext(customerC, "Banking Customer C", "desc")
|
||||
|
||||
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
|
||||
|
||||
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
|
||||
|
||||
Enterprise_Boundary(b1, "BankBoundary") {
|
||||
|
||||
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
System_Boundary(b2, "BankBoundary2") {
|
||||
System(SystemA, "Banking System A")
|
||||
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
|
||||
}
|
||||
|
||||
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
|
||||
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
|
||||
|
||||
Boundary(b3, "BankBoundary3", "boundary") {
|
||||
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
|
||||
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BiRel(customerA, SystemAA, "Uses")
|
||||
BiRel(SystemAA, SystemE, "Uses")
|
||||
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
|
||||
Rel(SystemC, customerA, "Sends e-mails to")
|
||||
|
||||
UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red")
|
||||
UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5")
|
||||
UpdateRelStyle(SystemAA, SystemE, $textColor="blue", $lineColor="blue", $offsetY="-10")
|
||||
UpdateRelStyle(SystemAA, SystemC, $textColor="blue", $lineColor="blue", $offsetY="-40", $offsetX="-50")
|
||||
UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20")
|
||||
|
||||
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
|
||||
|
||||
|
||||
```
|
||||
|
||||
For an example, see the source code demos/index.html
|
||||
|
||||
5 types of C4 charts are supported.
|
||||
|
||||
- System Context (C4Context)
|
||||
- Container diagram (C4Container)
|
||||
- Component diagram (C4Component)
|
||||
- Dynamic diagram (C4Dynamic)
|
||||
- Deployment diagram (C4Deployment)
|
||||
|
||||
Please refer to the linked document [C4-PlantUML syntax](https://github.com/plantuml-stdlib/C4-PlantUML/blob/master/README.md) for how to write the C4 diagram.
|
||||
|
||||
C4 diagram is fixed style, such as css color, so different css is not provided under different skins.
|
||||
updateElementStyle and UpdateElementStyle are written in the diagram last part. updateElementStyle is inconsistent with the original definition and updates the style of the relationship, including the offset of the text label relative to the original position.
|
||||
|
||||
The layout does not use a fully automated layout algorithm. The position of shapes is adjusted by changing the order in which statements are written. So there is no plan to support the following Layout statements.
|
||||
The number of shapes per row and the number of boundaries can be adjusted using UpdateLayoutConfig.
|
||||
|
||||
- Layout
|
||||
- Lay_U, Lay_Up
|
||||
- Lay_D, Lay_Down
|
||||
- Lay_L, Lay_Left
|
||||
- Lay_R, Lay_Right
|
||||
|
||||
The following unfinished features are not supported in the short term.
|
||||
|
||||
- [ ] sprite
|
||||
|
||||
- [ ] tags
|
||||
|
||||
- [ ] link
|
||||
|
||||
- [ ] Legend
|
||||
|
||||
- [x] System Context
|
||||
- [x] Person(alias, label, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] Person_Ext
|
||||
- [x] System(alias, label, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] SystemDb
|
||||
- [x] SystemQueue
|
||||
- [x] System_Ext
|
||||
- [x] SystemDb_Ext
|
||||
- [x] SystemQueue_Ext
|
||||
- [x] Boundary(alias, label, ?type, ?tags, $link)
|
||||
- [x] Enterprise_Boundary(alias, label, ?tags, $link)
|
||||
- [x] System_Boundary
|
||||
|
||||
- [x] Container diagram
|
||||
- [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] ContainerDb
|
||||
- [x] ContainerQueue
|
||||
- [x] Container_Ext
|
||||
- [x] ContainerDb_Ext
|
||||
- [x] ContainerQueue_Ext
|
||||
- [x] Container_Boundary(alias, label, ?tags, $link)
|
||||
|
||||
- [x] Component diagram
|
||||
- [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] ComponentDb
|
||||
- [x] ComponentQueue
|
||||
- [x] Component_Ext
|
||||
- [x] ComponentDb_Ext
|
||||
- [x] ComponentQueue_Ext
|
||||
|
||||
- [x] Dynamic diagram
|
||||
- [x] RelIndex(index, from, to, label, ?tags, $link)
|
||||
|
||||
- [x] Deployment diagram
|
||||
- [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node()
|
||||
- [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node()
|
||||
- [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node()
|
||||
|
||||
- [x] Relationship Types
|
||||
- [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link)
|
||||
- [x] BiRel (bidirectional relationship)
|
||||
- [x] Rel_U, Rel_Up
|
||||
- [x] Rel_D, Rel_Down
|
||||
- [x] Rel_L, Rel_Left
|
||||
- [x] Rel_R, Rel_Right
|
||||
- [x] Rel_Back
|
||||
- [x] RelIndex \* Compatible with C4-PlantUML syntax, but ignores the index parameter. The sequence number is determined by the order in which the rel statements are written.
|
||||
|
||||
- [ ] Custom tags/stereotypes support and skin param updates
|
||||
- [ ] AddElementTag(tagStereo, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new element tag. The styles of the tagged elements are updated and the tag is displayed in the calculated legend.
|
||||
- [ ] AddRelTag(tagStereo, ?textColor, ?lineColor, ?lineStyle, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new Relationship tag. The styles of the tagged relationships are updated and the tag is displayed in the calculated legend.
|
||||
- [x] UpdateElementStyle(elementName, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): This call updates the default style of the elements (component, ...) and creates no additional legend entry.
|
||||
- [x] UpdateRelStyle(from, to, ?textColor, ?lineColor, ?offsetX, ?offsetY): This call updates the default relationship colors and creates no additional legend entry. Two new parameters, offsetX and offsetY, are added to set the offset of the original position of the text.
|
||||
- [ ] RoundedBoxShape(): This call returns the name of the rounded box shape and can be used as ?shape argument.
|
||||
- [ ] EightSidedShape(): This call returns the name of the eight sided shape and can be used as ?shape argument.
|
||||
- [ ] DashedLine(): This call returns the name of the dashed line and can be used as ?lineStyle argument.
|
||||
- [ ] DottedLine(): This call returns the name of the dotted line and can be used as ?lineStyle argument.
|
||||
- [ ] BoldLine(): This call returns the name of the bold line and can be used as ?lineStyle argument.
|
||||
- [x] UpdateLayoutConfig(?c4ShapeInRow, ?c4BoundaryInRow): New. This call updates the default c4ShapeInRow(4) and c4BoundaryInRow(2).
|
||||
|
||||
There are two ways to assign parameters with question marks. One uses the non-named parameter assignment method in the order of the parameters, and the other uses the named parameter assignment method, where the name must start with a $ symbol.
|
||||
|
||||
Example: UpdateRelStyle(from, to, ?textColor, ?lineColor, ?offsetX, ?offsetY)
|
||||
|
||||
```
|
||||
UpdateRelStyle(customerA, bankA, "red", "blue", "-40", "60")
|
||||
UpdateRelStyle(customerA, bankA, $offsetX="-40", $offsetY="60", $lineColor="blue", $textColor="red")
|
||||
UpdateRelStyle(customerA, bankA, $offsetY="60")
|
||||
|
||||
```
|
||||
|
||||
## C4 System Context Diagram (C4Context)
|
||||
|
||||
```mermaid-example
|
||||
C4Context
|
||||
title System Context diagram for Internet Banking System
|
||||
Enterprise_Boundary(b0, "BankBoundary0") {
|
||||
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
|
||||
Person(customerB, "Banking Customer B")
|
||||
Person_Ext(customerC, "Banking Customer C", "desc")
|
||||
|
||||
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
|
||||
|
||||
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
|
||||
|
||||
Enterprise_Boundary(b1, "BankBoundary") {
|
||||
|
||||
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
System_Boundary(b2, "BankBoundary2") {
|
||||
System(SystemA, "Banking System A")
|
||||
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
|
||||
}
|
||||
|
||||
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
|
||||
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
|
||||
|
||||
Boundary(b3, "BankBoundary3", "boundary") {
|
||||
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
|
||||
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BiRel(customerA, SystemAA, "Uses")
|
||||
BiRel(SystemAA, SystemE, "Uses")
|
||||
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
|
||||
Rel(SystemC, customerA, "Sends e-mails to")
|
||||
|
||||
UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red")
|
||||
UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5")
|
||||
UpdateRelStyle(SystemAA, SystemE, $textColor="blue", $lineColor="blue", $offsetY="-10")
|
||||
UpdateRelStyle(SystemAA, SystemC, $textColor="blue", $lineColor="blue", $offsetY="-40", $offsetX="-50")
|
||||
UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20")
|
||||
|
||||
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context diagram for Internet Banking System
|
||||
Enterprise_Boundary(b0, "BankBoundary0") {
|
||||
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")
|
||||
Person(customerB, "Banking Customer B")
|
||||
Person_Ext(customerC, "Banking Customer C", "desc")
|
||||
|
||||
Person(customerD, "Banking Customer D", "A customer of the bank, <br/> with personal bank accounts.")
|
||||
|
||||
System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
|
||||
|
||||
Enterprise_Boundary(b1, "BankBoundary") {
|
||||
|
||||
SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
System_Boundary(b2, "BankBoundary2") {
|
||||
System(SystemA, "Banking System A")
|
||||
System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts. next line.")
|
||||
}
|
||||
|
||||
System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.")
|
||||
SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.")
|
||||
|
||||
Boundary(b3, "BankBoundary3", "boundary") {
|
||||
SystemQueue(SystemF, "Banking System F Queue", "A system of the bank.")
|
||||
SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BiRel(customerA, SystemAA, "Uses")
|
||||
BiRel(SystemAA, SystemE, "Uses")
|
||||
Rel(SystemAA, SystemC, "Sends e-mails", "SMTP")
|
||||
Rel(SystemC, customerA, "Sends e-mails to")
|
||||
|
||||
UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red")
|
||||
UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5")
|
||||
UpdateRelStyle(SystemAA, SystemE, $textColor="blue", $lineColor="blue", $offsetY="-10")
|
||||
UpdateRelStyle(SystemAA, SystemC, $textColor="blue", $lineColor="blue", $offsetY="-40", $offsetX="-50")
|
||||
UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20")
|
||||
|
||||
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
|
||||
|
||||
```
|
||||
|
||||
## C4 Container diagram (C4Container)
|
||||
|
||||
```mermaid-example
|
||||
C4Container
|
||||
title Container diagram for Internet Banking System
|
||||
|
||||
System_Ext(email_system, "E-Mail System", "The internal Microsoft Exchange system", $tags="v1.0")
|
||||
Person(customer, Customer, "A customer of the bank, with personal bank accounts", $tags="v1.0")
|
||||
|
||||
Container_Boundary(c1, "Internet Banking") {
|
||||
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser")
|
||||
Container_Ext(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
|
||||
Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
|
||||
ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")
|
||||
ContainerDb_Ext(backend_api, "API Application", "Java, Docker Container", "Provides Internet banking functionality via API")
|
||||
|
||||
}
|
||||
|
||||
System_Ext(banking_system, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
Rel(customer, web_app, "Uses", "HTTPS")
|
||||
UpdateRelStyle(customer, web_app, $offsetY="60", $offsetX="90")
|
||||
Rel(customer, spa, "Uses", "HTTPS")
|
||||
UpdateRelStyle(customer, spa, $offsetY="-40")
|
||||
Rel(customer, mobile_app, "Uses")
|
||||
UpdateRelStyle(customer, mobile_app, $offsetY="-30")
|
||||
|
||||
Rel(web_app, spa, "Delivers")
|
||||
UpdateRelStyle(web_app, spa, $offsetX="130")
|
||||
Rel(spa, backend_api, "Uses", "async, JSON/HTTPS")
|
||||
Rel(mobile_app, backend_api, "Uses", "async, JSON/HTTPS")
|
||||
Rel_Back(database, backend_api, "Reads from and writes to", "sync, JDBC")
|
||||
|
||||
Rel(email_system, customer, "Sends e-mails to")
|
||||
UpdateRelStyle(email_system, customer, $offsetX="-45")
|
||||
Rel(backend_api, email_system, "Sends e-mails using", "sync, SMTP")
|
||||
UpdateRelStyle(backend_api, email_system, $offsetY="-60")
|
||||
Rel(backend_api, banking_system, "Uses", "sync/async, XML/HTTPS")
|
||||
UpdateRelStyle(backend_api, banking_system, $offsetY="-50", $offsetX="-140")
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
C4Container
|
||||
title Container diagram for Internet Banking System
|
||||
|
||||
System_Ext(email_system, "E-Mail System", "The internal Microsoft Exchange system", $tags="v1.0")
|
||||
Person(customer, Customer, "A customer of the bank, with personal bank accounts", $tags="v1.0")
|
||||
|
||||
Container_Boundary(c1, "Internet Banking") {
|
||||
Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser")
|
||||
Container_Ext(mobile_app, "Mobile App", "C#, Xamarin", "Provides a limited subset of the Internet banking functionality to customers via their mobile device")
|
||||
Container(web_app, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking SPA")
|
||||
ContainerDb(database, "Database", "SQL Database", "Stores user registration information, hashed auth credentials, access logs, etc.")
|
||||
ContainerDb_Ext(backend_api, "API Application", "Java, Docker Container", "Provides Internet banking functionality via API")
|
||||
|
||||
}
|
||||
|
||||
System_Ext(banking_system, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
Rel(customer, web_app, "Uses", "HTTPS")
|
||||
UpdateRelStyle(customer, web_app, $offsetY="60", $offsetX="90")
|
||||
Rel(customer, spa, "Uses", "HTTPS")
|
||||
UpdateRelStyle(customer, spa, $offsetY="-40")
|
||||
Rel(customer, mobile_app, "Uses")
|
||||
UpdateRelStyle(customer, mobile_app, $offsetY="-30")
|
||||
|
||||
Rel(web_app, spa, "Delivers")
|
||||
UpdateRelStyle(web_app, spa, $offsetX="130")
|
||||
Rel(spa, backend_api, "Uses", "async, JSON/HTTPS")
|
||||
Rel(mobile_app, backend_api, "Uses", "async, JSON/HTTPS")
|
||||
Rel_Back(database, backend_api, "Reads from and writes to", "sync, JDBC")
|
||||
|
||||
Rel(email_system, customer, "Sends e-mails to")
|
||||
UpdateRelStyle(email_system, customer, $offsetX="-45")
|
||||
Rel(backend_api, email_system, "Sends e-mails using", "sync, SMTP")
|
||||
UpdateRelStyle(backend_api, email_system, $offsetY="-60")
|
||||
Rel(backend_api, banking_system, "Uses", "sync/async, XML/HTTPS")
|
||||
UpdateRelStyle(backend_api, banking_system, $offsetY="-50", $offsetX="-140")
|
||||
|
||||
```
|
||||
|
||||
## C4 Component diagram (C4Component)
|
||||
|
||||
```mermaid-example
|
||||
C4Component
|
||||
title Component diagram for Internet Banking System - API Application
|
||||
|
||||
Container(spa, "Single Page Application", "javascript and angular", "Provides all the internet banking functionality to customers via their web browser.")
|
||||
Container(ma, "Mobile App", "Xamarin", "Provides a limited subset to the internet banking functionality to customers via their mobile device.")
|
||||
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
System_Ext(mbs, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
Container_Boundary(api, "API Application") {
|
||||
Component(sign, "Sign In Controller", "MVC Rest Controller", "Allows users to sign in to the internet banking system")
|
||||
Component(accounts, "Accounts Summary Controller", "MVC Rest Controller", "Provides customers with a summary of their bank accounts")
|
||||
Component(security, "Security Component", "Spring Bean", "Provides functionality related to singing in, changing passwords, etc.")
|
||||
Component(mbsfacade, "Mainframe Banking System Facade", "Spring Bean", "A facade onto the mainframe banking system.")
|
||||
|
||||
Rel(sign, security, "Uses")
|
||||
Rel(accounts, mbsfacade, "Uses")
|
||||
Rel(security, db, "Read & write to", "JDBC")
|
||||
Rel(mbsfacade, mbs, "Uses", "XML/HTTPS")
|
||||
}
|
||||
|
||||
Rel_Back(spa, sign, "Uses", "JSON/HTTPS")
|
||||
Rel(spa, accounts, "Uses", "JSON/HTTPS")
|
||||
|
||||
Rel(ma, sign, "Uses", "JSON/HTTPS")
|
||||
Rel(ma, accounts, "Uses", "JSON/HTTPS")
|
||||
|
||||
UpdateRelStyle(spa, sign, $offsetY="-40")
|
||||
UpdateRelStyle(spa, accounts, $offsetX="40", $offsetY="40")
|
||||
|
||||
UpdateRelStyle(ma, sign, $offsetX="-90", $offsetY="40")
|
||||
UpdateRelStyle(ma, accounts, $offsetY="-40")
|
||||
|
||||
UpdateRelStyle(sign, security, $offsetX="-160", $offsetY="10")
|
||||
UpdateRelStyle(accounts, mbsfacade, $offsetX="140", $offsetY="10")
|
||||
UpdateRelStyle(security, db, $offsetY="-40")
|
||||
UpdateRelStyle(mbsfacade, mbs, $offsetY="-40")
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
C4Component
|
||||
title Component diagram for Internet Banking System - API Application
|
||||
|
||||
Container(spa, "Single Page Application", "javascript and angular", "Provides all the internet banking functionality to customers via their web browser.")
|
||||
Container(ma, "Mobile App", "Xamarin", "Provides a limited subset to the internet banking functionality to customers via their mobile device.")
|
||||
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
System_Ext(mbs, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||||
|
||||
Container_Boundary(api, "API Application") {
|
||||
Component(sign, "Sign In Controller", "MVC Rest Controller", "Allows users to sign in to the internet banking system")
|
||||
Component(accounts, "Accounts Summary Controller", "MVC Rest Controller", "Provides customers with a summary of their bank accounts")
|
||||
Component(security, "Security Component", "Spring Bean", "Provides functionality related to singing in, changing passwords, etc.")
|
||||
Component(mbsfacade, "Mainframe Banking System Facade", "Spring Bean", "A facade onto the mainframe banking system.")
|
||||
|
||||
Rel(sign, security, "Uses")
|
||||
Rel(accounts, mbsfacade, "Uses")
|
||||
Rel(security, db, "Read & write to", "JDBC")
|
||||
Rel(mbsfacade, mbs, "Uses", "XML/HTTPS")
|
||||
}
|
||||
|
||||
Rel_Back(spa, sign, "Uses", "JSON/HTTPS")
|
||||
Rel(spa, accounts, "Uses", "JSON/HTTPS")
|
||||
|
||||
Rel(ma, sign, "Uses", "JSON/HTTPS")
|
||||
Rel(ma, accounts, "Uses", "JSON/HTTPS")
|
||||
|
||||
UpdateRelStyle(spa, sign, $offsetY="-40")
|
||||
UpdateRelStyle(spa, accounts, $offsetX="40", $offsetY="40")
|
||||
|
||||
UpdateRelStyle(ma, sign, $offsetX="-90", $offsetY="40")
|
||||
UpdateRelStyle(ma, accounts, $offsetY="-40")
|
||||
|
||||
UpdateRelStyle(sign, security, $offsetX="-160", $offsetY="10")
|
||||
UpdateRelStyle(accounts, mbsfacade, $offsetX="140", $offsetY="10")
|
||||
UpdateRelStyle(security, db, $offsetY="-40")
|
||||
UpdateRelStyle(mbsfacade, mbs, $offsetY="-40")
|
||||
|
||||
```
|
||||
|
||||
## C4 Dynamic diagram (C4Dynamic)
|
||||
|
||||
```mermaid-example
|
||||
C4Dynamic
|
||||
title Dynamic diagram for Internet Banking System - API Application
|
||||
|
||||
ContainerDb(c4, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
Container(c1, "Single-Page Application", "JavaScript and Angular", "Provides all of the Internet banking functionality to customers via their web browser.")
|
||||
Container_Boundary(b, "API Application") {
|
||||
Component(c3, "Security Component", "Spring Bean", "Provides functionality Related to signing in, changing passwords, etc.")
|
||||
Component(c2, "Sign In Controller", "Spring MVC Rest Controller", "Allows users to sign in to the Internet Banking System.")
|
||||
}
|
||||
Rel(c1, c2, "Submits credentials to", "JSON/HTTPS")
|
||||
Rel(c2, c3, "Calls isAuthenticated() on")
|
||||
Rel(c3, c4, "select * from users where username = ?", "JDBC")
|
||||
|
||||
UpdateRelStyle(c1, c2, $textColor="red", $offsetY="-40")
|
||||
UpdateRelStyle(c2, c3, $textColor="red", $offsetX="-40", $offsetY="60")
|
||||
UpdateRelStyle(c3, c4, $textColor="red", $offsetY="-40", $offsetX="10")
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
C4Dynamic
|
||||
title Dynamic diagram for Internet Banking System - API Application
|
||||
|
||||
ContainerDb(c4, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
Container(c1, "Single-Page Application", "JavaScript and Angular", "Provides all of the Internet banking functionality to customers via their web browser.")
|
||||
Container_Boundary(b, "API Application") {
|
||||
Component(c3, "Security Component", "Spring Bean", "Provides functionality Related to signing in, changing passwords, etc.")
|
||||
Component(c2, "Sign In Controller", "Spring MVC Rest Controller", "Allows users to sign in to the Internet Banking System.")
|
||||
}
|
||||
Rel(c1, c2, "Submits credentials to", "JSON/HTTPS")
|
||||
Rel(c2, c3, "Calls isAuthenticated() on")
|
||||
Rel(c3, c4, "select * from users where username = ?", "JDBC")
|
||||
|
||||
UpdateRelStyle(c1, c2, $textColor="red", $offsetY="-40")
|
||||
UpdateRelStyle(c2, c3, $textColor="red", $offsetX="-40", $offsetY="60")
|
||||
UpdateRelStyle(c3, c4, $textColor="red", $offsetY="-40", $offsetX="10")
|
||||
|
||||
```
|
||||
|
||||
## C4 Deployment diagram (C4Deployment)
|
||||
|
||||
```mermaid-example
|
||||
C4Deployment
|
||||
title Deployment Diagram for Internet Banking System - Live
|
||||
|
||||
Deployment_Node(mob, "Customer's mobile device", "Apple IOS or Android"){
|
||||
Container(mobile, "Mobile App", "Xamarin", "Provides a limited subset of the Internet Banking functionality to customers via their mobile device.")
|
||||
}
|
||||
|
||||
Deployment_Node(comp, "Customer's computer", "Microsoft Windows or Apple macOS"){
|
||||
Deployment_Node(browser, "Web Browser", "Google Chrome, Mozilla Firefox,<br/> Apple Safari or Microsoft Edge"){
|
||||
Container(spa, "Single Page Application", "JavaScript and Angular", "Provides all of the Internet Banking functionality to customers via their web browser.")
|
||||
}
|
||||
}
|
||||
|
||||
Deployment_Node(plc, "Big Bank plc", "Big Bank plc data center"){
|
||||
Deployment_Node(dn, "bigbank-api*** x8", "Ubuntu 16.04 LTS"){
|
||||
Deployment_Node(apache, "Apache Tomcat", "Apache Tomcat 8.x"){
|
||||
Container(api, "API Application", "Java and Spring MVC", "Provides Internet Banking functionality via a JSON/HTTPS API.")
|
||||
}
|
||||
}
|
||||
Deployment_Node(bb2, "bigbank-web*** x4", "Ubuntu 16.04 LTS"){
|
||||
Deployment_Node(apache2, "Apache Tomcat", "Apache Tomcat 8.x"){
|
||||
Container(web, "Web Application", "Java and Spring MVC", "Delivers the static content and the Internet Banking single page application.")
|
||||
}
|
||||
}
|
||||
Deployment_Node(bigbankdb01, "bigbank-db01", "Ubuntu 16.04 LTS"){
|
||||
Deployment_Node(oracle, "Oracle - Primary", "Oracle 12c"){
|
||||
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
}
|
||||
}
|
||||
Deployment_Node(bigbankdb02, "bigbank-db02", "Ubuntu 16.04 LTS") {
|
||||
Deployment_Node(oracle2, "Oracle - Secondary", "Oracle 12c") {
|
||||
ContainerDb(db2, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rel(mobile, api, "Makes API calls to", "json/HTTPS")
|
||||
Rel(spa, api, "Makes API calls to", "json/HTTPS")
|
||||
Rel_U(web, spa, "Delivers to the customer's web browser")
|
||||
Rel(api, db, "Reads from and writes to", "JDBC")
|
||||
Rel(api, db2, "Reads from and writes to", "JDBC")
|
||||
Rel_R(db, db2, "Replicates data to")
|
||||
|
||||
UpdateRelStyle(spa, api, $offsetY="-40")
|
||||
UpdateRelStyle(web, spa, $offsetY="-40")
|
||||
UpdateRelStyle(api, db, $offsetY="-20", $offsetX="5")
|
||||
UpdateRelStyle(api, db2, $offsetX="-40", $offsetY="-20")
|
||||
UpdateRelStyle(db, db2, $offsetY="-10")
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
C4Deployment
|
||||
title Deployment Diagram for Internet Banking System - Live
|
||||
|
||||
Deployment_Node(mob, "Customer's mobile device", "Apple IOS or Android"){
|
||||
Container(mobile, "Mobile App", "Xamarin", "Provides a limited subset of the Internet Banking functionality to customers via their mobile device.")
|
||||
}
|
||||
|
||||
Deployment_Node(comp, "Customer's computer", "Microsoft Windows or Apple macOS"){
|
||||
Deployment_Node(browser, "Web Browser", "Google Chrome, Mozilla Firefox,<br/> Apple Safari or Microsoft Edge"){
|
||||
Container(spa, "Single Page Application", "JavaScript and Angular", "Provides all of the Internet Banking functionality to customers via their web browser.")
|
||||
}
|
||||
}
|
||||
|
||||
Deployment_Node(plc, "Big Bank plc", "Big Bank plc data center"){
|
||||
Deployment_Node(dn, "bigbank-api*** x8", "Ubuntu 16.04 LTS"){
|
||||
Deployment_Node(apache, "Apache Tomcat", "Apache Tomcat 8.x"){
|
||||
Container(api, "API Application", "Java and Spring MVC", "Provides Internet Banking functionality via a JSON/HTTPS API.")
|
||||
}
|
||||
}
|
||||
Deployment_Node(bb2, "bigbank-web*** x4", "Ubuntu 16.04 LTS"){
|
||||
Deployment_Node(apache2, "Apache Tomcat", "Apache Tomcat 8.x"){
|
||||
Container(web, "Web Application", "Java and Spring MVC", "Delivers the static content and the Internet Banking single page application.")
|
||||
}
|
||||
}
|
||||
Deployment_Node(bigbankdb01, "bigbank-db01", "Ubuntu 16.04 LTS"){
|
||||
Deployment_Node(oracle, "Oracle - Primary", "Oracle 12c"){
|
||||
ContainerDb(db, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
}
|
||||
}
|
||||
Deployment_Node(bigbankdb02, "bigbank-db02", "Ubuntu 16.04 LTS") {
|
||||
Deployment_Node(oracle2, "Oracle - Secondary", "Oracle 12c") {
|
||||
ContainerDb(db2, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rel(mobile, api, "Makes API calls to", "json/HTTPS")
|
||||
Rel(spa, api, "Makes API calls to", "json/HTTPS")
|
||||
Rel_U(web, spa, "Delivers to the customer's web browser")
|
||||
Rel(api, db, "Reads from and writes to", "JDBC")
|
||||
Rel(api, db2, "Reads from and writes to", "JDBC")
|
||||
Rel_R(db, db2, "Replicates data to")
|
||||
|
||||
UpdateRelStyle(spa, api, $offsetY="-40")
|
||||
UpdateRelStyle(web, spa, $offsetY="-40")
|
||||
UpdateRelStyle(api, db, $offsetY="-20", $offsetX="5")
|
||||
UpdateRelStyle(api, db2, $offsetX="-40", $offsetY="-20")
|
||||
UpdateRelStyle(db, db2, $offsetY="-10")
|
||||
|
||||
```
|
||||
|
||||
<!--- cspell:ignore bigbank bigbankdb techn mbsfacade --->
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,72 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/configuration.md](../../packages/mermaid/src/docs/config/configuration.md).
|
||||
|
||||
# Configuration
|
||||
|
||||
When mermaid starts, configuration is extracted to determine a configuration to be used for a diagram. There are 3 sources for configuration:
|
||||
|
||||
- The default configuration
|
||||
- Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
|
||||
- Frontmatter (v10.5.0+) - diagram authors can update selected configuration parameters in the frontmatter of the diagram. These are applied to the render config.
|
||||
- Directives (Deprecated by Frontmatter) - diagram authors can update selected configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
|
||||
**The render config** is configuration that is used when rendering by applying these configurations.
|
||||
|
||||
## Frontmatter config
|
||||
|
||||
The entire mermaid configuration (except the secure configs) can be overridden by the diagram author in the frontmatter of the diagram. The frontmatter is a YAML block at the top of the diagram.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: Hello Title
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: "#00ff00"
|
||||
---
|
||||
flowchart
|
||||
Hello --> World
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Hello Title
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: "#00ff00"
|
||||
---
|
||||
flowchart
|
||||
Hello --> World
|
||||
|
||||
```
|
||||
|
||||
## Theme configuration
|
||||
|
||||
## Starting mermaid
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
Site->>mermaid: initialize
|
||||
Site->>mermaid: content loaded
|
||||
mermaid->>mermaidAPI: init
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Site->>mermaid: initialize
|
||||
Site->>mermaid: content loaded
|
||||
mermaid->>mermaidAPI: init
|
||||
```
|
||||
|
||||
## Initialize
|
||||
|
||||
The initialize call is applied **only once**. It is called by the site integrator in order to override the default configuration at a site level.
|
||||
|
||||
## configApi.reset
|
||||
|
||||
This method resets the configuration for a diagram to the overall site configuration, which is the configuration provided by the site integrator. Before each rendering of a diagram, reset is called at the very beginning.
|
||||
@@ -0,0 +1,342 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/directives.md](../../packages/mermaid/src/docs/config/directives.md).
|
||||
|
||||
# Directives
|
||||
|
||||
> **Warning**
|
||||
> Directives are deprecated from v10.5.0. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
|
||||
|
||||
## Directives
|
||||
|
||||
Directives give a diagram author the capability to alter the appearance of a diagram before rendering by changing the applied configuration.
|
||||
|
||||
The significance of having directives is that you have them available while writing the diagram, and can modify the default global and diagram-specific configurations. So, directives are applied on top of the default configuration. The beauty of directives is that you can use them to alter configuration settings for a specific diagram, i.e. at an individual level.
|
||||
|
||||
While directives allow you to change most of the default configuration settings, there are some that are not available, for security reasons. Also, you have the _option to define the set of configurations_ that you wish to allow diagram authors to override with directives.
|
||||
|
||||
## Types of Directives options
|
||||
|
||||
Mermaid basically supports two types of configuration options to be overridden by directives.
|
||||
|
||||
1. _General/Top Level configurations_ : These are the configurations that are available and applied to all the diagram. **Some of the most important top-level** configurations are:
|
||||
- theme
|
||||
- fontFamily
|
||||
- logLevel
|
||||
- securityLevel
|
||||
- startOnLoad
|
||||
- secure
|
||||
|
||||
2. _Diagram-specific configurations_ : These are the configurations that are available and applied to a specific diagram. For each diagram there are specific configuration that will alter how that particular diagram looks and behaves.
|
||||
For example, `mirrorActors` is a configuration that is specific to the `SequenceDiagram` and alters whether the actors are mirrored or not. So this config is available only for the `SequenceDiagram` type.
|
||||
|
||||
**NOTE:** Not all configuration options are listed here. To get hold of all the configuration options, please refer to the [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code.
|
||||
|
||||
> **Note**
|
||||
> We plan to publish a complete list of top-level configurations & diagram-specific configurations with their possible values in the docs soon.
|
||||
|
||||
## Declaring directives
|
||||
|
||||
Now that we have defined the types of configurations that are available, we can learn how to declare directives.
|
||||
A directive always starts and ends with `%%` signs with directive text in between, like `%% {directive_text} %%`.
|
||||
|
||||
Here the structure of a directive text is like a nested key-value pair map or a JSON object with root being _init_. Where all the general configurations are defined in the top level, and all the diagram specific configurations are defined one level deeper with diagram type as key/root for that section.
|
||||
|
||||
The following code snippet shows the structure of a directive:
|
||||
|
||||
```
|
||||
%%{
|
||||
init: {
|
||||
"theme": "dark",
|
||||
"fontFamily": "monospace",
|
||||
"logLevel": "info",
|
||||
"htmlLabels": true,
|
||||
"flowchart": {
|
||||
"curve": "linear"
|
||||
},
|
||||
"sequence": {
|
||||
"mirrorActors": true
|
||||
}
|
||||
}
|
||||
}%%
|
||||
```
|
||||
|
||||
You can also define the directives in a single line, like this:
|
||||
|
||||
```
|
||||
%%{init: { **insert configuration options here** } }%%
|
||||
```
|
||||
|
||||
For example, the following code snippet:
|
||||
|
||||
```
|
||||
%%{init: { "sequence": { "mirrorActors":false }}}%%
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
The JSON object that is passed as {**argument**} must be valid key value pairs and encased in quotation marks or it will be ignored.
|
||||
Valid Key Value pairs can be found in config.
|
||||
|
||||
Example with a simple graph:
|
||||
|
||||
```mermaid-example
|
||||
%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%%
|
||||
graph LR
|
||||
A-->B
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%%
|
||||
graph LR
|
||||
A-->B
|
||||
```
|
||||
|
||||
Here the directive declaration will set the `logLevel` to `debug` and the `theme` to `dark` for a rendered mermaid diagram, changing the appearance of the diagram itself.
|
||||
|
||||
Note: You can use 'init' or 'initialize' as both are acceptable as init directives. Also note that `%%init%%` and `%%initialize%%` directives will be grouped together after they are parsed.
|
||||
|
||||
```mermaid-example
|
||||
%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%%
|
||||
%%{initialize: { 'logLevel': 'fatal', "theme":'dark', 'startOnLoad': true } }%%
|
||||
...
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%%
|
||||
%%{initialize: { 'logLevel': 'fatal', "theme":'dark', 'startOnLoad': true } }%%
|
||||
...
|
||||
```
|
||||
|
||||
For example, parsing the above generates a single `%%init%%` JSON object below, combining the two directives and carrying over the last value given for `loglevel`:
|
||||
|
||||
```json
|
||||
{
|
||||
"logLevel": "fatal",
|
||||
"theme": "dark",
|
||||
"startOnLoad": true
|
||||
}
|
||||
```
|
||||
|
||||
This will then be sent to `mermaid.initialize(...)` for rendering.
|
||||
|
||||
## Directive Examples
|
||||
|
||||
Now that the concept of directives has been explained, let us see some more examples of directive usage:
|
||||
|
||||
### Changing theme via directive
|
||||
|
||||
The following code snippet changes `theme` to `forest`:
|
||||
|
||||
`%%{init: { "theme": "forest" } }%%`
|
||||
|
||||
Possible theme values are: `default`, `base`, `dark`, `forest` and `neutral`.
|
||||
Default Value is `default`.
|
||||
|
||||
Example:
|
||||
|
||||
```mermaid-example
|
||||
%%{init: { "theme": "forest" } }%%
|
||||
graph TD
|
||||
A(Forest) --> B[/Another/]
|
||||
A --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: { "theme": "forest" } }%%
|
||||
graph TD
|
||||
A(Forest) --> B[/Another/]
|
||||
A --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
### Changing fontFamily via directive
|
||||
|
||||
The following code snippet changes fontFamily to Trebuchet MS, Verdana, Arial, Sans-Serif:
|
||||
|
||||
`%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%`
|
||||
|
||||
Example:
|
||||
|
||||
```mermaid-example
|
||||
%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%
|
||||
graph TD
|
||||
A(Forest) --> B[/Another/]
|
||||
A --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%
|
||||
graph TD
|
||||
A(Forest) --> B[/Another/]
|
||||
A --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
### Changing logLevel via directive
|
||||
|
||||
The following code snippet changes `logLevel` to `2`:
|
||||
|
||||
`%%{init: { "logLevel": 2 } }%%`
|
||||
|
||||
Possible `logLevel` values are:
|
||||
|
||||
- `1` for _debug_,
|
||||
- `2` for _info_
|
||||
- `3` for _warn_
|
||||
- `4` for _error_
|
||||
- `5` for _only fatal errors_
|
||||
|
||||
Default Value is `5`.
|
||||
|
||||
Example:
|
||||
|
||||
```mermaid-example
|
||||
%%{init: { "logLevel": 2 } }%%
|
||||
graph TD
|
||||
A(Forest) --> B[/Another/]
|
||||
A --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: { "logLevel": 2 } }%%
|
||||
graph TD
|
||||
A(Forest) --> B[/Another/]
|
||||
A --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
```
|
||||
|
||||
### Changing flowchart config via directive
|
||||
|
||||
Some common flowchart configurations are:
|
||||
|
||||
- ~~_htmlLabels_~~: Deprecated, [prefer setting this at the root level](/config/schema-docs/config#htmllabels).
|
||||
- _curve_: linear/curve
|
||||
- _diagramPadding_: number
|
||||
- _useMaxWidth_: number
|
||||
|
||||
For a complete list of flowchart configurations, see [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code.
|
||||
_Soon we plan to publish a complete list of all diagram-specific configurations updated in the docs._
|
||||
|
||||
The following code snippet changes flowchart config:
|
||||
|
||||
```
|
||||
%%{init: { "htmlLabels": true, "flowchart": { "curve": "linear" } } }%%
|
||||
```
|
||||
|
||||
Here we are overriding only the flowchart config, and not the general config, setting `htmlLabels` to `true` and `curve` to `linear`.
|
||||
|
||||
> **Warning**
|
||||
> **Deprecated:** `flowchart.htmlLabels` has been deprecated from (v\<MERMAID_RELEASE_VERSION>+). Use the global `htmlLabels` configuration instead. For example, instead of `"flowchart": { "htmlLabels": true }`, use `"htmlLabels": true` at the top level.
|
||||
|
||||
```mermaid-example
|
||||
%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%
|
||||
graph TD
|
||||
A(Forest) --> B[/Another/]
|
||||
A --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: { "flowchart": { "htmlLabels": true, "curve": "linear" } } }%%
|
||||
graph TD
|
||||
A(Forest) --> B[/Another/]
|
||||
A --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
```
|
||||
|
||||
### Changing Sequence diagram config via directive
|
||||
|
||||
Some common sequence diagram configurations are:
|
||||
|
||||
- _width_: number
|
||||
- _height_: number
|
||||
- _messageAlign_: left, center, right
|
||||
- _mirrorActors_: boolean
|
||||
- _useMaxWidth_: boolean
|
||||
- _rightAngles_: boolean
|
||||
- _showSequenceNumbers_: boolean
|
||||
- _wrap_: boolean
|
||||
|
||||
For a complete list of sequence diagram configurations, see [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code.
|
||||
_Soon we plan to publish a complete list of all diagram-specific configurations updated in the docs._
|
||||
|
||||
So, `wrap` by default has a value of `false` for sequence diagrams.
|
||||
|
||||
Let us see an example:
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Alice->Bob: Good.
|
||||
Bob->Alice: Cool
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Alice->Bob: Good.
|
||||
Bob->Alice: Cool
|
||||
```
|
||||
|
||||
Now let us enable wrap for sequence diagrams.
|
||||
|
||||
The following code snippet changes sequence diagram config for `wrap` to `true`:
|
||||
|
||||
`%%{init: { "sequence": { "wrap": true} } }%%`
|
||||
|
||||
By applying that snippet to the diagram above, `wrap` will be enabled:
|
||||
|
||||
```mermaid-example
|
||||
%%{init: { "sequence": { "wrap": true, "width":300 } } }%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Alice->Bob: Good.
|
||||
Bob->Alice: Cool
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: { "sequence": { "wrap": true, "width":300 } } }%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Alice->Bob: Good.
|
||||
Bob->Alice: Cool
|
||||
```
|
||||
@@ -0,0 +1,40 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/layouts.md](../../packages/mermaid/src/docs/config/layouts.md).
|
||||
|
||||
# Layouts
|
||||
|
||||
This page lists the available layout algorithms supported in Mermaid diagrams.
|
||||
|
||||
## Supported Layouts
|
||||
|
||||
- **elk**: [ELK (Eclipse Layout Kernel)](https://www.eclipse.org/elk/)
|
||||
- **tidy-tree**: Tidy tree layout for hierarchical diagrams [Tidy Tree Configuration](/config/tidy-tree)
|
||||
- **cose-bilkent**: Cose Bilkent layout for force-directed graphs
|
||||
- **dagre**: Dagre layout for layered graphs
|
||||
|
||||
## How to Use
|
||||
|
||||
You can specify the layout in your diagram's YAML config or initialization options. For example:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
graph TD;
|
||||
A-->B;
|
||||
B-->C;
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
graph TD;
|
||||
A-->B;
|
||||
B-->C;
|
||||
```
|
||||
@@ -0,0 +1,96 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/math.md](../../packages/mermaid/src/docs/config/math.md).
|
||||
|
||||
# Math Configuration (v10.9.0+)
|
||||
|
||||
Mermaid supports rendering mathematical expressions through the [KaTeX](https://katex.org/) typesetter.
|
||||
|
||||
## Usage
|
||||
|
||||
To render math within a diagram, surround the mathematical expression with the `$$` delimiter.
|
||||
|
||||
Note that at the moment, the only supported diagrams are below:
|
||||
|
||||
### Flowcharts
|
||||
|
||||
```mermaid-example
|
||||
graph LR
|
||||
A["$$x^2$$"] -->|"$$\sqrt{x+3}$$"| B("$$\frac{1}{2}$$")
|
||||
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\pi r^2$$")
|
||||
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
|
||||
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A["$$x^2$$"] -->|"$$\sqrt{x+3}$$"| B("$$\frac{1}{2}$$")
|
||||
A -->|"$$\overbrace{a+b+c}^{\text{note}}$$"| C("$$\pi r^2$$")
|
||||
B --> D("$$x = \begin{cases} a &\text{if } b \\ c &\text{if } d \end{cases}$$")
|
||||
C --> E("$$x(t)=c_1\begin{bmatrix}-\cos{t}+\sin{t}\\ 2\cos{t} \end{bmatrix}e^{2t}$$")
|
||||
```
|
||||
|
||||
### Sequence
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant 1 as $$\alpha$$
|
||||
participant 2 as $$\beta$$
|
||||
1->>2: Solve: $$\sqrt{2+2}$$
|
||||
2-->>1: Answer: $$2$$
|
||||
Note right of 2: $$\sqrt{2+2}=\sqrt{4}=2$$
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
autonumber
|
||||
participant 1 as $$\alpha$$
|
||||
participant 2 as $$\beta$$
|
||||
1->>2: Solve: $$\sqrt{2+2}$$
|
||||
2-->>1: Answer: $$2$$
|
||||
Note right of 2: $$\sqrt{2+2}=\sqrt{4}=2$$
|
||||
```
|
||||
|
||||
## Legacy Support
|
||||
|
||||
By default, MathML is used for rendering mathematical expressions. If you have users on [unsupported browsers](https://caniuse.com/?search=mathml), `legacyMathML` can be set in the config to fall back to CSS rendering. Note that **you must provide KaTeX's stylesheets on your own** as they do not come bundled with Mermaid.
|
||||
|
||||
Example with legacy mode enabled (the latest version of KaTeX's stylesheet can be found on their [docs](https://katex.org/docs/browser.html)):
|
||||
|
||||
```html
|
||||
<!doctype html>
|
||||
<!-- KaTeX requires the use of the HTML5 doctype. Without it, KaTeX may not render properly -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Please ensure the stylesheet's version matches with the KaTeX version in your package-lock -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/katex@{version_number}/dist/katex.min.css"
|
||||
integrity="sha384-{hash}"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
legacyMathML: true,
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Handling Rendering Differences
|
||||
|
||||
Due to differences between default fonts across operating systems and browser's MathML implementations, inconsistent results can be seen across platforms. If having consistent results are important, or the most optimal rendered results are desired, `forceLegacyMathML` can be enabled in the config.
|
||||
|
||||
This option will always use KaTeX's stylesheet instead of only when MathML is not supported (as with `legacyMathML`). Note that only `forceLegacyMathML` needs to be set.
|
||||
|
||||
If including KaTeX's stylesheet is not a concern, enabling this option is recommended to avoid scenarios where no MathML implementation within a browser provides the desired output (as seen below).
|
||||
|
||||

|
||||
@@ -0,0 +1,246 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/theming.md](../../packages/mermaid/src/docs/config/theming.md).
|
||||
|
||||
# Theme Configuration
|
||||
|
||||
Dynamic and integrated theme configuration was introduced in Mermaid version 8.7.0.
|
||||
|
||||
Themes can now be customized at the site-wide level, or on individual Mermaid diagrams. For site-wide theme customization, the `initialize` call is used. For diagram specific customization, frontmatter config is used.
|
||||
|
||||
## Available Themes
|
||||
|
||||
1. [**default**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-default.js) - This is the default theme for all diagrams.
|
||||
|
||||
2. [**neutral**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-neutral.js) - This theme is great for black and white documents that will be printed.
|
||||
|
||||
3. [**dark**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-dark.js) - This theme goes well with dark-colored elements or dark-mode.
|
||||
|
||||
4. [**forest**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-forest.js) - This theme contains shades of green.
|
||||
|
||||
5. [**base**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-base.js) - This is the only theme that can be modified. Use this theme as the base for customizations.
|
||||
|
||||
## Site-wide Theme
|
||||
|
||||
To customize themes site-wide, call the `initialize` method on the `mermaid`.
|
||||
|
||||
Example of `initialize` call setting `theme` to `base`:
|
||||
|
||||
```javascript
|
||||
mermaid.initialize({
|
||||
securityLevel: 'loose',
|
||||
theme: 'base',
|
||||
});
|
||||
```
|
||||
|
||||
## Diagram-specific Themes
|
||||
|
||||
To customize the theme of an individual diagram, use frontmatter config.
|
||||
|
||||
Example of frontmatter config setting the `theme` to `forest`:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
theme: 'forest'
|
||||
---
|
||||
graph TD
|
||||
a --> b
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
theme: 'forest'
|
||||
---
|
||||
graph TD
|
||||
a --> b
|
||||
```
|
||||
|
||||
> **Reminder**: the only theme that can be customized is the `base` theme. The following section covers how to use `themeVariables` for customizations.
|
||||
|
||||
## Customizing Themes with `themeVariables`
|
||||
|
||||
To make a custom theme, modify `themeVariables` via frontmatter config.
|
||||
|
||||
You will need to use the [base](#available-themes) theme as it is the only modifiable theme.
|
||||
|
||||
| Parameter | Description | Type | Properties |
|
||||
| -------------- | ---------------------------------- | ------ | ----------------------------------------------------------------------------------- |
|
||||
| themeVariables | Modifiable with frontmatter config | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables)) |
|
||||
|
||||
Example of modifying `themeVariables` using frontmatter config:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
theme: 'base'
|
||||
themeVariables:
|
||||
primaryColor: '#BB2528'
|
||||
primaryTextColor: '#fff'
|
||||
primaryBorderColor: '#7C0000'
|
||||
lineColor: '#F8B229'
|
||||
secondaryColor: '#006100'
|
||||
tertiaryColor: '#fff'
|
||||
---
|
||||
graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
B --> G[/Another/]
|
||||
C ==>|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
subgraph section
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
G
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
theme: 'base'
|
||||
themeVariables:
|
||||
primaryColor: '#BB2528'
|
||||
primaryTextColor: '#fff'
|
||||
primaryBorderColor: '#7C0000'
|
||||
lineColor: '#F8B229'
|
||||
secondaryColor: '#006100'
|
||||
tertiaryColor: '#fff'
|
||||
---
|
||||
graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
B --> G[/Another/]
|
||||
C ==>|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
subgraph section
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
G
|
||||
end
|
||||
```
|
||||
|
||||
## Color and Color Calculation
|
||||
|
||||
To ensure diagram readability, the default value of certain variables is calculated or derived from other variables. For example, `primaryBorderColor` is derived from the `primaryColor` variable. So if the `primaryColor` variable is customized, Mermaid will adjust `primaryBorderColor` automatically. Adjustments can mean a color inversion, a hue change, a darkening/lightening by 10%, etc.
|
||||
|
||||
The theming engine will only recognize hex colors and not color names. So, the value `#ff0000` will work, but `red` will not.
|
||||
|
||||
## Theme Variables
|
||||
|
||||
| Variable | Default value | Description |
|
||||
| -------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| darkMode | false | Affects how derived colors are calculated. Set value to `true` for dark mode. |
|
||||
| background | #f4f4f4 | Used to calculate color for items that should either be background colored or contrasting to the background |
|
||||
| fontFamily | trebuchet ms, verdana, arial | Font family for diagram text |
|
||||
| fontSize | 16px | Font size in pixels |
|
||||
| primaryColor | #fff4dd | Color to be used as background in nodes, other colors will be derived from this |
|
||||
| primaryTextColor | calculated from darkMode #ddd/#333 | Color to be used as text color in nodes using `primaryColor` |
|
||||
| secondaryColor | calculated from primaryColor | |
|
||||
| primaryBorderColor | calculated from primaryColor | Color to be used as border in nodes using `primaryColor` |
|
||||
| secondaryBorderColor | calculated from secondaryColor | Color to be used as border in nodes using `secondaryColor` |
|
||||
| secondaryTextColor | calculated from secondaryColor | Color to be used as text color in nodes using `secondaryColor` |
|
||||
| tertiaryColor | calculated from primaryColor | |
|
||||
| tertiaryBorderColor | calculated from tertiaryColor | Color to be used as border in nodes using `tertiaryColor` |
|
||||
| tertiaryTextColor | calculated from tertiaryColor | Color to be used as text color in nodes using `tertiaryColor` |
|
||||
| noteBkgColor | #fff5ad | Color used as background in notes |
|
||||
| noteTextColor | #333 | Text color in note rectangles |
|
||||
| noteBorderColor | calculated from noteBkgColor | Border color in note rectangles |
|
||||
| lineColor | calculated from background | |
|
||||
| textColor | calculated from primaryTextColor | Text in diagram over the background for instance text on labels and on signals in sequence diagram or the title in Gantt diagram |
|
||||
| mainBkg | calculated from primaryColor | Background in flowchart objects like rects/circles, class diagram classes, sequence diagram etc |
|
||||
| errorBkgColor | tertiaryColor | Color for syntax error message |
|
||||
| errorTextColor | tertiaryTextColor | Color for syntax error message |
|
||||
|
||||
## Flowchart Variables
|
||||
|
||||
| Variable | Default value | Description |
|
||||
| ------------------- | ------------------------------ | --------------------------- |
|
||||
| nodeBorder | primaryBorderColor | Node Border Color |
|
||||
| clusterBkg | tertiaryColor | Background in subgraphs |
|
||||
| clusterBorder | tertiaryBorderColor | Cluster Border Color |
|
||||
| defaultLinkColor | lineColor | Link Color |
|
||||
| titleColor | tertiaryTextColor | Title Color |
|
||||
| edgeLabelBackground | calculated from secondaryColor | |
|
||||
| nodeTextColor | primaryTextColor | Color for text inside Nodes |
|
||||
|
||||
## Sequence Diagram Variables
|
||||
|
||||
| Variable | Default value | Description |
|
||||
| --------------------- | ------------------------------ | --------------------------- |
|
||||
| actorBkg | mainBkg | Actor Background Color |
|
||||
| actorBorder | primaryBorderColor | Actor Border Color |
|
||||
| actorTextColor | primaryTextColor | Actor Text Color |
|
||||
| actorLineColor | actorBorder | Actor Line Color |
|
||||
| signalColor | textColor | Signal Color |
|
||||
| signalTextColor | textColor | Signal Text Color |
|
||||
| labelBoxBkgColor | actorBkg | Label Box Background Color |
|
||||
| labelBoxBorderColor | actorBorder | Label Box Border Color |
|
||||
| labelTextColor | actorTextColor | Label Text Color |
|
||||
| loopTextColor | actorTextColor | Loop Text Color |
|
||||
| activationBorderColor | calculated from secondaryColor | Activation Border Color |
|
||||
| activationBkgColor | secondaryColor | Activation Background Color |
|
||||
| sequenceNumberColor | calculated from lineColor | Sequence Number Color |
|
||||
|
||||
## Pie Diagram Variables
|
||||
|
||||
| Variable | Default value | Description |
|
||||
| ------------------- | ------------------------------ | ------------------------------------------ |
|
||||
| pie1 | primaryColor | Fill for 1st section in pie diagram |
|
||||
| pie2 | secondaryColor | Fill for 2nd section in pie diagram |
|
||||
| pie3 | calculated from tertiary | Fill for 3rd section in pie diagram |
|
||||
| pie4 | calculated from primaryColor | Fill for 4th section in pie diagram |
|
||||
| pie5 | calculated from secondaryColor | Fill for 5th section in pie diagram |
|
||||
| pie6 | calculated from tertiaryColor | Fill for 6th section in pie diagram |
|
||||
| pie7 | calculated from primaryColor | Fill for 7th section in pie diagram |
|
||||
| pie8 | calculated from primaryColor | Fill for 8th section in pie diagram |
|
||||
| pie9 | calculated from primaryColor | Fill for 9th section in pie diagram |
|
||||
| pie10 | calculated from primaryColor | Fill for 10th section in pie diagram |
|
||||
| pie11 | calculated from primaryColor | Fill for 11th section in pie diagram |
|
||||
| pie12 | calculated from primaryColor | Fill for 12th section in pie diagram |
|
||||
| pieTitleTextSize | 25px | Title text size |
|
||||
| pieTitleTextColor | taskTextDarkColor | Title text color |
|
||||
| pieSectionTextSize | 17px | Text size of individual section labels |
|
||||
| pieSectionTextColor | textColor | Text color of individual section labels |
|
||||
| pieLegendTextSize | 17px | Text size of labels in diagram legend |
|
||||
| pieLegendTextColor | taskTextDarkColor | Text color of labels in diagram legend |
|
||||
| pieStrokeColor | black | Border color of individual pie sections |
|
||||
| pieStrokeWidth | 2px | Border width of individual pie sections |
|
||||
| pieOuterStrokeWidth | 2px | Border width of pie diagram's outer circle |
|
||||
| pieOuterStrokeColor | black | Border color of pie diagram's outer circle |
|
||||
| pieOpacity | 0.7 | Opacity of individual pie sections |
|
||||
|
||||
## State Colors
|
||||
|
||||
| Variable | Default value | Description |
|
||||
| ------------- | ---------------- | -------------------------------------------- |
|
||||
| labelColor | primaryTextColor | |
|
||||
| altBackground | tertiaryColor | Used for background in deep composite states |
|
||||
|
||||
## Class Colors
|
||||
|
||||
| Variable | Default value | Description |
|
||||
| --------- | ------------- | ------------------------------- |
|
||||
| classText | textColor | Color of Text in class diagrams |
|
||||
|
||||
## User Journey Colors
|
||||
|
||||
| Variable | Default value | Description |
|
||||
| --------- | ------------------------------ | --------------------------------------- |
|
||||
| fillType0 | primaryColor | Fill for 1st section in journey diagram |
|
||||
| fillType1 | secondaryColor | Fill for 2nd section in journey diagram |
|
||||
| fillType2 | calculated from primaryColor | Fill for 3rd section in journey diagram |
|
||||
| fillType3 | calculated from secondaryColor | Fill for 4th section in journey diagram |
|
||||
| fillType4 | calculated from primaryColor | Fill for 5th section in journey diagram |
|
||||
| fillType5 | calculated from secondaryColor | Fill for 6th section in journey diagram |
|
||||
| fillType6 | calculated from primaryColor | Fill for 7th section in journey diagram |
|
||||
| fillType7 | calculated from secondaryColor | Fill for 8th section in journey diagram |
|
||||
@@ -0,0 +1,89 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/tidy-tree.md](../../packages/mermaid/src/docs/config/tidy-tree.md).
|
||||
|
||||
# Tidy-tree Layout
|
||||
|
||||
The **tidy-tree** layout arranges nodes in a hierarchical, tree-like structure. It is especially useful for diagrams where parent-child relationships are important, such as mindmaps.
|
||||
|
||||
## Features
|
||||
|
||||
- Organizes nodes in a tidy, non-overlapping tree
|
||||
- Ideal for mindmaps and hierarchical data
|
||||
- Automatically adjusts spacing for readability
|
||||
|
||||
## Example Usage
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
```
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
```
|
||||
|
||||
## Note
|
||||
|
||||
- Currently, tidy-tree is primarily supported for mindmap diagrams.
|
||||
@@ -0,0 +1,670 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md](../../packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md).
|
||||
|
||||
# Entity Relationship Diagrams
|
||||
|
||||
> An entity–relationship model (or ER model) describes interrelated things of interest in a specific domain of knowledge. A basic ER model is composed of entity types (which classify the things of interest) and specifies relationships that can exist between entities (instances of those entity types) [Wikipedia](https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model).
|
||||
|
||||
Note that practitioners of ER modelling almost always refer to _entity types_ simply as _entities_. For example the `CUSTOMER` entity _type_ would be referred to simply as the `CUSTOMER` entity. This is so common it would be inadvisable to do anything else, but technically an entity is an abstract _instance_ of an entity type, and this is what an ER diagram shows - abstract instances, and the relationships between them. This is why entities are always named using singular nouns.
|
||||
|
||||
Mermaid can render ER diagrams
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: Order example
|
||||
---
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Order example
|
||||
---
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
```
|
||||
|
||||
Entity names are often capitalised, although there is no accepted standard on this, and it is not required in Mermaid.
|
||||
|
||||
Relationships between entities are represented by lines with end markers representing cardinality. Mermaid uses the most popular crow's foot notation. The crow's foot intuitively conveys the possibility of many instances of the entity that it connects to.
|
||||
|
||||
ER diagrams can be used for various purposes, ranging from abstract logical models devoid of any implementation details, through to physical models of relational database tables. It can be useful to include attribute definitions on ER diagrams to aid comprehension of the purpose and meaning of entities. These do not necessarily need to be exhaustive; often a small subset of attributes is enough. Mermaid allows them to be defined in terms of their _type_ and _name_.
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER {
|
||||
string name
|
||||
string custNumber
|
||||
string sector
|
||||
}
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
ORDER {
|
||||
int orderNumber
|
||||
string deliveryAddress
|
||||
}
|
||||
LINE-ITEM {
|
||||
string productCode
|
||||
int quantity
|
||||
float pricePerUnit
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER {
|
||||
string name
|
||||
string custNumber
|
||||
string sector
|
||||
}
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
ORDER {
|
||||
int orderNumber
|
||||
string deliveryAddress
|
||||
}
|
||||
LINE-ITEM {
|
||||
string productCode
|
||||
int quantity
|
||||
float pricePerUnit
|
||||
}
|
||||
```
|
||||
|
||||
When including attributes on ER diagrams, you must decide whether to include foreign keys as attributes. This probably depends on how closely you are trying to represent relational table structures. If your diagram is a _logical_ model which is not meant to imply a relational implementation, then it is better to leave these out because the associative relationships already convey the way that entities are associated. For example, a JSON data structure can implement a one-to-many relationship without the need for foreign key properties, using arrays. Similarly an object-oriented programming language may use pointers or references to collections. Even for models that are intended for relational implementation, you might decide that inclusion of foreign key attributes duplicates information already portrayed by the relationships, and does not add meaning to entities. Ultimately, it's your choice.
|
||||
|
||||
## Syntax
|
||||
|
||||
### Entities and Relationships
|
||||
|
||||
Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to label the relationship. Each statement consists of the following parts:
|
||||
|
||||
```
|
||||
<first-entity> [<relationship> <second-entity> : <relationship-label>]
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `first-entity` is the name of an entity. Names support any unicode characters and can include spaces if surrounded by double quotes (e.g. "name with space").
|
||||
- `relationship` describes the way that both entities inter-relate. See below.
|
||||
- `second-entity` is the name of the other entity.
|
||||
- `relationship-label` describes the relationship from the perspective of the first entity.
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
PROPERTY ||--|{ ROOM : contains
|
||||
```
|
||||
|
||||
This statement can be read as _a property contains one or more rooms, and a room is part of one and only one property_. You can see that the label here is from the first entity's perspective: a property contains a room, but a room does not contain a property. When considered from the perspective of the second entity, the equivalent label is usually very easy to infer. (Some ER diagrams label relationships from both perspectives, but this is not supported here, and is usually superfluous).
|
||||
|
||||
Only the `first-entity` part of a statement is mandatory. This makes it possible to show an entity with no relationships, which can be useful during iterative construction of diagrams. If any other parts of a statement are specified, then all parts are mandatory.
|
||||
|
||||
#### Unicode text
|
||||
|
||||
Entity names, relationships, and attributes all support unicode text.
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
"This ❤ Unicode"
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
"This ❤ Unicode"
|
||||
```
|
||||
|
||||
#### Markdown formatting
|
||||
|
||||
Markdown formatting and text is also supported.
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
"This **is** _Markdown_"
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
"This **is** _Markdown_"
|
||||
```
|
||||
|
||||
### Relationship Syntax
|
||||
|
||||
The `relationship` part of each statement can be broken down into three sub-components:
|
||||
|
||||
- the cardinality of the first entity with respect to the second
|
||||
- whether the relationship confers identity on a 'child' entity
|
||||
- the cardinality of the second entity with respect to the first
|
||||
|
||||
Cardinality is a property that describes how many elements of another entity can be related to the entity in question. In the above example a `PROPERTY` can have one or more `ROOM` instances associated to it, whereas a `ROOM` can only be associated with one `PROPERTY`. In each cardinality marker there are two characters. The outermost character represents a maximum value, and the innermost character represents a minimum value. The table below summarises possible cardinalities.
|
||||
|
||||
| Value (left) | Value (right) | Meaning |
|
||||
| :----------: | :-----------: | ----------------------------- |
|
||||
| `\|o` | `o\|` | Zero or one |
|
||||
| `\|\|` | `\|\|` | Exactly one |
|
||||
| `}o` | `o{` | Zero or more (no upper limit) |
|
||||
| `}\|` | `\|{` | One or more (no upper limit) |
|
||||
|
||||
**Aliases**
|
||||
|
||||
| Value (left) | Value (right) | Alias for |
|
||||
| :----------: | :-----------: | ------------ |
|
||||
| one or zero | one or zero | Zero or one |
|
||||
| zero or one | zero or one | Zero or one |
|
||||
| one or more | one or more | One or more |
|
||||
| one or many | one or many | One or more |
|
||||
| many(1) | many(1) | One or more |
|
||||
| 1+ | 1+ | One or more |
|
||||
| zero or more | zero or more | Zero or more |
|
||||
| zero or many | zero or many | Zero or more |
|
||||
| many(0) | many(0) | Zero or more |
|
||||
| 0+ | 0+ | Zero or more |
|
||||
| only one | only one | Exactly one |
|
||||
| 1 | 1 | Exactly one |
|
||||
|
||||
### Identification
|
||||
|
||||
Relationships may be classified as either _identifying_ or _non-identifying_ and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question cannot have independent existence without the other. For example a firm that insures people to drive cars might need to store data on `NAMED-DRIVER`s. In modelling this we might start out by observing that a `CAR` can be driven by many `PERSON` instances, and a `PERSON` can drive many `CAR`s - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: `PERSON }|..|{ CAR : "driver"`. Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a `NAMED-DRIVER` cannot exist without both a `PERSON` and a `CAR` - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
|
||||
|
||||
| Value | Alias for |
|
||||
| :---: | :---------------: |
|
||||
| -- | _identifying_ |
|
||||
| .. | _non-identifying_ |
|
||||
|
||||
**Aliases**
|
||||
|
||||
| Value | Alias for |
|
||||
| :-----------: | :---------------: |
|
||||
| to | _identifying_ |
|
||||
| optionally to | _non-identifying_ |
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
PERSON }o..o{ NAMED-DRIVER : is
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
PERSON }o..o{ NAMED-DRIVER : is
|
||||
```
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
CAR 1 to zero or more NAMED-DRIVER : allows
|
||||
PERSON many(0) optionally to 0+ NAMED-DRIVER : is
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CAR 1 to zero or more NAMED-DRIVER : allows
|
||||
PERSON many(0) optionally to 0+ NAMED-DRIVER : is
|
||||
```
|
||||
|
||||
### Attributes
|
||||
|
||||
Attributes can be defined for entities by specifying the entity name followed by a block containing multiple `type name` pairs, where a block is delimited by an opening `{` and a closing `}`. The attributes are rendered inside the entity boxes. For example:
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
CAR {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON ||--o{ NAMED-DRIVER : is
|
||||
PERSON {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
CAR {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON ||--o{ NAMED-DRIVER : is
|
||||
PERSON {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
```
|
||||
|
||||
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
|
||||
|
||||
### Entity Name Aliases
|
||||
|
||||
An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name. Alias names follow all of the same rules as entity names.
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
p[Person] {
|
||||
string firstName
|
||||
string lastName
|
||||
}
|
||||
a["Customer Account"] {
|
||||
string email
|
||||
}
|
||||
p ||--o| a : has
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
p[Person] {
|
||||
string firstName
|
||||
string lastName
|
||||
}
|
||||
a["Customer Account"] {
|
||||
string email
|
||||
}
|
||||
p ||--o| a : has
|
||||
```
|
||||
|
||||
#### Attribute Keys and Comments
|
||||
|
||||
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key (markdown formatting and unicode is not supported for keys). To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`). A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
CAR {
|
||||
string registrationNumber PK
|
||||
string make
|
||||
string model
|
||||
string[] parts
|
||||
}
|
||||
PERSON ||--o{ NAMED-DRIVER : is
|
||||
PERSON {
|
||||
string driversLicense PK "The license #"
|
||||
string(99) firstName "Only 99 characters are allowed"
|
||||
string lastName
|
||||
string phone UK
|
||||
int age
|
||||
}
|
||||
NAMED-DRIVER {
|
||||
string carRegistrationNumber PK, FK
|
||||
string driverLicence PK, FK
|
||||
}
|
||||
MANUFACTURER only one to zero or more CAR : makes
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
CAR {
|
||||
string registrationNumber PK
|
||||
string make
|
||||
string model
|
||||
string[] parts
|
||||
}
|
||||
PERSON ||--o{ NAMED-DRIVER : is
|
||||
PERSON {
|
||||
string driversLicense PK "The license #"
|
||||
string(99) firstName "Only 99 characters are allowed"
|
||||
string lastName
|
||||
string phone UK
|
||||
int age
|
||||
}
|
||||
NAMED-DRIVER {
|
||||
string carRegistrationNumber PK, FK
|
||||
string driverLicence PK, FK
|
||||
}
|
||||
MANUFACTURER only one to zero or more CAR : makes
|
||||
```
|
||||
|
||||
### Direction
|
||||
|
||||
The direction statement declares the direction of the diagram.
|
||||
|
||||
This declares that the diagram is oriented from top to bottom (`TB`). This can be reversed to be oriented from bottom to top (`BT`).
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
direction TB
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER {
|
||||
string name
|
||||
string custNumber
|
||||
string sector
|
||||
}
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
ORDER {
|
||||
int orderNumber
|
||||
string deliveryAddress
|
||||
}
|
||||
LINE-ITEM {
|
||||
string productCode
|
||||
int quantity
|
||||
float pricePerUnit
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
direction TB
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER {
|
||||
string name
|
||||
string custNumber
|
||||
string sector
|
||||
}
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
ORDER {
|
||||
int orderNumber
|
||||
string deliveryAddress
|
||||
}
|
||||
LINE-ITEM {
|
||||
string productCode
|
||||
int quantity
|
||||
float pricePerUnit
|
||||
}
|
||||
```
|
||||
|
||||
This declares that the diagram is oriented from left to right (`LR`). This can be reversed to be oriented from right to left (`RL`).
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
direction LR
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER {
|
||||
string name
|
||||
string custNumber
|
||||
string sector
|
||||
}
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
ORDER {
|
||||
int orderNumber
|
||||
string deliveryAddress
|
||||
}
|
||||
LINE-ITEM {
|
||||
string productCode
|
||||
int quantity
|
||||
float pricePerUnit
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
direction LR
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER {
|
||||
string name
|
||||
string custNumber
|
||||
string sector
|
||||
}
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
ORDER {
|
||||
int orderNumber
|
||||
string deliveryAddress
|
||||
}
|
||||
LINE-ITEM {
|
||||
string productCode
|
||||
int quantity
|
||||
float pricePerUnit
|
||||
}
|
||||
```
|
||||
|
||||
Possible diagram orientations are:
|
||||
|
||||
- TB - Top to bottom
|
||||
- BT - Bottom to top
|
||||
- RL - Right to left
|
||||
- LR - Left to right
|
||||
|
||||
### Styling a node
|
||||
|
||||
It is possible to apply specific styles such as a thicker border or a different background color to a node.
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
id1||--||id2 : label
|
||||
style id1 fill:#f9f,stroke:#333,stroke-width:4px
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
id1||--||id2 : label
|
||||
style id1 fill:#f9f,stroke:#333,stroke-width:4px
|
||||
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
|
||||
```
|
||||
|
||||
It is also possible to attach styles to a list of nodes in one statement:
|
||||
|
||||
```
|
||||
style nodeId1,nodeId2 styleList
|
||||
```
|
||||
|
||||
#### Classes
|
||||
|
||||
More convenient than defining the style every time is to define a class of styles and attach this class to the nodes that
|
||||
should have a different look.
|
||||
|
||||
A class definition looks like the example below:
|
||||
|
||||
```
|
||||
classDef className fill:#f9f,stroke:#333,stroke-width:4px
|
||||
```
|
||||
|
||||
It is also possible to define multiple classes in one statement:
|
||||
|
||||
```
|
||||
classDef firstClassName,secondClassName font-size:12pt
|
||||
```
|
||||
|
||||
Attachment of a class to a node is done as per below:
|
||||
|
||||
```
|
||||
class nodeId1 className
|
||||
```
|
||||
|
||||
It is also possible to attach a class to a list of nodes in one statement:
|
||||
|
||||
```
|
||||
class nodeId1,nodeId2 className
|
||||
```
|
||||
|
||||
Multiple classes can be attached at the same time as well:
|
||||
|
||||
```
|
||||
class nodeId1,nodeId2 className1,className2
|
||||
```
|
||||
|
||||
A shorter form of adding a class is to attach the classname to the node using the `:::`operator as per below:
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
direction TB
|
||||
CAR:::someclass {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON:::someclass {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
HOUSE:::someclass
|
||||
|
||||
classDef someclass fill:#f96
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
direction TB
|
||||
CAR:::someclass {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON:::someclass {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
HOUSE:::someclass
|
||||
|
||||
classDef someclass fill:#f96
|
||||
```
|
||||
|
||||
This form can be used when declaring relationships between entities:
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
CAR {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
PERSON:::foo ||--|| CAR : owns
|
||||
PERSON o{--|| HOUSE:::bar : has
|
||||
|
||||
classDef foo stroke:#f00
|
||||
classDef bar stroke:#0f0
|
||||
classDef foobar stroke:#00f
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CAR {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
PERSON:::foo ||--|| CAR : owns
|
||||
PERSON o{--|| HOUSE:::bar : has
|
||||
|
||||
classDef foo stroke:#f00
|
||||
classDef bar stroke:#0f0
|
||||
classDef foobar stroke:#00f
|
||||
```
|
||||
|
||||
Similar to the class statement, the shorthand syntax can also apply multiple classes at once:
|
||||
|
||||
```
|
||||
nodeId:::className1,className2
|
||||
```
|
||||
|
||||
### Default class
|
||||
|
||||
If a class is named default it will be assigned to all classes without specific class definitions.
|
||||
|
||||
```
|
||||
classDef default fill:#f9f,stroke:#333,stroke-width:4px;
|
||||
```
|
||||
|
||||
> **Note:** Custom styles from style or other class statements take priority and will overwrite the default styles. (e.g. The `default` class gives nodes a background color of pink but the `blue` class will give that node a background color of blue if applied.)
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
CAR {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
PERSON:::foo ||--|| CAR : owns
|
||||
PERSON o{--|| HOUSE:::bar : has
|
||||
|
||||
classDef default fill:#f9f,stroke-width:4px
|
||||
classDef foo stroke:#f00
|
||||
classDef bar stroke:#0f0
|
||||
classDef foobar stroke:#00f
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
CAR {
|
||||
string registrationNumber
|
||||
string make
|
||||
string model
|
||||
}
|
||||
PERSON {
|
||||
string firstName
|
||||
string lastName
|
||||
int age
|
||||
}
|
||||
PERSON:::foo ||--|| CAR : owns
|
||||
PERSON o{--|| HOUSE:::bar : has
|
||||
|
||||
classDef default fill:#f9f,stroke-width:4px
|
||||
classDef foo stroke:#f00
|
||||
classDef bar stroke:#0f0
|
||||
classDef foobar stroke:#00f
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Layout
|
||||
|
||||
The layout of the diagram is handled by [`render()`](../config/setup/mermaid/interfaces/Mermaid.md#render). The default layout is dagre.
|
||||
|
||||
For larger or more-complex diagrams, you can alternatively apply the ELK (Eclipse Layout Kernel) layout using your YAML frontmatter's `config`. For more information, see [Customizing ELK Layout](../intro/syntax-reference.md#customizing-elk-layout).
|
||||
|
||||
```yaml
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
```
|
||||
|
||||
Your Mermaid code should be similar to the following:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: Order example
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Order example
|
||||
config:
|
||||
layout: elk
|
||||
---
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE-ITEM : contains
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : uses
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> Note that the site needs to use mermaid version 9.4+ for this to work and have this featured enabled in the lazy-loading configuration.
|
||||
|
||||
<!--- cspell:locale en,en-gb --->
|
||||
@@ -0,0 +1,301 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/examples.md](../../packages/mermaid/src/docs/syntax/examples.md).
|
||||
|
||||
# Examples
|
||||
|
||||
This page contains a collection of examples of diagrams and charts that can be created through mermaid and its myriad applications.
|
||||
|
||||
**If you wish to learn how to support mermaid on your webpage, read the [Beginner's Guide](../config/usage.md?id=usage).**
|
||||
|
||||
**If you wish to learn about mermaid's syntax, Read the [Diagram Syntax](../syntax/flowchart.md?id=flowcharts-basic-syntax) section.**
|
||||
|
||||
## Basic Pie Chart
|
||||
|
||||
```mermaid-example
|
||||
pie title NETFLIX
|
||||
"Time spent looking for movie" : 90
|
||||
"Time spent watching it" : 10
|
||||
```
|
||||
|
||||
```mermaid
|
||||
pie title NETFLIX
|
||||
"Time spent looking for movie" : 90
|
||||
"Time spent watching it" : 10
|
||||
```
|
||||
|
||||
```mermaid-example
|
||||
pie title What Voldemort doesn't have?
|
||||
"FRIENDS" : 2
|
||||
"FAMILY" : 3
|
||||
"NOSE" : 45
|
||||
```
|
||||
|
||||
```mermaid
|
||||
pie title What Voldemort doesn't have?
|
||||
"FRIENDS" : 2
|
||||
"FAMILY" : 3
|
||||
"NOSE" : 45
|
||||
```
|
||||
|
||||
## Basic sequence diagram
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
Alice ->> Bob: Hello Bob, how are you?
|
||||
Bob-->>John: How about you John?
|
||||
Bob--x Alice: I am good thanks!
|
||||
Bob-x John: I am good thanks!
|
||||
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
|
||||
|
||||
Bob-->Alice: Checking with John...
|
||||
Alice->John: Yes... John, how are you?
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice ->> Bob: Hello Bob, how are you?
|
||||
Bob-->>John: How about you John?
|
||||
Bob--x Alice: I am good thanks!
|
||||
Bob-x John: I am good thanks!
|
||||
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
|
||||
|
||||
Bob-->Alice: Checking with John...
|
||||
Alice->John: Yes... John, how are you?
|
||||
```
|
||||
|
||||
## Basic flowchart
|
||||
|
||||
```mermaid-example
|
||||
graph LR
|
||||
A[Square Rect] -- Link text --> B((Circle))
|
||||
A --> C(Round Rect)
|
||||
B --> D{Rhombus}
|
||||
C --> D
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[Square Rect] -- Link text --> B((Circle))
|
||||
A --> C(Round Rect)
|
||||
B --> D{Rhombus}
|
||||
C --> D
|
||||
```
|
||||
|
||||
## Larger flowchart with some styling
|
||||
|
||||
```mermaid-example
|
||||
graph TB
|
||||
sq[Square shape] --> ci((Circle shape))
|
||||
|
||||
subgraph A
|
||||
od>Odd shape]-- Two line<br/>edge comment --> ro
|
||||
di{Diamond with <br/> line break} -.-> ro(Rounded<br>square<br>shape)
|
||||
di==>ro2(Rounded square shape)
|
||||
end
|
||||
|
||||
%% Notice that no text in shape are added here instead that is appended further down
|
||||
e --> od3>Really long text with linebreak<br>in an Odd shape]
|
||||
|
||||
%% Comments after double percent signs
|
||||
e((Inner / circle<br>and some odd <br>special characters)) --> f(,.?!+-*ز)
|
||||
|
||||
cyr[Cyrillic]-->cyr2((Circle shape Начало));
|
||||
|
||||
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
|
||||
classDef orange fill:#f96,stroke:#333,stroke-width:4px;
|
||||
class sq,e green
|
||||
class di orange
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
sq[Square shape] --> ci((Circle shape))
|
||||
|
||||
subgraph A
|
||||
od>Odd shape]-- Two line<br/>edge comment --> ro
|
||||
di{Diamond with <br/> line break} -.-> ro(Rounded<br>square<br>shape)
|
||||
di==>ro2(Rounded square shape)
|
||||
end
|
||||
|
||||
%% Notice that no text in shape are added here instead that is appended further down
|
||||
e --> od3>Really long text with linebreak<br>in an Odd shape]
|
||||
|
||||
%% Comments after double percent signs
|
||||
e((Inner / circle<br>and some odd <br>special characters)) --> f(,.?!+-*ز)
|
||||
|
||||
cyr[Cyrillic]-->cyr2((Circle shape Начало));
|
||||
|
||||
classDef green fill:#9f6,stroke:#333,stroke-width:2px;
|
||||
classDef orange fill:#f96,stroke:#333,stroke-width:4px;
|
||||
class sq,e green
|
||||
class di orange
|
||||
```
|
||||
|
||||
## SequenceDiagram: Loops, alt and opt
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
loop Daily query
|
||||
Alice->>Bob: Hello Bob, how are you?
|
||||
alt is sick
|
||||
Bob->>Alice: Not so good :(
|
||||
else is well
|
||||
Bob->>Alice: Feeling fresh like a daisy
|
||||
end
|
||||
|
||||
opt Extra response
|
||||
Bob->>Alice: Thanks for asking
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
loop Daily query
|
||||
Alice->>Bob: Hello Bob, how are you?
|
||||
alt is sick
|
||||
Bob->>Alice: Not so good :(
|
||||
else is well
|
||||
Bob->>Alice: Feeling fresh like a daisy
|
||||
end
|
||||
|
||||
opt Extra response
|
||||
Bob->>Alice: Thanks for asking
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## SequenceDiagram: Message to self in loop
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
Alice->>John: Hello John, how are you?
|
||||
loop HealthCheck
|
||||
John->>John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts<br/>prevail...
|
||||
John-->>Alice: Great!
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
Alice->>John: Hello John, how are you?
|
||||
loop HealthCheck
|
||||
John->>John: Fight against hypochondria
|
||||
end
|
||||
Note right of John: Rational thoughts<br/>prevail...
|
||||
John-->>Alice: Great!
|
||||
John->>Bob: How about you?
|
||||
Bob-->>John: Jolly good!
|
||||
```
|
||||
|
||||
## Sequence Diagram: Blogging app service communication
|
||||
|
||||
```mermaid-example
|
||||
sequenceDiagram
|
||||
participant web as Web Browser
|
||||
participant blog as Blog Service
|
||||
participant account as Account Service
|
||||
participant mail as Mail Service
|
||||
participant db as Storage
|
||||
|
||||
Note over web,db: The user must be logged in to submit blog posts
|
||||
web->>+account: Logs in using credentials
|
||||
account->>db: Query stored accounts
|
||||
db->>account: Respond with query result
|
||||
|
||||
alt Credentials not found
|
||||
account->>web: Invalid credentials
|
||||
else Credentials found
|
||||
account->>-web: Successfully logged in
|
||||
|
||||
Note over web,db: When the user is authenticated, they can now submit new posts
|
||||
web->>+blog: Submit new post
|
||||
blog->>db: Store post data
|
||||
|
||||
par Notifications
|
||||
blog--)mail: Send mail to blog subscribers
|
||||
blog--)db: Store in-site notifications
|
||||
and Response
|
||||
blog-->>-web: Successfully posted
|
||||
end
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant web as Web Browser
|
||||
participant blog as Blog Service
|
||||
participant account as Account Service
|
||||
participant mail as Mail Service
|
||||
participant db as Storage
|
||||
|
||||
Note over web,db: The user must be logged in to submit blog posts
|
||||
web->>+account: Logs in using credentials
|
||||
account->>db: Query stored accounts
|
||||
db->>account: Respond with query result
|
||||
|
||||
alt Credentials not found
|
||||
account->>web: Invalid credentials
|
||||
else Credentials found
|
||||
account->>-web: Successfully logged in
|
||||
|
||||
Note over web,db: When the user is authenticated, they can now submit new posts
|
||||
web->>+blog: Submit new post
|
||||
blog->>db: Store post data
|
||||
|
||||
par Notifications
|
||||
blog--)mail: Send mail to blog subscribers
|
||||
blog--)db: Store in-site notifications
|
||||
and Response
|
||||
blog-->>-web: Successfully posted
|
||||
end
|
||||
end
|
||||
|
||||
```
|
||||
|
||||
## A commit flow diagram.
|
||||
|
||||
```mermaid-example
|
||||
gitGraph:
|
||||
commit "Ashish"
|
||||
branch newbranch
|
||||
checkout newbranch
|
||||
commit id:"1111"
|
||||
commit tag:"test"
|
||||
checkout main
|
||||
commit type: HIGHLIGHT
|
||||
commit
|
||||
merge newbranch
|
||||
commit
|
||||
branch b2
|
||||
commit
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gitGraph:
|
||||
commit "Ashish"
|
||||
branch newbranch
|
||||
checkout newbranch
|
||||
commit id:"1111"
|
||||
commit tag:"test"
|
||||
checkout main
|
||||
commit type: HIGHLIGHT
|
||||
commit
|
||||
merge newbranch
|
||||
commit
|
||||
branch b2
|
||||
commit
|
||||
```
|
||||
|
||||
<!--- cspell:ignore Ashish newbranch --->
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,708 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/gantt.md](../../packages/mermaid/src/docs/syntax/gantt.md).
|
||||
|
||||
# Gantt diagrams
|
||||
|
||||
> A Gantt chart is a type of bar chart, first developed by Karol Adamiecki in 1896, and independently by Henry Gantt in the 1910s, that illustrates a project schedule and the amount of time it would take for any one project to finish. Gantt charts illustrate number of days between the start and finish dates of the terminal elements and summary elements of a project.
|
||||
|
||||
## A note to users
|
||||
|
||||
Gantt Charts will record each scheduled task as one continuous bar that extends from the left to the right. The x axis represents time and the y records the different tasks and the order in which they are to be completed.
|
||||
|
||||
It is important to remember that when a date, day, or collection of dates specific to a task are "excluded", the Gantt Chart will accommodate those changes by extending an equal number of days, towards the right, not by creating a gap inside the task.
|
||||
As shown here 
|
||||
|
||||
However, if the excluded dates are between two tasks that are set to start consecutively, the excluded dates will be skipped graphically and left blank, and the following task will begin after the end of the excluded dates.
|
||||
As shown here 
|
||||
|
||||
A Gantt chart is useful for tracking the amount of time it would take before a project is finished, but it can also be used to graphically represent "non-working days", with a few tweaks.
|
||||
|
||||
Mermaid can render Gantt diagrams as SVG, PNG or a MarkDown link that can be pasted into docs.
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :after a1, 20d
|
||||
section Another
|
||||
Task in Another :2014-01-12, 12d
|
||||
another task :24d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :after a1, 20d
|
||||
section Another
|
||||
Task in Another :2014-01-12, 12d
|
||||
another task :24d
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
title Adding GANTT diagram functionality to mermaid
|
||||
excludes weekends
|
||||
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
|
||||
|
||||
section A section
|
||||
Completed task :done, des1, 2014-01-06,2014-01-08
|
||||
Active task :active, des2, 2014-01-09, 3d
|
||||
Future task : des3, after des2, 5d
|
||||
Future task2 : des4, after des3, 5d
|
||||
|
||||
section Critical tasks
|
||||
Completed task in the critical line :crit, done, 2014-01-06,24h
|
||||
Implement parser and jison :crit, done, after des1, 2d
|
||||
Create tests for parser :crit, active, 3d
|
||||
Future task in critical line :crit, 5d
|
||||
Create tests for renderer :2d
|
||||
Add to mermaid :until isadded
|
||||
Functionality added :milestone, isadded, 2014-01-25, 0d
|
||||
|
||||
section Documentation
|
||||
Describe gantt syntax :active, a1, after des1, 3d
|
||||
Add gantt diagram to demo page :after a1 , 20h
|
||||
Add another diagram to demo page :doc1, after a1 , 48h
|
||||
|
||||
section Last section
|
||||
Describe gantt syntax :after doc1, 3d
|
||||
Add gantt diagram to demo page :20h
|
||||
Add another diagram to demo page :48h
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
title Adding GANTT diagram functionality to mermaid
|
||||
excludes weekends
|
||||
%% (`excludes` accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".)
|
||||
|
||||
section A section
|
||||
Completed task :done, des1, 2014-01-06,2014-01-08
|
||||
Active task :active, des2, 2014-01-09, 3d
|
||||
Future task : des3, after des2, 5d
|
||||
Future task2 : des4, after des3, 5d
|
||||
|
||||
section Critical tasks
|
||||
Completed task in the critical line :crit, done, 2014-01-06,24h
|
||||
Implement parser and jison :crit, done, after des1, 2d
|
||||
Create tests for parser :crit, active, 3d
|
||||
Future task in critical line :crit, 5d
|
||||
Create tests for renderer :2d
|
||||
Add to mermaid :until isadded
|
||||
Functionality added :milestone, isadded, 2014-01-25, 0d
|
||||
|
||||
section Documentation
|
||||
Describe gantt syntax :active, a1, after des1, 3d
|
||||
Add gantt diagram to demo page :after a1 , 20h
|
||||
Add another diagram to demo page :doc1, after a1 , 48h
|
||||
|
||||
section Last section
|
||||
Describe gantt syntax :after doc1, 3d
|
||||
Add gantt diagram to demo page :20h
|
||||
Add another diagram to demo page :48h
|
||||
```
|
||||
|
||||
Tasks are by default sequential. A task start date defaults to the end date of the preceding task.
|
||||
|
||||
A colon, `:`, separates the task title from its metadata.
|
||||
Metadata items are separated by a comma, `,`. Valid tags are `active`, `done`, `crit`, and `milestone`. Tags are optional, but if used, they must be specified first.
|
||||
After processing the tags, the remaining metadata items are interpreted as follows:
|
||||
|
||||
1. If a single item is specified, it determines when the task ends. It can either be a specific date/time or a duration. If a duration is specified, it is added to the start date of the task to determine the end date of the task, taking into account any exclusions.
|
||||
2. If two items are specified, the last item is interpreted as in the previous case. The first item can either specify an explicit start date/time (in the format specified by `dateFormat`) or reference another task using `after <otherTaskID> [[otherTaskID2 [otherTaskID3]]...]`. In the latter case, the start date of the task will be set according to the latest end date of any referenced task.
|
||||
3. If three items are specified, the last two will be interpreted as in the previous case. The first item will denote the ID of the task, which can be referenced using the `later <taskID>` syntax.
|
||||
|
||||
| Metadata syntax | Start date | End date | ID |
|
||||
| ---------------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------- | -------- |
|
||||
| `<taskID>, <startDate>, <endDate>` | `startdate` as interpreted using `dateformat` | `endDate` as interpreted using `dateformat` | `taskID` |
|
||||
| `<taskID>, <startDate>, <length>` | `startdate` as interpreted using `dateformat` | Start date + `length` | `taskID` |
|
||||
| `<taskID>, after <otherTaskId>, <endDate>` | End date of previously specified task `otherTaskID` | `endDate` as interpreted using `dateformat` | `taskID` |
|
||||
| `<taskID>, after <otherTaskId>, <length>` | End date of previously specified task `otherTaskID` | Start date + `length` | `taskID` |
|
||||
| `<taskID>, <startDate>, until <otherTaskId>` | `startdate` as interpreted using `dateformat` | Start date of previously specified task `otherTaskID` | `taskID` |
|
||||
| `<taskID>, after <otherTaskId>, until <otherTaskId>` | End date of previously specified task `otherTaskID` | Start date of previously specified task `otherTaskID` | `taskID` |
|
||||
| `<startDate>, <endDate>` | `startdate` as interpreted using `dateformat` | `enddate` as interpreted using `dateformat` | n/a |
|
||||
| `<startDate>, <length>` | `startdate` as interpreted using `dateformat` | Start date + `length` | n/a |
|
||||
| `after <otherTaskID>, <endDate>` | End date of previously specified task `otherTaskID` | `enddate` as interpreted using `dateformat` | n/a |
|
||||
| `after <otherTaskID>, <length>` | End date of previously specified task `otherTaskID` | Start date + `length` | n/a |
|
||||
| `<startDate>, until <otherTaskId>` | `startdate` as interpreted using `dateformat` | Start date of previously specified task `otherTaskID` | n/a |
|
||||
| `after <otherTaskId>, until <otherTaskId>` | End date of previously specified task `otherTaskID` | Start date of previously specified task `otherTaskID` | n/a |
|
||||
| `<endDate>` | End date of preceding task | `enddate` as interpreted using `dateformat` | n/a |
|
||||
| `<length>` | End date of preceding task | Start date + `length` | n/a |
|
||||
| `until <otherTaskId>` | End date of preceding task | Start date of previously specified task `otherTaskID` | n/a |
|
||||
|
||||
> **Note**
|
||||
> Support for keyword `until` was added in (v10.9.0+). This can be used to define a task which is running until some other specific task or milestone starts.
|
||||
|
||||
For simplicity, the table does not show the use of multiple tasks listed with the `after` keyword. Here is an example of how to use it and how it's interpreted:
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
apple :a, 2017-07-20, 1w
|
||||
banana :crit, b, 2017-07-23, 1d
|
||||
cherry :active, c, after b a, 1d
|
||||
kiwi :d, 2017-07-20, until b c
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
apple :a, 2017-07-20, 1w
|
||||
banana :crit, b, 2017-07-23, 1d
|
||||
cherry :active, c, after b a, 1d
|
||||
kiwi :d, 2017-07-20, until b c
|
||||
```
|
||||
|
||||
### Title
|
||||
|
||||
The `title` is an _optional_ string to be displayed at the top of the Gantt chart to describe the chart as a whole.
|
||||
|
||||
### Excludes
|
||||
|
||||
The `excludes` is an _optional_ attribute that accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".
|
||||
These date will be marked on the graph, and be excluded from the duration calculation of tasks. Meaning that if there are excluded dates during a task interval, the number of 'skipped' days will be added to the end of the task to ensure the duration is as specified in the code.
|
||||
|
||||
#### Weekend (v\11.0.0+)
|
||||
|
||||
When excluding weekends, it is possible to configure the weekends to be either Friday and Saturday or Saturday and Sunday. By default weekends are Saturday and Sunday.
|
||||
To define the weekend start day, there is an _optional_ attribute `weekend` that can be added in a new line followed by either `friday` or `saturday`.
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
title A Gantt Diagram Excluding Fri - Sat weekends
|
||||
dateFormat YYYY-MM-DD
|
||||
excludes weekends
|
||||
weekend friday
|
||||
section Section
|
||||
A task :a1, 2024-01-01, 30d
|
||||
Another task :after a1, 20d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title A Gantt Diagram Excluding Fri - Sat weekends
|
||||
dateFormat YYYY-MM-DD
|
||||
excludes weekends
|
||||
weekend friday
|
||||
section Section
|
||||
A task :a1, 2024-01-01, 30d
|
||||
Another task :after a1, 20d
|
||||
```
|
||||
|
||||
### Section statements
|
||||
|
||||
You can divide the chart into various sections, for example to separate different parts of a project like development and documentation.
|
||||
|
||||
To do so, start a line with the `section` keyword and give it a name. (Note that unlike with the [title for the entire chart](#title), this name is _required_.
|
||||
|
||||
### Milestones
|
||||
|
||||
You can add milestones to the diagrams. Milestones differ from tasks as they represent a single instant in time and are identified by the keyword `milestone`. Below is an example on how to use milestones. As you may notice, the exact location of the milestone is determined by the initial date for the milestone and the "duration" of the task this way: _initial date_+_duration_/2.
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
dateFormat HH:mm
|
||||
axisFormat %H:%M
|
||||
Initial milestone : milestone, m1, 17:49, 2m
|
||||
Task A : 10m
|
||||
Task B : 5m
|
||||
Final milestone : milestone, m2, 18:08, 4m
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
dateFormat HH:mm
|
||||
axisFormat %H:%M
|
||||
Initial milestone : milestone, m1, 17:49, 2m
|
||||
Task A : 10m
|
||||
Task B : 5m
|
||||
Final milestone : milestone, m2, 18:08, 4m
|
||||
```
|
||||
|
||||
### Vertical Markers
|
||||
|
||||
The `vert` keyword lets you add vertical lines to your Gantt chart, making it easy to highlight important dates like deadlines, events, or checkpoints. These markers extend across the entire chart and are positioned based on the date you provide. Unlike milestones, vertical markers don’t take up a row. They’re purely visual reference points that help break up the timeline and make important moments easier to spot.
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
dateFormat HH:mm
|
||||
axisFormat %H:%M
|
||||
Initial vert : vert, v1, 17:30, 2m
|
||||
Task A : 3m
|
||||
Task B : 8m
|
||||
Final vert : vert, v2, 17:58, 4m
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
dateFormat HH:mm
|
||||
axisFormat %H:%M
|
||||
Initial vert : vert, v1, 17:30, 2m
|
||||
Task A : 3m
|
||||
Task B : 8m
|
||||
Final vert : vert, v2, 17:58, 4m
|
||||
```
|
||||
|
||||
## Setting dates
|
||||
|
||||
`dateFormat` defines the format of the date **input** of your gantt elements. How these dates are represented in the rendered chart **output** are defined by `axisFormat`.
|
||||
|
||||
### Input date format
|
||||
|
||||
The default input date format is `YYYY-MM-DD`. You can define your custom `dateFormat`.
|
||||
|
||||
```markdown
|
||||
dateFormat YYYY-MM-DD
|
||||
```
|
||||
|
||||
The following formatting options are supported:
|
||||
|
||||
| Input | Example | Description |
|
||||
| ---------- | -------------- | ------------------------------------------------------ |
|
||||
| `YYYY` | 2014 | 4 digit year |
|
||||
| `YY` | 14 | 2 digit year |
|
||||
| `Q` | 1..4 | Quarter of year. Sets month to first month in quarter. |
|
||||
| `M MM` | 1..12 | Month number |
|
||||
| `MMM MMMM` | January..Dec | Month name in locale set by `dayjs.locale()` |
|
||||
| `D DD` | 1..31 | Day of month |
|
||||
| `Do` | 1st..31st | Day of month with ordinal |
|
||||
| `DDD DDDD` | 1..365 | Day of year |
|
||||
| `X` | 1410715640.579 | Unix timestamp |
|
||||
| `x` | 1410715640579 | Unix ms timestamp |
|
||||
| `H HH` | 0..23 | 24 hour time |
|
||||
| `h hh` | 1..12 | 12 hour time used with `a A`. |
|
||||
| `a A` | am pm | Post or ante meridiem |
|
||||
| `m mm` | 0..59 | Minutes |
|
||||
| `s ss` | 0..59 | Seconds |
|
||||
| `S` | 0..9 | Tenths of a second |
|
||||
| `SS` | 0..99 | Hundreds of a second |
|
||||
| `SSS` | 0..999 | Thousandths of a second |
|
||||
| `Z ZZ` | +12:00 | Offset from UTC as +-HH:mm, +-HHmm, or Z |
|
||||
|
||||
More info in: <https://day.js.org/docs/en/parse/string-format/>
|
||||
|
||||
### Output date format on the axis
|
||||
|
||||
The default output date format is `YYYY-MM-DD`. You can define your custom `axisFormat`, like `2020-Q1` for the first quarter of the year 2020.
|
||||
|
||||
```markdown
|
||||
axisFormat %Y-%m-%d
|
||||
```
|
||||
|
||||
The following formatting strings are supported:
|
||||
|
||||
| Format | Definition |
|
||||
| ------ | ------------------------------------------------------------------------------------------ |
|
||||
| %a | abbreviated weekday name |
|
||||
| %A | full weekday name |
|
||||
| %b | abbreviated month name |
|
||||
| %B | full month name |
|
||||
| %c | date and time, as "%a %b %e %H:%M:%S %Y" |
|
||||
| %d | zero-padded day of the month as a decimal number \[01,31] |
|
||||
| %e | space-padded day of the month as a decimal number \[ 1,31]; equivalent to %\_d |
|
||||
| %H | hour (24-hour clock) as a decimal number \[00,23] |
|
||||
| %I | hour (12-hour clock) as a decimal number \[01,12] |
|
||||
| %j | day of the year as a decimal number \[001,366] |
|
||||
| %m | month as a decimal number \[01,12] |
|
||||
| %M | minute as a decimal number \[00,59] |
|
||||
| %L | milliseconds as a decimal number \[000, 999] |
|
||||
| %p | either AM or PM |
|
||||
| %S | second as a decimal number \[00,61] |
|
||||
| %U | week number of the year (Sunday as the first day of the week) as a decimal number \[00,53] |
|
||||
| %w | weekday as a decimal number \[0(Sunday),6] |
|
||||
| %W | week number of the year (Monday as the first day of the week) as a decimal number \[00,53] |
|
||||
| %x | date, as "%m/%d/%Y" |
|
||||
| %X | time, as "%H:%M:%S" |
|
||||
| %y | year without century as a decimal number \[00,99] |
|
||||
| %Y | year with century as a decimal number |
|
||||
| %Z | time zone offset, such as "-0700" |
|
||||
| %% | a literal "%" character |
|
||||
|
||||
More info in: <https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format>
|
||||
|
||||
### Axis ticks (v10.3.0+)
|
||||
|
||||
The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`.
|
||||
|
||||
```markdown
|
||||
tickInterval 1day
|
||||
```
|
||||
|
||||
The pattern is:
|
||||
|
||||
```javascript
|
||||
/^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/;
|
||||
```
|
||||
|
||||
More info in: <https://github.com/d3/d3-time#interval_every>
|
||||
|
||||
Week-based `tickInterval`s start the week on sunday by default. If you wish to specify another weekday on which the `tickInterval` should start, use the `weekday` option:
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
tickInterval 1week
|
||||
weekday monday
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
tickInterval 1week
|
||||
weekday monday
|
||||
```
|
||||
|
||||
> **Warning**
|
||||
> `millisecond` and `second` support was added in v10.3.0
|
||||
|
||||
## Output in compact mode
|
||||
|
||||
The compact mode allows you to display multiple tasks in the same row. Compact mode can be enabled for a gantt chart by setting the display mode of the graph via preceding YAML settings.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :a2, 2014-01-20, 25d
|
||||
Another one :a3, 2014-02-10, 20d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :a2, 2014-01-20, 25d
|
||||
Another one :a3, 2014-02-10, 20d
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax.
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
%% This is a comment
|
||||
dateFormat YYYY-MM-DD
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :after a1, 20d
|
||||
section Another
|
||||
Task in Another :2014-01-12, 12d
|
||||
another task :24d
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
%% This is a comment
|
||||
dateFormat YYYY-MM-DD
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
Another task :after a1, 20d
|
||||
section Another
|
||||
Task in Another :2014-01-12, 12d
|
||||
another task :24d
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
Styling of the Gantt diagram is done by defining a number of CSS classes. During rendering, these classes are extracted from the file located at src/diagrams/gantt/styles.js
|
||||
|
||||
### Classes used
|
||||
|
||||
| Class | Description |
|
||||
| --------------------- | ---------------------------------------------------------------------- |
|
||||
| grid.tick | Styling for the Grid Lines |
|
||||
| grid.path | Styling for the Grid's borders |
|
||||
| .taskText | Task Text Styling |
|
||||
| .taskTextOutsideRight | Styling for Task Text that exceeds the activity bar towards the right. |
|
||||
| .taskTextOutsideLeft | Styling for Task Text that exceeds the activity bar, towards the left. |
|
||||
| todayMarker | Toggle and Styling for the "Today Marker" |
|
||||
|
||||
### Sample stylesheet
|
||||
|
||||
```css
|
||||
.grid .tick {
|
||||
stroke: lightgrey;
|
||||
opacity: 0.3;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
.grid path {
|
||||
stroke-width: 0;
|
||||
}
|
||||
|
||||
#tag {
|
||||
color: white;
|
||||
background: #fa283d;
|
||||
width: 150px;
|
||||
position: absolute;
|
||||
display: none;
|
||||
padding: 3px 6px;
|
||||
margin-left: -80px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#tag:before {
|
||||
border: solid transparent;
|
||||
content: ' ';
|
||||
height: 0;
|
||||
left: 50%;
|
||||
margin-left: -5px;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
border-width: 10px;
|
||||
border-bottom-color: #fa283d;
|
||||
top: -20px;
|
||||
}
|
||||
.taskText {
|
||||
fill: white;
|
||||
text-anchor: middle;
|
||||
}
|
||||
.taskTextOutsideRight {
|
||||
fill: black;
|
||||
text-anchor: start;
|
||||
}
|
||||
.taskTextOutsideLeft {
|
||||
fill: black;
|
||||
text-anchor: end;
|
||||
}
|
||||
```
|
||||
|
||||
## Today marker
|
||||
|
||||
You can style or hide the marker for the current date. To style it, add a value for the `todayMarker` key.
|
||||
|
||||
```
|
||||
todayMarker stroke-width:5px,stroke:#0f0,opacity:0.5
|
||||
```
|
||||
|
||||
To hide the marker, set `todayMarker` to `off`.
|
||||
|
||||
```
|
||||
todayMarker off
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
It is possible to adjust the margins for rendering the gantt diagram.
|
||||
|
||||
This is done by defining the `ganttConfig` part of the configuration object.
|
||||
How to use the CLI is described in the [mermaidCLI](../config/mermaidCLI.md) page.
|
||||
|
||||
mermaid.ganttConfig can be set to a JSON string with config parameters or the corresponding object.
|
||||
|
||||
```javascript
|
||||
mermaid.ganttConfig = {
|
||||
titleTopMargin: 25, // Margin top for the text over the diagram
|
||||
barHeight: 20, // The height of the bars in the graph
|
||||
barGap: 4, // The margin between the different activities in the gantt diagram
|
||||
topPadding: 75, // Margin between title and gantt diagram and between axis and gantt diagram.
|
||||
rightPadding: 75, // The space allocated for the section name to the right of the activities
|
||||
leftPadding: 75, // The space allocated for the section name to the left of the activities
|
||||
gridLineStartPadding: 10, // Vertical starting position of the grid lines
|
||||
fontSize: 12, // Font size
|
||||
sectionFontSize: 24, // Font size for sections
|
||||
numberSectionStyles: 1, // The number of alternating section styles
|
||||
axisFormat: '%d/%m', // Date/time format of the axis
|
||||
tickInterval: '1week', // Axis ticks
|
||||
topAxis: true, // When this flag is set, date labels will be added to the top of the chart
|
||||
displayMode: 'compact', // Turns compact mode on
|
||||
weekday: 'sunday', // On which day a week-based interval should start
|
||||
};
|
||||
```
|
||||
|
||||
### Possible configuration params:
|
||||
|
||||
| Param | Description | Default value |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||
| mirrorActor | Turns on/off the rendering of actors below the diagram as well as above it | false |
|
||||
| bottomMarginAdj | Adjusts how far down the graph ended. Wide borders styles with css could generate unwanted clipping which is why this config param exists. | 1 |
|
||||
|
||||
## Interaction
|
||||
|
||||
It is possible to bind a click event to a task. The click can lead to either a javascript callback or to a link which will be opened in the current browser tab. **Note**: This functionality is disabled when using `securityLevel='strict'` and enabled when using `securityLevel='loose'`.
|
||||
|
||||
```
|
||||
click taskId call callback(arguments)
|
||||
click taskId href URL
|
||||
```
|
||||
|
||||
- taskId is the id of the task
|
||||
- callback is the name of a javascript function defined on the page displaying the graph, the function will be called with the taskId as the parameter if no other arguments are specified.
|
||||
|
||||
Beginner's tip—a full example using interactive links in an HTML context:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<pre class="mermaid">
|
||||
gantt
|
||||
dateFormat YYYY-MM-DD
|
||||
|
||||
section Clickable
|
||||
Visit mermaidjs :active, cl1, 2014-01-07, 3d
|
||||
Print arguments :cl2, after cl1, 3d
|
||||
Print task :cl3, after cl2, 3d
|
||||
|
||||
click cl1 href "https://mermaidjs.github.io/"
|
||||
click cl2 call printArguments("test1", "test2", test3)
|
||||
click cl3 call printTask()
|
||||
</pre>
|
||||
|
||||
<script>
|
||||
const printArguments = function (arg1, arg2, arg3) {
|
||||
alert('printArguments called with arguments: ' + arg1 + ', ' + arg2 + ', ' + arg3);
|
||||
};
|
||||
const printTask = function (taskId) {
|
||||
alert('taskId: ' + taskId);
|
||||
};
|
||||
const config = {
|
||||
startOnLoad: true,
|
||||
securityLevel: 'loose',
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Bar chart (using gantt chart)
|
||||
|
||||
```mermaid-example
|
||||
gantt
|
||||
title Git Issues - days since last update
|
||||
dateFormat X
|
||||
axisFormat %s
|
||||
section Issue19062
|
||||
71 : 0, 71
|
||||
section Issue19401
|
||||
36 : 0, 36
|
||||
section Issue193
|
||||
34 : 0, 34
|
||||
section Issue7441
|
||||
9 : 0, 9
|
||||
section Issue1300
|
||||
5 : 0, 5
|
||||
```
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title Git Issues - days since last update
|
||||
dateFormat X
|
||||
axisFormat %s
|
||||
section Issue19062
|
||||
71 : 0, 71
|
||||
section Issue19401
|
||||
36 : 0, 36
|
||||
section Issue193
|
||||
34 : 0, 34
|
||||
section Issue7441
|
||||
9 : 0, 9
|
||||
section Issue1300
|
||||
5 : 0, 5
|
||||
```
|
||||
|
||||
### Timeline (with comments, CSS, config in frontmatter)
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
# Frontmatter config, YAML comments
|
||||
title: Ignored if specified in chart
|
||||
displayMode: compact #gantt specific setting but works at this level too
|
||||
config:
|
||||
# theme: forest
|
||||
# themeCSS: " #item36 { fill: CadetBlue } "
|
||||
themeCSS: " // YAML supports multiline strings using a newline markers: \n
|
||||
#item36 { fill: CadetBlue } \n
|
||||
|
||||
// Custom marker workaround CSS from forum (below) \n
|
||||
rect[id^=workaround] { height: calc(100% - 50px) ; transform: translate(9px, 25px); y: 0; width: 1.5px; stroke: none; fill: red; } \n
|
||||
text[id^=workaround] { fill: red; y: 100%; font-size: 15px;}
|
||||
"
|
||||
gantt:
|
||||
useWidth: 400
|
||||
rightPadding: 0
|
||||
topAxis: true #false
|
||||
numberSectionStyles: 2
|
||||
---
|
||||
gantt
|
||||
title Timeline - Gantt Sampler
|
||||
dateFormat YYYY
|
||||
axisFormat %y
|
||||
%% this next line doesn't recognise 'decade' or 'year', but will silently ignore
|
||||
tickInterval 1decade
|
||||
|
||||
section Issue19062
|
||||
71 : item71, 1900, 1930
|
||||
section Issue19401
|
||||
36 : item36, 1913, 1935
|
||||
section Issue1300
|
||||
94 : item94, 1910, 1915
|
||||
5 : item5, 1920, 1925
|
||||
0 : milestone, item0, 1918, 1s
|
||||
9 : vert, 1906, 1s %% not yet official
|
||||
64 : workaround, 1923, 1s %% custom CSS object https://github.com/mermaid-js/mermaid/issues/3250
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
# Frontmatter config, YAML comments
|
||||
title: Ignored if specified in chart
|
||||
displayMode: compact #gantt specific setting but works at this level too
|
||||
config:
|
||||
# theme: forest
|
||||
# themeCSS: " #item36 { fill: CadetBlue } "
|
||||
themeCSS: " // YAML supports multiline strings using a newline markers: \n
|
||||
#item36 { fill: CadetBlue } \n
|
||||
|
||||
// Custom marker workaround CSS from forum (below) \n
|
||||
rect[id^=workaround] { height: calc(100% - 50px) ; transform: translate(9px, 25px); y: 0; width: 1.5px; stroke: none; fill: red; } \n
|
||||
text[id^=workaround] { fill: red; y: 100%; font-size: 15px;}
|
||||
"
|
||||
gantt:
|
||||
useWidth: 400
|
||||
rightPadding: 0
|
||||
topAxis: true #false
|
||||
numberSectionStyles: 2
|
||||
---
|
||||
gantt
|
||||
title Timeline - Gantt Sampler
|
||||
dateFormat YYYY
|
||||
axisFormat %y
|
||||
%% this next line doesn't recognise 'decade' or 'year', but will silently ignore
|
||||
tickInterval 1decade
|
||||
|
||||
section Issue19062
|
||||
71 : item71, 1900, 1930
|
||||
section Issue19401
|
||||
36 : item36, 1913, 1935
|
||||
section Issue1300
|
||||
94 : item94, 1910, 1915
|
||||
5 : item5, 1920, 1925
|
||||
0 : milestone, item0, 1918, 1s
|
||||
9 : vert, 1906, 1s %% not yet official
|
||||
64 : workaround, 1923, 1s %% custom CSS object https://github.com/mermaid-js/mermaid/issues/3250
|
||||
```
|
||||
|
||||
<!--- cspell:ignore isadded --->
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,161 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/kanban.md](../../packages/mermaid/src/docs/syntax/kanban.md).
|
||||
|
||||
# Mermaid Kanban Diagram Documentation
|
||||
|
||||
Mermaid’s Kanban diagram allows you to create visual representations of tasks moving through different stages of a workflow. This guide explains how to use the Kanban diagram syntax, based on the provided example.
|
||||
|
||||
## Overview
|
||||
|
||||
A Kanban diagram in Mermaid starts with the kanban keyword, followed by the definition of columns (stages) and tasks within those columns.
|
||||
|
||||
```mermaid-example
|
||||
kanban
|
||||
column1[Column Title]
|
||||
task1[Task Description]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
kanban
|
||||
column1[Column Title]
|
||||
task1[Task Description]
|
||||
```
|
||||
|
||||
## Defining Columns
|
||||
|
||||
Columns represent the different stages in your workflow, such as “Todo,” “In Progress,” “Done,” etc. Each column is defined using a unique identifier and a title enclosed in square brackets.
|
||||
|
||||
**Syntax:**
|
||||
|
||||
```
|
||||
columnId[Column Title]
|
||||
```
|
||||
|
||||
- columnId: A unique identifier for the column.
|
||||
- \[Column Title]: The title displayed on the column header.
|
||||
|
||||
Like this `id1[Todo]`
|
||||
|
||||
## Adding Tasks to Columns
|
||||
|
||||
Tasks are listed under their respective columns with an indentation. Each task also has a unique identifier and a description enclosed in square brackets.
|
||||
|
||||
**Syntax:**
|
||||
|
||||
```
|
||||
taskId[Task Description]
|
||||
```
|
||||
|
||||
```
|
||||
• taskId: A unique identifier for the task.
|
||||
• [Task Description]: The description of the task.
|
||||
```
|
||||
|
||||
**Example:**
|
||||
|
||||
```
|
||||
docs[Create Documentation]
|
||||
```
|
||||
|
||||
## Adding Metadata to Tasks
|
||||
|
||||
You can include additional metadata for each task using the @{ ... } syntax. Metadata can contain key-value pairs like assigned, ticket, priority, etc. This will be rendered added to the rendering of the node.
|
||||
|
||||
## Supported Metadata Keys
|
||||
|
||||
```
|
||||
• assigned: Specifies who is responsible for the task.
|
||||
• ticket: Links the task to a ticket or issue number.
|
||||
• priority: Indicates the urgency of the task. Allowed values: 'Very High', 'High', 'Low' and 'Very Low'
|
||||
```
|
||||
|
||||
```mermaid-example
|
||||
kanban
|
||||
todo[Todo]
|
||||
id3[Update Database Function]@{ ticket: MC-2037, assigned: 'knsv', priority: 'High' }
|
||||
```
|
||||
|
||||
```mermaid
|
||||
kanban
|
||||
todo[Todo]
|
||||
id3[Update Database Function]@{ ticket: MC-2037, assigned: 'knsv', priority: 'High' }
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams `ticketBaseUrl`. This can be set as in the following example:
|
||||
|
||||
```yaml
|
||||
---
|
||||
config:
|
||||
kanban:
|
||||
ticketBaseUrl: 'https://yourproject.atlassian.net/browse/#TICKET#'
|
||||
---
|
||||
```
|
||||
|
||||
When the kanban item has an assigned ticket number the ticket number in the diagram will have a link to an external system where the ticket is defined. The `ticketBaseUrl` sets the base URL to the external system and #TICKET# is replaced with the ticket value from task metadata to create a valid link.
|
||||
|
||||
## Full Example
|
||||
|
||||
Below is the full Kanban diagram based on the provided example:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
kanban:
|
||||
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
|
||||
---
|
||||
kanban
|
||||
Todo
|
||||
[Create Documentation]
|
||||
docs[Create Blog about the new diagram]
|
||||
[In progress]
|
||||
id6[Create renderer so that it works in all cases. We also add some extra text here for testing purposes. And some more just for the extra flare.]
|
||||
id9[Ready for deploy]
|
||||
id8[Design grammar]@{ assigned: 'knsv' }
|
||||
id10[Ready for test]
|
||||
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
|
||||
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
|
||||
id11[Done]
|
||||
id5[define getData]
|
||||
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
|
||||
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
|
||||
|
||||
id12[Can't reproduce]
|
||||
id3[Weird flickering in Firefox]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
kanban:
|
||||
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
|
||||
---
|
||||
kanban
|
||||
Todo
|
||||
[Create Documentation]
|
||||
docs[Create Blog about the new diagram]
|
||||
[In progress]
|
||||
id6[Create renderer so that it works in all cases. We also add some extra text here for testing purposes. And some more just for the extra flare.]
|
||||
id9[Ready for deploy]
|
||||
id8[Design grammar]@{ assigned: 'knsv' }
|
||||
id10[Ready for test]
|
||||
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
|
||||
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
|
||||
id11[Done]
|
||||
id5[define getData]
|
||||
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
|
||||
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
|
||||
|
||||
id12[Can't reproduce]
|
||||
id3[Weird flickering in Firefox]
|
||||
```
|
||||
|
||||
In conclusion, creating a Kanban diagram in Mermaid is a straightforward process that effectively visualizes your workflow. Start by using the kanban keyword to initiate the diagram. Define your columns with unique identifiers and titles to represent different stages of your project. Under each column, list your tasks—also with unique identifiers—and provide detailed descriptions as needed. Remember that proper indentation is crucial; tasks must be indented under their parent columns to maintain the correct structure.
|
||||
|
||||
You can enhance your diagram by adding optional metadata to tasks using the @{ ... } syntax, which allows you to include additional context such as assignee, ticket numbers, and priority levels. For further customization, utilize the configuration block at the top of your file to set global options like ticketBaseUrl for linking tickets directly from your diagram.
|
||||
|
||||
By adhering to these guidelines—ensuring unique identifiers, proper indentation, and utilizing metadata and configuration options—you can create a comprehensive and customized Kanban board that effectively maps out your project’s workflow using Mermaid.
|
||||
@@ -0,0 +1,335 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/mindmap.md](../../packages/mermaid/src/docs/syntax/mindmap.md).
|
||||
|
||||
# Mindmap
|
||||
|
||||
> Mindmap: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part.
|
||||
|
||||
"A mind map is a diagram used to visually organize information into a hierarchy, showing relationships among pieces of the whole. It is often created around a single concept, drawn as an image in the center of a blank page, to which associated representations of ideas such as images, words and parts of words are added. Major ideas are connected directly to the central concept, and other ideas branch out from those major ideas." Wikipedia
|
||||
|
||||
### An example of a mindmap.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
Tools
|
||||
Pen and paper
|
||||
Mermaid
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectiveness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
Tools
|
||||
Pen and paper
|
||||
Mermaid
|
||||
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
The syntax for creating Mindmaps is simple and relies on indentation for setting the levels in the hierarchy.
|
||||
|
||||
In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further than the previous lines defining the nodes B and C.
|
||||
|
||||
```
|
||||
mindmap
|
||||
Root
|
||||
A
|
||||
B
|
||||
C
|
||||
```
|
||||
|
||||
In summary is a simple text outline where there is one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
Root
|
||||
A
|
||||
B
|
||||
C
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
Root
|
||||
A
|
||||
B
|
||||
C
|
||||
```
|
||||
|
||||
In this way we can use a text outline to generate a hierarchical mindmap.
|
||||
|
||||
## Different shapes
|
||||
|
||||
Mermaid mindmaps can show nodes using different shapes. When specifying a shape for a node the syntax is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts, even though they are not all supported from the start.
|
||||
|
||||
Mindmap can show the following shapes:
|
||||
|
||||
### Square
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
id[I am a square]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
id[I am a square]
|
||||
```
|
||||
|
||||
### Rounded square
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
id(I am a rounded square)
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
id(I am a rounded square)
|
||||
```
|
||||
|
||||
### Circle
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
id((I am a circle))
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
id((I am a circle))
|
||||
```
|
||||
|
||||
### Bang
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
id))I am a bang((
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
id))I am a bang((
|
||||
```
|
||||
|
||||
### Cloud
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
id)I am a cloud(
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
id)I am a cloud(
|
||||
```
|
||||
|
||||
### Hexagon
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
id{{I am a hexagon}}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
id{{I am a hexagon}}
|
||||
```
|
||||
|
||||
### Default
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
I am the default shape
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
I am the default shape
|
||||
```
|
||||
|
||||
More shapes will be added, beginning with the shapes available in flowcharts.
|
||||
|
||||
# Icons and classes
|
||||
|
||||
## Icons
|
||||
|
||||
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and [Font Awesome 5](https://fontawesome.com/v5/search?o=r&m=free) are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
Root
|
||||
A
|
||||
::icon(fa fa-book)
|
||||
B(B)
|
||||
::icon(mdi mdi-skull-outline)
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
Root
|
||||
A
|
||||
::icon(fa fa-book)
|
||||
B(B)
|
||||
::icon(mdi mdi-skull-outline)
|
||||
```
|
||||
|
||||
## Classes
|
||||
|
||||
Again the syntax for adding classes is similar to flowcharts. You can add classes using a triple colon following a number of css classes separated by space. In the following example one of the nodes has two custom classes attached urgent turning the background red and the text white and large increasing the font size:
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
Root
|
||||
A[A]
|
||||
:::urgent large
|
||||
B(B)
|
||||
C
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
Root
|
||||
A[A]
|
||||
:::urgent large
|
||||
B(B)
|
||||
C
|
||||
```
|
||||
|
||||
_These classes need to be supplied by the site administrator._
|
||||
|
||||
## Unclear indentation
|
||||
|
||||
The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can see how the calculations are performed. Let us start with placing C with a smaller indentation than `B` but larger then `A`.
|
||||
|
||||
```
|
||||
mindmap
|
||||
Root
|
||||
A
|
||||
B
|
||||
C
|
||||
```
|
||||
|
||||
This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is neither a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Then Mermaid relies on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
Root
|
||||
A
|
||||
B
|
||||
C
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
Root
|
||||
A
|
||||
B
|
||||
C
|
||||
```
|
||||
|
||||
## Markdown Strings
|
||||
|
||||
The "Markdown Strings" feature enhances mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
id1["`**Root** with
|
||||
a second line
|
||||
Unicode works too: 🤓`"]
|
||||
id2["`The dog in **the** hog... a *very long text* that wraps to a new line`"]
|
||||
id3[Regular labels still works]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
id1["`**Root** with
|
||||
a second line
|
||||
Unicode works too: 🤓`"]
|
||||
id2["`The dog in **the** hog... a *very long text* that wraps to a new line`"]
|
||||
id3[Regular labels still works]
|
||||
```
|
||||
|
||||
Formatting:
|
||||
|
||||
- For bold text, use double asterisks \*\* before and after the text.
|
||||
- For italics, use single asterisks \* before and after the text.
|
||||
- With traditional strings, you needed to add <br> tags for text to wrap in nodes. However, markdown strings automatically wrap text when it becomes too long and allows you to start a new line by simply using a newline character instead of a <br> tag.
|
||||
|
||||
## Integrating with your library/website.
|
||||
|
||||
Mindmap uses the experimental lazy loading & async rendering features which could change in the future. From version 9.4.0 this diagram is included in mermaid but use lazy loading in order to keep the size of mermaid down. This is important in order to be able to add additional diagrams going forward.
|
||||
|
||||
You can still use the pre 9.4.0 method to add mermaid with mindmaps to a web page:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9.3.0/dist/mermaid.esm.min.mjs';
|
||||
import mindmap from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9.3.0/dist/mermaid-mindmap.esm.min.mjs';
|
||||
await mermaid.registerExternalDiagrams([mindmap]);
|
||||
</script>
|
||||
```
|
||||
|
||||
From version 9.4.0 you can simplify this code to:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
</script>
|
||||
```
|
||||
|
||||
You can also refer the [implementation in the live editor](https://github.com/mermaid-js/mermaid-live-editor/blob/develop/src/lib/util/mermaid.ts) to see how the async loading is done.
|
||||
|
||||
<!---
|
||||
cspell:locale en,en-gb
|
||||
cspell:ignore Buzan
|
||||
--->
|
||||
|
||||
## Layouts
|
||||
|
||||
Mermaid also supports a Tidy Tree layout for mindmaps.
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: tidy-tree
|
||||
---
|
||||
mindmap
|
||||
root((mindmap is a long thing))
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
```
|
||||
|
||||
Instructions to add and register tidy-tree layout are present in [Tidy Tree Configuration](/config/tidy-tree)
|
||||
@@ -0,0 +1,153 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/packet.md](../../packages/mermaid/src/docs/syntax/packet.md).
|
||||
|
||||
# Packet Diagram (v11.0.0+)
|
||||
|
||||
## Introduction
|
||||
|
||||
A packet diagram is a visual representation used to illustrate the structure and contents of a network packet. Network packets are the fundamental units of data transferred over a network.
|
||||
|
||||
## Usage
|
||||
|
||||
This diagram type is particularly useful for developers, network engineers, educators, and students who require a clear and concise way to represent the structure of network packets.
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
packet
|
||||
start: "Block name" %% Single-bit block
|
||||
start-end: "Block name" %% Multi-bit blocks
|
||||
... More Fields ...
|
||||
```
|
||||
|
||||
### Bits Syntax (v11.7.0+)
|
||||
|
||||
Using start and end bit counts can be difficult, especially when modifying a design. For this we add a bit count field, which starts from the end of the previous field automagically. Use `+<count>` to set the number of bits, thus:
|
||||
|
||||
```
|
||||
packet
|
||||
+1: "Block name" %% Single-bit block
|
||||
+8: "Block name" %% 8-bit block
|
||||
9-15: "Manually set start and end, it's fine to mix and match"
|
||||
... More Fields ...
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: "TCP Packet"
|
||||
---
|
||||
packet
|
||||
0-15: "Source Port"
|
||||
16-31: "Destination Port"
|
||||
32-63: "Sequence Number"
|
||||
64-95: "Acknowledgment Number"
|
||||
96-99: "Data Offset"
|
||||
100-105: "Reserved"
|
||||
106: "URG"
|
||||
107: "ACK"
|
||||
108: "PSH"
|
||||
109: "RST"
|
||||
110: "SYN"
|
||||
111: "FIN"
|
||||
112-127: "Window"
|
||||
128-143: "Checksum"
|
||||
144-159: "Urgent Pointer"
|
||||
160-191: "(Options and Padding)"
|
||||
192-255: "Data (variable length)"
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: "TCP Packet"
|
||||
---
|
||||
packet
|
||||
0-15: "Source Port"
|
||||
16-31: "Destination Port"
|
||||
32-63: "Sequence Number"
|
||||
64-95: "Acknowledgment Number"
|
||||
96-99: "Data Offset"
|
||||
100-105: "Reserved"
|
||||
106: "URG"
|
||||
107: "ACK"
|
||||
108: "PSH"
|
||||
109: "RST"
|
||||
110: "SYN"
|
||||
111: "FIN"
|
||||
112-127: "Window"
|
||||
128-143: "Checksum"
|
||||
144-159: "Urgent Pointer"
|
||||
160-191: "(Options and Padding)"
|
||||
192-255: "Data (variable length)"
|
||||
```
|
||||
|
||||
```mermaid-example
|
||||
packet
|
||||
title UDP Packet
|
||||
+16: "Source Port"
|
||||
+16: "Destination Port"
|
||||
32-47: "Length"
|
||||
48-63: "Checksum"
|
||||
64-95: "Data (variable length)"
|
||||
```
|
||||
|
||||
```mermaid
|
||||
packet
|
||||
title UDP Packet
|
||||
+16: "Source Port"
|
||||
+16: "Destination Port"
|
||||
32-47: "Length"
|
||||
48-63: "Checksum"
|
||||
64-95: "Data (variable length)"
|
||||
```
|
||||
|
||||
## Details of Syntax
|
||||
|
||||
- **Ranges**: Each line after the title represents a different field in the packet. The range (e.g., `0-15`) indicates the bit positions in the packet.
|
||||
- **Field Description**: A brief description of what the field represents, enclosed in quotes.
|
||||
|
||||
## Configuration
|
||||
|
||||
Please refer to the [configuration](/config/schema-docs/config-defs-packet-diagram-config.html) guide for details.
|
||||
|
||||
<!--
|
||||
|
||||
Theme variables are not currently working due to a mermaid bug. The passed values are not being propagated into styles function.
|
||||
|
||||
## Theme Variables
|
||||
|
||||
| Property | Description | Default Value |
|
||||
| ---------------- | -------------------------- | ------------- |
|
||||
| byteFontSize | Font size of the bytes | '10px' |
|
||||
| startByteColor | Color of the starting byte | 'black' |
|
||||
| endByteColor | Color of the ending byte | 'black' |
|
||||
| labelColor | Color of the labels | 'black' |
|
||||
| labelFontSize | Font size of the labels | '12px' |
|
||||
| titleColor | Color of the title | 'black' |
|
||||
| titleFontSize | Font size of the title | '14px' |
|
||||
| blockStrokeColor | Color of the block stroke | 'black' |
|
||||
| blockStrokeWidth | Width of the block stroke | '1' |
|
||||
| blockFillColor | Fill color of the block | '#efefef' |
|
||||
|
||||
## Example on config and theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
packet:
|
||||
showBits: true
|
||||
themeVariables:
|
||||
packet:
|
||||
startByteColor: red
|
||||
---
|
||||
packet
|
||||
0-15: "Source Port"
|
||||
16-31: "Destination Port"
|
||||
32-63: "Sequence Number"
|
||||
```
|
||||
|
||||
-->
|
||||
@@ -0,0 +1,93 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/pie.md](../../packages/mermaid/src/docs/syntax/pie.md).
|
||||
|
||||
# Pie chart diagrams
|
||||
|
||||
> A pie chart (or a circle chart) is a circular statistical graphic, which is divided into slices to illustrate numerical proportion. In a pie chart, the arc length of each slice (and consequently its central angle and area), is proportional to the quantity it represents. While it is named for its resemblance to a pie which has been sliced, there are variations on the way it can be presented. The earliest known pie chart is generally credited to William Playfair's Statistical Breviary of 1801
|
||||
> -Wikipedia
|
||||
|
||||
Mermaid can render Pie Chart diagrams.
|
||||
|
||||
```mermaid-example
|
||||
pie title Pets adopted by volunteers
|
||||
"Dogs" : 386
|
||||
"Cats" : 85
|
||||
"Rats" : 15
|
||||
```
|
||||
|
||||
```mermaid
|
||||
pie title Pets adopted by volunteers
|
||||
"Dogs" : 386
|
||||
"Cats" : 85
|
||||
"Rats" : 15
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
Drawing a pie chart is really simple in mermaid.
|
||||
|
||||
- Start with `pie` keyword to begin the diagram
|
||||
- `showData` to render the actual data values after the legend text. This is **_OPTIONAL_**
|
||||
- Followed by `title` keyword and its value in string to give a title to the pie-chart. This is **_OPTIONAL_**
|
||||
- Followed by dataSet. Pie slices will be ordered clockwise in the same order as the labels.
|
||||
- `label` for a section in the pie diagram within `" "` quotes.
|
||||
- Followed by `:` colon as separator
|
||||
- Followed by `positive numeric value` (supported up to two decimal places)
|
||||
|
||||
**Note:**
|
||||
|
||||
> Pie chart values must be **positive numbers greater than zero**.
|
||||
> **Negative values are not allowed** and will result in an error.
|
||||
|
||||
\[pie] \[showData] (OPTIONAL)
|
||||
\[title] \[titlevalue] (OPTIONAL)
|
||||
"\[datakey1]" : \[dataValue1]
|
||||
"\[datakey2]" : \[dataValue2]
|
||||
"\[datakey3]" : \[dataValue3]
|
||||
.
|
||||
.
|
||||
|
||||
## Example
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
pie:
|
||||
textPosition: 0.5
|
||||
themeVariables:
|
||||
pieOuterStrokeWidth: "5px"
|
||||
---
|
||||
pie showData
|
||||
title Key elements in Product X
|
||||
"Calcium" : 42.96
|
||||
"Potassium" : 50.05
|
||||
"Magnesium" : 10.01
|
||||
"Iron" : 5
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
pie:
|
||||
textPosition: 0.5
|
||||
themeVariables:
|
||||
pieOuterStrokeWidth: "5px"
|
||||
---
|
||||
pie showData
|
||||
title Key elements in Product X
|
||||
"Calcium" : 42.96
|
||||
"Potassium" : 50.05
|
||||
"Magnesium" : 10.01
|
||||
"Iron" : 5
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Possible pie diagram configuration parameters:
|
||||
|
||||
| Parameter | Description | Default value |
|
||||
| -------------- | ------------------------------------------------------------------------------------------------------------ | ------------- |
|
||||
| `textPosition` | The axial position of the pie slice labels, from 0.0 at the center to 1.0 at the outside edge of the circle. | `0.75` |
|
||||
@@ -0,0 +1,267 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/quadrantChart.md](../../packages/mermaid/src/docs/syntax/quadrantChart.md).
|
||||
|
||||
# Quadrant Chart
|
||||
|
||||
> A quadrant chart is a visual representation of data that is divided into four quadrants. It is used to plot data points on a two-dimensional grid, with one variable represented on the x-axis and another variable represented on the y-axis. The quadrants are determined by dividing the chart into four equal parts based on a set of criteria that is specific to the data being analyzed. Quadrant charts are often used to identify patterns and trends in data, and to prioritize actions based on the position of data points within the chart. They are commonly used in business, marketing, and risk management, among other fields.
|
||||
|
||||
## Example
|
||||
|
||||
```mermaid-example
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.3, 0.6]
|
||||
Campaign B: [0.45, 0.23]
|
||||
Campaign C: [0.57, 0.69]
|
||||
Campaign D: [0.78, 0.34]
|
||||
Campaign E: [0.40, 0.34]
|
||||
Campaign F: [0.35, 0.78]
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
> **Note**
|
||||
> If there are no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
||||
> If there are points **x-axis** labels will rendered from the left of the respective quadrant also they will be displayed at the bottom of the chart, and **y-axis** labels will be rendered at the bottom of the respective quadrant, the quadrant text will render at the top of the respective quadrant.
|
||||
|
||||
> **Note**
|
||||
> For points x and y value min value is 0 and max value is 1.
|
||||
|
||||
### Title
|
||||
|
||||
The title is a short description of the chart and it will always render on top of the chart.
|
||||
|
||||
#### Example
|
||||
|
||||
```
|
||||
quadrantChart
|
||||
title This is a sample example
|
||||
```
|
||||
|
||||
### x-axis
|
||||
|
||||
The x-axis determines what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `x-axis <text> --> <text>` both the left and right axis text will be rendered.
|
||||
2. `x-axis <text>` only the left axis text will be rendered.
|
||||
|
||||
### y-axis
|
||||
|
||||
The y-axis determines what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `y-axis <text> --> <text>` both the bottom and top axis text will be rendered.
|
||||
2. `y-axis <text>` only the bottom axis text will be rendered.
|
||||
|
||||
### Quadrants text
|
||||
|
||||
The `quadrant-[1,2,3,4]` determine what text would be displayed inside the quadrants.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `quadrant-1 <text>` determine what text will be rendered inside the top right quadrant.
|
||||
2. `quadrant-2 <text>` determine what text will be rendered inside the top left quadrant.
|
||||
3. `quadrant-3 <text>` determine what text will be rendered inside the bottom left quadrant.
|
||||
4. `quadrant-4 <text>` determine what text will be rendered inside the bottom right quadrant.
|
||||
|
||||
### Points
|
||||
|
||||
Points are used to plot a circle inside the quadrantChart. The syntax is `<text>: [x, y]` here x and y value is in the range 0 - 1.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `Point 1: [0.75, 0.80]` here the Point 1 will be drawn in the top right quadrant.
|
||||
2. `Point 2: [0.35, 0.24]` here the Point 2 will be drawn in the bottom left quadrant.
|
||||
|
||||
## Chart Configurations
|
||||
|
||||
| Parameter | Description | Default value |
|
||||
| --------------------------------- | -------------------------------------------------------------------------------------------------- | :-----------: |
|
||||
| chartWidth | Width of the chart | 500 |
|
||||
| chartHeight | Height of the chart | 500 |
|
||||
| titlePadding | Top and Bottom padding of the title | 10 |
|
||||
| titleFontSize | Title font size | 20 |
|
||||
| quadrantPadding | Padding outside all the quadrants | 5 |
|
||||
| quadrantTextTopPadding | Quadrant text top padding when text is drawn on top ( not data points are there) | 5 |
|
||||
| quadrantLabelFontSize | Quadrant text font size | 16 |
|
||||
| quadrantInternalBorderStrokeWidth | Border stroke width inside the quadrants | 1 |
|
||||
| quadrantExternalBorderStrokeWidth | Quadrant external border stroke width | 2 |
|
||||
| xAxisLabelPadding | Top and bottom padding of x-axis text | 5 |
|
||||
| xAxisLabelFontSize | X-axis texts font size | 16 |
|
||||
| xAxisPosition | Position of x-axis (top , bottom) if there are points the x-axis will always be rendered in bottom | 'top' |
|
||||
| yAxisLabelPadding | Left and Right padding of y-axis text | 5 |
|
||||
| yAxisLabelFontSize | Y-axis texts font size | 16 |
|
||||
| yAxisPosition | Position of y-axis (left , right) | 'left' |
|
||||
| pointTextPadding | Padding between point and the below text | 5 |
|
||||
| pointLabelFontSize | Point text font size | 12 |
|
||||
| pointRadius | Radius of the point to be drawn | 5 |
|
||||
|
||||
## Chart Theme Variables
|
||||
|
||||
| Parameter | Description |
|
||||
| -------------------------------- | --------------------------------------- |
|
||||
| quadrant1Fill | Fill color of the top right quadrant |
|
||||
| quadrant2Fill | Fill color of the top left quadrant |
|
||||
| quadrant3Fill | Fill color of the bottom left quadrant |
|
||||
| quadrant4Fill | Fill color of the bottom right quadrant |
|
||||
| quadrant1TextFill | Text color of the top right quadrant |
|
||||
| quadrant2TextFill | Text color of the top left quadrant |
|
||||
| quadrant3TextFill | Text color of the bottom left quadrant |
|
||||
| quadrant4TextFill | Text color of the bottom right quadrant |
|
||||
| quadrantPointFill | Points fill color |
|
||||
| quadrantPointTextFill | Points text color |
|
||||
| quadrantXAxisTextFill | X-axis text color |
|
||||
| quadrantYAxisTextFill | Y-axis text color |
|
||||
| quadrantInternalBorderStrokeFill | Quadrants inner border color |
|
||||
| quadrantExternalBorderStrokeFill | Quadrants outer border color |
|
||||
| quadrantTitleFill | Title color |
|
||||
|
||||
## Example on config and theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
quadrantChart:
|
||||
chartWidth: 400
|
||||
chartHeight: 400
|
||||
themeVariables:
|
||||
quadrant1TextFill: "ff0000"
|
||||
---
|
||||
quadrantChart
|
||||
x-axis Urgent --> Not Urgent
|
||||
y-axis Not Important --> "Important ❤"
|
||||
quadrant-1 Plan
|
||||
quadrant-2 Do
|
||||
quadrant-3 Delegate
|
||||
quadrant-4 Delete
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
quadrantChart:
|
||||
chartWidth: 400
|
||||
chartHeight: 400
|
||||
themeVariables:
|
||||
quadrant1TextFill: "ff0000"
|
||||
---
|
||||
quadrantChart
|
||||
x-axis Urgent --> Not Urgent
|
||||
y-axis Not Important --> "Important ❤"
|
||||
quadrant-1 Plan
|
||||
quadrant-2 Do
|
||||
quadrant-3 Delegate
|
||||
quadrant-4 Delete
|
||||
```
|
||||
|
||||
### Point styling
|
||||
|
||||
Points can either be styled directly or with defined shared classes
|
||||
|
||||
1. Direct styling
|
||||
|
||||
```md
|
||||
Point A: [0.9, 0.0] radius: 12
|
||||
Point B: [0.8, 0.1] color: #ff3300, radius: 10
|
||||
Point C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
|
||||
Point D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
|
||||
```
|
||||
|
||||
2. Classes styling
|
||||
|
||||
```md
|
||||
Point A:::class1: [0.9, 0.0]
|
||||
Point B:::class2: [0.8, 0.1]
|
||||
Point C:::class3: [0.7, 0.2]
|
||||
Point D:::class3: [0.7, 0.2]
|
||||
classDef class1 color: #109060
|
||||
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
|
||||
classDef class3 color: #f00fff, radius : 10
|
||||
```
|
||||
|
||||
#### Available styles:
|
||||
|
||||
| Parameter | Description |
|
||||
| ------------ | ---------------------------------------------------------------------- |
|
||||
| color | Fill color of the point |
|
||||
| radius | Radius of the point |
|
||||
| stroke-width | Border width of the point |
|
||||
| stroke-color | Border color of the point (useless when stroke-width is not specified) |
|
||||
|
||||
> **Note**
|
||||
> Order of preference:
|
||||
>
|
||||
> 1. Direct styles
|
||||
> 2. Class styles
|
||||
> 3. Theme styles
|
||||
|
||||
## Example on styling
|
||||
|
||||
```mermaid-example
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.9, 0.0] radius: 12
|
||||
Campaign B:::class1: [0.8, 0.1] color: #ff3300, radius: 10
|
||||
Campaign C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
|
||||
Campaign D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
|
||||
Campaign E:::class2: [0.5, 0.4]
|
||||
Campaign F:::class3: [0.4, 0.5] color: #0000ff
|
||||
classDef class1 color: #109060
|
||||
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
|
||||
classDef class3 color: #f00fff, radius : 10
|
||||
```
|
||||
|
||||
```mermaid
|
||||
quadrantChart
|
||||
title Reach and engagement of campaigns
|
||||
x-axis Low Reach --> High Reach
|
||||
y-axis Low Engagement --> High Engagement
|
||||
quadrant-1 We should expand
|
||||
quadrant-2 Need to promote
|
||||
quadrant-3 Re-evaluate
|
||||
quadrant-4 May be improved
|
||||
Campaign A: [0.9, 0.0] radius: 12
|
||||
Campaign B:::class1: [0.8, 0.1] color: #ff3300, radius: 10
|
||||
Campaign C: [0.7, 0.2] radius: 25, color: #00ff33, stroke-color: #10f0f0
|
||||
Campaign D: [0.6, 0.3] radius: 15, stroke-color: #00ff0f, stroke-width: 5px ,color: #ff33f0
|
||||
Campaign E:::class2: [0.5, 0.4]
|
||||
Campaign F:::class3: [0.4, 0.5] color: #0000ff
|
||||
classDef class1 color: #109060
|
||||
classDef class2 color: #908342, radius : 10, stroke-color: #310085, stroke-width: 10px
|
||||
classDef class3 color: #f00fff, radius : 10
|
||||
```
|
||||
@@ -0,0 +1,269 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/radar.md](../../packages/mermaid/src/docs/syntax/radar.md).
|
||||
|
||||
# Radar Diagram (v11.6.0+)
|
||||
|
||||
## Introduction
|
||||
|
||||
A radar diagram is a simple way to plot low-dimensional data in a circular format.
|
||||
|
||||
It is also known as a **radar chart**, **spider chart**, **star chart**, **cobweb chart**, **polar chart**, or **Kiviat diagram**.
|
||||
|
||||
## Usage
|
||||
|
||||
This diagram type is particularly useful for developers, data scientists, and engineers who require a clear and concise way to represent data in a circular format.
|
||||
|
||||
It is commonly used to graphically summarize and compare the performance of multiple entities across multiple dimensions.
|
||||
|
||||
## Syntax
|
||||
|
||||
```md
|
||||
radar-beta
|
||||
axis A, B, C, D, E
|
||||
curve c1{1,2,3,4,5}
|
||||
curve c2{5,4,3,2,1}
|
||||
... More Fields ...
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: "Grades"
|
||||
---
|
||||
radar-beta
|
||||
axis m["Math"], s["Science"], e["English"]
|
||||
axis h["History"], g["Geography"], a["Art"]
|
||||
curve a["Alice"]{85, 90, 80, 70, 75, 90}
|
||||
curve b["Bob"]{70, 75, 85, 80, 90, 85}
|
||||
|
||||
max 100
|
||||
min 0
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: "Grades"
|
||||
---
|
||||
radar-beta
|
||||
axis m["Math"], s["Science"], e["English"]
|
||||
axis h["History"], g["Geography"], a["Art"]
|
||||
curve a["Alice"]{85, 90, 80, 70, 75, 90}
|
||||
curve b["Bob"]{70, 75, 85, 80, 90, 85}
|
||||
|
||||
max 100
|
||||
min 0
|
||||
```
|
||||
|
||||
```mermaid-example
|
||||
radar-beta
|
||||
title Restaurant Comparison
|
||||
axis food["Food Quality"], service["Service"], price["Price"]
|
||||
axis ambiance["Ambiance"]
|
||||
|
||||
curve a["Restaurant A"]{4, 3, 2, 4}
|
||||
curve b["Restaurant B"]{3, 4, 3, 3}
|
||||
curve c["Restaurant C"]{2, 3, 4, 2}
|
||||
curve d["Restaurant D"]{2, 2, 4, 3}
|
||||
|
||||
graticule polygon
|
||||
max 5
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
radar-beta
|
||||
title Restaurant Comparison
|
||||
axis food["Food Quality"], service["Service"], price["Price"]
|
||||
axis ambiance["Ambiance"]
|
||||
|
||||
curve a["Restaurant A"]{4, 3, 2, 4}
|
||||
curve b["Restaurant B"]{3, 4, 3, 3}
|
||||
curve c["Restaurant C"]{2, 3, 4, 2}
|
||||
curve d["Restaurant D"]{2, 2, 4, 3}
|
||||
|
||||
graticule polygon
|
||||
max 5
|
||||
|
||||
```
|
||||
|
||||
## Details of Syntax
|
||||
|
||||
### Title
|
||||
|
||||
`title`: The title is an optional field that allows to render a title at the top of the radar diagram.
|
||||
|
||||
```
|
||||
radar-beta
|
||||
title Title of the Radar Diagram
|
||||
...
|
||||
```
|
||||
|
||||
### Axis
|
||||
|
||||
`axis`: The axis keyword is used to define the axes of the radar diagram.
|
||||
|
||||
Each axis is represented by an ID and an optional label.
|
||||
|
||||
Multiple axes can be defined in a single line.
|
||||
|
||||
```
|
||||
radar-beta
|
||||
axis id1["Label1"]
|
||||
axis id2["Label2"], id3["Label3"]
|
||||
...
|
||||
```
|
||||
|
||||
### Curve
|
||||
|
||||
`curve`: The curve keyword is used to define the data points for a curve in the radar diagram.
|
||||
|
||||
Each curve is represented by an ID, an optional label, and a list of values.
|
||||
|
||||
Values can be defined by a list of numbers or a list of key-value pairs. If key-value pairs are used, the key represents the axis ID and the value represents the data point. Else, the data points are assumed to be in the order of the axes defined.
|
||||
|
||||
Multiple curves can be defined in a single line.
|
||||
|
||||
```
|
||||
radar-beta
|
||||
axis axis1, axis2, axis3
|
||||
curve id1["Label1"]{1, 2, 3}
|
||||
curve id2["Label2"]{4, 5, 6}, id3{7, 8, 9}
|
||||
curve id4{ axis3: 30, axis1: 20, axis2: 10 }
|
||||
...
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `showLegend`: The showLegend keyword is used to show or hide the legend in the radar diagram. The legend is shown by default.
|
||||
- `max`: The maximum value for the radar diagram. This is used to scale the radar diagram. If not provided, the maximum value is calculated from the data points.
|
||||
- `min`: The minimum value for the radar diagram. This is used to scale the radar diagram. If not provided, the minimum value is `0`.
|
||||
- `graticule`: The graticule keyword is used to define the type of graticule to be rendered in the radar diagram. The graticule can be `circle` or `polygon`. If not provided, the default graticule is `circle`.
|
||||
- `ticks`: The ticks keyword is used to define the number of ticks on the graticule. It is the number of concentric circles or polygons drawn to indicate the scale of the radar diagram. If not provided, the default number of ticks is `5`.
|
||||
|
||||
```
|
||||
radar-beta
|
||||
...
|
||||
showLegend true
|
||||
max 100
|
||||
min 0
|
||||
graticule circle
|
||||
ticks 5
|
||||
...
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Please refer to the [configuration](/config/schema-docs/config-defs-radar-diagram-config.html) guide for details.
|
||||
|
||||
| Parameter | Description | Default Value |
|
||||
| --------------- | ---------------------------------------- | ------------- |
|
||||
| width | Width of the radar diagram | `600` |
|
||||
| height | Height of the radar diagram | `600` |
|
||||
| marginTop | Top margin of the radar diagram | `50` |
|
||||
| marginBottom | Bottom margin of the radar diagram | `50` |
|
||||
| marginLeft | Left margin of the radar diagram | `50` |
|
||||
| marginRight | Right margin of the radar diagram | `50` |
|
||||
| axisScaleFactor | Scale factor for the axis | `1` |
|
||||
| axisLabelFactor | Factor to adjust the axis label position | `1.05` |
|
||||
| curveTension | Tension for the rounded curves | `0.17` |
|
||||
|
||||
## Theme Variables
|
||||
|
||||
### Global Theme Variables
|
||||
|
||||
> **Note**
|
||||
> The default values for these variables depend on the theme used. To override the default values, set the desired values in the themeVariables section of the configuration:
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> config:
|
||||
> themeVariables:
|
||||
> cScale0: "#FF0000"
|
||||
> cScale1: "#00FF00"
|
||||
>
|
||||
> ---
|
||||
|
||||
Radar charts support the color scales `cScale${i}` where `i` is a number from `0` to the theme's maximum number of colors in its color scale. Usually, the maximum number of colors is `12`.
|
||||
|
||||
| Property | Description |
|
||||
| ---------- | ------------------------------ |
|
||||
| fontSize | Font size of the title |
|
||||
| titleColor | Color of the title |
|
||||
| cScale${i} | Color scale for the i-th curve |
|
||||
|
||||
### Radar Style Options
|
||||
|
||||
> **Note**
|
||||
> Specific variables for radar resides inside the `radar` key. To set the radar style options, use this syntax.
|
||||
>
|
||||
> ---
|
||||
>
|
||||
> config:
|
||||
> themeVariables:
|
||||
> radar:
|
||||
> axisColor: "#FF0000"
|
||||
>
|
||||
> ---
|
||||
|
||||
| Property | Description | Default Value |
|
||||
| -------------------- | ---------------------------- | ------------- |
|
||||
| axisColor | Color of the axis lines | `black` |
|
||||
| axisStrokeWidth | Width of the axis lines | `1` |
|
||||
| axisLabelFontSize | Font size of the axis labels | `12px` |
|
||||
| curveOpacity | Opacity of the curves | `0.7` |
|
||||
| curveStrokeWidth | Width of the curves | `2` |
|
||||
| graticuleColor | Color of the graticule | `black` |
|
||||
| graticuleOpacity | Opacity of the graticule | `0.5` |
|
||||
| graticuleStrokeWidth | Width of the graticule | `1` |
|
||||
| legendBoxSize | Size of the legend box | `10` |
|
||||
| legendFontSize | Font size of the legend | `14px` |
|
||||
|
||||
## Example on config and theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
radar:
|
||||
axisScaleFactor: 0.25
|
||||
curveTension: 0.1
|
||||
theme: base
|
||||
themeVariables:
|
||||
cScale0: "#FF0000"
|
||||
cScale1: "#00FF00"
|
||||
cScale2: "#0000FF"
|
||||
radar:
|
||||
curveOpacity: 0
|
||||
---
|
||||
radar-beta
|
||||
axis A, B, C, D, E
|
||||
curve c1{1,2,3,4,5}
|
||||
curve c2{5,4,3,2,1}
|
||||
curve c3{3,3,3,3,3}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
radar:
|
||||
axisScaleFactor: 0.25
|
||||
curveTension: 0.1
|
||||
theme: base
|
||||
themeVariables:
|
||||
cScale0: "#FF0000"
|
||||
cScale1: "#00FF00"
|
||||
cScale2: "#0000FF"
|
||||
radar:
|
||||
curveOpacity: 0
|
||||
---
|
||||
radar-beta
|
||||
axis A, B, C, D, E
|
||||
curve c1{1,2,3,4,5}
|
||||
curve c2{5,4,3,2,1}
|
||||
curve c3{3,3,3,3,3}
|
||||
```
|
||||
|
||||
<!--- cspell:ignore Kiviat --->
|
||||
@@ -0,0 +1,495 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/requirementDiagram.md](../../packages/mermaid/src/docs/syntax/requirementDiagram.md).
|
||||
|
||||
# Requirement Diagram
|
||||
|
||||
> A Requirement diagram provides a visualization for requirements and their connections, to each other and other documented elements. The modeling specs follow those defined by SysML v1.6.
|
||||
|
||||
Rendering requirements is straightforward.
|
||||
|
||||
```mermaid-example
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: the test text.
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
test_entity - satisfies -> test_req
|
||||
```
|
||||
|
||||
```mermaid
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: the test text.
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
test_entity - satisfies -> test_req
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
There are three types of components to a requirement diagram: requirement, element, and relationship.
|
||||
|
||||
The grammar for defining each is defined below. Words denoted in angle brackets, such as `<word>`, are enumerated keywords that have options elaborated in a table. `user_defined_...` is use in any place where user input is expected.
|
||||
|
||||
An important note on user text: all input can be surrounded in quotes or not. For example, both `id: "here is an example"` and `id: here is an example` are both valid. However, users must be careful with unquoted input. The parser will fail if another keyword is detected.
|
||||
|
||||
### Requirement
|
||||
|
||||
A requirement definition contains a requirement type, name, id, text, risk, and verification method. The syntax follows:
|
||||
|
||||
```
|
||||
<type> user_defined_name {
|
||||
id: user_defined_id
|
||||
text: user_defined text
|
||||
risk: <risk>
|
||||
verifymethod: <method>
|
||||
}
|
||||
```
|
||||
|
||||
Type, risk, and method are enumerations defined in SysML.
|
||||
|
||||
| Keyword | Options |
|
||||
| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
||||
| Type | requirement, functionalRequirement, interfaceRequirement, performanceRequirement, physicalRequirement, designConstraint |
|
||||
| Risk | Low, Medium, High |
|
||||
| VerificationMethod | Analysis, Inspection, Test, Demonstration |
|
||||
|
||||
### Element
|
||||
|
||||
An element definition contains an element name, type, and document reference. These three are all user defined. The element feature is intended to be lightweight but allow requirements to be connected to portions of other documents.
|
||||
|
||||
```
|
||||
element user_defined_name {
|
||||
type: user_defined_type
|
||||
docref: user_defined_ref
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown Formatting
|
||||
|
||||
In places where user defined text is possible (like names, requirement text, element docref, etc.), you can:
|
||||
|
||||
- Surround the text in quotes: `"example text"`
|
||||
- Use markdown formatting inside quotes: `"**bold text** and *italics*"`
|
||||
|
||||
Example:
|
||||
|
||||
```mermaid-example
|
||||
requirementDiagram
|
||||
|
||||
requirement "__test_req__" {
|
||||
id: 1
|
||||
text: "*italicized text* **bold text**"
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
requirementDiagram
|
||||
|
||||
requirement "__test_req__" {
|
||||
id: 1
|
||||
text: "*italicized text* **bold text**"
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
```
|
||||
|
||||
### Relationship
|
||||
|
||||
Relationships are comprised of a source node, destination node, and relationship type.
|
||||
|
||||
Each follows the definition format of
|
||||
|
||||
```
|
||||
{name of source} - <type> -> {name of destination}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
{name of destination} <- <type> - {name of source}
|
||||
```
|
||||
|
||||
"name of source" and "name of destination" should be names of requirement or element nodes defined elsewhere.
|
||||
|
||||
A relationship type can be one of contains, copies, derives, satisfies, verifies, refines, or traces.
|
||||
|
||||
Each relationship is labeled in the diagram.
|
||||
|
||||
## Larger Example
|
||||
|
||||
This example uses all features of the diagram.
|
||||
|
||||
```mermaid-example
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: the test text.
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
functionalRequirement test_req2 {
|
||||
id: 1.1
|
||||
text: the second test text.
|
||||
risk: low
|
||||
verifymethod: inspection
|
||||
}
|
||||
|
||||
performanceRequirement test_req3 {
|
||||
id: 1.2
|
||||
text: the third test text.
|
||||
risk: medium
|
||||
verifymethod: demonstration
|
||||
}
|
||||
|
||||
interfaceRequirement test_req4 {
|
||||
id: 1.2.1
|
||||
text: the fourth test text.
|
||||
risk: medium
|
||||
verifymethod: analysis
|
||||
}
|
||||
|
||||
physicalRequirement test_req5 {
|
||||
id: 1.2.2
|
||||
text: the fifth test text.
|
||||
risk: medium
|
||||
verifymethod: analysis
|
||||
}
|
||||
|
||||
designConstraint test_req6 {
|
||||
id: 1.2.3
|
||||
text: the sixth test text.
|
||||
risk: medium
|
||||
verifymethod: analysis
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
element test_entity2 {
|
||||
type: word doc
|
||||
docRef: reqs/test_entity
|
||||
}
|
||||
|
||||
element test_entity3 {
|
||||
type: "test suite"
|
||||
docRef: github.com/all_the_tests
|
||||
}
|
||||
|
||||
|
||||
test_entity - satisfies -> test_req2
|
||||
test_req - traces -> test_req2
|
||||
test_req - contains -> test_req3
|
||||
test_req3 - contains -> test_req4
|
||||
test_req4 - derives -> test_req5
|
||||
test_req5 - refines -> test_req6
|
||||
test_entity3 - verifies -> test_req5
|
||||
test_req <- copies - test_entity2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: the test text.
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
functionalRequirement test_req2 {
|
||||
id: 1.1
|
||||
text: the second test text.
|
||||
risk: low
|
||||
verifymethod: inspection
|
||||
}
|
||||
|
||||
performanceRequirement test_req3 {
|
||||
id: 1.2
|
||||
text: the third test text.
|
||||
risk: medium
|
||||
verifymethod: demonstration
|
||||
}
|
||||
|
||||
interfaceRequirement test_req4 {
|
||||
id: 1.2.1
|
||||
text: the fourth test text.
|
||||
risk: medium
|
||||
verifymethod: analysis
|
||||
}
|
||||
|
||||
physicalRequirement test_req5 {
|
||||
id: 1.2.2
|
||||
text: the fifth test text.
|
||||
risk: medium
|
||||
verifymethod: analysis
|
||||
}
|
||||
|
||||
designConstraint test_req6 {
|
||||
id: 1.2.3
|
||||
text: the sixth test text.
|
||||
risk: medium
|
||||
verifymethod: analysis
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
element test_entity2 {
|
||||
type: word doc
|
||||
docRef: reqs/test_entity
|
||||
}
|
||||
|
||||
element test_entity3 {
|
||||
type: "test suite"
|
||||
docRef: github.com/all_the_tests
|
||||
}
|
||||
|
||||
|
||||
test_entity - satisfies -> test_req2
|
||||
test_req - traces -> test_req2
|
||||
test_req - contains -> test_req3
|
||||
test_req3 - contains -> test_req4
|
||||
test_req4 - derives -> test_req5
|
||||
test_req5 - refines -> test_req6
|
||||
test_entity3 - verifies -> test_req5
|
||||
test_req <- copies - test_entity2
|
||||
```
|
||||
|
||||
## Direction
|
||||
|
||||
The diagram can be rendered in different directions using the `direction` statement. Valid values are:
|
||||
|
||||
- `TB` - Top to Bottom (default)
|
||||
- `BT` - Bottom to Top
|
||||
- `LR` - Left to Right
|
||||
- `RL` - Right to Left
|
||||
|
||||
Example:
|
||||
|
||||
```mermaid-example
|
||||
requirementDiagram
|
||||
|
||||
direction LR
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: the test text.
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
test_entity - satisfies -> test_req
|
||||
```
|
||||
|
||||
```mermaid
|
||||
requirementDiagram
|
||||
|
||||
direction LR
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: the test text.
|
||||
risk: high
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
test_entity - satisfies -> test_req
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
Requirements and elements can be styled using direct styling or classes. As a rule of thumb, when applying styles or classes, it accepts a list of requirement or element names and a list of class names allowing multiple assignments at a time (The only exception is the shorthand syntax `:::` which can assign multiple classes but only to one requirement or element at a time).
|
||||
|
||||
### Direct Styling
|
||||
|
||||
Use the `style` keyword to apply CSS styles directly:
|
||||
|
||||
```mermaid-example
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: styling example
|
||||
risk: low
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
style test_req fill:#ffa,stroke:#000, color: green
|
||||
style test_entity fill:#f9f,stroke:#333, color: blue
|
||||
```
|
||||
|
||||
```mermaid
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: styling example
|
||||
risk: low
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
style test_req fill:#ffa,stroke:#000, color: green
|
||||
style test_entity fill:#f9f,stroke:#333, color: blue
|
||||
```
|
||||
|
||||
### Class Definitions
|
||||
|
||||
Define reusable styles using `classDef`:
|
||||
|
||||
```mermaid-example
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: "class styling example"
|
||||
risk: low
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
classDef important fill:#f96,stroke:#333,stroke-width:4px
|
||||
classDef test fill:#ffa,stroke:#000
|
||||
```
|
||||
|
||||
```mermaid
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req {
|
||||
id: 1
|
||||
text: "class styling example"
|
||||
risk: low
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
classDef important fill:#f96,stroke:#333,stroke-width:4px
|
||||
classDef test fill:#ffa,stroke:#000
|
||||
```
|
||||
|
||||
### Default class
|
||||
|
||||
If a class is named default it will be applied to all nodes. Specific styles and classes should be defined afterwards to override the applied default styling.
|
||||
|
||||
```
|
||||
classDef default fill:#f9f,stroke:#333,stroke-width:4px;
|
||||
```
|
||||
|
||||
### Applying Classes
|
||||
|
||||
Classes can be applied in two ways:
|
||||
|
||||
1. Using the `class` keyword:
|
||||
|
||||
```
|
||||
class test_req,test_entity important
|
||||
```
|
||||
|
||||
2. Using the shorthand syntax with `:::` either during the definition or afterwards:
|
||||
|
||||
```
|
||||
requirement test_req:::important {
|
||||
id: 1
|
||||
text: class styling example
|
||||
risk: low
|
||||
verifymethod: test
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
element test_elem {
|
||||
}
|
||||
|
||||
test_elem:::myClass
|
||||
```
|
||||
|
||||
### Combined Example
|
||||
|
||||
```mermaid-example
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req:::important {
|
||||
id: 1
|
||||
text: "class styling example"
|
||||
risk: low
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
classDef important font-weight:bold
|
||||
|
||||
class test_entity important
|
||||
style test_entity fill:#f9f,stroke:#333
|
||||
```
|
||||
|
||||
```mermaid
|
||||
requirementDiagram
|
||||
|
||||
requirement test_req:::important {
|
||||
id: 1
|
||||
text: "class styling example"
|
||||
risk: low
|
||||
verifymethod: test
|
||||
}
|
||||
|
||||
element test_entity {
|
||||
type: simulation
|
||||
}
|
||||
|
||||
classDef important font-weight:bold
|
||||
|
||||
class test_entity important
|
||||
style test_entity fill:#f9f,stroke:#333
|
||||
```
|
||||
|
||||
<!--- cspell:ignore reqs --->
|
||||
@@ -0,0 +1,305 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/sankey.md](../../packages/mermaid/src/docs/syntax/sankey.md).
|
||||
|
||||
# Sankey diagram (v10.3.0+)
|
||||
|
||||
> A sankey diagram is a visualization used to depict a flow from one set of values to another.
|
||||
|
||||
> **Warning**
|
||||
> This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
|
||||
|
||||
The things being connected are called nodes and the connections are called links.
|
||||
|
||||
## Example
|
||||
|
||||
This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
sankey:
|
||||
showValues: false
|
||||
---
|
||||
sankey
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
Bio-conversion,Liquid,0.597
|
||||
Bio-conversion,Losses,26.862
|
||||
Bio-conversion,Solid,280.322
|
||||
Bio-conversion,Gas,81.144
|
||||
Biofuel imports,Liquid,35
|
||||
Biomass imports,Solid,35
|
||||
Coal imports,Coal,11.606
|
||||
Coal reserves,Coal,63.965
|
||||
Coal,Solid,75.571
|
||||
District heating,Industry,10.639
|
||||
District heating,Heating and cooling - commercial,22.505
|
||||
District heating,Heating and cooling - homes,46.184
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
Electricity grid,Industry,342.165
|
||||
Electricity grid,Road transport,37.797
|
||||
Electricity grid,Agriculture,4.412
|
||||
Electricity grid,Heating and cooling - commercial,40.858
|
||||
Electricity grid,Losses,56.691
|
||||
Electricity grid,Rail transport,7.863
|
||||
Electricity grid,Lighting & appliances - commercial,90.008
|
||||
Electricity grid,Lighting & appliances - homes,93.494
|
||||
Gas imports,Ngas,40.719
|
||||
Gas reserves,Ngas,82.233
|
||||
Gas,Heating and cooling - commercial,0.129
|
||||
Gas,Losses,1.401
|
||||
Gas,Thermal generation,151.891
|
||||
Gas,Agriculture,2.096
|
||||
Gas,Industry,48.58
|
||||
Geothermal,Electricity grid,7.013
|
||||
H2 conversion,H2,20.897
|
||||
H2 conversion,Losses,6.242
|
||||
H2,Road transport,20.897
|
||||
Hydro,Electricity grid,6.995
|
||||
Liquid,Industry,121.066
|
||||
Liquid,International shipping,128.69
|
||||
Liquid,Road transport,135.835
|
||||
Liquid,Domestic aviation,14.458
|
||||
Liquid,International aviation,206.267
|
||||
Liquid,Agriculture,3.64
|
||||
Liquid,National navigation,33.218
|
||||
Liquid,Rail transport,4.413
|
||||
Marine algae,Bio-conversion,4.375
|
||||
Ngas,Gas,122.952
|
||||
Nuclear,Thermal generation,839.978
|
||||
Oil imports,Oil,504.287
|
||||
Oil reserves,Oil,107.703
|
||||
Oil,Liquid,611.99
|
||||
Other waste,Solid,56.587
|
||||
Other waste,Bio-conversion,77.81
|
||||
Pumped heat,Heating and cooling - homes,193.026
|
||||
Pumped heat,Heating and cooling - commercial,70.672
|
||||
Solar PV,Electricity grid,59.901
|
||||
Solar Thermal,Heating and cooling - homes,19.263
|
||||
Solar,Solar Thermal,19.263
|
||||
Solar,Solar PV,59.901
|
||||
Solid,Agriculture,0.882
|
||||
Solid,Thermal generation,400.12
|
||||
Solid,Industry,46.477
|
||||
Thermal generation,Electricity grid,525.531
|
||||
Thermal generation,Losses,787.129
|
||||
Thermal generation,District heating,79.329
|
||||
Tidal,Electricity grid,9.452
|
||||
UK land based bioenergy,Bio-conversion,182.01
|
||||
Wave,Electricity grid,19.013
|
||||
Wind,Electricity grid,289.366
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
sankey:
|
||||
showValues: false
|
||||
---
|
||||
sankey
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
Bio-conversion,Liquid,0.597
|
||||
Bio-conversion,Losses,26.862
|
||||
Bio-conversion,Solid,280.322
|
||||
Bio-conversion,Gas,81.144
|
||||
Biofuel imports,Liquid,35
|
||||
Biomass imports,Solid,35
|
||||
Coal imports,Coal,11.606
|
||||
Coal reserves,Coal,63.965
|
||||
Coal,Solid,75.571
|
||||
District heating,Industry,10.639
|
||||
District heating,Heating and cooling - commercial,22.505
|
||||
District heating,Heating and cooling - homes,46.184
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
Electricity grid,Industry,342.165
|
||||
Electricity grid,Road transport,37.797
|
||||
Electricity grid,Agriculture,4.412
|
||||
Electricity grid,Heating and cooling - commercial,40.858
|
||||
Electricity grid,Losses,56.691
|
||||
Electricity grid,Rail transport,7.863
|
||||
Electricity grid,Lighting & appliances - commercial,90.008
|
||||
Electricity grid,Lighting & appliances - homes,93.494
|
||||
Gas imports,Ngas,40.719
|
||||
Gas reserves,Ngas,82.233
|
||||
Gas,Heating and cooling - commercial,0.129
|
||||
Gas,Losses,1.401
|
||||
Gas,Thermal generation,151.891
|
||||
Gas,Agriculture,2.096
|
||||
Gas,Industry,48.58
|
||||
Geothermal,Electricity grid,7.013
|
||||
H2 conversion,H2,20.897
|
||||
H2 conversion,Losses,6.242
|
||||
H2,Road transport,20.897
|
||||
Hydro,Electricity grid,6.995
|
||||
Liquid,Industry,121.066
|
||||
Liquid,International shipping,128.69
|
||||
Liquid,Road transport,135.835
|
||||
Liquid,Domestic aviation,14.458
|
||||
Liquid,International aviation,206.267
|
||||
Liquid,Agriculture,3.64
|
||||
Liquid,National navigation,33.218
|
||||
Liquid,Rail transport,4.413
|
||||
Marine algae,Bio-conversion,4.375
|
||||
Ngas,Gas,122.952
|
||||
Nuclear,Thermal generation,839.978
|
||||
Oil imports,Oil,504.287
|
||||
Oil reserves,Oil,107.703
|
||||
Oil,Liquid,611.99
|
||||
Other waste,Solid,56.587
|
||||
Other waste,Bio-conversion,77.81
|
||||
Pumped heat,Heating and cooling - homes,193.026
|
||||
Pumped heat,Heating and cooling - commercial,70.672
|
||||
Solar PV,Electricity grid,59.901
|
||||
Solar Thermal,Heating and cooling - homes,19.263
|
||||
Solar,Solar Thermal,19.263
|
||||
Solar,Solar PV,59.901
|
||||
Solid,Agriculture,0.882
|
||||
Solid,Thermal generation,400.12
|
||||
Solid,Industry,46.477
|
||||
Thermal generation,Electricity grid,525.531
|
||||
Thermal generation,Losses,787.129
|
||||
Thermal generation,District heating,79.329
|
||||
Tidal,Electricity grid,9.452
|
||||
UK land based bioenergy,Bio-conversion,182.01
|
||||
Wave,Electricity grid,19.013
|
||||
Wind,Electricity grid,289.366
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
The idea behind syntax is that a user types `sankey` keyword first, then pastes raw CSV below and get the result.
|
||||
|
||||
It implements CSV standard as [described here](https://www.ietf.org/rfc/rfc4180.txt) with subtle **differences**:
|
||||
|
||||
- CSV must contain **3 columns only**
|
||||
- It is **allowed** to have **empty lines** without comma separators for visual purposes
|
||||
|
||||
### Basic
|
||||
|
||||
It is implied that 3 columns inside CSV should represent `source`, `target` and `value` accordingly:
|
||||
|
||||
```mermaid-example
|
||||
sankey
|
||||
|
||||
%% source,target,value
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey
|
||||
|
||||
%% source,target,value
|
||||
Electricity grid,Over generation / exports,104.453
|
||||
Electricity grid,Heating and cooling - homes,113.726
|
||||
Electricity grid,H2 conversion,27.14
|
||||
```
|
||||
|
||||
### Empty Lines
|
||||
|
||||
CSV does not support empty lines without comma delimiters by default. But you can add them if needed:
|
||||
|
||||
```mermaid-example
|
||||
sankey
|
||||
|
||||
Bio-conversion,Losses,26.862
|
||||
|
||||
Bio-conversion,Solid,280.322
|
||||
|
||||
Bio-conversion,Gas,81.144
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey
|
||||
|
||||
Bio-conversion,Losses,26.862
|
||||
|
||||
Bio-conversion,Solid,280.322
|
||||
|
||||
Bio-conversion,Gas,81.144
|
||||
```
|
||||
|
||||
### Commas
|
||||
|
||||
If you need to have a comma, wrap it in double quotes:
|
||||
|
||||
```mermaid-example
|
||||
sankey
|
||||
|
||||
Pumped heat,"Heating and cooling, homes",193.026
|
||||
Pumped heat,"Heating and cooling, commercial",70.672
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey
|
||||
|
||||
Pumped heat,"Heating and cooling, homes",193.026
|
||||
Pumped heat,"Heating and cooling, commercial",70.672
|
||||
```
|
||||
|
||||
### Double Quotes
|
||||
|
||||
If you need to have double quote, put a pair of them inside quoted string:
|
||||
|
||||
```mermaid-example
|
||||
sankey
|
||||
|
||||
Pumped heat,"Heating and cooling, ""homes""",193.026
|
||||
Pumped heat,"Heating and cooling, ""commercial""",70.672
|
||||
```
|
||||
|
||||
```mermaid
|
||||
sankey
|
||||
|
||||
Pumped heat,"Heating and cooling, ""homes""",193.026
|
||||
Pumped heat,"Heating and cooling, ""commercial""",70.672
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can customize link colors, node alignments and diagram dimensions.
|
||||
|
||||
```html
|
||||
<script>
|
||||
const config = {
|
||||
startOnLoad: true,
|
||||
securityLevel: 'loose',
|
||||
sankey: {
|
||||
width: 800,
|
||||
height: 400,
|
||||
linkColor: 'source',
|
||||
nodeAlignment: 'left',
|
||||
},
|
||||
};
|
||||
mermaid.initialize(config);
|
||||
</script>
|
||||
```
|
||||
|
||||
### Links Coloring
|
||||
|
||||
You can adjust links' color by setting `linkColor` to one of those:
|
||||
|
||||
- `source` - link will be of a source node color
|
||||
- `target` - link will be of a target node color
|
||||
- `gradient` - link color will be smoothly transient between source and target node colors
|
||||
- hex code of color, like `#a1a1a1`
|
||||
|
||||
### Node Alignment
|
||||
|
||||
Graph layout can be changed by setting `nodeAlignment` to:
|
||||
|
||||
- `justify`
|
||||
- `center`
|
||||
- `left`
|
||||
- `right`
|
||||
|
||||
<!--- cspell:ignore Ngas bioenergy biofuel --->
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,672 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/stateDiagram.md](../../packages/mermaid/src/docs/syntax/stateDiagram.md).
|
||||
|
||||
# State diagrams
|
||||
|
||||
> "A state diagram is a type of diagram used in computer science and related fields to describe the behavior of systems.
|
||||
> State diagrams require that the system described is composed of a finite number of states; sometimes, this is indeed the
|
||||
> case, while at other times this is a reasonable abstraction." Wikipedia
|
||||
|
||||
Mermaid can render state diagrams. The syntax tries to be compliant with the syntax used in plantUml as this will make
|
||||
it easier for users to share diagrams between mermaid and plantUml.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: Simple sample
|
||||
---
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Simple sample
|
||||
---
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
Older renderer:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
In state diagrams systems are described in terms of _states_ and how one _state_ can change to another _state_ via
|
||||
a _transition._ The example diagram above shows three states: **Still**, **Moving** and **Crash**. You start in the
|
||||
**Still** state. From **Still** you can change to the **Moving** state. From **Moving** you can change either back to the **Still** state or to
|
||||
the **Crash** state. There is no transition from **Still** to **Crash**. (You can't crash if you're still.)
|
||||
|
||||
## States
|
||||
|
||||
A state can be declared in multiple ways. The simplest way is to define a state with just an id:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
stateId
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
stateId
|
||||
```
|
||||
|
||||
Another way is by using the state keyword with a description as per below:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
state "This is a state description" as s2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state "This is a state description" as s2
|
||||
```
|
||||
|
||||
Another way to define a state with a description is to define the state id followed by a colon and the description:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
s2 : This is a state description
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
s2 : This is a state description
|
||||
```
|
||||
|
||||
## Transitions
|
||||
|
||||
Transitions are path/edges when one state passes into another. This is represented using text arrow, "-->".
|
||||
|
||||
When you define a transition between two states and the states are not already defined, the undefined states are defined
|
||||
with the id from the transition. You can later add descriptions to states defined this way.
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
s1 --> s2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
s1 --> s2
|
||||
```
|
||||
|
||||
It is possible to add text to a transition to describe what it represents:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
s1 --> s2: A transition
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
s1 --> s2: A transition
|
||||
```
|
||||
|
||||
## Start and End
|
||||
|
||||
There are two special states indicating the start and stop of the diagram. These are written with the \[\*] syntax and
|
||||
the direction of the transition to it defines it either as a start or a stop state.
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
[*] --> s1
|
||||
s1 --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> s1
|
||||
s1 --> [*]
|
||||
```
|
||||
|
||||
## Composite states
|
||||
|
||||
In a real world use of state diagrams you often end up with diagrams that are multidimensional as one state can
|
||||
have several internal states. These are called composite states in this terminology.
|
||||
|
||||
In order to define a composite state you need to use the state keyword followed by an id and the body of the composite
|
||||
state between {}. You can name a composite state on a separate line just like a simple state. See the example below:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
state First {
|
||||
[*] --> second
|
||||
second --> [*]
|
||||
}
|
||||
|
||||
[*] --> NamedComposite
|
||||
NamedComposite: Another Composite
|
||||
state NamedComposite {
|
||||
[*] --> namedSimple
|
||||
namedSimple --> [*]
|
||||
namedSimple: Another simple
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
state First {
|
||||
[*] --> second
|
||||
second --> [*]
|
||||
}
|
||||
|
||||
[*] --> NamedComposite
|
||||
NamedComposite: Another Composite
|
||||
state NamedComposite {
|
||||
[*] --> namedSimple
|
||||
namedSimple --> [*]
|
||||
namedSimple: Another simple
|
||||
}
|
||||
```
|
||||
|
||||
You can do this in several layers:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
|
||||
state First {
|
||||
[*] --> Second
|
||||
|
||||
state Second {
|
||||
[*] --> second
|
||||
second --> Third
|
||||
|
||||
state Third {
|
||||
[*] --> third
|
||||
third --> [*]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
|
||||
state First {
|
||||
[*] --> Second
|
||||
|
||||
state Second {
|
||||
[*] --> second
|
||||
second --> Third
|
||||
|
||||
state Third {
|
||||
[*] --> third
|
||||
third --> [*]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also define transitions also between composite states:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
First --> Second
|
||||
First --> Third
|
||||
|
||||
state First {
|
||||
[*] --> fir
|
||||
fir --> [*]
|
||||
}
|
||||
state Second {
|
||||
[*] --> sec
|
||||
sec --> [*]
|
||||
}
|
||||
state Third {
|
||||
[*] --> thi
|
||||
thi --> [*]
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> First
|
||||
First --> Second
|
||||
First --> Third
|
||||
|
||||
state First {
|
||||
[*] --> fir
|
||||
fir --> [*]
|
||||
}
|
||||
state Second {
|
||||
[*] --> sec
|
||||
sec --> [*]
|
||||
}
|
||||
state Third {
|
||||
[*] --> thi
|
||||
thi --> [*]
|
||||
}
|
||||
```
|
||||
|
||||
_You cannot define transitions between internal states belonging to different composite states_
|
||||
|
||||
## Choice
|
||||
|
||||
Sometimes you need to model a choice between two or more paths, you can do so using <\<choice>>.
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
state if_state <<choice>>
|
||||
[*] --> IsPositive
|
||||
IsPositive --> if_state
|
||||
if_state --> False: if n < 0
|
||||
if_state --> True : if n >= 0
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state if_state <<choice>>
|
||||
[*] --> IsPositive
|
||||
IsPositive --> if_state
|
||||
if_state --> False: if n < 0
|
||||
if_state --> True : if n >= 0
|
||||
```
|
||||
|
||||
## Forks
|
||||
|
||||
It is possible to specify a fork in the diagram using <\<fork>> <\<join>>.
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
fork_state --> State3
|
||||
|
||||
state join_state <<join>>
|
||||
State2 --> join_state
|
||||
State3 --> join_state
|
||||
join_state --> State4
|
||||
State4 --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
fork_state --> State3
|
||||
|
||||
state join_state <<join>>
|
||||
State2 --> join_state
|
||||
State3 --> join_state
|
||||
join_state --> State4
|
||||
State4 --> [*]
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
Sometimes nothing says it better than a Post-it note. That is also the case in state diagrams.
|
||||
|
||||
Here you can choose to put the note to the _right of_ or to the _left of_ a node.
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
State1: The state with a note
|
||||
note right of State1
|
||||
Important information! You can write
|
||||
notes.
|
||||
end note
|
||||
State1 --> State2
|
||||
note left of State2 : This is the note to the left.
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
State1: The state with a note
|
||||
note right of State1
|
||||
Important information! You can write
|
||||
notes.
|
||||
end note
|
||||
State1 --> State2
|
||||
note left of State2 : This is the note to the left.
|
||||
```
|
||||
|
||||
## Concurrency
|
||||
|
||||
As in plantUml you can specify concurrency using the -- symbol.
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
[*] --> Active
|
||||
|
||||
state Active {
|
||||
[*] --> NumLockOff
|
||||
NumLockOff --> NumLockOn : EvNumLockPressed
|
||||
NumLockOn --> NumLockOff : EvNumLockPressed
|
||||
--
|
||||
[*] --> CapsLockOff
|
||||
CapsLockOff --> CapsLockOn : EvCapsLockPressed
|
||||
CapsLockOn --> CapsLockOff : EvCapsLockPressed
|
||||
--
|
||||
[*] --> ScrollLockOff
|
||||
ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
|
||||
ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Active
|
||||
|
||||
state Active {
|
||||
[*] --> NumLockOff
|
||||
NumLockOff --> NumLockOn : EvNumLockPressed
|
||||
NumLockOn --> NumLockOff : EvNumLockPressed
|
||||
--
|
||||
[*] --> CapsLockOff
|
||||
CapsLockOff --> CapsLockOn : EvCapsLockPressed
|
||||
CapsLockOn --> CapsLockOff : EvCapsLockPressed
|
||||
--
|
||||
[*] --> ScrollLockOff
|
||||
ScrollLockOff --> ScrollLockOn : EvScrollLockPressed
|
||||
ScrollLockOn --> ScrollLockOff : EvScrollLockPressed
|
||||
}
|
||||
```
|
||||
|
||||
## Setting the direction of the diagram
|
||||
|
||||
With state diagrams you can use the direction statement to set the direction which the diagram will render like in this
|
||||
example.
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram
|
||||
direction LR
|
||||
[*] --> A
|
||||
A --> B
|
||||
B --> C
|
||||
state B {
|
||||
direction LR
|
||||
a --> b
|
||||
}
|
||||
B --> D
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
direction LR
|
||||
[*] --> A
|
||||
A --> B
|
||||
B --> C
|
||||
state B {
|
||||
direction LR
|
||||
a --> b
|
||||
}
|
||||
B --> D
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
Comments can be entered within a state diagram chart, which will be ignored by the parser. Comments need to be on their
|
||||
own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next
|
||||
newline will be treated as a comment, including any diagram syntax
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
%% this is a comment
|
||||
Still --> Moving
|
||||
Moving --> Still %% another comment
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
%% this is a comment
|
||||
Still --> Moving
|
||||
Moving --> Still %% another comment
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
```
|
||||
|
||||
## Styling with classDefs
|
||||
|
||||
As with other diagrams (like flowcharts), you can define a style in the diagram itself and apply that named style to a
|
||||
state or states in the diagram.
|
||||
|
||||
**These are the current limitations with state diagram classDefs:**
|
||||
|
||||
1. Cannot be applied to start or end states
|
||||
2. Cannot be applied to or within composite states
|
||||
|
||||
_These are in development and will be available in a future version._
|
||||
|
||||
You define a style using the `classDef` keyword, which is short for "class definition" (where "class" means something
|
||||
like a _CSS class_)
|
||||
followed by _a name for the style,_
|
||||
and then one or more _property-value pairs_. Each _property-value pair_ is
|
||||
a _[valid CSS property name](https://www.w3.org/TR/CSS/#properties)_ followed by a colon (`:`) and then a _value._
|
||||
|
||||
Here is an example of a classDef with just one property-value pair:
|
||||
|
||||
```txt
|
||||
classDef movement font-style:italic;
|
||||
```
|
||||
|
||||
where
|
||||
|
||||
- the _name_ of the style is `movement`
|
||||
- the only _property_ is `font-style` and its _value_ is `italic`
|
||||
|
||||
If you want to have more than one _property-value pair_ then you put a comma (`,`) between each _property-value pair._
|
||||
|
||||
Here is an example with three property-value pairs:
|
||||
|
||||
```txt
|
||||
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
|
||||
```
|
||||
|
||||
where
|
||||
|
||||
- the _name_ of the style is `badBadEvent`
|
||||
- the first _property_ is `fill` and its _value_ is `#f00`
|
||||
- the second _property_ is `color` and its _value_ is `white`
|
||||
- the third _property_ is `font-weight` and its _value_ is `bold`
|
||||
- the fourth _property_ is `stroke-width` and its _value_ is `2px`
|
||||
- the fifth _property_ is `stroke` and its _value_ is `yellow`
|
||||
|
||||
### Apply classDef styles to states
|
||||
|
||||
There are two ways to apply a `classDef` style to a state:
|
||||
|
||||
1. use the `class` keyword to apply a classDef style to one or more states in a single statement, or
|
||||
2. use the `:::` operator to apply a classDef style to a state as it is being used in a transition statement (e.g. with an arrow
|
||||
to/from another state)
|
||||
|
||||
#### 1. `class` statement
|
||||
|
||||
A `class` statement tells Mermaid to apply the named classDef to one or more classes. The form is:
|
||||
|
||||
```txt
|
||||
class [one or more state names, separated by commas] [name of a style defined with classDef]
|
||||
```
|
||||
|
||||
Here is an example applying the `badBadEvent` style to a state named `Crash`:
|
||||
|
||||
```txt
|
||||
class Crash badBadEvent
|
||||
```
|
||||
|
||||
Here is an example applying the `movement` style to the two states `Moving` and `Crash`:
|
||||
|
||||
```txt
|
||||
class Moving, Crash movement
|
||||
```
|
||||
|
||||
Here is a diagram that shows the examples in use. Note that the `Crash` state has two classDef styles applied: `movement`
|
||||
and `badBadEvent`
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram
|
||||
direction TB
|
||||
|
||||
accTitle: This is the accessible title
|
||||
accDescr: This is an accessible description
|
||||
|
||||
classDef notMoving fill:white
|
||||
classDef movement font-style:italic
|
||||
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
|
||||
|
||||
[*]--> Still
|
||||
Still --> [*]
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
|
||||
class Still notMoving
|
||||
class Moving, Crash movement
|
||||
class Crash badBadEvent
|
||||
class end badBadEvent
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
direction TB
|
||||
|
||||
accTitle: This is the accessible title
|
||||
accDescr: This is an accessible description
|
||||
|
||||
classDef notMoving fill:white
|
||||
classDef movement font-style:italic
|
||||
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
|
||||
|
||||
[*]--> Still
|
||||
Still --> [*]
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
|
||||
class Still notMoving
|
||||
class Moving, Crash movement
|
||||
class Crash badBadEvent
|
||||
class end badBadEvent
|
||||
```
|
||||
|
||||
#### 2. `:::` operator to apply a style to a state
|
||||
|
||||
You can apply a classDef style to a state using the `:::` (three colons) operator. The syntax is
|
||||
|
||||
```txt
|
||||
[state]:::[style name]
|
||||
```
|
||||
|
||||
You can use this in a diagram within a statement using a class. This includes the start and end states. For example:
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram
|
||||
direction TB
|
||||
|
||||
accTitle: This is the accessible title
|
||||
accDescr: This is an accessible description
|
||||
|
||||
classDef notMoving fill:white
|
||||
classDef movement font-style:italic;
|
||||
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
|
||||
|
||||
[*] --> Still:::notMoving
|
||||
Still --> [*]
|
||||
Still --> Moving:::movement
|
||||
Moving --> Still
|
||||
Moving --> Crash:::movement
|
||||
Crash:::badBadEvent --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
direction TB
|
||||
|
||||
accTitle: This is the accessible title
|
||||
accDescr: This is an accessible description
|
||||
|
||||
classDef notMoving fill:white
|
||||
classDef movement font-style:italic;
|
||||
classDef badBadEvent fill:#f00,color:white,font-weight:bold,stroke-width:2px,stroke:yellow
|
||||
|
||||
[*] --> Still:::notMoving
|
||||
Still --> [*]
|
||||
Still --> Moving:::movement
|
||||
Moving --> Still
|
||||
Moving --> Crash:::movement
|
||||
Crash:::badBadEvent --> [*]
|
||||
```
|
||||
|
||||
## Spaces in state names
|
||||
|
||||
Spaces can be added to a state by first defining the state with an id and then referencing the id later.
|
||||
|
||||
In the following example there is a state with the id **yswsii** and description **Your state with spaces in it**.
|
||||
After it has been defined, **yswsii** is used in the diagram in the first transition (`[*] --> yswsii`)
|
||||
and also in the transition to **YetAnotherState** (`yswsii --> YetAnotherState`).
|
||||
(**yswsii** has been styled so that it is different from the other states.)
|
||||
|
||||
```mermaid-example
|
||||
stateDiagram
|
||||
classDef yourState font-style:italic,font-weight:bold,fill:white
|
||||
|
||||
yswsii: Your state with spaces in it
|
||||
[*] --> yswsii:::yourState
|
||||
[*] --> SomeOtherState
|
||||
SomeOtherState --> YetAnotherState
|
||||
yswsii --> YetAnotherState
|
||||
YetAnotherState --> [*]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
stateDiagram
|
||||
classDef yourState font-style:italic,font-weight:bold,fill:white
|
||||
|
||||
yswsii: Your state with spaces in it
|
||||
[*] --> yswsii:::yourState
|
||||
[*] --> SomeOtherState
|
||||
SomeOtherState --> YetAnotherState
|
||||
yswsii --> YetAnotherState
|
||||
YetAnotherState --> [*]
|
||||
```
|
||||
|
||||
<!--- cspell:ignore yswsii --->
|
||||
@@ -0,0 +1,540 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/timeline.md](../../packages/mermaid/src/docs/syntax/timeline.md).
|
||||
|
||||
# Timeline Diagram
|
||||
|
||||
> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part.
|
||||
|
||||
"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life" [(Wikipedia)](https://en.wikipedia.org/wiki/Timeline).
|
||||
|
||||
### An example of a timeline
|
||||
|
||||
```mermaid-example
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook
|
||||
: Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
```
|
||||
|
||||
```mermaid
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook
|
||||
: Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
The syntax for creating Timeline diagram is simple. You always start with the `timeline` keyword to let mermaid know that you want to create a timeline diagram.
|
||||
|
||||
After that there is a possibility to add a title to the timeline. This is done by adding a line with the keyword `title` followed by the title text.
|
||||
|
||||
Then you add the timeline data, where you always start with a time period, followed by a colon and then the text for the event. Optionally you can add a second colon and then the text for the event. So, you can have one or more events per time period.
|
||||
|
||||
```json
|
||||
{time period} : {event}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```json
|
||||
{time period} : {event} : {event}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```json
|
||||
{time period} : {event}
|
||||
: {event}
|
||||
: {event}
|
||||
```
|
||||
|
||||
**NOTE**: Both time period and event are simple text, and not limited to numbers.
|
||||
|
||||
Let us look at the syntax for the example above.
|
||||
|
||||
```mermaid-example
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
```
|
||||
|
||||
```mermaid
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
```
|
||||
|
||||
In this way we can use a text outline to generate a timeline diagram.
|
||||
The sequence of time period and events is important, as it will be used to draw the timeline. The first time period will be placed at the left side of the timeline, and the last time period will be placed at the right side of the timeline.
|
||||
|
||||
Similarly, the first event will be placed at the top for that specific time period, and the last event will be placed at the bottom.
|
||||
|
||||
## Grouping of time periods in sections/ages
|
||||
|
||||
You can group time periods in sections/ages. This is done by adding a line with the keyword `section` followed by the section name.
|
||||
|
||||
All subsequent time periods will be placed in this section until a new section is defined.
|
||||
|
||||
If no section is defined, all time periods will be placed in the default section.
|
||||
|
||||
Let us look at an example, where we have grouped the time periods in sections.
|
||||
|
||||
```mermaid-example
|
||||
timeline
|
||||
title Timeline of Industrial Revolution
|
||||
section 17th-20th century
|
||||
Industry 1.0 : Machinery, Water power, Steam <br>power
|
||||
Industry 2.0 : Electricity, Internal combustion engine, Mass production
|
||||
Industry 3.0 : Electronics, Computers, Automation
|
||||
section 21st century
|
||||
Industry 4.0 : Internet, Robotics, Internet of Things
|
||||
Industry 5.0 : Artificial intelligence, Big data, 3D printing
|
||||
```
|
||||
|
||||
```mermaid
|
||||
timeline
|
||||
title Timeline of Industrial Revolution
|
||||
section 17th-20th century
|
||||
Industry 1.0 : Machinery, Water power, Steam <br>power
|
||||
Industry 2.0 : Electricity, Internal combustion engine, Mass production
|
||||
Industry 3.0 : Electronics, Computers, Automation
|
||||
section 21st century
|
||||
Industry 4.0 : Internet, Robotics, Internet of Things
|
||||
Industry 5.0 : Artificial intelligence, Big data, 3D printing
|
||||
```
|
||||
|
||||
As you can see, the time periods are placed in the sections, and the sections are placed in the order they are defined.
|
||||
|
||||
All time periods and events under a given section follow a similar color scheme. This is done to make it easier to see the relationship between time periods and events.
|
||||
|
||||
## Wrapping of text for long time-periods or events
|
||||
|
||||
By default, the text for time-periods and events will be wrapped if it is too long. This is done to avoid that the text is drawn outside the diagram.
|
||||
|
||||
You can also use `<br>` to force a line break.
|
||||
|
||||
Let us look at another example, where we have a long time period, and a long event.
|
||||
|
||||
```mermaid-example
|
||||
timeline
|
||||
title England's History Timeline
|
||||
section Stone Age
|
||||
7600 BC : Britain's oldest known house was built in Orkney, Scotland
|
||||
6000 BC : Sea levels rise and Britain becomes an island.<br> The people who live here are hunter-gatherers.
|
||||
section Bronze Age
|
||||
2300 BC : People arrive from Europe and settle in Britain. <br>They bring farming and metalworking.
|
||||
: New styles of pottery and ways of burying the dead appear.
|
||||
2200 BC : The last major building works are completed at Stonehenge.<br> People now bury their dead in stone circles.
|
||||
: The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
timeline
|
||||
title England's History Timeline
|
||||
section Stone Age
|
||||
7600 BC : Britain's oldest known house was built in Orkney, Scotland
|
||||
6000 BC : Sea levels rise and Britain becomes an island.<br> The people who live here are hunter-gatherers.
|
||||
section Bronze Age
|
||||
2300 BC : People arrive from Europe and settle in Britain. <br>They bring farming and metalworking.
|
||||
: New styles of pottery and ways of burying the dead appear.
|
||||
2200 BC : The last major building works are completed at Stonehenge.<br> People now bury their dead in stone circles.
|
||||
: The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
|
||||
|
||||
```
|
||||
|
||||
```mermaid-example
|
||||
timeline
|
||||
title MermaidChart 2023 Timeline
|
||||
section 2023 Q1 <br> Release Personal Tier
|
||||
Bullet 1 : sub-point 1a : sub-point 1b
|
||||
: sub-point 1c
|
||||
Bullet 2 : sub-point 2a : sub-point 2b
|
||||
section 2023 Q2 <br> Release XYZ Tier
|
||||
Bullet 3 : sub-point <br> 3a : sub-point 3b
|
||||
: sub-point 3c
|
||||
Bullet 4 : sub-point 4a : sub-point 4b
|
||||
```
|
||||
|
||||
```mermaid
|
||||
timeline
|
||||
title MermaidChart 2023 Timeline
|
||||
section 2023 Q1 <br> Release Personal Tier
|
||||
Bullet 1 : sub-point 1a : sub-point 1b
|
||||
: sub-point 1c
|
||||
Bullet 2 : sub-point 2a : sub-point 2b
|
||||
section 2023 Q2 <br> Release XYZ Tier
|
||||
Bullet 3 : sub-point <br> 3a : sub-point 3b
|
||||
: sub-point 3c
|
||||
Bullet 4 : sub-point 4a : sub-point 4b
|
||||
```
|
||||
|
||||
## Styling of time periods and events
|
||||
|
||||
As explained earlier, each section has a color scheme, and each time period and event under a section follow the similar color scheme.
|
||||
|
||||
However, if there is no section defined, then we have two possibilities:
|
||||
|
||||
1. Style time periods individually, i.e. each time period(and its corresponding events) will have its own color scheme. This is the DEFAULT behavior.
|
||||
|
||||
```mermaid-example
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
|
||||
```
|
||||
|
||||
**NOTE**: that there are no sections defined, and each time period and its corresponding events will have its own color scheme.
|
||||
|
||||
2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme.
|
||||
|
||||
You will need to add this option either via mermaid.initialize function or directives.
|
||||
|
||||
```javascript
|
||||
mermaid.initialize({
|
||||
theme: 'base',
|
||||
startOnLoad: true,
|
||||
logLevel: 0,
|
||||
timeline: {
|
||||
disableMulticolor: false,
|
||||
},
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
let us look at same example, where we have disabled the multiColor option.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'base'
|
||||
timeline:
|
||||
disableMulticolor: true
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'base'
|
||||
timeline:
|
||||
disableMulticolor: true
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
|
||||
```
|
||||
|
||||
### Customizing Color scheme
|
||||
|
||||
You can customize the color scheme using the `cScale0` to `cScale11` theme variables, which will change the background colors. Mermaid allows you to set unique colors for up-to 12 sections, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on.
|
||||
In case you have more than 12 sections, the color scheme will start to repeat.
|
||||
|
||||
If you also want to change the foreground color of a section, you can do so use theme variables corresponding `cScaleLabel0` to `cScaleLabel11` variables.
|
||||
|
||||
**NOTE**: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values.
|
||||
|
||||
Example:
|
||||
|
||||
Now let's override the default values for the `cScale0` to `cScale2` variables:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'default'
|
||||
themeVariables:
|
||||
cScale0: '#ff0000'
|
||||
cScaleLabel0: '#ffffff'
|
||||
cScale1: '#00ff00'
|
||||
cScale2: '#0000ff'
|
||||
cScaleLabel2: '#ffffff'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'default'
|
||||
themeVariables:
|
||||
cScale0: '#ff0000'
|
||||
cScaleLabel0: '#ffffff'
|
||||
cScale1: '#00ff00'
|
||||
cScale2: '#0000ff'
|
||||
cScaleLabel2: '#ffffff'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
|
||||
```
|
||||
|
||||
See how the colors are changed to the values specified in the theme variables.
|
||||
|
||||
## Themes
|
||||
|
||||
Mermaid supports a bunch of pre-defined themes which you can use to find the right one for you. PS: you can actually override an existing theme's variable to get your own custom theme going. Learn more about [theming your diagram](../config/theming.md).
|
||||
|
||||
The following are the different pre-defined theme options:
|
||||
|
||||
- `base`
|
||||
- `forest`
|
||||
- `dark`
|
||||
- `default`
|
||||
- `neutral`
|
||||
|
||||
**NOTE**: To change theme you can either use the `initialize` call or _directives_. Learn more about [directives](../config/directives.md)
|
||||
Let's put them to use, and see how our sample diagram looks in different themes:
|
||||
|
||||
### Base Theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'base'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'base'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
### Forest Theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'forest'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'forest'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
### Dark Theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'dark'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'dark'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
### Default Theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'default'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'default'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
### Neutral Theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'neutral'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
logLevel: 'debug'
|
||||
theme: 'neutral'
|
||||
---
|
||||
timeline
|
||||
title History of Social Media Platform
|
||||
2002 : LinkedIn
|
||||
2004 : Facebook : Google
|
||||
2005 : YouTube
|
||||
2006 : Twitter
|
||||
2007 : Tumblr
|
||||
2008 : Instagram
|
||||
2010 : Pinterest
|
||||
```
|
||||
|
||||
## Integrating with your library/website
|
||||
|
||||
Timeline uses experimental lazy loading & async rendering features which could change in the future.The lazy loading is important in order to be able to add additional diagrams going forward.
|
||||
|
||||
You can use this method to add mermaid including the timeline diagram to a web page:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
</script>
|
||||
```
|
||||
|
||||
You can also refer the [implementation in the live editor](https://github.com/mermaid-js/mermaid-live-editor/blob/develop/src/lib/util/mermaid.ts) to see how the async loading is done.
|
||||
@@ -0,0 +1,353 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/treemap.md](../../packages/mermaid/src/docs/syntax/treemap.md).
|
||||
|
||||
# Treemap Diagram
|
||||
|
||||
> A treemap diagram displays hierarchical data as a set of nested rectangles. Each branch of the tree is represented by a rectangle, which is then tiled with smaller rectangles representing sub-branches.
|
||||
|
||||
> **Warning**
|
||||
> This is a new diagram type in Mermaid. Its syntax may evolve in future versions.
|
||||
|
||||
## Introduction
|
||||
|
||||
Treemap diagrams are an effective way to visualize hierarchical data and show proportions between categories and subcategories. The size of each rectangle is proportional to the value it represents, making it easy to compare different parts of a hierarchy.
|
||||
|
||||
Treemap diagrams are particularly useful for:
|
||||
|
||||
- Visualizing hierarchical data structures
|
||||
- Comparing proportions between categories
|
||||
- Displaying large amounts of hierarchical data in a limited space
|
||||
- Identifying patterns and outliers in hierarchical data
|
||||
|
||||
## Syntax
|
||||
|
||||
```
|
||||
treemap-beta
|
||||
"Section 1"
|
||||
"Leaf 1.1": 12
|
||||
"Section 1.2"
|
||||
"Leaf 1.2.1": 12
|
||||
"Section 2"
|
||||
"Leaf 2.1": 20
|
||||
"Leaf 2.2": 25
|
||||
```
|
||||
|
||||
### Node Definition
|
||||
|
||||
Nodes in a treemap are defined using the following syntax:
|
||||
|
||||
- **Section/Parent nodes**: Defined with quoted text `"Section Name"`
|
||||
- **Leaf nodes with values**: Defined with quoted text followed by a colon and value `"Leaf Name": value`
|
||||
- **Hierarchy**: Created using indentation (spaces or tabs)
|
||||
- **Styling**: Nodes can be styled using the `:::class` syntax
|
||||
|
||||
## Examples
|
||||
|
||||
### Basic Treemap
|
||||
|
||||
```mermaid-example
|
||||
treemap-beta
|
||||
"Category A"
|
||||
"Item A1": 10
|
||||
"Item A2": 20
|
||||
"Category B"
|
||||
"Item B1": 15
|
||||
"Item B2": 25
|
||||
```
|
||||
|
||||
```mermaid
|
||||
treemap-beta
|
||||
"Category A"
|
||||
"Item A1": 10
|
||||
"Item A2": 20
|
||||
"Category B"
|
||||
"Item B1": 15
|
||||
"Item B2": 25
|
||||
```
|
||||
|
||||
### Hierarchical Treemap
|
||||
|
||||
```mermaid-example
|
||||
treemap-beta
|
||||
"Products"
|
||||
"Electronics"
|
||||
"Phones": 50
|
||||
"Computers": 30
|
||||
"Accessories": 20
|
||||
"Clothing"
|
||||
"Men's": 40
|
||||
"Women's": 40
|
||||
```
|
||||
|
||||
```mermaid
|
||||
treemap-beta
|
||||
"Products"
|
||||
"Electronics"
|
||||
"Phones": 50
|
||||
"Computers": 30
|
||||
"Accessories": 20
|
||||
"Clothing"
|
||||
"Men's": 40
|
||||
"Women's": 40
|
||||
```
|
||||
|
||||
### Treemap with Styling
|
||||
|
||||
```mermaid-example
|
||||
treemap-beta
|
||||
"Section 1"
|
||||
"Leaf 1.1": 12
|
||||
"Section 1.2":::class1
|
||||
"Leaf 1.2.1": 12
|
||||
"Section 2"
|
||||
"Leaf 2.1": 20:::class1
|
||||
"Leaf 2.2": 25
|
||||
"Leaf 2.3": 12
|
||||
|
||||
classDef class1 fill:red,color:blue,stroke:#FFD600;
|
||||
```
|
||||
|
||||
```mermaid
|
||||
treemap-beta
|
||||
"Section 1"
|
||||
"Leaf 1.1": 12
|
||||
"Section 1.2":::class1
|
||||
"Leaf 1.2.1": 12
|
||||
"Section 2"
|
||||
"Leaf 2.1": 20:::class1
|
||||
"Leaf 2.2": 25
|
||||
"Leaf 2.3": 12
|
||||
|
||||
classDef class1 fill:red,color:blue,stroke:#FFD600;
|
||||
```
|
||||
|
||||
## Styling and Configuration
|
||||
|
||||
Treemap diagrams can be customized using Mermaid's styling and configuration options.
|
||||
|
||||
### Using classDef for Styling
|
||||
|
||||
You can define custom styles for nodes using the `classDef` syntax, which is a standard feature across many Mermaid diagram types:
|
||||
|
||||
```mermaid-example
|
||||
treemap-beta
|
||||
"Main"
|
||||
"A": 20
|
||||
"B":::important
|
||||
"B1": 10
|
||||
"B2": 15
|
||||
"C": 5
|
||||
|
||||
classDef important fill:#f96,stroke:#333,stroke-width:2px;
|
||||
```
|
||||
|
||||
```mermaid
|
||||
treemap-beta
|
||||
"Main"
|
||||
"A": 20
|
||||
"B":::important
|
||||
"B1": 10
|
||||
"B2": 15
|
||||
"C": 5
|
||||
|
||||
classDef important fill:#f96,stroke:#333,stroke-width:2px;
|
||||
```
|
||||
|
||||
### Theme Configuration
|
||||
|
||||
You can customize the colors of your treemap using the theme configuration:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
theme: 'forest'
|
||||
---
|
||||
treemap-beta
|
||||
"Category A"
|
||||
"Item A1": 10
|
||||
"Item A2": 20
|
||||
"Category B"
|
||||
"Item B1": 15
|
||||
"Item B2": 25
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
theme: 'forest'
|
||||
---
|
||||
treemap-beta
|
||||
"Category A"
|
||||
"Item A1": 10
|
||||
"Item A2": 20
|
||||
"Category B"
|
||||
"Item B1": 15
|
||||
"Item B2": 25
|
||||
```
|
||||
|
||||
### Diagram Padding
|
||||
|
||||
You can adjust the padding around the treemap diagram using the `diagramPadding` configuration option:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
treemap:
|
||||
diagramPadding: 200
|
||||
---
|
||||
treemap-beta
|
||||
"Category A"
|
||||
"Item A1": 10
|
||||
"Item A2": 20
|
||||
"Category B"
|
||||
"Item B1": 15
|
||||
"Item B2": 25
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
treemap:
|
||||
diagramPadding: 200
|
||||
---
|
||||
treemap-beta
|
||||
"Category A"
|
||||
"Item A1": 10
|
||||
"Item A2": 20
|
||||
"Category B"
|
||||
"Item B1": 15
|
||||
"Item B2": 25
|
||||
```
|
||||
|
||||
## Configuration Options
|
||||
|
||||
The treemap diagram supports the following configuration options:
|
||||
|
||||
| Option | Description | Default |
|
||||
| -------------- | --------------------------------------------------------------------------- | ------- |
|
||||
| useMaxWidth | When true, the diagram width is set to 100% and scales with available space | true |
|
||||
| padding | Internal padding between nodes | 10 |
|
||||
| diagramPadding | Padding around the entire diagram | 8 |
|
||||
| showValues | Whether to show values in the treemap | true |
|
||||
| nodeWidth | Width of nodes | 100 |
|
||||
| nodeHeight | Height of nodes | 40 |
|
||||
| borderWidth | Width of borders | 1 |
|
||||
| valueFontSize | Font size for values | 12 |
|
||||
| labelFontSize | Font size for labels | 14 |
|
||||
| valueFormat | Format for values (see Value Formatting section) | ',' |
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Value Formatting
|
||||
|
||||
Values in treemap diagrams can be formatted to display in different ways using the `valueFormat` configuration option. This option primarily uses [D3's format specifiers](https://github.com/d3/d3-format#locale_format) to control how numbers are displayed, with some additional special cases for common formats.
|
||||
|
||||
Some common format patterns:
|
||||
|
||||
- `,` - Thousands separator (default)
|
||||
- `$` - Add dollar sign
|
||||
- `.1f` - Show one decimal place
|
||||
- `.1%` - Show as percentage with one decimal place
|
||||
- `$0,0` - Dollar sign with thousands separator
|
||||
- `$.2f` - Dollar sign with 2 decimal places
|
||||
- `$,.2f` - Dollar sign with thousands separator and 2 decimal places
|
||||
|
||||
The treemap diagram supports both standard D3 format specifiers and some common currency formats that combine the dollar sign with other formatting options.
|
||||
|
||||
Example with currency formatting:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
treemap:
|
||||
valueFormat: '$0,0'
|
||||
---
|
||||
treemap-beta
|
||||
"Budget"
|
||||
"Operations"
|
||||
"Salaries": 700000
|
||||
"Equipment": 200000
|
||||
"Supplies": 100000
|
||||
"Marketing"
|
||||
"Advertising": 400000
|
||||
"Events": 100000
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
treemap:
|
||||
valueFormat: '$0,0'
|
||||
---
|
||||
treemap-beta
|
||||
"Budget"
|
||||
"Operations"
|
||||
"Salaries": 700000
|
||||
"Equipment": 200000
|
||||
"Supplies": 100000
|
||||
"Marketing"
|
||||
"Advertising": 400000
|
||||
"Events": 100000
|
||||
```
|
||||
|
||||
Example with percentage formatting:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
treemap:
|
||||
valueFormat: '$.1%'
|
||||
---
|
||||
treemap-beta
|
||||
"Market Share"
|
||||
"Company A": 0.35
|
||||
"Company B": 0.25
|
||||
"Company C": 0.15
|
||||
"Others": 0.25
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
treemap:
|
||||
valueFormat: '$.1%'
|
||||
---
|
||||
treemap-beta
|
||||
"Market Share"
|
||||
"Company A": 0.35
|
||||
"Company B": 0.25
|
||||
"Company C": 0.15
|
||||
"Others": 0.25
|
||||
```
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
Treemap diagrams are commonly used for:
|
||||
|
||||
1. **Financial Data**: Visualizing budget allocations, market shares, or portfolio compositions
|
||||
2. **File System Analysis**: Showing disk space usage by folders and files
|
||||
3. **Population Demographics**: Displaying population distribution across regions and subregions
|
||||
4. **Product Hierarchies**: Visualizing product categories and their sales volumes
|
||||
5. **Organizational Structures**: Representing departments and team sizes in a company
|
||||
|
||||
## Limitations
|
||||
|
||||
- Treemap diagrams work best when the data has a natural hierarchy
|
||||
- Very small values may be difficult to see or label in a treemap diagram
|
||||
- Deep hierarchies (many levels) can be challenging to represent clearly
|
||||
- Treemap diagrams are not well suited for representing data with negative values
|
||||
|
||||
## Related Diagrams
|
||||
|
||||
If treemap diagrams don't suit your needs, consider these alternatives:
|
||||
|
||||
- [**Pie Charts**](./pie.md): For simple proportion comparisons without hierarchy
|
||||
- **Sunburst Diagrams**: For hierarchical data with a radial layout (yet to be released in Mermaid).
|
||||
- [**Sankey Diagrams**](./sankey.md): For flow-based hierarchical data
|
||||
|
||||
## Notes
|
||||
|
||||
The treemap diagram implementation in Mermaid is designed to be simple to use while providing powerful visualization capabilities. As this is a newer diagram type, feedback and feature requests are welcome through the Mermaid GitHub repository.
|
||||
@@ -0,0 +1,42 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/userJourney.md](../../packages/mermaid/src/docs/syntax/userJourney.md).
|
||||
|
||||
# User Journey Diagram
|
||||
|
||||
> User journeys describe at a high level of detail exactly what steps different users take to complete a specific task within a system, application or website. This technique shows the current (as-is) user workflow, and reveals areas of improvement for the to-be workflow. (Wikipedia)
|
||||
|
||||
Mermaid can render user journey diagrams:
|
||||
|
||||
```mermaid-example
|
||||
journey
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
Go upstairs: 3: Me
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 5: Me
|
||||
```
|
||||
|
||||
```mermaid
|
||||
journey
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
Go upstairs: 3: Me
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 5: Me
|
||||
```
|
||||
|
||||
Each user journey is split into sections, these describe the part of the task
|
||||
the user is trying to complete.
|
||||
|
||||
Tasks syntax is `Task name: <score>: <comma separated list of actors>`
|
||||
|
||||
Score is a number between 1 and 5, inclusive.
|
||||
@@ -0,0 +1,250 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/xyChart.md](../../packages/mermaid/src/docs/syntax/xyChart.md).
|
||||
|
||||
# XY Chart
|
||||
|
||||
> In the context of mermaid-js, the XY chart is a comprehensive charting module that encompasses various types of charts that utilize both x-axis and y-axis for data representation. Presently, it includes two fundamental chart types: the bar chart and the line chart. These charts are designed to visually display and analyze data that involve two numerical variables.
|
||||
|
||||
> It's important to note that while the current implementation of mermaid-js includes these two chart types, the framework is designed to be dynamic and adaptable. Therefore, it has the capacity for expansion and the inclusion of additional chart types in the future. This means that users can expect an evolving suite of charting options within the XY chart module, catering to various data visualization needs as new chart types are introduced over time.
|
||||
|
||||
## Example
|
||||
|
||||
```mermaid-example
|
||||
xychart
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
xychart
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
> **Note**
|
||||
> All text values that contain only one word can be written without `"`. If a text value has many words in it, specifically if it contains spaces, enclose the value in `"`
|
||||
|
||||
### Orientations
|
||||
|
||||
The chart can be drawn horizontal or vertical, default value is vertical.
|
||||
|
||||
```
|
||||
xychart horizontal
|
||||
...
|
||||
```
|
||||
|
||||
### Title
|
||||
|
||||
The title is a short description of the chart and it will always render on top of the chart.
|
||||
|
||||
#### Example
|
||||
|
||||
```
|
||||
xychart
|
||||
title "This is a simple example"
|
||||
...
|
||||
```
|
||||
|
||||
> **Note**
|
||||
> If the title is a single word one no need to use `"`, but if it has space `"` is needed
|
||||
|
||||
### x-axis
|
||||
|
||||
The x-axis primarily serves as a categorical value, although it can also function as a numeric range value when needed.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `x-axis title min --> max` x-axis will function as numeric with the given range
|
||||
2. `x-axis "title with space" [cat1, "cat2 with space", cat3]` x-axis if categorical, categories are text type
|
||||
|
||||
### y-axis
|
||||
|
||||
The y-axis is employed to represent numerical range values, it cannot have categorical values.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `y-axis title min --> max`
|
||||
2. `y-axis title` it will only add the title, the range will be auto generated from data.
|
||||
|
||||
> **Note**
|
||||
> Both x and y axis are optional if not provided we will try to create the range
|
||||
|
||||
### Line chart
|
||||
|
||||
A line chart offers the capability to graphically depict lines.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `line [2.3, 45, .98, -3.4]` it can have all valid numeric values.
|
||||
|
||||
### Bar chart
|
||||
|
||||
A bar chart offers the capability to graphically depict bars.
|
||||
|
||||
#### Example
|
||||
|
||||
1. `bar [2.3, 45, .98, -3.4]` it can have all valid numeric values.
|
||||
|
||||
#### Simplest example
|
||||
|
||||
The only two things required are the chart name (`xychart`) and one data set. So you will be able to draw a chart with a simple config like
|
||||
|
||||
```
|
||||
xychart
|
||||
line [+1.3, .6, 2.4, -.34]
|
||||
```
|
||||
|
||||
## Chart Configurations
|
||||
|
||||
| Parameter | Description | Default value |
|
||||
| ------------------------ | ------------------------------------------------------------- | :-----------: |
|
||||
| width | Width of the chart | 700 |
|
||||
| height | Height of the chart | 500 |
|
||||
| titlePadding | Top and Bottom padding of the title | 10 |
|
||||
| titleFontSize | Title font size | 20 |
|
||||
| showTitle | Title to be shown or not | true |
|
||||
| xAxis | xAxis configuration | AxisConfig |
|
||||
| yAxis | yAxis configuration | AxisConfig |
|
||||
| chartOrientation | 'vertical' or 'horizontal' | 'vertical' |
|
||||
| plotReservedSpacePercent | Minimum space plots will take inside the chart | 50 |
|
||||
| showDataLabel | Should show the value corresponding to the bar within the bar | false |
|
||||
|
||||
### AxisConfig
|
||||
|
||||
| Parameter | Description | Default value |
|
||||
| ------------- | ------------------------------------ | :-----------: |
|
||||
| showLabel | Show axis labels or tick values | true |
|
||||
| labelFontSize | Font size of the label to be drawn | 14 |
|
||||
| labelPadding | Top and Bottom padding of the label | 5 |
|
||||
| showTitle | Axis title to be shown or not | true |
|
||||
| titleFontSize | Axis title font size | 16 |
|
||||
| titlePadding | Top and Bottom padding of Axis title | 5 |
|
||||
| showTick | Tick to be shown or not | true |
|
||||
| tickLength | How long the tick will be | 5 |
|
||||
| tickWidth | How width the tick will be | 2 |
|
||||
| showAxisLine | Axis line to be shown or not | true |
|
||||
| axisLineWidth | Thickness of the axis line | 2 |
|
||||
|
||||
## Chart Theme Variables
|
||||
|
||||
Themes for xychart reside inside the `xychart` attribute, allowing customization through the following syntax:
|
||||
|
||||
```yaml
|
||||
---
|
||||
config:
|
||||
themeVariables:
|
||||
xyChart:
|
||||
titleColor: '#ff0000'
|
||||
---
|
||||
```
|
||||
|
||||
| Parameter | Description |
|
||||
| ---------------- | --------------------------------------------------------- |
|
||||
| backgroundColor | Background color of the whole chart |
|
||||
| titleColor | Color of the Title text |
|
||||
| xAxisLabelColor | Color of the x-axis labels |
|
||||
| xAxisTitleColor | Color of the x-axis title |
|
||||
| xAxisTickColor | Color of the x-axis tick |
|
||||
| xAxisLineColor | Color of the x-axis line |
|
||||
| yAxisLabelColor | Color of the y-axis labels |
|
||||
| yAxisTitleColor | Color of the y-axis title |
|
||||
| yAxisTickColor | Color of the y-axis tick |
|
||||
| yAxisLineColor | Color of the y-axis line |
|
||||
| plotColorPalette | String of colors separated by comma e.g. "#f3456, #43445" |
|
||||
|
||||
### Setting Colors for Lines and Bars
|
||||
|
||||
To set the color for lines and bars, use the `plotColorPalette` parameter. Colors in the palette will correspond sequentially to the elements in your chart (e.g., first bar/line will use the first color specified in the palette).
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
themeVariables:
|
||||
xyChart:
|
||||
plotColorPalette: '#000000, #0000FF, #00FF00, #FF0000'
|
||||
---
|
||||
xychart
|
||||
title "Different Colors in xyChart"
|
||||
x-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]
|
||||
y-axis "valuesY" 0 --> 50
|
||||
%% Black line
|
||||
line [10,20,30,40]
|
||||
%% Blue bar
|
||||
bar [20,30,25,35]
|
||||
%% Green bar
|
||||
bar [15,25,20,30]
|
||||
%% Red line
|
||||
line [5,15,25,35]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
themeVariables:
|
||||
xyChart:
|
||||
plotColorPalette: '#000000, #0000FF, #00FF00, #FF0000'
|
||||
---
|
||||
xychart
|
||||
title "Different Colors in xyChart"
|
||||
x-axis "categoriesX" ["Category 1", "Category 2", "Category 3", "Category 4"]
|
||||
y-axis "valuesY" 0 --> 50
|
||||
%% Black line
|
||||
line [10,20,30,40]
|
||||
%% Blue bar
|
||||
bar [20,30,25,35]
|
||||
%% Green bar
|
||||
bar [15,25,20,30]
|
||||
%% Red line
|
||||
line [5,15,25,35]
|
||||
```
|
||||
|
||||
## Example on config and theme
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
xyChart:
|
||||
width: 900
|
||||
height: 600
|
||||
showDataLabel: true
|
||||
themeVariables:
|
||||
xyChart:
|
||||
titleColor: "#ff0000"
|
||||
---
|
||||
xychart
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
xyChart:
|
||||
width: 900
|
||||
height: 600
|
||||
showDataLabel: true
|
||||
themeVariables:
|
||||
xyChart:
|
||||
titleColor: "#ff0000"
|
||||
---
|
||||
xychart
|
||||
title "Sales Revenue"
|
||||
x-axis [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec]
|
||||
y-axis "Revenue (in $)" 4000 --> 11000
|
||||
bar [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
line [5000, 6000, 7500, 8200, 9500, 10500, 11000, 10200, 9200, 8500, 7000, 6000]
|
||||
```
|
||||
@@ -0,0 +1,474 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/zenuml.md](../../packages/mermaid/src/docs/syntax/zenuml.md).
|
||||
|
||||
# ZenUML
|
||||
|
||||
> A Sequence diagram is an interaction diagram that shows how processes operate with one another and in what order.
|
||||
|
||||
Mermaid can render sequence diagrams with [ZenUML](https://zenuml.com). Note that ZenUML uses a different
|
||||
syntax than the original Sequence Diagram in mermaid.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Demo
|
||||
Alice->John: Hello John, how are you?
|
||||
John->Alice: Great!
|
||||
Alice->John: See you later!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Demo
|
||||
Alice->John: Hello John, how are you?
|
||||
John->Alice: Great!
|
||||
Alice->John: See you later!
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
### Participants
|
||||
|
||||
The participants can be defined implicitly as in the first example on this page. The participants or actors are
|
||||
rendered in order of appearance in the diagram source text. Sometimes you might want to show the participants in a
|
||||
different order than how they appear in the first message. It is possible to specify the actor's order of
|
||||
appearance by doing the following:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Declare participant (optional)
|
||||
Bob
|
||||
Alice
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Declare participant (optional)
|
||||
Bob
|
||||
Alice
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
### Annotators
|
||||
|
||||
If you specifically want to use symbols instead of just rectangles with text you can do so by using the annotator syntax to declare participants as per below.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Annotators
|
||||
@Actor Alice
|
||||
@Database Bob
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Annotators
|
||||
@Actor Alice
|
||||
@Database Bob
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
Here are the available annotators:
|
||||

|
||||
|
||||
### Aliases
|
||||
|
||||
The participants can have a convenient identifier and a descriptive label.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Aliases
|
||||
A as Alice
|
||||
J as John
|
||||
A->J: Hello John, how are you?
|
||||
J->A: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Aliases
|
||||
A as Alice
|
||||
J as John
|
||||
A->J: Hello John, how are you?
|
||||
J->A: Great!
|
||||
```
|
||||
|
||||
## Messages
|
||||
|
||||
Messages can be one of:
|
||||
|
||||
1. Sync message
|
||||
2. Async message
|
||||
3. Creation message
|
||||
4. Reply message
|
||||
|
||||
### Sync message
|
||||
|
||||
You can think of a sync (blocking) method in a programming language.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Sync message
|
||||
A.SyncMessage
|
||||
A.SyncMessage(with, parameters) {
|
||||
B.nestedSyncMessage()
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Sync message
|
||||
A.SyncMessage
|
||||
A.SyncMessage(with, parameters) {
|
||||
B.nestedSyncMessage()
|
||||
}
|
||||
```
|
||||
|
||||
### Async message
|
||||
|
||||
You can think of an async (non-blocking) method in a programming language.
|
||||
Fire an event and forget about it.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Async message
|
||||
Alice->Bob: How are you?
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Async message
|
||||
Alice->Bob: How are you?
|
||||
```
|
||||
|
||||
### Creation message
|
||||
|
||||
We use `new` keyword to create an object.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
new A1
|
||||
new A2(with, parameters)
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
new A1
|
||||
new A2(with, parameters)
|
||||
```
|
||||
|
||||
### Reply message
|
||||
|
||||
There are three ways to express a reply message:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
// 1. assign a variable from a sync message.
|
||||
a = A.SyncMessage()
|
||||
|
||||
// 1.1. optionally give the variable a type
|
||||
SomeType a = A.SyncMessage()
|
||||
|
||||
// 2. use return keyword
|
||||
A.SyncMessage() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. use @return or @reply annotator on an async message
|
||||
@return
|
||||
A->B: result
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
// 1. assign a variable from a sync message.
|
||||
a = A.SyncMessage()
|
||||
|
||||
// 1.1. optionally give the variable a type
|
||||
SomeType a = A.SyncMessage()
|
||||
|
||||
// 2. use return keyword
|
||||
A.SyncMessage() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. use @return or @reply annotator on an async message
|
||||
@return
|
||||
A->B: result
|
||||
```
|
||||
|
||||
The third way `@return` is rarely used, but it is useful when you want to return to one level up.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Reply message
|
||||
Client->A.method() {
|
||||
B.method() {
|
||||
if(condition) {
|
||||
return x1
|
||||
// return early
|
||||
@return
|
||||
A->Client: x11
|
||||
}
|
||||
}
|
||||
return x2
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Reply message
|
||||
Client->A.method() {
|
||||
B.method() {
|
||||
if(condition) {
|
||||
return x1
|
||||
// return early
|
||||
@return
|
||||
A->Client: x11
|
||||
}
|
||||
}
|
||||
return x2
|
||||
}
|
||||
```
|
||||
|
||||
## Nesting
|
||||
|
||||
Sync messages and Creation messages are naturally nestable with `{}`.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
A.method() {
|
||||
B.nested_sync_method()
|
||||
B->C: nested async message
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
A.method() {
|
||||
B.nested_sync_method()
|
||||
B->C: nested async message
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
It is possible to add comments to a sequence diagram with `// comment` syntax.
|
||||
Comments will be rendered above the messages or fragments. Comments on other places
|
||||
are ignored. Markdown is supported.
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
// a comment on a participant will not be rendered
|
||||
BookService
|
||||
// a comment on a message.
|
||||
// **Markdown** is supported.
|
||||
BookService.getBook()
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
// a comment on a participant will not be rendered
|
||||
BookService
|
||||
// a comment on a message.
|
||||
// **Markdown** is supported.
|
||||
BookService.getBook()
|
||||
```
|
||||
|
||||
## Loops
|
||||
|
||||
It is possible to express loops in a ZenUML diagram. This is done by any of the
|
||||
following notations:
|
||||
|
||||
1. while
|
||||
2. for
|
||||
3. forEach, foreach
|
||||
4. loop
|
||||
|
||||
```zenuml
|
||||
while(condition) {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->John: Hello John, how are you?
|
||||
while(true) {
|
||||
John->Alice: Great!
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->John: Hello John, how are you?
|
||||
while(true) {
|
||||
John->Alice: Great!
|
||||
}
|
||||
```
|
||||
|
||||
## Alt
|
||||
|
||||
It is possible to express alternative paths in a sequence diagram. This is done by the notation
|
||||
|
||||
```zenuml
|
||||
if(condition1) {
|
||||
...statements...
|
||||
} else if(condition2) {
|
||||
...statements...
|
||||
} else {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
if(is_sick) {
|
||||
Bob->Alice: Not so good :(
|
||||
} else {
|
||||
Bob->Alice: Feeling fresh like a daisy
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
if(is_sick) {
|
||||
Bob->Alice: Not so good :(
|
||||
} else {
|
||||
Bob->Alice: Feeling fresh like a daisy
|
||||
}
|
||||
```
|
||||
|
||||
## Opt
|
||||
|
||||
It is possible to render an `opt` fragment. This is done by the notation
|
||||
|
||||
```zenuml
|
||||
opt {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Not so good :(
|
||||
opt {
|
||||
Bob->Alice: Thanks for asking
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Not so good :(
|
||||
opt {
|
||||
Bob->Alice: Thanks for asking
|
||||
}
|
||||
```
|
||||
|
||||
## Parallel
|
||||
|
||||
It is possible to show actions that are happening in parallel.
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```zenuml
|
||||
par {
|
||||
statement1
|
||||
statement2
|
||||
statement3
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
par {
|
||||
Alice->Bob: Hello guys!
|
||||
Alice->John: Hello guys!
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
par {
|
||||
Alice->Bob: Hello guys!
|
||||
Alice->John: Hello guys!
|
||||
}
|
||||
```
|
||||
|
||||
## Try/Catch/Finally (Break)
|
||||
|
||||
It is possible to indicate a stop of the sequence within the flow (usually used to model exceptions).
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```
|
||||
try {
|
||||
...statements...
|
||||
} catch {
|
||||
...statements...
|
||||
} finally {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
try {
|
||||
Consumer->API: Book something
|
||||
API->BookingService: Start booking process
|
||||
} catch {
|
||||
API->Consumer: show failure
|
||||
} finally {
|
||||
API->BookingService: rollback status
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
try {
|
||||
Consumer->API: Book something
|
||||
API->BookingService: Start booking process
|
||||
} catch {
|
||||
API->Consumer: show failure
|
||||
} finally {
|
||||
API->BookingService: rollback status
|
||||
}
|
||||
```
|
||||
|
||||
## Integrating with your library/website.
|
||||
|
||||
Zenuml uses the experimental lazy loading & async rendering features which could change in the future.
|
||||
|
||||
You can use this method to add mermaid including the zenuml diagram to a web page:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import zenuml from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@0.1.0/dist/mermaid-zenuml.esm.min.mjs';
|
||||
await mermaid.registerExternalDiagrams([zenuml]);
|
||||
</script>
|
||||
```
|
||||
+2
-1
@@ -185,5 +185,6 @@ ruflo-mcp-stderr.log
|
||||
.claude/agents/templates/
|
||||
.claude/agents/testing/
|
||||
.claude/agents/v3/
|
||||
.claude/commands/
|
||||
.claude/commands/*
|
||||
!.claude/commands/security-review.md
|
||||
.claude/helpers/
|
||||
|
||||
@@ -2,3 +2,4 @@ node_modules/
|
||||
.git/
|
||||
bin/
|
||||
CLAUDE.md
|
||||
.claude/skills/mermaid/
|
||||
|
||||
@@ -60,11 +60,14 @@ final class AdminPricingTiersController extends Controller
|
||||
/** POST /api/admin/pricing-tiers */
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
$todayMsk = Carbon::now('Europe/Moscow')->toDateString();
|
||||
|
||||
$request->validate([
|
||||
'tiers' => ['required', 'array', 'size:7'],
|
||||
'tiers.*.tier_no' => ['required', 'integer', 'between:1,7'],
|
||||
'tiers.*.leads_in_tier' => ['nullable', 'integer', 'min:1'],
|
||||
'tiers.*.price_rub' => ['required', 'numeric', 'min:0'],
|
||||
'effective_from' => ['sometimes', 'date_format:Y-m-d', 'after:'.$todayMsk],
|
||||
]);
|
||||
|
||||
/** @var array<int, array{tier_no:int, leads_in_tier:?int, price_rub:string|float}> $tiers */
|
||||
@@ -89,7 +92,8 @@ final class AdminPricingTiersController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
$effectiveFrom = Carbon::now('Europe/Moscow')->startOfMonth()->addMonth()->toDateString();
|
||||
$effectiveFrom = $request->input('effective_from')
|
||||
?? Carbon::now('Europe/Moscow')->startOfMonth()->addMonth()->toDateString();
|
||||
$adminUserId = $this->resolveAdminUserId($request);
|
||||
|
||||
DB::transaction(function () use ($tiers, $effectiveFrom, $adminUserId, $request): void {
|
||||
|
||||
@@ -428,3 +428,69 @@ export async function notifyIncidentRkn(id: number): Promise<ApiAdminIncidentDet
|
||||
);
|
||||
return data.incident;
|
||||
}
|
||||
|
||||
// === SaaS-admin → Тарифная сетка (Plan 4 / Sprint 5C G3) ===
|
||||
|
||||
export interface AdminPricingTier {
|
||||
tier_no: number;
|
||||
leads_in_tier: number | null;
|
||||
price_per_lead_kopecks: number;
|
||||
effective_from: string;
|
||||
}
|
||||
|
||||
export interface PricingTiersResponse {
|
||||
active: AdminPricingTier[];
|
||||
scheduled: Record<string, AdminPricingTier[]>;
|
||||
}
|
||||
|
||||
export interface PricingTierEditorRow {
|
||||
tier_no: number;
|
||||
leads_in_tier: number | null;
|
||||
price_rub: string;
|
||||
}
|
||||
|
||||
export async function getPricingTiers(): Promise<PricingTiersResponse> {
|
||||
const { data } = await apiClient.get<{ data: PricingTiersResponse }>('/api/admin/pricing-tiers');
|
||||
return { active: data.data.active, scheduled: data.data.scheduled ?? {} };
|
||||
}
|
||||
|
||||
export async function createPricingTiers(
|
||||
tiers: PricingTierEditorRow[],
|
||||
effectiveFrom?: string,
|
||||
): Promise<{ effective_from: string }> {
|
||||
await ensureCsrfCookie();
|
||||
const payload: { tiers: PricingTierEditorRow[]; effective_from?: string } = { tiers };
|
||||
if (effectiveFrom) payload.effective_from = effectiveFrom;
|
||||
const { data } = await apiClient.post<{ effective_from: string }>('/api/admin/pricing-tiers', payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteScheduledPricingTier(effectiveFrom: string): Promise<void> {
|
||||
await ensureCsrfCookie();
|
||||
await apiClient.delete(`/api/admin/pricing-tiers/scheduled/${effectiveFrom}`);
|
||||
}
|
||||
|
||||
// === SaaS-admin → Цены поставщиков (Plan 4 / Sprint 5C G3) ===
|
||||
|
||||
export interface AdminSupplier {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
cost_rub: string;
|
||||
quality_score: string;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export async function getAdminSuppliers(): Promise<AdminSupplier[]> {
|
||||
const { data } = await apiClient.get<{ data: AdminSupplier[] }>('/api/admin/suppliers');
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function updateAdminSupplier(
|
||||
id: number,
|
||||
payload: { cost_rub: string; quality_score: string; is_active: boolean },
|
||||
): Promise<AdminSupplier> {
|
||||
await ensureCsrfCookie();
|
||||
const { data } = await apiClient.patch<{ data: AdminSupplier }>(`/api/admin/suppliers/${id}`, payload);
|
||||
return data.data;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* 3-step state-machine:
|
||||
* 1. 'reason' — textarea для основания (≥30 chars) → POST /api/admin/impersonation/init.
|
||||
* 2. 'verify' — показ email клиента + ввод 6-значного кода → /api/admin/impersonation/verify.
|
||||
* На dev показывается _dev_plain_code (на prod исчезнет после MailService).
|
||||
* На dev показывается _dev_plain_code (за import.meta.env.DEV; на prod баннер не рендерится).
|
||||
* 3. 'active' — chip «Сессия активна», кнопка «Завершить» → /api/admin/impersonation/end.
|
||||
*
|
||||
* NB: на MVP saas-admin auth не реализован, requested_by передаётся параметром
|
||||
@@ -49,6 +49,10 @@ const expiresAt = ref<string | null>(null);
|
||||
const devPlainCode = ref<string | null>(null);
|
||||
const usedAtIso = ref<string | null>(null);
|
||||
|
||||
// I4: явный frontend DEV-gate. import.meta.env.DEV статически заменяется Vite —
|
||||
// в prod-сборке = false, баннер с плейн-кодом tree-shake'ится.
|
||||
const isDevEnv = import.meta.env.DEV;
|
||||
|
||||
const reasonLength = computed(() => reason.value.trim().length);
|
||||
const reasonRemaining = computed(() => Math.max(0, 30 - reasonLength.value));
|
||||
const reasonValid = computed(() => reasonLength.value >= 30);
|
||||
@@ -216,7 +220,7 @@ function close() {
|
||||
data-testid="code-input"
|
||||
/>
|
||||
<v-alert
|
||||
v-if="devPlainCode"
|
||||
v-if="isDevEnv && devPlainCode"
|
||||
type="success"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
|
||||
@@ -46,7 +46,15 @@ const tariffPriceText = computed(() => {
|
||||
@click="$emit('topup')"
|
||||
>Пополнить</v-btn
|
||||
>
|
||||
<v-btn variant="outlined" prepend-icon="mdi-autorenew" size="small"> Автопополнение </v-btn>
|
||||
<v-tooltip text="Автопополнение будет доступно после подключения платёжного шлюза.">
|
||||
<template #activator="{ props: tipProps }">
|
||||
<span v-bind="tipProps" class="d-inline-flex">
|
||||
<v-btn variant="outlined" prepend-icon="mdi-autorenew" size="small" disabled>
|
||||
Автопополнение
|
||||
</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
@@ -78,7 +86,13 @@ const tariffPriceText = computed(() => {
|
||||
</ul>
|
||||
</template>
|
||||
<div v-else class="tariff-empty mt-2">Тариф не выбран</div>
|
||||
<v-btn variant="outlined" size="small" class="mt-auto">Сменить тариф →</v-btn>
|
||||
<v-tooltip text="Самостоятельная смена тарифа появится после запуска биллинга.">
|
||||
<template #activator="{ props: tipProps }">
|
||||
<span v-bind="tipProps" class="mt-auto d-inline-flex">
|
||||
<v-btn variant="outlined" size="small" disabled>Сменить тариф →</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
||||
import type { MockDeal } from '../../composables/mockDeals';
|
||||
import { type DealEvent, MOCK_EVENTS } from '../../composables/mockDealEvents';
|
||||
import { type DealEvent } from '../../composables/mockDealEvents';
|
||||
import { mapApiDealEvent } from '../../composables/dealsApiMapper';
|
||||
import * as dealsApi from '../../api/deals';
|
||||
import * as remindersApi from '../../api/reminders';
|
||||
@@ -56,8 +56,8 @@ function formatCost(cost: number): string {
|
||||
}
|
||||
|
||||
// Activity timeline: при наличии tenant_id делаем GET /api/deals/{id} и
|
||||
// показываем реальные events. На fail / без tenant_id — fallback на MOCK_EVENTS.
|
||||
const events = ref<DealEvent[]>([...MOCK_EVENTS]);
|
||||
// показываем реальные events. На fail / без tenant_id — events пуст + eventsFetchError.
|
||||
const events = ref<DealEvent[]>([]);
|
||||
const eventsLoading = ref(false);
|
||||
const eventsFetchError = ref(false);
|
||||
|
||||
@@ -116,7 +116,7 @@ function formatReminderTime(iso: string | null): string {
|
||||
|
||||
async function loadEvents() {
|
||||
if (!props.deal || !props.tenantId) {
|
||||
events.value = [...MOCK_EVENTS];
|
||||
events.value = [];
|
||||
commentDraft.value = '';
|
||||
return;
|
||||
}
|
||||
@@ -128,7 +128,7 @@ async function loadEvents() {
|
||||
commentDraft.value = res.deal.comment ?? '';
|
||||
} catch {
|
||||
eventsFetchError.value = true;
|
||||
events.value = [...MOCK_EVENTS];
|
||||
events.value = [];
|
||||
commentDraft.value = '';
|
||||
} finally {
|
||||
eventsLoading.value = false;
|
||||
|
||||
@@ -14,15 +14,16 @@ import * as dealsApi from '../../api/deals';
|
||||
import { extractErrorMessage } from '../../api/client';
|
||||
import { ref, watch } from 'vue';
|
||||
import { LEAD_STATUSES } from '../../composables/leadStatuses';
|
||||
import { MOCK_MANAGERS, MOCK_PROJECTS, type MockDeal, type MockManager } from '../../composables/mockDeals';
|
||||
import { type MockDeal, type MockManager } from '../../composables/mockDeals';
|
||||
|
||||
/**
|
||||
* Управление source для проектов и менеджеров. Если tenantId передан, загружаем
|
||||
* с backend через GET /api/projects, /api/managers. На fail (network) —
|
||||
* fallback на MOCK_PROJECTS/MOCK_MANAGERS (UI всё равно работоспособен).
|
||||
* Списки проектов и менеджеров грузятся с backend через GET /api/projects,
|
||||
* /api/managers при открытии диалога (если передан tenantId). На fail —
|
||||
* списки пустые + degradation-alert (lookupsFailed), создание блокируется
|
||||
* до повторной успешной загрузки.
|
||||
*/
|
||||
const projectOptions = ref<string[]>([...MOCK_PROJECTS]);
|
||||
const managerOptions = ref<MockManager[]>([...MOCK_MANAGERS]);
|
||||
const projectOptions = ref<string[]>([]);
|
||||
const managerOptions = ref<MockManager[]>([]);
|
||||
// Map name → backend-id, нужен только когда manager_id отправляется на backend.
|
||||
const managerIdByName = ref<Map<string, number>>(new Map());
|
||||
|
||||
@@ -77,7 +78,7 @@ const errors = ref<Record<string, string>>({});
|
||||
const submitError = ref<string | null>(null);
|
||||
const busy = ref(false);
|
||||
|
||||
// Audit C6: loadLookups упал → показываем degradation-alert (списки = mock).
|
||||
// Audit C6: loadLookups упал → показываем degradation-alert (списки пусты).
|
||||
const lookupsFailed = ref(false);
|
||||
|
||||
// Регенерируем ID на каждое создание для local-mode. На API — backend SERIAL.
|
||||
@@ -175,7 +176,7 @@ async function submit() {
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ lookupsFailed });
|
||||
defineExpose({ lookupsFailed, projectOptions, managerOptions });
|
||||
|
||||
function close() {
|
||||
dialogOpen.value = false;
|
||||
@@ -205,8 +206,7 @@ function close() {
|
||||
class="mb-3"
|
||||
data-testid="lookups-error-alert"
|
||||
>
|
||||
Не удалось загрузить списки проектов и менеджеров — показаны примерные значения. Проверьте выбор
|
||||
перед сохранением.
|
||||
Не удалось загрузить списки проектов и менеджеров — попробуйте позже.
|
||||
</v-alert>
|
||||
<v-row dense>
|
||||
<v-col cols="12" md="6">
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* Мок платежа «в обработке» для pending-баннера BillingView.
|
||||
*
|
||||
* Кошелёк / транзакции / счета подключены к real API (api/billing.ts) в
|
||||
* Sprint 2 Plan C (E3). Pending-баннер — отдельный эпик E4 (Sprint 5);
|
||||
* до его реализации остаётся mock.
|
||||
*/
|
||||
export interface PendingPayment {
|
||||
code: string;
|
||||
amount: number;
|
||||
method: string;
|
||||
startedAt: string;
|
||||
autoCancelAt: string;
|
||||
timeoutMinutes: number;
|
||||
}
|
||||
|
||||
export const MOCK_PENDING: PendingPayment | null = {
|
||||
code: 'TX-89421',
|
||||
amount: 5000,
|
||||
method: 'ЮKassa',
|
||||
startedAt: '14:21',
|
||||
autoCancelAt: '14:51',
|
||||
timeoutMinutes: 30,
|
||||
};
|
||||
@@ -6,9 +6,8 @@
|
||||
* Sprint 2 Plan C (E3): Overview-таб подвязан на real API
|
||||
* (GET /api/billing/wallet → BalanceCard + шапка; TransactionsTable и
|
||||
* InvoicesTable тянут данные сами). Списания — ChargesTab (Plan 4).
|
||||
*
|
||||
* Pending-баннер остаётся mock (MOCK_PENDING) — это отдельный эпик E4
|
||||
* (Sprint 5). TopupDialog «Пополнить баланс» — Task 5 (E1).
|
||||
* Sprint 5C (E4): pending-баннер убран — платёжного шлюза нет (Б-1), реального состояния «платёж в обработке» в БД не существует.
|
||||
* TopupDialog «Пополнить баланс» — Task 5 (E1).
|
||||
*/
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import BalanceCard from '../components/billing/BalanceCard.vue';
|
||||
@@ -16,7 +15,6 @@ import TransactionsTable from '../components/billing/TransactionsTable.vue';
|
||||
import InvoicesTable from '../components/billing/InvoicesTable.vue';
|
||||
import TopupDialog from '../components/billing/TopupDialog.vue';
|
||||
import ChargesTab from './billing/ChargesTab.vue';
|
||||
import { MOCK_PENDING } from '../composables/mockBilling';
|
||||
import { formatPlain, featureLabel } from '../composables/billingFormatters';
|
||||
import { getWallet, type Wallet } from '../api/billing';
|
||||
import { extractErrorMessage } from '../api/client';
|
||||
@@ -111,19 +109,6 @@ defineExpose({ loadWallet, wallet, topupOpen });
|
||||
</v-alert>
|
||||
|
||||
<template v-else-if="wallet">
|
||||
<v-alert
|
||||
v-if="MOCK_PENDING"
|
||||
type="info"
|
||||
variant="tonal"
|
||||
density="compact"
|
||||
class="mt-4"
|
||||
role="status"
|
||||
>
|
||||
<strong>1 платёж в обработке</strong> — {{ formatPlain(MOCK_PENDING.amount) }} от
|
||||
{{ MOCK_PENDING.method }}, начат {{ MOCK_PENDING.startedAt }}. Авто-восстановление в
|
||||
{{ MOCK_PENDING.autoCancelAt }} ({{ MOCK_PENDING.timeoutMinutes }} мин).
|
||||
</v-alert>
|
||||
|
||||
<BalanceCard
|
||||
:wallet-rub="walletRub"
|
||||
:leads-balance="leadsBalance"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Список сделок — центральный экран CRM. Используется менеджерами ежедневно.
|
||||
*
|
||||
* Источник дизайна: liderra_v8_handoff/concepts/v8_deals.html.
|
||||
* MVP: page-head + chiprow со срезами + поиск + v-data-table с mock'ами.
|
||||
* MVP: page-head + chiprow со срезами + поиск + v-data-table (данные из API).
|
||||
*
|
||||
* Не входит в этот коммит (отдельные TODO):
|
||||
* - Drawer с деталями сделки при клике на строку (правая панель v-navigation-drawer right).
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { computed, defineAsyncComponent, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { DEALS_TABS, MOCK_DEALS, type MockDeal } from '../composables/mockDeals';
|
||||
import { DEALS_TABS, type MockDeal } from '../composables/mockDeals';
|
||||
import { mapApiDeal } from '../composables/dealsApiMapper';
|
||||
import { usePolling } from '../composables/usePolling';
|
||||
// Sprint 2 Phase B / O-perf-06: lazy-imports для тяжёлых компонентов, гейтящихся
|
||||
@@ -107,10 +107,9 @@ const selected = ref<number[]>([]);
|
||||
const filterProjects = ref<string[]>([]);
|
||||
const filterManagers = ref<string[]>([]);
|
||||
|
||||
// Локальная reactive-копия. При наличии auth.user.tenant_id — fetch через
|
||||
// API (см. onMounted ниже); на network/500 — fallback на MOCK_DEALS чтобы UI
|
||||
// оставался работоспособным (полезно для dev и Vitest jsdom-среды).
|
||||
const dealsState = reactive<MockDeal[]>(MOCK_DEALS.map((d) => ({ ...d, manager: { ...d.manager } })));
|
||||
// Локальная reactive-копия. Наполняется через API (loadDeals/onMounted);
|
||||
// до загрузки и при ошибке пуст.
|
||||
const dealsState = reactive<MockDeal[]>([]);
|
||||
const loading = ref(false);
|
||||
const fetchError = ref(false);
|
||||
|
||||
@@ -130,7 +129,7 @@ async function loadDeals() {
|
||||
const mapped = deals.map((d) => mapApiDeal(d));
|
||||
dealsState.splice(0, dealsState.length, ...mapped);
|
||||
} catch {
|
||||
fetchError.value = true; // оставляем MOCK_DEALS как fallback
|
||||
fetchError.value = true; // state остаётся пустым — показываем error-alert
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
@@ -720,7 +719,7 @@ const waitingPay = computed(() => dealsState.filter((d) => d.statusSlug === 'wai
|
||||
class="mt-3"
|
||||
data-testid="fetch-error-alert"
|
||||
>
|
||||
Backend недоступен — показаны mock-данные.
|
||||
Не удалось загрузить сделки. Попробуйте обновить.
|
||||
</v-alert>
|
||||
|
||||
<!-- Task 15: wrapper с .ld-hover-lift + .ld-stagger-row для quiet-luxury motion
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
*/
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { LEAD_STATUSES } from '../composables/leadStatuses';
|
||||
import { MOCK_DEALS, type MockDeal } from '../composables/mockDeals';
|
||||
import { type MockDeal } from '../composables/mockDeals';
|
||||
import { mapApiDeal } from '../composables/dealsApiMapper';
|
||||
import { usePolling } from '../composables/usePolling';
|
||||
import * as dealsApi from '../api/deals';
|
||||
@@ -44,11 +44,10 @@ interface DraggableChangeEvent {
|
||||
}
|
||||
|
||||
// Reactive Record<slug, MockDeal[]> — отдельный массив для каждой колонки
|
||||
// (vuedraggable v-model требует независимые arrays). Deep-clone объектов
|
||||
// сделок чтобы не мутировать MOCK_DEALS const при DnD.
|
||||
// (vuedraggable v-model требует независимые arrays).
|
||||
const dealsByStatus = reactive<Record<string, MockDeal[]>>(
|
||||
LEAD_STATUSES.reduce<Record<string, MockDeal[]>>((acc, s) => {
|
||||
acc[s.slug] = MOCK_DEALS.filter((d) => d.statusSlug === s.slug).map((d) => ({ ...d }));
|
||||
acc[s.slug] = [];
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
@@ -108,7 +107,7 @@ function onOpenDeal(id: number) {
|
||||
}
|
||||
}
|
||||
|
||||
const totalDeals = ref(MOCK_DEALS.length);
|
||||
const totalDeals = ref(0);
|
||||
const fetchError = ref(false);
|
||||
|
||||
const newDealOpen = ref(false);
|
||||
@@ -139,7 +138,7 @@ async function loadDeals() {
|
||||
}
|
||||
totalDeals.value = total;
|
||||
} catch {
|
||||
fetchError.value = true; // оставляем MOCK_DEALS как fallback
|
||||
fetchError.value = true; // state остаётся пустым — показываем error-alert
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +204,7 @@ defineExpose({
|
||||
class="mt-3"
|
||||
data-testid="fetch-error-alert"
|
||||
>
|
||||
Backend недоступен — показаны mock-данные.
|
||||
Не удалось загрузить сделки. Попробуйте обновить.
|
||||
</v-alert>
|
||||
|
||||
<div class="kanban-board mt-4" tabindex="0" role="region" aria-label="Канбан-доска воронки продаж">
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
* Сводный биллинг по всем тенантам: выручка, MRR, retention, refunds.
|
||||
* Источник данных: aggregate balance_transactions / invoices / tariff_subscriptions.
|
||||
*
|
||||
* MVP — только display-вьюха с mock-данными. Backend `/api/admin/billing/*`
|
||||
* подключается отдельным коммитом.
|
||||
* Данные грузятся с backend GET /api/admin/billing.
|
||||
*/
|
||||
import { ADMIN_BILLING_SUMMARY as MOCK_SUMMARY, ADMIN_BILLING_TENANTS } from '../../composables/mockAdmin';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { usePolling } from '../../composables/usePolling';
|
||||
import * as adminApi from '../../api/admin';
|
||||
@@ -17,11 +15,6 @@ import { extractErrorMessage } from '../../api/client';
|
||||
|
||||
const search = ref('');
|
||||
|
||||
/**
|
||||
* Reactive-копия — initial = MOCK для UI без backend'а; replace на API на mount.
|
||||
* View работает в обоих режимах: row может быть из mock (узкие enum-types)
|
||||
* или из API (открытые string-типы).
|
||||
*/
|
||||
type BillingRow = {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -34,25 +27,13 @@ type BillingRow = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
const rowsState = reactive<BillingRow[]>(
|
||||
ADMIN_BILLING_TENANTS.map((r) => ({
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
inn: r.inn,
|
||||
tariff: r.tariff,
|
||||
balance_rub: r.balance_rub,
|
||||
monthly_topups_rub: r.monthly_topups_rub,
|
||||
monthly_charges_rub: r.monthly_charges_rub,
|
||||
mrr_rub: r.mrr_rub,
|
||||
status: r.status,
|
||||
})),
|
||||
);
|
||||
const rowsState = reactive<BillingRow[]>([]);
|
||||
|
||||
const summary = reactive({
|
||||
total_mrr_rub: MOCK_SUMMARY.total_mrr_rub,
|
||||
monthly_revenue_rub: MOCK_SUMMARY.monthly_revenue_rub,
|
||||
overdue_count: MOCK_SUMMARY.overdue_count,
|
||||
refunds_count_30d: MOCK_SUMMARY.refunds_count_30d,
|
||||
total_mrr_rub: 0,
|
||||
monthly_revenue_rub: 0,
|
||||
overdue_count: 0,
|
||||
refunds_count_30d: 0,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
@@ -255,7 +236,7 @@ function tariffLabel(t: string): string {
|
||||
class="mb-4"
|
||||
data-testid="fetch-error-alert"
|
||||
>
|
||||
Backend недоступен — показаны mock-данные.
|
||||
Не удалось загрузить биллинг. Попробуйте обновить.
|
||||
</v-alert>
|
||||
|
||||
<!-- Stats row -->
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
* Категории: PDN-breach, service_outage, security, billing, data_loss.
|
||||
* При PDN-breach — обязательное уведомление РКН за 24 ч (152-ФЗ).
|
||||
*
|
||||
* MVP — display + фильтр по статусу/severity. Backend `/api/admin/incidents`
|
||||
* подключается отдельным коммитом.
|
||||
* Display + фильтр по статусу/severity. Данные с backend GET /api/admin/incidents.
|
||||
*/
|
||||
import { ADMIN_INCIDENTS } from '../../composables/mockAdmin';
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { usePolling } from '../../composables/usePolling';
|
||||
@@ -73,32 +71,12 @@ function categoryLabel(c: string): string {
|
||||
return categoryMap[c] ?? c;
|
||||
}
|
||||
|
||||
// Reactive — initial = MOCK; replace на API на mount.
|
||||
const rowsState = reactive<IncidentRow[]>(
|
||||
ADMIN_INCIDENTS.map((r) => ({
|
||||
id: r.id,
|
||||
incident_id: r.incident_id,
|
||||
title: r.title,
|
||||
severity: r.severity,
|
||||
category: r.category,
|
||||
status: r.status,
|
||||
detected_at: r.detected_at,
|
||||
affected_tenants: r.affected_tenants,
|
||||
rkn_notified: r.rkn_notified,
|
||||
rkn_deadline_at: r.rkn_deadline_at,
|
||||
})),
|
||||
);
|
||||
// Reactive — наполняется через loadIncidents (API).
|
||||
const rowsState = reactive<IncidentRow[]>([]);
|
||||
const stats = reactive({ open: 0, investigating: 0, rkn_pending: 0 });
|
||||
const loading = ref(false);
|
||||
const fetchError = ref(false);
|
||||
|
||||
// Initial stats из mock (UI consistency без backend'а).
|
||||
stats.open = rowsState.filter((r) => r.status === 'open').length;
|
||||
stats.investigating = rowsState.filter((r) => r.status === 'investigating').length;
|
||||
stats.rkn_pending = rowsState.filter(
|
||||
(r) => (r.category === 'pdn_breach' || r.category === 'data_breach') && !r.rkn_notified,
|
||||
).length;
|
||||
|
||||
async function loadIncidents() {
|
||||
loading.value = true;
|
||||
fetchError.value = false;
|
||||
@@ -176,7 +154,7 @@ function formatDate(iso: string): string {
|
||||
class="mb-4"
|
||||
data-testid="fetch-error-alert"
|
||||
>
|
||||
Backend недоступен — показаны mock-данные.
|
||||
Не удалось загрузить инциденты. Попробуйте обновить.
|
||||
</v-alert>
|
||||
|
||||
<v-row class="mb-4" data-testid="incidents-stats">
|
||||
|
||||
@@ -50,14 +50,24 @@
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-btn color="primary" prepend-icon="mdi-pencil" data-testid="open-editor-btn" @click="editorOpen = true">
|
||||
<v-btn color="primary" prepend-icon="mdi-pencil" data-testid="open-editor-btn" @click="openEditor">
|
||||
Редактировать сетку (с {{ nextMonthStart }})
|
||||
</v-btn>
|
||||
|
||||
<v-dialog v-model="editorOpen" max-width="900">
|
||||
<v-card>
|
||||
<v-card-title>Новая сетка (effective_from = {{ nextMonthStart }})</v-card-title>
|
||||
<v-card-title>Новая сетка (effective_from = {{ effectiveFrom }})</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="effectiveFrom"
|
||||
type="date"
|
||||
label="Дата вступления в силу"
|
||||
:min="minEffectiveFrom"
|
||||
density="compact"
|
||||
class="mb-3"
|
||||
style="max-width: 240px"
|
||||
data-testid="effective-from-input"
|
||||
/>
|
||||
<table class="editor-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -102,6 +112,21 @@
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-dialog v-model="deleteDialogOpen" max-width="440">
|
||||
<v-card>
|
||||
<v-card-title>Удалить запланированный набор?</v-card-title>
|
||||
<v-card-text>
|
||||
Запланированная сетка с <strong>{{ deleteTarget }}</strong> будет удалена.
|
||||
Действие необратимо.
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="deleteDialogOpen = false">Отмена</v-btn>
|
||||
<v-btn color="error" data-testid="confirm-delete-btn" @click="performDelete">Удалить</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-snackbar
|
||||
v-model="successToastOpen"
|
||||
:timeout="4000"
|
||||
@@ -116,7 +141,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { getPricingTiers, createPricingTiers, deleteScheduledPricingTier, type AdminPricingTier, type PricingTierEditorRow } from '../../api/admin';
|
||||
import { extractErrorMessage } from '../../api/client';
|
||||
|
||||
/**
|
||||
@@ -128,20 +153,8 @@ import { extractErrorMessage } from '../../api/client';
|
||||
* defineExpose ниже — для Vitest unit-тестов.
|
||||
*/
|
||||
|
||||
interface Tier {
|
||||
tier_no: number;
|
||||
leads_in_tier: number | null;
|
||||
price_per_lead_kopecks: number;
|
||||
effective_from: string;
|
||||
}
|
||||
interface EditorRow {
|
||||
tier_no: number;
|
||||
leads_in_tier: number | null;
|
||||
price_rub: string;
|
||||
}
|
||||
|
||||
const active = ref<Tier[]>([]);
|
||||
const scheduled = ref<Record<string, Tier[]>>({});
|
||||
const active = ref<AdminPricingTier[]>([]);
|
||||
const scheduled = ref<Record<string, AdminPricingTier[]>>({});
|
||||
const editorOpen = ref(false);
|
||||
const saving = ref(false);
|
||||
|
||||
@@ -150,7 +163,11 @@ const errorMessage = ref<string | null>(null);
|
||||
const successMessage = ref<string | null>(null);
|
||||
const successToastOpen = ref(false);
|
||||
|
||||
const defaultEditor: EditorRow[] = [
|
||||
// G10: диалог подтверждения удаления (замена window.confirm).
|
||||
const deleteDialogOpen = ref(false);
|
||||
const deleteTarget = ref<string | null>(null);
|
||||
|
||||
const defaultEditor: PricingTierEditorRow[] = [
|
||||
{ tier_no: 1, leads_in_tier: 100, price_rub: '500.00' },
|
||||
{ tier_no: 2, leads_in_tier: 200, price_rub: '450.00' },
|
||||
{ tier_no: 3, leads_in_tier: 400, price_rub: '400.00' },
|
||||
@@ -159,7 +176,7 @@ const defaultEditor: EditorRow[] = [
|
||||
{ tier_no: 6, leads_in_tier: 3000, price_rub: '270.00' },
|
||||
{ tier_no: 7, leads_in_tier: null, price_rub: '250.00' },
|
||||
];
|
||||
const editor = ref<EditorRow[]>(JSON.parse(JSON.stringify(defaultEditor)));
|
||||
const editor = ref<PricingTierEditorRow[]>(JSON.parse(JSON.stringify(defaultEditor)));
|
||||
|
||||
const tierHeaders = [
|
||||
{ title: '№', key: 'tier_no', sortable: false, width: 80 },
|
||||
@@ -174,13 +191,21 @@ const nextMonthStart = computed(() => {
|
||||
return d.toISOString().slice(0, 10);
|
||||
});
|
||||
|
||||
const effectiveFrom = ref<string>(nextMonthStart.value);
|
||||
|
||||
const minEffectiveFrom = computed(() => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() + 1);
|
||||
return d.toISOString().slice(0, 10);
|
||||
});
|
||||
|
||||
const hasScheduled = computed(() => Object.keys(scheduled.value).length > 0);
|
||||
|
||||
async function load(): Promise<void> {
|
||||
try {
|
||||
const { data } = await axios.get('/api/admin/pricing-tiers');
|
||||
active.value = data.data.active;
|
||||
scheduled.value = data.data.scheduled || {};
|
||||
const data = await getPricingTiers();
|
||||
active.value = data.active;
|
||||
scheduled.value = data.scheduled;
|
||||
} catch (err) {
|
||||
errorMessage.value = extractErrorMessage(err, 'Не удалось загрузить тарифную сетку.');
|
||||
}
|
||||
@@ -191,9 +216,9 @@ async function submit(): Promise<void> {
|
||||
errorMessage.value = null;
|
||||
successMessage.value = null;
|
||||
try {
|
||||
await axios.post('/api/admin/pricing-tiers', { tiers: editor.value });
|
||||
await createPricingTiers(editor.value, effectiveFrom.value);
|
||||
editorOpen.value = false;
|
||||
successMessage.value = `Сохранено: новая сетка вступит в силу с ${nextMonthStart.value}.`;
|
||||
successMessage.value = `Сохранено: новая сетка вступит в силу с ${effectiveFrom.value}.`;
|
||||
successToastOpen.value = true;
|
||||
await load();
|
||||
} catch (err) {
|
||||
@@ -204,19 +229,31 @@ async function submit(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmDelete(effectiveFrom: string): Promise<void> {
|
||||
if (!window.confirm(`Удалить запланированный набор с ${effectiveFrom}?`)) {
|
||||
return;
|
||||
}
|
||||
function openEditor(): void {
|
||||
effectiveFrom.value = nextMonthStart.value;
|
||||
editorOpen.value = true;
|
||||
}
|
||||
|
||||
function confirmDelete(effectiveFromDate: string): void {
|
||||
deleteTarget.value = effectiveFromDate;
|
||||
deleteDialogOpen.value = true;
|
||||
}
|
||||
|
||||
async function performDelete(): Promise<void> {
|
||||
const effectiveFromDate = deleteTarget.value;
|
||||
if (effectiveFromDate === null) return;
|
||||
deleteDialogOpen.value = false;
|
||||
errorMessage.value = null;
|
||||
successMessage.value = null;
|
||||
try {
|
||||
await axios.delete(`/api/admin/pricing-tiers/scheduled/${effectiveFrom}`);
|
||||
successMessage.value = `Удалено: запланированный набор с ${effectiveFrom}.`;
|
||||
await deleteScheduledPricingTier(effectiveFromDate);
|
||||
successMessage.value = `Удалено: запланированный набор с ${effectiveFromDate}.`;
|
||||
successToastOpen.value = true;
|
||||
await load();
|
||||
} catch (err) {
|
||||
errorMessage.value = extractErrorMessage(err, 'Не удалось удалить запланированный набор.');
|
||||
} finally {
|
||||
deleteTarget.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +262,9 @@ onMounted(load);
|
||||
defineExpose({
|
||||
load,
|
||||
submit,
|
||||
openEditor,
|
||||
confirmDelete,
|
||||
performDelete,
|
||||
editorOpen,
|
||||
active,
|
||||
scheduled,
|
||||
@@ -234,6 +273,9 @@ defineExpose({
|
||||
successMessage,
|
||||
successToastOpen,
|
||||
saving,
|
||||
effectiveFrom,
|
||||
deleteDialogOpen,
|
||||
deleteTarget,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { getAdminSuppliers, updateAdminSupplier, type AdminSupplier } from '../../api/admin';
|
||||
import { extractErrorMessage } from '../../api/client';
|
||||
|
||||
/**
|
||||
@@ -100,16 +100,7 @@ import { extractErrorMessage } from '../../api/client';
|
||||
* defineExpose ниже — для Vitest unit-тестов.
|
||||
*/
|
||||
|
||||
interface SupplierRow {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
cost_rub: string;
|
||||
quality_score: string;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
const suppliers = ref<SupplierRow[]>([]);
|
||||
const suppliers = ref<AdminSupplier[]>([]);
|
||||
const saving = reactive<Record<number, boolean>>({});
|
||||
const errorMessages = reactive<Record<number, string>>({});
|
||||
const fetchError = ref<string | null>(null);
|
||||
@@ -128,22 +119,17 @@ const headers = [
|
||||
async function load(): Promise<void> {
|
||||
fetchError.value = null;
|
||||
try {
|
||||
const { data } = await axios.get('/api/admin/suppliers');
|
||||
suppliers.value = data.data;
|
||||
suppliers.value = await getAdminSuppliers();
|
||||
} catch (err) {
|
||||
fetchError.value = extractErrorMessage(err, 'Не удалось загрузить список поставщиков.');
|
||||
}
|
||||
}
|
||||
|
||||
async function save(s: SupplierRow): Promise<void> {
|
||||
async function save(s: AdminSupplier): Promise<void> {
|
||||
saving[s.id] = true;
|
||||
delete errorMessages[s.id]; // очистить предыдущую ошибку перед retry
|
||||
try {
|
||||
await axios.patch(`/api/admin/suppliers/${s.id}`, {
|
||||
cost_rub: s.cost_rub,
|
||||
quality_score: s.quality_score,
|
||||
is_active: s.is_active,
|
||||
});
|
||||
await updateAdminSupplier(s.id, { cost_rub: s.cost_rub, quality_score: s.quality_score, is_active: s.is_active });
|
||||
successToastText.value = `Сохранено: ${s.name} (${s.code}).`;
|
||||
successToastOpen.value = true;
|
||||
} catch (err) {
|
||||
|
||||
@@ -5,10 +5,8 @@
|
||||
* Глобальные настройки SaaS-уровня (system_settings по schema v8.7 §10):
|
||||
* лимиты квот, тарифные планы, фичефлаги, fallback supplier_id.
|
||||
*
|
||||
* MVP — display + read-only edit-режим. Backend `/api/admin/system-settings`
|
||||
* + edit-flow подключаются отдельным коммитом.
|
||||
* Display + edit-режим. Данные с backend GET /api/admin/system-settings.
|
||||
*/
|
||||
import { ADMIN_SYSTEM_SETTINGS } from '../../composables/mockAdmin';
|
||||
import type { AdminSystemSetting } from '../../composables/mockAdmin';
|
||||
import * as adminApi from '../../api/admin';
|
||||
import type { SystemSetting as ApiSystemSetting } from '../../api/admin';
|
||||
@@ -21,13 +19,10 @@ const loading = ref(false);
|
||||
const fetchError = ref<string | null>(null);
|
||||
|
||||
/**
|
||||
* Settings-state. Инициируется mock-данными (fallback если backend недоступен),
|
||||
* на mount — replace через `adminApi.listSystemSettings()`.
|
||||
*
|
||||
* Type-narrowing: AdminSystemSetting (mock) vs ApiSystemSetting различаются
|
||||
* только origin (mock vs БД), shape совместим — оба `{key, value, type, ...}`.
|
||||
* Settings-state. Наполняется на mount через `adminApi.listSystemSettings()`.
|
||||
* До загрузки и при ошибке — пустой; ошибка показывается через fetchError-banner.
|
||||
*/
|
||||
const settingsState = reactive<AdminSystemSetting[]>([...ADMIN_SYSTEM_SETTINGS]);
|
||||
const settingsState = reactive<AdminSystemSetting[]>([]);
|
||||
|
||||
async function loadSettings() {
|
||||
loading.value = true;
|
||||
@@ -37,8 +32,8 @@ async function loadSettings() {
|
||||
// Replace всё содержимое сохранив reactive-ref.
|
||||
settingsState.splice(0, settingsState.length, ...(fromApi as unknown as AdminSystemSetting[]));
|
||||
} catch (err) {
|
||||
// На fail оставляем mock (не очищаем UI). Показываем error-banner.
|
||||
fetchError.value = extractErrorMessage(err, 'Не удалось загрузить настройки с сервера. Показаны mock-данные.');
|
||||
// На fail — settingsState пустой, показываем error-banner.
|
||||
fetchError.value = extractErrorMessage(err, 'Не удалось загрузить настройки с сервера. Попробуйте обновить.');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { MOCK_STATS, MOCK_TENANTS, type AdminTenant, type TenantStatus } from '../../composables/mockTenants';
|
||||
import { type AdminTenant, type TenantStatus } from '../../composables/mockTenants';
|
||||
import { mapApiAdminTenant } from '../../composables/adminTenantsMapper';
|
||||
import { usePolling } from '../../composables/usePolling';
|
||||
import * as adminApi from '../../api/admin';
|
||||
@@ -29,8 +29,8 @@ import TenantsTable from '../../components/admin/tenants/TenantsTable.vue';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const tenantsState = reactive<AdminTenant[]>(MOCK_TENANTS.map((t) => ({ ...t })));
|
||||
const stats = reactive({ ...MOCK_STATS });
|
||||
const tenantsState = reactive<AdminTenant[]>([]);
|
||||
const stats = reactive({ total: 0, active: 0, trial: 0, overdue: 0, monthlyRevenueRub: 0 });
|
||||
const loading = ref(false);
|
||||
const fetchError = ref(false);
|
||||
|
||||
@@ -123,7 +123,7 @@ const filteredTenants = computed<AdminTenant[]>(() => {
|
||||
class="mt-3"
|
||||
data-testid="fetch-error-alert"
|
||||
>
|
||||
Backend недоступен — показаны mock-данные.
|
||||
Не удалось загрузить тенантов. Попробуйте обновить.
|
||||
</v-alert>
|
||||
|
||||
<TenantsFilters
|
||||
|
||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
use App\Models\PricingTier;
|
||||
use Database\Seeders\PricingTierSeeder;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
uses(DatabaseTransactions::class);
|
||||
@@ -91,6 +92,60 @@ it('DELETE /scheduled/{effective_from} removes future tiers only', function () {
|
||||
expect(PricingTier::where('effective_from', '1970-01-01')->count())->toBe(7);
|
||||
});
|
||||
|
||||
it('store accepts a custom effective_from date', function (): void {
|
||||
$custom = Carbon::now('Europe/Moscow')->addMonths(3)->toDateString();
|
||||
|
||||
$response = $this->postJson('/api/admin/pricing-tiers', [
|
||||
'tiers' => [
|
||||
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
||||
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
||||
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
||||
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
||||
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
||||
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
||||
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
||||
],
|
||||
'effective_from' => $custom,
|
||||
]);
|
||||
|
||||
$response->assertCreated()->assertJson(['effective_from' => $custom]);
|
||||
expect(PricingTier::where('effective_from', $custom)->count())->toBe(7);
|
||||
});
|
||||
|
||||
it('store rejects effective_from равную сегодня', function (): void {
|
||||
$today = Carbon::now('Europe/Moscow')->toDateString();
|
||||
|
||||
$this->postJson('/api/admin/pricing-tiers', [
|
||||
'tiers' => [
|
||||
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
||||
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
||||
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
||||
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
||||
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
||||
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
||||
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
||||
],
|
||||
'effective_from' => $today,
|
||||
])->assertStatus(422);
|
||||
});
|
||||
|
||||
it('store rejects effective_from in the past', function (): void {
|
||||
$past = Carbon::now('Europe/Moscow')->subDay()->toDateString();
|
||||
|
||||
$this->postJson('/api/admin/pricing-tiers', [
|
||||
'tiers' => [
|
||||
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
||||
['tier_no' => 2, 'leads_in_tier' => 150, 'price_rub' => '550.00'],
|
||||
['tier_no' => 3, 'leads_in_tier' => 300, 'price_rub' => '500.00'],
|
||||
['tier_no' => 4, 'leads_in_tier' => 700, 'price_rub' => '450.00'],
|
||||
['tier_no' => 5, 'leads_in_tier' => 1500, 'price_rub' => '400.00'],
|
||||
['tier_no' => 6, 'leads_in_tier' => 3000, 'price_rub' => '350.00'],
|
||||
['tier_no' => 7, 'leads_in_tier' => null, 'price_rub' => '300.00'],
|
||||
],
|
||||
'effective_from' => $past,
|
||||
])->assertStatus(422);
|
||||
});
|
||||
|
||||
it('writes audit-trail row in saas_admin_audit_log on POST', function () {
|
||||
$this->postJson('/api/admin/pricing-tiers', ['tiers' => [
|
||||
['tier_no' => 1, 'leads_in_tier' => 50, 'price_rub' => '600.00'],
|
||||
|
||||
@@ -1,8 +1,46 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import AdminBillingView from '../../resources/js/views/admin/AdminBillingView.vue';
|
||||
import { ADMIN_BILLING_TENANTS, ADMIN_BILLING_SUMMARY } from '../../resources/js/composables/mockAdmin';
|
||||
|
||||
vi.mock('../../resources/js/api/admin', async (importOriginal) => {
|
||||
const orig = await importOriginal<typeof import('../../resources/js/api/admin')>();
|
||||
return {
|
||||
...orig,
|
||||
listAdminBilling: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const adminApi = await import('../../resources/js/api/admin');
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(adminApi.listAdminBilling).mockResolvedValue({
|
||||
tenants: ADMIN_BILLING_TENANTS.map((r) => ({
|
||||
id: r.id,
|
||||
subdomain: `tenant${r.id}`,
|
||||
organization_name: r.name,
|
||||
contact_email: `t${r.id}@test.io`,
|
||||
status: r.status === 'overdue' ? 'active' : r.status,
|
||||
balance_rub: String(r.balance_rub),
|
||||
tariff_id: 1,
|
||||
tariff_name: { start: 'Старт', basic: 'Базовый', pro: 'Команда', enterprise: 'Enterprise' }[r.tariff] ?? r.tariff,
|
||||
mrr_rub: String(r.mrr_rub),
|
||||
monthly_topups_rub: String(r.monthly_topups_rub),
|
||||
monthly_charges_rub: String(r.monthly_charges_rub),
|
||||
last_payment_at: r.last_payment_at,
|
||||
chargeback_unrecovered_rub: r.status === 'overdue' ? '1.00' : '0.00',
|
||||
})),
|
||||
summary: {
|
||||
total_mrr_rub: String(ADMIN_BILLING_SUMMARY.total_mrr_rub),
|
||||
monthly_revenue_rub: String(ADMIN_BILLING_SUMMARY.monthly_revenue_rub),
|
||||
overdue_count: ADMIN_BILLING_SUMMARY.overdue_count,
|
||||
refunds_count_30d: ADMIN_BILLING_SUMMARY.refunds_count_30d,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const mountView = async () => {
|
||||
const router = createRouter({
|
||||
@@ -11,9 +49,11 @@ const mountView = async () => {
|
||||
});
|
||||
await router.push('/admin/billing');
|
||||
await router.isReady();
|
||||
return mount(AdminBillingView, {
|
||||
const wrapper = mount(AdminBillingView, {
|
||||
global: { plugins: [createVuetify(), router] },
|
||||
});
|
||||
await flushPromises();
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
describe('AdminBillingView.vue', () => {
|
||||
|
||||
@@ -93,7 +93,7 @@ describe('AdminBillingView ↔ GET /api/admin/billing integration', () => {
|
||||
expect(vm.summary.refunds_count_30d).toBe(3);
|
||||
});
|
||||
|
||||
it('reject → fetchError=true + alert виден + MOCK fallback остаётся', async () => {
|
||||
it('reject → fetchError=true + alert виден + rowsState пустой', async () => {
|
||||
vi.mocked(adminApi.listAdminBilling).mockRejectedValueOnce(new Error('500'));
|
||||
|
||||
const wrapper = mountView();
|
||||
@@ -101,7 +101,7 @@ describe('AdminBillingView ↔ GET /api/admin/billing integration', () => {
|
||||
|
||||
const vm = wrapper.vm as unknown as { fetchError: boolean; rowsState: unknown[] };
|
||||
expect(vm.fetchError).toBe(true);
|
||||
expect(vm.rowsState.length).toBeGreaterThan(0);
|
||||
expect(vm.rowsState.length).toBe(0);
|
||||
expect(wrapper.find('[data-testid="fetch-error-alert"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,52 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import AdminIncidentsView from '../../resources/js/views/admin/AdminIncidentsView.vue';
|
||||
import { ADMIN_INCIDENTS } from '../../resources/js/composables/mockAdmin';
|
||||
|
||||
vi.mock('../../resources/js/api/admin', async (importOriginal) => {
|
||||
const orig = await importOriginal<typeof import('../../resources/js/api/admin')>();
|
||||
return {
|
||||
...orig,
|
||||
listAdminIncidents: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const adminApi = await import('../../resources/js/api/admin');
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.mocked(adminApi.listAdminIncidents).mockResolvedValue({
|
||||
incidents: ADMIN_INCIDENTS.map((r) => ({
|
||||
id: r.id,
|
||||
incident_id: r.incident_id,
|
||||
type: r.category as string,
|
||||
severity: r.severity,
|
||||
summary: r.title,
|
||||
started_at: r.detected_at,
|
||||
detected_at: r.detected_at,
|
||||
resolved_at: null,
|
||||
status: (r.status === 'closed' ? 'resolved' : r.status) as 'open' | 'investigating' | 'resolved',
|
||||
affected_tenants_count: r.affected_tenants,
|
||||
affected_users_count: null,
|
||||
rkn_notified: r.rkn_notified,
|
||||
rkn_notified_at: null,
|
||||
rkn_deadline_at: r.rkn_deadline_at,
|
||||
})),
|
||||
total: ADMIN_INCIDENTS.length,
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
summary: {
|
||||
open: ADMIN_INCIDENTS.filter((r) => r.status === 'open').length,
|
||||
investigating: ADMIN_INCIDENTS.filter((r) => r.status === 'investigating').length,
|
||||
rkn_pending: ADMIN_INCIDENTS.filter(
|
||||
(r) => ['pdn_breach', 'data_breach'].includes(r.category) && !r.rkn_notified,
|
||||
).length,
|
||||
total_unresolved: ADMIN_INCIDENTS.filter((r) => r.status !== 'resolved' && r.status !== 'closed').length,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const mountView = async () => {
|
||||
const router = createRouter({
|
||||
@@ -14,7 +58,9 @@ const mountView = async () => {
|
||||
});
|
||||
await router.push('/admin/incidents');
|
||||
await router.isReady();
|
||||
return { wrapper: mount(AdminIncidentsView, { global: { plugins: [createVuetify(), router] } }), router };
|
||||
const wrapper = mount(AdminIncidentsView, { global: { plugins: [createVuetify(), router] } });
|
||||
await flushPromises();
|
||||
return { wrapper, router };
|
||||
};
|
||||
|
||||
describe('AdminIncidentsView.vue', () => {
|
||||
@@ -57,7 +103,7 @@ describe('AdminIncidentsView.vue', () => {
|
||||
it('клик по строке инцидента вызывает router.push на admin-incident-detail', async () => {
|
||||
const { wrapper, router } = await mountView();
|
||||
const pushSpy = vi.spyOn(router, 'push');
|
||||
// get first row — mock data has id from ADMIN_INCIDENTS[0]
|
||||
// get first row — populated via API mock
|
||||
const vm = wrapper.vm as unknown as { rowsState: Array<{ id: number }> };
|
||||
const firstId = vm.rowsState[0].id;
|
||||
const row = wrapper.find(`[data-testid="incident-row-${firstId}"]`);
|
||||
|
||||
@@ -97,7 +97,7 @@ describe('AdminIncidentsView ↔ GET /api/admin/incidents integration', () => {
|
||||
expect(vm.stats.rkn_pending).toBe(1);
|
||||
});
|
||||
|
||||
it('reject → fetchError=true + alert виден + MOCK fallback', async () => {
|
||||
it('reject → fetchError=true + alert виден + rowsState пустой', async () => {
|
||||
vi.mocked(adminApi.listAdminIncidents).mockRejectedValueOnce(new Error('500'));
|
||||
|
||||
const wrapper = mountView();
|
||||
@@ -105,7 +105,7 @@ describe('AdminIncidentsView ↔ GET /api/admin/incidents integration', () => {
|
||||
|
||||
const vm = wrapper.vm as unknown as { fetchError: boolean; rowsState: unknown[] };
|
||||
expect(vm.fetchError).toBe(true);
|
||||
expect(vm.rowsState.length).toBeGreaterThan(0);
|
||||
expect(vm.rowsState.length).toBe(0);
|
||||
expect(wrapper.find('[data-testid="fetch-error-alert"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,31 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import axios from 'axios';
|
||||
|
||||
import AdminPricingTiersView from '../../resources/js/views/admin/AdminPricingTiersView.vue';
|
||||
|
||||
vi.mock('axios');
|
||||
/**
|
||||
* Создаёт объект, который проходит `axios.isAxiosError()` (проверяет флаг `isAxiosError: true`),
|
||||
* с нужным `response.data.message`.
|
||||
*/
|
||||
function makeAxiosError(message: string, status = 422): unknown {
|
||||
return Object.assign(new Error(message), {
|
||||
isAxiosError: true,
|
||||
response: { status, data: { message } },
|
||||
});
|
||||
}
|
||||
|
||||
vi.mock('../../resources/js/api/admin', async (importOriginal) => {
|
||||
const orig = await importOriginal<typeof import('../../resources/js/api/admin')>();
|
||||
return {
|
||||
...orig,
|
||||
getPricingTiers: vi.fn(),
|
||||
createPricingTiers: vi.fn(),
|
||||
deleteScheduledPricingTier: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const adminApi = await import('../../resources/js/api/admin');
|
||||
|
||||
// Auto-импорт компонентов/директив Vuetify подхватывает vite-plugin-vuetify
|
||||
// из vitest.config.ts (см. AdminBillingView.spec.ts).
|
||||
@@ -23,12 +43,9 @@ const mockTiers = [
|
||||
|
||||
describe('AdminPricingTiersView', () => {
|
||||
beforeEach(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({ data: { data: { active: mockTiers, scheduled: {} } } });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.post as any).mockResolvedValue({ data: { effective_from: '2026-06-01' } });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.delete as any).mockResolvedValue({ data: { ok: true } });
|
||||
vi.mocked(adminApi.getPricingTiers).mockResolvedValue({ active: mockTiers, scheduled: {} });
|
||||
vi.mocked(adminApi.createPricingTiers).mockResolvedValue({ effective_from: '2026-06-01' });
|
||||
vi.mocked(adminApi.deleteScheduledPricingTier).mockResolvedValue(undefined);
|
||||
});
|
||||
|
||||
it('renders 7 tier rows from /api/admin/pricing-tiers', async () => {
|
||||
@@ -61,43 +78,80 @@ describe('AdminPricingTiersView', () => {
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await (wrapper.vm as any).submit();
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'/api/admin/pricing-tiers',
|
||||
expect.objectContaining({
|
||||
tiers: expect.arrayContaining([expect.objectContaining({ tier_no: 7, leads_in_tier: null })]),
|
||||
}),
|
||||
expect(adminApi.createPricingTiers).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([expect.objectContaining({ tier_no: 7, leads_in_tier: null })]),
|
||||
expect.any(String),
|
||||
);
|
||||
});
|
||||
|
||||
it('confirmDelete triggers DELETE to /scheduled/{date}', async () => {
|
||||
window.confirm = vi.fn(() => true);
|
||||
it('редактор содержит поле даты effective_from', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, {
|
||||
global: {
|
||||
plugins: [vuetify],
|
||||
stubs: { VDialog: { template: '<div><slot /></div>' } },
|
||||
},
|
||||
});
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(wrapper.vm as any).editorOpen = true;
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('[data-testid="effective-from-input"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('submit передаёт выбранную effective_from в createPricingTiers', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await (wrapper.vm as any).confirmDelete('2026-06-01');
|
||||
expect(axios.delete).toHaveBeenCalledWith('/api/admin/pricing-tiers/scheduled/2026-06-01');
|
||||
(wrapper.vm as any).effectiveFrom = '2026-09-01';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await (wrapper.vm as any).submit();
|
||||
expect(adminApi.createPricingTiers).toHaveBeenCalledWith(expect.any(Array), '2026-09-01');
|
||||
});
|
||||
|
||||
it('confirmDelete открывает диалог подтверждения, DELETE не вызывается сразу', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const vm = wrapper.vm as any;
|
||||
vm.confirmDelete('2026-06-01');
|
||||
expect(vm.deleteDialogOpen).toBe(true);
|
||||
expect(vm.deleteTarget).toBe('2026-06-01');
|
||||
expect(adminApi.deleteScheduledPricingTier).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('openEditor сбрасывает effectiveFrom к дефолту (nextMonthStart)', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const vm = wrapper.vm as any;
|
||||
vm.effectiveFrom = '2099-01-01';
|
||||
vm.openEditor();
|
||||
expect(vm.editorOpen).toBe(true);
|
||||
expect(vm.effectiveFrom).not.toBe('2099-01-01');
|
||||
});
|
||||
|
||||
it('performDelete вызывает deleteScheduledPricingTier для выбранной даты', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const vm = wrapper.vm as any;
|
||||
vm.confirmDelete('2026-06-01');
|
||||
await vm.performDelete();
|
||||
expect(adminApi.deleteScheduledPricingTier).toHaveBeenCalledWith('2026-06-01');
|
||||
expect(vm.deleteDialogOpen).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AdminPricingTiersView error handling (Sprint 1 G1)', () => {
|
||||
beforeEach(() => {
|
||||
// axios.isAxiosError is auto-mocked as vi.fn() by vi.mock('axios').
|
||||
// We need it to return true so extractErrorMessage() can read response.data.message.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.isAxiosError as any).mockReturnValue(true);
|
||||
});
|
||||
afterEach(() => {
|
||||
// Cleanup mockReturnValue to prevent leak into other describe blocks (review I-1).
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('submit() shows errorMessage when axios.post rejects with 422', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({ data: { data: { active: mockTiers, scheduled: {} } } });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.post as any).mockRejectedValue({
|
||||
response: { status: 422, data: { message: 'Validation failed: tier 7 leads_in_tier must be null' } },
|
||||
});
|
||||
it('submit() shows errorMessage when createPricingTiers rejects with 422', async () => {
|
||||
vi.mocked(adminApi.getPricingTiers).mockResolvedValue({ active: mockTiers, scheduled: {} });
|
||||
vi.mocked(adminApi.createPricingTiers).mockRejectedValue(
|
||||
makeAxiosError('Validation failed: tier 7 leads_in_tier must be null', 422),
|
||||
);
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// Open editor first (submit is called from dialog) so we can verify it stays open on error.
|
||||
@@ -114,10 +168,8 @@ describe('AdminPricingTiersView error handling (Sprint 1 G1)', () => {
|
||||
});
|
||||
|
||||
it('submit() shows successMessage on 200', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({ data: { data: { active: mockTiers, scheduled: {} } } });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.post as any).mockResolvedValue({ data: { effective_from: '2026-06-01' } });
|
||||
vi.mocked(adminApi.getPricingTiers).mockResolvedValue({ active: mockTiers, scheduled: {} });
|
||||
vi.mocked(adminApi.createPricingTiers).mockResolvedValue({ effective_from: '2026-06-01' });
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -131,34 +183,29 @@ describe('AdminPricingTiersView error handling (Sprint 1 G1)', () => {
|
||||
expect(vm.successToastOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('confirmDelete() shows errorMessage when axios.delete rejects', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({ data: { data: { active: mockTiers, scheduled: {} } } });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.delete as any).mockRejectedValue({
|
||||
response: { status: 500, data: { message: 'Database connection failed' } },
|
||||
});
|
||||
window.confirm = vi.fn(() => true);
|
||||
it('performDelete() shows errorMessage when deleteScheduledPricingTier rejects', async () => {
|
||||
vi.mocked(adminApi.getPricingTiers).mockResolvedValue({ active: mockTiers, scheduled: {} });
|
||||
vi.mocked(adminApi.deleteScheduledPricingTier).mockRejectedValue(
|
||||
makeAxiosError('Database connection failed', 500),
|
||||
);
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await (wrapper.vm as any).confirmDelete('2026-06-01');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
expect((wrapper.vm as any).errorMessage).toContain('Database connection failed');
|
||||
});
|
||||
|
||||
it('confirmDelete() shows successMessage on OK', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({ data: { data: { active: mockTiers, scheduled: {} } } });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.delete as any).mockResolvedValue({ data: { ok: true } });
|
||||
window.confirm = vi.fn(() => true);
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await (wrapper.vm as any).confirmDelete('2026-06-01');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const vm = wrapper.vm as any;
|
||||
vm.confirmDelete('2026-06-01');
|
||||
await vm.performDelete();
|
||||
expect(vm.errorMessage).toContain('Database connection failed');
|
||||
});
|
||||
|
||||
it('performDelete() shows successMessage on OK', async () => {
|
||||
vi.mocked(adminApi.getPricingTiers).mockResolvedValue({ active: mockTiers, scheduled: {} });
|
||||
vi.mocked(adminApi.deleteScheduledPricingTier).mockResolvedValue(undefined);
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const vm = wrapper.vm as any;
|
||||
vm.confirmDelete('2026-06-01');
|
||||
await vm.performDelete();
|
||||
expect(vm.successMessage).toContain('Удалено');
|
||||
expect(vm.successToastOpen).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import axios from 'axios';
|
||||
|
||||
import AdminSupplierPricesView from '../../resources/js/views/admin/AdminSupplierPricesView.vue';
|
||||
|
||||
vi.mock('axios');
|
||||
/**
|
||||
* Создаёт объект, который проходит `axios.isAxiosError()` (проверяет флаг `isAxiosError: true`),
|
||||
* с нужным `response.data.message`.
|
||||
*/
|
||||
function makeAxiosError(message: string, status = 422): unknown {
|
||||
return Object.assign(new Error(message), {
|
||||
isAxiosError: true,
|
||||
response: { status, data: { message } },
|
||||
});
|
||||
}
|
||||
|
||||
vi.mock('../../resources/js/api/admin', async (importOriginal) => {
|
||||
const orig = await importOriginal<typeof import('../../resources/js/api/admin')>();
|
||||
return {
|
||||
...orig,
|
||||
getAdminSuppliers: vi.fn(),
|
||||
updateAdminSupplier: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const adminApi = await import('../../resources/js/api/admin');
|
||||
|
||||
// Auto-импорт компонентов/директив Vuetify подхватывает vite-plugin-vuetify
|
||||
// из vitest.config.ts (см. AdminPricingTiersView.spec.ts).
|
||||
@@ -19,10 +38,8 @@ const mockSuppliers = [
|
||||
|
||||
describe('AdminSupplierPricesView', () => {
|
||||
beforeEach(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({ data: { data: mockSuppliers } });
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.patch as any).mockResolvedValue({ data: { data: mockSuppliers[0] } });
|
||||
vi.mocked(adminApi.getAdminSuppliers).mockResolvedValue(mockSuppliers);
|
||||
vi.mocked(adminApi.updateAdminSupplier).mockResolvedValue(mockSuppliers[0]);
|
||||
});
|
||||
|
||||
it('renders 3 supplier rows', async () => {
|
||||
@@ -45,7 +62,7 @@ describe('AdminSupplierPricesView', () => {
|
||||
quality_score: '1.00',
|
||||
is_active: true,
|
||||
});
|
||||
expect(axios.patch).toHaveBeenCalledWith('/api/admin/suppliers/1', {
|
||||
expect(adminApi.updateAdminSupplier).toHaveBeenCalledWith(1, {
|
||||
cost_rub: '2.00',
|
||||
quality_score: '1.00',
|
||||
is_active: true,
|
||||
@@ -82,30 +99,17 @@ describe('AdminSupplierPricesView', () => {
|
||||
});
|
||||
|
||||
describe('AdminSupplierPricesView error handling (Sprint 1 G2)', () => {
|
||||
beforeEach(() => {
|
||||
// axios.isAxiosError is auto-mocked as vi.fn() by vi.mock('axios').
|
||||
// We need it to return true so extractErrorMessage() can read response.data.message.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.isAxiosError as any).mockReturnValue(true);
|
||||
});
|
||||
afterEach(() => {
|
||||
// Cleanup mockReturnValue to prevent leak into other describe blocks (review I-1).
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('save() shows per-row errorMessage when axios.patch rejects', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({
|
||||
data: {
|
||||
data: [
|
||||
{ id: 1, code: 'B1', name: 'Supplier 1', cost_rub: '120.00', quality_score: '8.50', is_active: true },
|
||||
],
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.patch as any).mockRejectedValue({
|
||||
response: { status: 422, data: { message: 'cost_rub must be non-negative' } },
|
||||
});
|
||||
it('save() shows per-row errorMessage when updateAdminSupplier rejects', async () => {
|
||||
vi.mocked(adminApi.getAdminSuppliers).mockResolvedValue([
|
||||
{ id: 1, code: 'B1', name: 'Supplier 1', cost_rub: '120.00', quality_score: '8.50', is_active: true },
|
||||
]);
|
||||
vi.mocked(adminApi.updateAdminSupplier).mockRejectedValue(
|
||||
makeAxiosError('cost_rub must be non-negative', 422),
|
||||
);
|
||||
const wrapper = mount(AdminSupplierPricesView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const row = { id: 1, code: 'B1', name: 'Supplier 1', cost_rub: '-5.00', quality_score: '8.50', is_active: true };
|
||||
@@ -118,16 +122,12 @@ describe('AdminSupplierPricesView error handling (Sprint 1 G2)', () => {
|
||||
});
|
||||
|
||||
it('save() shows successMessage on 200', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({
|
||||
data: {
|
||||
data: [
|
||||
{ id: 2, code: 'B2', name: 'Supplier 2', cost_rub: '100.00', quality_score: '9.00', is_active: true },
|
||||
],
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.patch as any).mockResolvedValue({ data: { ok: true } });
|
||||
vi.mocked(adminApi.getAdminSuppliers).mockResolvedValue([
|
||||
{ id: 2, code: 'B2', name: 'Supplier 2', cost_rub: '100.00', quality_score: '9.00', is_active: true },
|
||||
]);
|
||||
vi.mocked(adminApi.updateAdminSupplier).mockResolvedValue(
|
||||
{ id: 2, code: 'B2', name: 'Supplier 2', cost_rub: '110.00', quality_score: '9.00', is_active: true },
|
||||
);
|
||||
const wrapper = mount(AdminSupplierPricesView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
const row = { id: 2, code: 'B2', name: 'Supplier 2', cost_rub: '110.00', quality_score: '9.00', is_active: true };
|
||||
@@ -141,11 +141,10 @@ describe('AdminSupplierPricesView error handling (Sprint 1 G2)', () => {
|
||||
expect(vm.saving[2]).toBe(false);
|
||||
});
|
||||
|
||||
it('load() sets fetchError when axios.get rejects', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockRejectedValue({
|
||||
response: { status: 500, data: { message: 'Database connection lost' } },
|
||||
});
|
||||
it('load() sets fetchError when getAdminSuppliers rejects', async () => {
|
||||
vi.mocked(adminApi.getAdminSuppliers).mockRejectedValue(
|
||||
makeAxiosError('Database connection lost', 500),
|
||||
);
|
||||
const wrapper = mount(AdminSupplierPricesView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -154,18 +153,17 @@ describe('AdminSupplierPricesView error handling (Sprint 1 G2)', () => {
|
||||
});
|
||||
|
||||
it('save() clears previous error on successful retry', async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.get as any).mockResolvedValue({
|
||||
data: { data: [{ id: 3, code: 'B3', name: 'Supplier 3', cost_rub: '100.00', quality_score: '8.00', is_active: true }] },
|
||||
});
|
||||
vi.mocked(adminApi.getAdminSuppliers).mockResolvedValue([
|
||||
{ id: 3, code: 'B3', name: 'Supplier 3', cost_rub: '100.00', quality_score: '8.00', is_active: true },
|
||||
]);
|
||||
// First call fails
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.patch as any).mockRejectedValueOnce({
|
||||
response: { status: 500, data: { message: 'transient' } },
|
||||
});
|
||||
vi.mocked(adminApi.updateAdminSupplier).mockRejectedValueOnce(
|
||||
makeAxiosError('transient', 500),
|
||||
);
|
||||
// Second call succeeds
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(axios.patch as any).mockResolvedValueOnce({ data: { ok: true } });
|
||||
vi.mocked(adminApi.updateAdminSupplier).mockResolvedValueOnce(
|
||||
{ id: 3, code: 'B3', name: 'Supplier 3', cost_rub: '100.00', quality_score: '8.00', is_active: true },
|
||||
);
|
||||
|
||||
const wrapper = mount(AdminSupplierPricesView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
|
||||
@@ -26,12 +26,14 @@ const mountView = async () => {
|
||||
});
|
||||
await router.push('/admin/system');
|
||||
await router.isReady();
|
||||
return mount(AdminSystemView, {
|
||||
const wrapper = mount(AdminSystemView, {
|
||||
global: {
|
||||
plugins: [createVuetify(), router],
|
||||
stubs: { SystemSettingEditDialog: true },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
describe('AdminSystemView.vue', () => {
|
||||
@@ -120,15 +122,15 @@ describe('AdminSystemView.vue', () => {
|
||||
expect(adminApi.listSystemSettings).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('при сетевой ошибке показывает warning-banner + сохраняет mock-данные', async () => {
|
||||
it('при сетевой ошибке показывает warning-banner + settingsState пустой', async () => {
|
||||
vi.mocked(adminApi.listSystemSettings).mockRejectedValueOnce(new Error('Network down'));
|
||||
const wrapper = await mountView();
|
||||
await flushPromises();
|
||||
const banner = wrapper.find('[data-testid="fetch-error-alert"]');
|
||||
expect(banner.exists()).toBe(true);
|
||||
// Mock-настройки остались (fallback)
|
||||
// Пустой при ошибке — без mock-fallback
|
||||
const rows = wrapper.findAll('[data-testid="setting-row"]');
|
||||
expect(rows.length).toBe(7);
|
||||
expect(rows.length).toBe(0);
|
||||
});
|
||||
|
||||
it('onSettingUpdated обновляет value и updated_at в settingsState', async () => {
|
||||
|
||||
@@ -1,12 +1,33 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import AdminTenantsView from '../../resources/js/views/admin/AdminTenantsView.vue';
|
||||
import { MOCK_STATS, MOCK_TENANTS } from '../../resources/js/composables/mockTenants';
|
||||
import { MOCK_STATS, MOCK_TENANTS, type AdminTenant } from '../../resources/js/composables/mockTenants';
|
||||
|
||||
// Мокаем api/admin: listAdminTenants возвращает пустой ответ —
|
||||
// smoke-тесты затем seed'ят tenantsState/stats напрямую через vm (defineExpose).
|
||||
vi.mock('../../resources/js/api/admin', async (importOriginal) => {
|
||||
const orig = await importOriginal<typeof import('../../resources/js/api/admin')>();
|
||||
return {
|
||||
...orig,
|
||||
listAdminTenants: vi.fn().mockResolvedValue({
|
||||
tenants: [],
|
||||
total: 0,
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
stats: { total: 0, active: 0, trial: 0, overdue: 0 },
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('AdminTenantsView.vue', () => {
|
||||
const factory = () => {
|
||||
/** Монтирует view, ждёт mount-цикл, затем seed'ит state фикстурами. */
|
||||
const factory = async () => {
|
||||
// useRouter() в AdminTenantsView требует router-context в тестах.
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
@@ -15,22 +36,34 @@ describe('AdminTenantsView.vue', () => {
|
||||
{ path: '/admin/tenants/:code', name: 'admin-tenant-detail', component: { template: '<div />' } },
|
||||
],
|
||||
});
|
||||
return mount(AdminTenantsView, {
|
||||
await router.push('/admin/tenants');
|
||||
await router.isReady();
|
||||
const wrapper = mount(AdminTenantsView, {
|
||||
global: {
|
||||
plugins: [createVuetify(), router],
|
||||
// ImpersonationDialog stubим — внутри использует api/admin axios.
|
||||
stubs: { ImpersonationDialog: true },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
// Seed state напрямую через defineExpose — имитирует успешную загрузку с теми же фикстурами.
|
||||
const vm = wrapper.vm as unknown as {
|
||||
tenantsState: AdminTenant[];
|
||||
stats: typeof MOCK_STATS;
|
||||
};
|
||||
vm.tenantsState.splice(0, vm.tenantsState.length, ...MOCK_TENANTS.map((t) => ({ ...t })));
|
||||
Object.assign(vm.stats, MOCK_STATS);
|
||||
await wrapper.vm.$nextTick();
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
it('монтируется и содержит заголовок «Тенанты»', () => {
|
||||
const wrapper = factory();
|
||||
it('монтируется и содержит заголовок «Тенанты»', async () => {
|
||||
const wrapper = await factory();
|
||||
expect(wrapper.find('h1').text()).toBe('Тенанты');
|
||||
});
|
||||
|
||||
it('показывает 5 stats: всего/активны/trial/просрочка/выручка', () => {
|
||||
const wrapper = factory();
|
||||
it('показывает 5 stats: всего/активны/trial/просрочка/выручка', async () => {
|
||||
const wrapper = await factory();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain(`${MOCK_STATS.total}`); // 142
|
||||
expect(text).toContain('всего');
|
||||
@@ -45,22 +78,22 @@ describe('AdminTenantsView.vue', () => {
|
||||
expect(text).toMatch(/1\s+248\s+600\s*₽/);
|
||||
});
|
||||
|
||||
it('таблица содержит 7 колонок (Тенант/Статус/Тариф/Баланс/Желаем×факт/MRR/Активность)', () => {
|
||||
const wrapper = factory();
|
||||
it('таблица содержит 7 колонок (Тенант/Статус/Тариф/Баланс/Желаем×факт/MRR/Активность)', async () => {
|
||||
const wrapper = await factory();
|
||||
const headers = wrapper.findAll('thead th').map((h) => h.text());
|
||||
['Тенант', 'Статус', 'Тариф', 'Баланс', 'Желаем×факт', 'MRR', 'Активность'].forEach((label) => {
|
||||
expect(headers.some((h) => h.includes(label))).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('рендерит все 7 mock-tenants', () => {
|
||||
const wrapper = factory();
|
||||
it('рендерит все 7 mock-tenants', async () => {
|
||||
const wrapper = await factory();
|
||||
const rows = wrapper.findAll('tbody tr');
|
||||
expect(rows.length).toBe(MOCK_TENANTS.length);
|
||||
});
|
||||
|
||||
it('первая строка — Окна Москва ООО + ИНН + Активен + Команда', () => {
|
||||
const wrapper = factory();
|
||||
it('первая строка — Окна Москва ООО + ИНН + Активен + Команда', async () => {
|
||||
const wrapper = await factory();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('Окна Москва ООО');
|
||||
expect(text).toContain('ИНН 7724444444');
|
||||
@@ -68,37 +101,37 @@ describe('AdminTenantsView.vue', () => {
|
||||
expect(text).toContain('Команда');
|
||||
});
|
||||
|
||||
it('overdue-тенант (Двери Премиум) показывает «Просрочка 3 дня» + отрицательный баланс', () => {
|
||||
const wrapper = factory();
|
||||
it('overdue-тенант (Двери Премиум) показывает «Просрочка 3 дня» + отрицательный баланс', async () => {
|
||||
const wrapper = await factory();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('Двери Премиум');
|
||||
expect(text).toContain('Просрочка 3 дня');
|
||||
expect(text).toMatch(/−1\s+200/); // -1200 без 0 ₽
|
||||
});
|
||||
|
||||
it('trial-тенант (Ремонт под ключ) показывает «Trial · 4 дня» + MRR=—', () => {
|
||||
const wrapper = factory();
|
||||
it('trial-тенант (Ремонт под ключ) показывает «Trial · 4 дня» + MRR=—', async () => {
|
||||
const wrapper = await factory();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('Ремонт под ключ');
|
||||
expect(text).toContain('Trial · 4 дня');
|
||||
});
|
||||
|
||||
it('suspended-тенант (Оконные системы РФ) показывает «Приостановлен»', () => {
|
||||
const wrapper = factory();
|
||||
it('suspended-тенант (Оконные системы РФ) показывает «Приостановлен»', async () => {
|
||||
const wrapper = await factory();
|
||||
const text = wrapper.text();
|
||||
expect(text).toContain('Оконные системы РФ');
|
||||
expect(text).toContain('Приостановлен');
|
||||
});
|
||||
|
||||
it('содержит search-input с placeholder «ИНН, юр. лицо, email админа…»', () => {
|
||||
const wrapper = factory();
|
||||
it('содержит search-input с placeholder «ИНН, юр. лицо, email админа…»', async () => {
|
||||
const wrapper = await factory();
|
||||
const input = wrapper.find('input[type="text"]');
|
||||
expect(input.exists()).toBe(true);
|
||||
expect(input.attributes('placeholder')).toContain('ИНН');
|
||||
});
|
||||
|
||||
it('фильтр по search оставляет только matching-tenants', async () => {
|
||||
const wrapper = factory();
|
||||
const wrapper = await factory();
|
||||
const input = wrapper.find('input[type="text"]');
|
||||
await input.setValue('Натяжные');
|
||||
await wrapper.vm.$nextTick();
|
||||
@@ -107,15 +140,15 @@ describe('AdminTenantsView.vue', () => {
|
||||
expect(rows[0].text()).toContain('Натяжные потолки СПб');
|
||||
});
|
||||
|
||||
it('содержит Экспорт-кнопку и фильтры Статус/Тариф', () => {
|
||||
const wrapper = factory();
|
||||
it('содержит Экспорт-кнопку и фильтры Статус/Тариф', async () => {
|
||||
const wrapper = await factory();
|
||||
expect(wrapper.text()).toContain('Экспорт');
|
||||
expect(wrapper.find('[data-testid="filter-statuses"]').exists()).toBe(true);
|
||||
expect(wrapper.find('[data-testid="filter-tariffs"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('фильтр по статусу «overdue» оставляет только просроченных', async () => {
|
||||
const wrapper = factory();
|
||||
const wrapper = await factory();
|
||||
const vm = wrapper.vm as unknown as { filterStatuses: string[] };
|
||||
vm.filterStatuses = ['overdue'];
|
||||
await wrapper.vm.$nextTick();
|
||||
@@ -125,7 +158,7 @@ describe('AdminTenantsView.vue', () => {
|
||||
});
|
||||
|
||||
it('фильтр по тарифу «Pro» оставляет 1 row', async () => {
|
||||
const wrapper = factory();
|
||||
const wrapper = await factory();
|
||||
const vm = wrapper.vm as unknown as { filterTariffs: string[] };
|
||||
vm.filterTariffs = ['Pro'];
|
||||
await wrapper.vm.$nextTick();
|
||||
@@ -135,7 +168,7 @@ describe('AdminTenantsView.vue', () => {
|
||||
});
|
||||
|
||||
it('clearFilters сбрасывает оба фильтра + кнопка «Сбросить» появляется только когда фильтры активны', async () => {
|
||||
const wrapper = factory();
|
||||
const wrapper = await factory();
|
||||
const vm = wrapper.vm as unknown as {
|
||||
filterStatuses: string[];
|
||||
filterTariffs: string[];
|
||||
@@ -152,8 +185,8 @@ describe('AdminTenantsView.vue', () => {
|
||||
expect(vm.filterTariffs).toEqual([]);
|
||||
});
|
||||
|
||||
it('каждая строка имеет impersonate-кнопку (mdi-account-switch) с уникальным data-testid', () => {
|
||||
const wrapper = factory();
|
||||
it('каждая строка имеет impersonate-кнопку (mdi-account-switch) с уникальным data-testid', async () => {
|
||||
const wrapper = await factory();
|
||||
// Все 7 mock-tenants должны иметь кнопку
|
||||
MOCK_TENANTS.forEach((t) => {
|
||||
const btn = wrapper.find(`[data-testid="impersonate-btn-${t.id}"]`);
|
||||
@@ -161,8 +194,8 @@ describe('AdminTenantsView.vue', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('impersonate-кнопка disabled для suspended-тенанта (Оконные системы РФ id=105)', () => {
|
||||
const wrapper = factory();
|
||||
it('impersonate-кнопка disabled для suspended-тенанта (Оконные системы РФ id=105)', async () => {
|
||||
const wrapper = await factory();
|
||||
const suspendedBtn = wrapper.find('[data-testid="impersonate-btn-105"]');
|
||||
expect(suspendedBtn.exists()).toBe(true);
|
||||
// v-btn disabled-state — атрибут disabled на DOM-элементе
|
||||
@@ -170,7 +203,7 @@ describe('AdminTenantsView.vue', () => {
|
||||
});
|
||||
|
||||
it('click на impersonate-кнопке открывает ImpersonationDialog с правильным tenant', async () => {
|
||||
const wrapper = factory();
|
||||
const wrapper = await factory();
|
||||
// До click — диалог закрыт (modelValue=false)
|
||||
const dialogStub = wrapper.findComponent({ name: 'ImpersonationDialog' });
|
||||
expect(dialogStub.exists()).toBe(true);
|
||||
@@ -186,4 +219,28 @@ describe('AdminTenantsView.vue', () => {
|
||||
expect(dialogStub.props('tenant')).toMatchObject({ id: 42, name: 'Окна Москва ООО' });
|
||||
expect(dialogStub.props('requestedBy')).toBe(1);
|
||||
});
|
||||
|
||||
it('API reject → tenantsState пустой + fetch-error-alert виден', async () => {
|
||||
const adminApi = await import('../../resources/js/api/admin');
|
||||
vi.mocked(adminApi.listAdminTenants).mockRejectedValueOnce(new Error('Network error'));
|
||||
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [
|
||||
{ path: '/admin/tenants', name: 'admin-tenants', component: AdminTenantsView },
|
||||
{ path: '/admin/tenants/:code', name: 'admin-tenant-detail', component: { template: '<div />' } },
|
||||
],
|
||||
});
|
||||
await router.push('/admin/tenants');
|
||||
await router.isReady();
|
||||
const wrapper = mount(AdminTenantsView, {
|
||||
global: { plugins: [createVuetify(), router], stubs: { ImpersonationDialog: true } },
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const vm = wrapper.vm as unknown as { fetchError: boolean; tenantsState: unknown[] };
|
||||
expect(vm.fetchError).toBe(true);
|
||||
expect(vm.tenantsState.length).toBe(0);
|
||||
expect(wrapper.find('[data-testid="fetch-error-alert"]').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,7 +103,7 @@ describe('AdminTenantsView ↔ GET /api/admin/tenants integration', () => {
|
||||
expect(vm.stats.trial).toBe(1);
|
||||
});
|
||||
|
||||
it('reject → fetchError=true + alert виден + MOCK_TENANTS остаётся', async () => {
|
||||
it('reject → fetchError=true + alert виден + tenantsState пустой', async () => {
|
||||
vi.mocked(adminApi.listAdminTenants).mockRejectedValueOnce(new Error('500'));
|
||||
|
||||
const wrapper = await mountView();
|
||||
@@ -111,7 +111,7 @@ describe('AdminTenantsView ↔ GET /api/admin/tenants integration', () => {
|
||||
|
||||
const vm = wrapper.vm as unknown as { fetchError: boolean; tenantsState: unknown[] };
|
||||
expect(vm.fetchError).toBe(true);
|
||||
expect(vm.tenantsState.length).toBeGreaterThan(0); // mock-fallback
|
||||
expect(vm.tenantsState.length).toBe(0); // пустой при ошибке, не mock-fallback
|
||||
expect(wrapper.find('[data-testid="fetch-error-alert"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import BalanceCard from '../../resources/js/components/billing/BalanceCard.vue';
|
||||
|
||||
const vuetify = createVuetify();
|
||||
|
||||
function factory() {
|
||||
return mount(BalanceCard, {
|
||||
global: { plugins: [vuetify] },
|
||||
props: {
|
||||
walletRub: 14250,
|
||||
leadsBalance: 285,
|
||||
tariffName: 'Про',
|
||||
tariffPrice: '990.00',
|
||||
tariffFeatures: ['Webhook', 'Канбан'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('BalanceCard.vue', () => {
|
||||
it('кнопка «Пополнить» активна и эмитит topup', async () => {
|
||||
const wrapper = factory();
|
||||
const btn = wrapper.findAll('button').find((b) => b.text().includes('Пополнить'));
|
||||
expect(btn).toBeDefined();
|
||||
expect(btn!.attributes('disabled')).toBeUndefined();
|
||||
await btn!.trigger('click');
|
||||
expect(wrapper.emitted('topup')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('кнопка «Автопополнение» disabled (E2 — нет backend)', () => {
|
||||
const wrapper = factory();
|
||||
const btn = wrapper.findAll('button').find((b) => b.text().includes('Автопополнение'));
|
||||
expect(btn).toBeDefined();
|
||||
expect(btn!.attributes('disabled')).toBeDefined();
|
||||
});
|
||||
|
||||
it('кнопка «Сменить тариф» disabled (E2 — нет backend)', () => {
|
||||
const wrapper = factory();
|
||||
const btn = wrapper.findAll('button').find((b) => b.text().includes('Сменить тариф'));
|
||||
expect(btn).toBeDefined();
|
||||
expect(btn!.attributes('disabled')).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -114,4 +114,10 @@ describe('BillingView.vue', () => {
|
||||
await btn!.trigger('click');
|
||||
expect((wrapper.vm as unknown as { topupOpen: boolean }).topupOpen).toBe(true);
|
||||
});
|
||||
|
||||
it('не показывает pending-баннер (E4 — mock убран)', async () => {
|
||||
const wrapper = factory();
|
||||
await flushPromises();
|
||||
expect(wrapper.text()).not.toContain('в обработке');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,7 +4,6 @@ import { createVuetify } from 'vuetify';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import DealDetailDrawer from '../../resources/js/components/deals/DealDetailDrawer.vue';
|
||||
import { MOCK_DEALS } from '../../resources/js/composables/mockDeals';
|
||||
import { MOCK_EVENTS } from '../../resources/js/composables/mockDealEvents';
|
||||
|
||||
beforeEach(() => {
|
||||
setActivePinia(createPinia());
|
||||
@@ -77,10 +76,10 @@ describe('DealDetailDrawer.vue', () => {
|
||||
expect(text).toMatch(/1\s+850\s*₽/); // sampleDeal.cost = 1850
|
||||
});
|
||||
|
||||
it('рендерит timeline с MOCK_EVENTS (6 событий)', () => {
|
||||
it('рендерит timeline без событий (без tenantId events пуст — I3)', () => {
|
||||
const wrapper = factory({ open: true, deal: sampleDeal });
|
||||
const items = wrapper.findAll('.timeline-item');
|
||||
expect(items).toHaveLength(MOCK_EVENTS.length);
|
||||
expect(items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('emit-ит update:open=false при close-кнопке', async () => {
|
||||
|
||||
@@ -4,7 +4,6 @@ import { createVuetify } from 'vuetify';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import DealDetailDrawer from '../../resources/js/components/deals/DealDetailDrawer.vue';
|
||||
import { MOCK_DEALS } from '../../resources/js/composables/mockDeals';
|
||||
import { MOCK_EVENTS } from '../../resources/js/composables/mockDealEvents';
|
||||
import type { GetDealResponse, ApiDealEvent } from '../../resources/js/api/deals';
|
||||
|
||||
vi.mock('../../resources/js/api/deals', async (importOriginal) => {
|
||||
@@ -49,13 +48,13 @@ function makeApiEvent(overrides: Partial<ApiDealEvent> = {}): ApiDealEvent {
|
||||
}
|
||||
|
||||
describe('DealDetailDrawer ↔ GET /api/deals/{id} integration', () => {
|
||||
it('БЕЗ tenantId — getDeal не вызывается, показываются MOCK_EVENTS', async () => {
|
||||
it('БЕЗ tenantId — getDeal не вызывается, events пуст (I3)', async () => {
|
||||
const wrapper = factory({ open: true });
|
||||
await flushPromises();
|
||||
|
||||
expect(dealsApi.getDeal).not.toHaveBeenCalled();
|
||||
const items = wrapper.findAll('.timeline-item');
|
||||
expect(items).toHaveLength(MOCK_EVENTS.length);
|
||||
expect(items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('С tenantId — getDeal вызывается, events заменяются на API', async () => {
|
||||
@@ -97,7 +96,7 @@ describe('DealDetailDrawer ↔ GET /api/deals/{id} integration', () => {
|
||||
expect(wrapper.text()).toContain('new → paid');
|
||||
});
|
||||
|
||||
it('getDeal reject → eventsFetchError=true, alert виден, MOCK_EVENTS как fallback', async () => {
|
||||
it('getDeal reject → eventsFetchError=true, alert виден, events пуст (I3)', async () => {
|
||||
vi.mocked(dealsApi.getDeal).mockRejectedValueOnce(new Error('500'));
|
||||
|
||||
const wrapper = factory({ open: true, tenantId: 1 });
|
||||
@@ -106,9 +105,9 @@ describe('DealDetailDrawer ↔ GET /api/deals/{id} integration', () => {
|
||||
const vm = wrapper.vm as unknown as { eventsFetchError: boolean };
|
||||
expect(vm.eventsFetchError).toBe(true);
|
||||
expect(wrapper.find('[data-testid="events-fetch-error-alert"]').exists()).toBe(true);
|
||||
// Fallback на MOCK_EVENTS.
|
||||
// I3: нет mock-fallback — events пуст.
|
||||
const items = wrapper.findAll('.timeline-item');
|
||||
expect(items).toHaveLength(MOCK_EVENTS.length);
|
||||
expect(items).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('open=false → getDeal не вызывается', async () => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import DealsView from '../../resources/js/views/DealsView.vue';
|
||||
import KanbanView from '../../resources/js/views/KanbanView.vue';
|
||||
import { useAuthStore } from '../../resources/js/stores/auth';
|
||||
import type { ApiDeal } from '../../resources/js/api/deals';
|
||||
import { MOCK_DEALS } from '../../resources/js/composables/mockDeals';
|
||||
|
||||
vi.mock('../../resources/js/api/deals', async (importOriginal) => {
|
||||
const orig = await importOriginal<typeof import('../../resources/js/api/deals')>();
|
||||
@@ -86,14 +87,13 @@ const mountKanbanView = () =>
|
||||
});
|
||||
|
||||
describe('DealsView ↔ GET /api/deals integration', () => {
|
||||
it('БЕЗ auth.user.tenant_id — listDeals не вызывается, fallback на MOCK_DEALS', async () => {
|
||||
it('БЕЗ auth.user.tenant_id — listDeals не вызывается, dealsState пустой', async () => {
|
||||
setupAuth(null);
|
||||
const wrapper = await mountDealsView();
|
||||
await flushPromises();
|
||||
expect(dealsApi.listDeals).not.toHaveBeenCalled();
|
||||
// MOCK_DEALS содержит 12 элементов — fallback виден.
|
||||
const vm = wrapper.vm as unknown as { dealsState: { id: number }[] };
|
||||
expect(vm.dealsState.length).toBeGreaterThan(0);
|
||||
expect(vm.dealsState.length).toBe(0);
|
||||
});
|
||||
|
||||
it('С auth.user.tenant_id — listDeals вызывается с tenantId + replace dealsState', async () => {
|
||||
@@ -120,7 +120,7 @@ describe('DealsView ↔ GET /api/deals integration', () => {
|
||||
expect(vm.dealsState.find((d) => d.id === 200)?.name).toBe('Из API #1');
|
||||
});
|
||||
|
||||
it('listDeals reject → fetchError=true, alert виден, MOCK_DEALS остаётся как fallback', async () => {
|
||||
it('listDeals reject → fetchError=true, alert виден, dealsState пустой', async () => {
|
||||
setupAuth(1);
|
||||
vi.mocked(dealsApi.listDeals).mockRejectedValueOnce(new Error('network'));
|
||||
|
||||
@@ -129,7 +129,7 @@ describe('DealsView ↔ GET /api/deals integration', () => {
|
||||
|
||||
const vm = wrapper.vm as unknown as { fetchError: boolean; dealsState: unknown[] };
|
||||
expect(vm.fetchError).toBe(true);
|
||||
expect(vm.dealsState.length).toBeGreaterThan(0);
|
||||
expect(vm.dealsState.length).toBe(0);
|
||||
// Alert виден.
|
||||
expect(wrapper.find('[data-testid="fetch-error-alert"]').exists()).toBe(true);
|
||||
});
|
||||
@@ -280,6 +280,9 @@ describe('DealsView ↔ GET /api/deals integration', () => {
|
||||
applyBulkStatus: (slug: string) => Promise<void>;
|
||||
dealsState: { id: number; statusSlug: string }[];
|
||||
};
|
||||
// Засеваем dealsState mock-фикстурой (после I3 init пустой)
|
||||
vm.dealsState.push(...MOCK_DEALS.map((d) => ({ ...d, manager: { ...d.manager } })));
|
||||
await flushPromises();
|
||||
vm.selected = [1];
|
||||
await flushPromises();
|
||||
await vm.applyBulkStatus('paid');
|
||||
@@ -441,6 +444,9 @@ describe('DealsView ↔ GET /api/deals integration', () => {
|
||||
applyBulkDelete: () => Promise<void>;
|
||||
dealsState: { id: number }[];
|
||||
};
|
||||
// Засеваем dealsState mock-фикстурой (после I3 init пустой)
|
||||
vm.dealsState.push(...MOCK_DEALS.map((d) => ({ ...d, manager: { ...d.manager } })));
|
||||
await flushPromises();
|
||||
const before = vm.dealsState.length;
|
||||
vm.selected = [1, 2];
|
||||
await flushPromises();
|
||||
@@ -540,6 +546,9 @@ describe('DealsView ↔ GET /api/deals integration', () => {
|
||||
dealsState: { id: number }[];
|
||||
lastDeletedSnapshot: { id: number }[];
|
||||
};
|
||||
// Засеваем dealsState mock-фикстурой (после I3 init пустой)
|
||||
vm.dealsState.push(...MOCK_DEALS.map((d) => ({ ...d, manager: { ...d.manager } })));
|
||||
await flushPromises();
|
||||
|
||||
const sample = vm.dealsState[0];
|
||||
vm.selected = [sample.id];
|
||||
@@ -651,7 +660,7 @@ describe('KanbanView ↔ GET /api/deals integration', () => {
|
||||
expect(vm.fetchError).toBe(false);
|
||||
});
|
||||
|
||||
it('listDeals reject → fetchError=true, MOCK_DEALS остаётся в колонках', async () => {
|
||||
it('listDeals reject → fetchError=true, колонки пусты', async () => {
|
||||
setupAuth(1);
|
||||
vi.mocked(dealsApi.listDeals).mockRejectedValueOnce(new Error('500'));
|
||||
|
||||
@@ -663,9 +672,8 @@ describe('KanbanView ↔ GET /api/deals integration', () => {
|
||||
fetchError: boolean;
|
||||
};
|
||||
expect(vm.fetchError).toBe(true);
|
||||
// Хотя бы одна колонка с mock-сделками заполнена (изначальный state).
|
||||
const filledColumns = Object.values(vm.dealsByStatus).filter((arr) => arr.length > 0);
|
||||
expect(filledColumns.length).toBeGreaterThan(0);
|
||||
expect(filledColumns.length).toBe(0);
|
||||
});
|
||||
|
||||
it('reload-btn вызывает listDeals второй раз', async () => {
|
||||
|
||||
@@ -4,13 +4,19 @@ import { createVuetify } from 'vuetify';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import DealsView from '../../resources/js/views/DealsView.vue';
|
||||
import { MOCK_DEALS } from '../../resources/js/composables/mockDeals';
|
||||
import { MOCK_DEALS, type MockDeal } from '../../resources/js/composables/mockDeals';
|
||||
import * as dealsApi from '../../resources/js/api/deals';
|
||||
import { useAuthStore } from '../../resources/js/stores/auth';
|
||||
import type { AuthUser } from '../../resources/js/api/auth';
|
||||
|
||||
// Smoke-тесты DealsView с mock-данными.
|
||||
|
||||
/** Засевает dealsState фикстурой MOCK_DEALS (имитирует успешный API-ответ). */
|
||||
function seedDealsState(wrapper: ReturnType<typeof mount>) {
|
||||
const vm = wrapper.vm as unknown as { dealsState: MockDeal[] };
|
||||
vm.dealsState.push(...MOCK_DEALS.map((d) => ({ ...d, manager: { ...d.manager } })));
|
||||
}
|
||||
|
||||
const mountDeals = async () => {
|
||||
setActivePinia(createPinia());
|
||||
const router = createRouter({
|
||||
@@ -25,12 +31,16 @@ const mountDeals = async () => {
|
||||
// layout-injection от v-app. В Vitest vite-plugin-vuetify auto-import не
|
||||
// работает, layout-context недоступен. Stub'им сам Drawer (тестируется
|
||||
// отдельно в DealDetailDrawer.spec.ts).
|
||||
return mount(DealsView, {
|
||||
const wrapper = mount(DealsView, {
|
||||
global: {
|
||||
plugins: [createVuetify(), router],
|
||||
stubs: { DealDetailDrawer: true, NewDealDialog: true },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
seedDealsState(wrapper);
|
||||
await flushPromises();
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
/** Audit C8/F3: монтирует DealsView по произвольному пути (с query-параметрами). */
|
||||
@@ -42,12 +52,16 @@ const mountDealsViewAt = async (path: string) => {
|
||||
});
|
||||
await router.push(path);
|
||||
await router.isReady();
|
||||
return mount(DealsView, {
|
||||
const wrapper = mount(DealsView, {
|
||||
global: {
|
||||
plugins: [createVuetify(), router],
|
||||
stubs: { DealDetailDrawer: true, NewDealDialog: true },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
seedDealsState(wrapper);
|
||||
await flushPromises();
|
||||
return wrapper;
|
||||
};
|
||||
|
||||
describe('DealsView.vue', () => {
|
||||
@@ -295,7 +309,36 @@ describe('DealsView.vue', () => {
|
||||
// Audit C8/F3: deep-link /deals?openId=
|
||||
it('route.query.openId открывает drawer соответствующей сделки', async () => {
|
||||
const openId = MOCK_DEALS[0].id;
|
||||
const wrapper = await mountDealsViewAt(`/deals?openId=${openId}`);
|
||||
// Мокаем API чтобы loadDeals заполнил state до вызова openDealFromQuery в onMounted.
|
||||
vi.spyOn(dealsApi, 'listDeals').mockResolvedValue({
|
||||
deals: MOCK_DEALS.map((d) => ({
|
||||
id: d.id,
|
||||
name: d.name,
|
||||
phone: d.phone,
|
||||
status: d.statusSlug,
|
||||
project_name: d.project,
|
||||
manager_name: d.manager.name,
|
||||
cost: d.cost,
|
||||
created_at: new Date(Date.now() - d.receivedMinutesAgo * 60000).toISOString(),
|
||||
deleted_at: null,
|
||||
})),
|
||||
total: MOCK_DEALS.length,
|
||||
} as never);
|
||||
setActivePinia(createPinia());
|
||||
const auth = useAuthStore();
|
||||
auth.user = { id: 1, tenant_id: 42, email: 'test@test.com' } as AuthUser;
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [{ path: '/deals', component: DealsView }],
|
||||
});
|
||||
await router.push(`/deals?openId=${openId}`);
|
||||
await router.isReady();
|
||||
const wrapper = mount(DealsView, {
|
||||
global: {
|
||||
plugins: [createVuetify(), router],
|
||||
stubs: { DealDetailDrawer: true, NewDealDialog: true },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
const vm = wrapper.vm as unknown as { drawerOpen: boolean; selectedDeal: { id: number } | null };
|
||||
expect(vm.drawerOpen).toBe(true);
|
||||
@@ -372,4 +415,33 @@ test('C3: exportAllFiltered на пустом списке показывает
|
||||
expect(vm.exportToastText).toBe('Список пуст — нечего экспортировать.');
|
||||
});
|
||||
|
||||
// I3 regression: API reject → dealsState пустой + fetchError=true (нет mock-fallback)
|
||||
// Faithful-паттерн: auth + mock ДО mount, onMounted сам вызывает loadDeals.
|
||||
test('I3: loadDeals reject оставляет dealsState пустым и выставляет fetchError', async () => {
|
||||
vi.spyOn(dealsApi, 'listDeals').mockRejectedValue(new Error('500'));
|
||||
setActivePinia(createPinia());
|
||||
const auth = useAuthStore();
|
||||
auth.user = { id: 1, tenant_id: 42, email: 'test@test.com' } as AuthUser;
|
||||
const router = createRouter({
|
||||
history: createMemoryHistory(),
|
||||
routes: [{ path: '/deals', component: DealsView }],
|
||||
});
|
||||
await router.push('/deals');
|
||||
await router.isReady();
|
||||
const wrapper = mount(DealsView, {
|
||||
global: {
|
||||
plugins: [createVuetify(), router],
|
||||
stubs: { DealDetailDrawer: true, NewDealDialog: true },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const vm = wrapper.vm as unknown as {
|
||||
dealsState: MockDeal[];
|
||||
fetchError: boolean;
|
||||
};
|
||||
expect(vm.dealsState.length).toBe(0);
|
||||
expect(vm.fetchError).toBe(true);
|
||||
});
|
||||
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
|
||||
@@ -52,6 +52,10 @@ describe('ImpersonationDialog.vue', () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it('не рендерит content когда modelValue=false', () => {
|
||||
const wrapper = factory({ modelValue: false, tenant: sampleTenant });
|
||||
expect(wrapper.find('.dialog-stub').exists()).toBe(false);
|
||||
@@ -198,4 +202,26 @@ describe('ImpersonationDialog.vue', () => {
|
||||
expect(events).toBeDefined();
|
||||
expect(events?.[0]).toEqual([false]);
|
||||
});
|
||||
|
||||
it('I4 — dev-code-banner НЕ рендерится когда import.meta.env.DEV=false (prod)', async () => {
|
||||
vi.stubEnv('DEV', false);
|
||||
|
||||
vi.mocked(adminApi.impersonationInit).mockResolvedValue({
|
||||
token_id: 42,
|
||||
expires_at: '2026-05-09T12:00:00Z',
|
||||
sent_to_email: 'admin@okna-moscow.ru',
|
||||
_dev_plain_code: '123456',
|
||||
});
|
||||
|
||||
const wrapper = factory({ modelValue: true, tenant: sampleTenant });
|
||||
await wrapper.find('[data-testid="reason-input"] textarea').setValue(
|
||||
'Тикет SUP-12453: клиент сообщил, что в карточке сделки не сохраняется коммент.',
|
||||
);
|
||||
await wrapper.find('[data-testid="submit-init-btn"]').trigger('click');
|
||||
await flushPromises();
|
||||
|
||||
// step verify достигнут — devPlainCode='123456' есть, но DEV=false → баннер не рендерится
|
||||
expect(wrapper.text()).toContain('Код отправлен на email клиента');
|
||||
expect(wrapper.find('[data-testid="dev-code-banner"]').exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createPinia, setActivePinia } from 'pinia';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import { createMemoryHistory, createRouter } from 'vue-router';
|
||||
import KanbanView from '../../resources/js/views/KanbanView.vue';
|
||||
import { MOCK_DEALS } from '../../resources/js/composables/mockDeals';
|
||||
|
||||
// KanbanView содержит DealDetailDrawer (VNavigationDrawer) — требует
|
||||
// injected layout от v-app, недоступной в Vitest. Stub'им как в KanbanView.spec.ts.
|
||||
@@ -28,6 +29,12 @@ describe('KanbanView — redesigned', () => {
|
||||
it('card containers have ld-hover-lift class', async () => {
|
||||
const w = setup();
|
||||
await flushPromises();
|
||||
// Засеваем dealsByStatus (после I3 init пустой — карточек нет)
|
||||
const vm = w.vm as unknown as { dealsByStatus: Record<string, unknown[]> };
|
||||
for (const d of MOCK_DEALS) {
|
||||
(vm.dealsByStatus[d.statusSlug] ??= []).push({ ...d, manager: { ...d.manager } });
|
||||
}
|
||||
await flushPromises();
|
||||
expect(w.html()).toMatch(/ld-hover-lift/);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { mount, flushPromises } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import { createPinia, setActivePinia } from 'pinia';
|
||||
import KanbanView from '../../resources/js/views/KanbanView.vue';
|
||||
import { useAuthStore } from '../../resources/js/stores/auth';
|
||||
import * as dealsApi from '../../resources/js/api/deals';
|
||||
import { LEAD_STATUSES } from '../../resources/js/composables/leadStatuses';
|
||||
import { MOCK_DEALS, type MockDeal } from '../../resources/js/composables/mockDeals';
|
||||
|
||||
describe('KanbanView.vue', () => {
|
||||
// KanbanView содержит DealDetailDrawer (v-navigation-drawer), который требует
|
||||
@@ -97,6 +98,15 @@ describe('KanbanView.vue', () => {
|
||||
|
||||
it('обновляет statusSlug сделки при drop в новую колонку (event.added)', async () => {
|
||||
const wrapper = factory();
|
||||
// Засеваем dealsByStatus фикстурой MOCK_DEALS (init теперь пустой).
|
||||
const vm = wrapper.vm as unknown as { dealsByStatus: Record<string, MockDeal[]> };
|
||||
for (const deal of MOCK_DEALS) {
|
||||
if (vm.dealsByStatus[deal.statusSlug]) {
|
||||
vm.dealsByStatus[deal.statusSlug].push({ ...deal });
|
||||
}
|
||||
}
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const cols = wrapper.findAllComponents({ name: 'KanbanColumn' });
|
||||
// Берём сделку из первой колонки (new) и эмулируем «added» в paid-колонке.
|
||||
const newCol = cols[0]; // new — sortOrder=1
|
||||
@@ -114,6 +124,33 @@ describe('KanbanView.vue', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// I3 regression: API reject → dealsByStatus пустые + fetchError=true (нет mock-fallback)
|
||||
// Faithful-паттерн: auth + mock ДО mount, onMounted сам вызывает loadDeals.
|
||||
describe('KanbanView I3 regression', () => {
|
||||
it('loadDeals reject оставляет dealsByStatus пустыми и выставляет fetchError', async () => {
|
||||
vi.spyOn(dealsApi, 'listDeals').mockRejectedValue(new Error('500'));
|
||||
setActivePinia(createPinia());
|
||||
const auth = useAuthStore();
|
||||
auth.user = { id: 1, tenant_id: 42, email: 'test@test.com' } as never;
|
||||
const wrapper = mount(KanbanView, {
|
||||
global: {
|
||||
plugins: [createVuetify()],
|
||||
stubs: { DealDetailDrawer: true, NewDealDialog: true },
|
||||
},
|
||||
});
|
||||
await flushPromises();
|
||||
|
||||
const vm = wrapper.vm as unknown as {
|
||||
dealsByStatus: Record<string, MockDeal[]>;
|
||||
fetchError: boolean;
|
||||
};
|
||||
expect(vm.fetchError).toBe(true);
|
||||
// Все колонки пусты — нет mock-fallback
|
||||
const allDeals = Object.values(vm.dealsByStatus).flat();
|
||||
expect(allDeals.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('KanbanView DnD persist (Sprint 1 C4)', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
@@ -288,6 +288,17 @@ describe('NewDealDialog.vue', () => {
|
||||
expect(closeEmits === undefined || !closeEmits.some((e) => e[0] === false)).toBe(true);
|
||||
});
|
||||
|
||||
it('I3: без tenantId — projectOptions и managerOptions пусты (нет mock-fallback)', async () => {
|
||||
const wrapper = factory({ modelValue: true }); // нет tenantId
|
||||
await flushPromises();
|
||||
const vm = wrapper.vm as unknown as {
|
||||
projectOptions: string[];
|
||||
managerOptions: unknown[];
|
||||
};
|
||||
expect(vm.projectOptions).toHaveLength(0);
|
||||
expect(vm.managerOptions).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('C6: при провале loadLookups показывает degradation-alert', async () => {
|
||||
vi.spyOn(dealsApi, 'listProjects').mockRejectedValue(new Error('network'));
|
||||
vi.spyOn(dealsApi, 'listManagers').mockRejectedValue(new Error('network'));
|
||||
|
||||
@@ -1308,3 +1308,25 @@ recollage
|
||||
замапленного
|
||||
замапленных
|
||||
замапленные
|
||||
|
||||
# A6 architecture tooling — adr-kit / mermaid / architecture-patterns (2026-05-17)
|
||||
ADR
|
||||
rvdbreemen
|
||||
secondsky
|
||||
NNN
|
||||
MMM
|
||||
Nygard
|
||||
DDD
|
||||
mmdc
|
||||
inertiajs
|
||||
Sev
|
||||
вендоренный
|
||||
|
||||
# D3 audit-risk tooling integration (Прил. Н #39-40)
|
||||
unvetted
|
||||
mcpmarket
|
||||
behaviour
|
||||
triada
|
||||
trailofbits
|
||||
hackathon
|
||||
субсет
|
||||
|
||||
+2
-1
@@ -22,7 +22,8 @@
|
||||
"bin/**",
|
||||
"package-lock.json",
|
||||
"*.svg",
|
||||
"**/*.sql"
|
||||
"**/*.sql",
|
||||
".claude/skills/mermaid/**"
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"Email",
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
# Plugin Stack Rules — Superpowers + Frontend Design (v3.2)
|
||||
# Plugin Stack Rules — Superpowers + Frontend Design (v3.4)
|
||||
|
||||
**Дата:** 16.05.2026
|
||||
**Дата:** 17.05.2026
|
||||
**Назначение:** свод правил совместного использования плагинов Claude Code в проекте Лидерра — paired-stack ядро `obra/superpowers` (14 skills) + `anthropics/frontend-design`, плюс расширенный пул UI-инструментов `ui-ux-pro-max` (skill, marketplace `nextlevelbuilder/ui-ux-pro-max-skill`) и `21st.dev Magic MCP` (MCP-сервер `magic`), плюс инфраструктурный `claude-md-management` (skills, marketplace `anthropics/claude-plugins-official`), плюс **debug-runtime MCP** `@sentry/mcp-server` + `@modelcontextprotocol/server-redis` (v2.1+, R10.1 Блок 3).
|
||||
|
||||
**v3.4** — D3 audit-security: R10.1 Блок 1 (`enabledPlugins`) +2 строки — **Trail of Bits Skills** (`trailofbits/skills`, субсет 8 плагинов) + **security-guidance** (`anthropics/claude-plugins-official`). Новая категория **audit-security** (Tooling #39-40, раздел D3 карты) — не UI → вне R6.0/R6.1/R14, как debug-runtime/infrastructure/architecture-tooling. Содержательных изменений R0–R9/R11–R14: 0. Связано: Tooling v2.4, Pravila v1.18, CLAUDE.md v2.4.
|
||||
|
||||
**v3.3** — A6 architecture-tooling: R10.1 Блок 1 (`enabledPlugins`) +2 строки — **adr-kit** (`rvdbreemen/adr-kit`) + **architecture-patterns** (`secondsky/claude-skills`); Блок 1 +note про **mermaid-skill** (вендоренный сторонний скил). Новая категория **architecture-tooling** (Tooling #36-38, раздел A6 карты) — не UI → вне R6.0/R6.1/R14, как debug-runtime/infrastructure. Содержательных изменений R0–R9/R11–R14: 0. Связано: Tooling v2.3, Pravila v1.17, CLAUDE.md v2.3.
|
||||
|
||||
**v3.2** — реколлаж R0: sub-policy → top-of-stack gate (ruflo не entry-point по факту рантайма: 0 задач, рой idle). R0 title восстановлен, уровень −1 убран из R0.1 таблицы, R0.2 абзац перед gate-диаграммой возвращён к stack-gate формулировке. Связано: Pravila v1.16, CLAUDE.md v2.2, Tooling v2.2.
|
||||
|
||||
**v3.1** — sync: cross-ref на Pravila v1.15 (новый §14 Ruflo Queen routing hard-rule — триггер queen/королева → безусловный route через ruflo Queen). Содержательных изменений R0–R14: 0.
|
||||
|
||||
**v3.0** — major: R0 stack-gate → sub-policy paired-stack delegation pattern под ruflo Queen-led routing. R0.1 +ruflo level −1; R0.6 +п.11 swarm-pause-without-review (sequential continuation после v2.0 R15 removal). Связано: ruflo v3.7.0-alpha.38 big-bang integration 2026-05-15.
|
||||
|
||||
Снимает запрет CLAUDE.md §5 п.5 на Frontend Design plugin (действовал до v1.77 включительно). Документ — внутренне непротиворечив: 8 первичных конфликтов закрыты в v1.0 + 5 патчей по реальным трениям A–E в v1.1 + 4 новых правила R10–R13 против перекрытий с другими плагинами в v1.2 + 6 уточняющих патчей F–K по найденным трениям второго порядка в v1.3 + 1 новое правило R14 (pipeline внешних UI-генераторов: UPM + 21st Magic MCP) в v1.4 (R15 motion-системы введены в v1.4 и удалены в v2.0) + 5 структурных правок аудита в v1.5 (R10.1 разбит на 3 блока, R0.4.A SoT cross-ref, R10.4/R14.7 tier-метки, R8 +тай-брейкер FD↔21st, R0.1 scope-метка) + 3 правки второго аудита в v1.6 (R0.4.A свёрнут до cross-ref на Pravila §12.3 SoT — устраняет дрейф формулировок; R0.6 hard-стопы пронумерованы 1–11 для надёжности cross-refs «пункт 9/10/11»; R0.1 +Tooling Прил. Н slot уровня 2b) + 1 правка третьего аудита в v1.7 (sync cross-refs на актуальные версии связанных документов после bump'ов CLAUDE.md v1.86 / Tooling v1.15; description-fix описки «уровня 2.5»→«уровня 2b» внутри changelog'а v1.6, сверка с фактическим R0.1) + 1 правка четвёртого аудита в v2.0 (12.05.2026 — снят R15 motion-runtime по решению заказчика; conscious rollback v1.4 audited construction; framer-motion переведён из regulatory hard-запрета в technical-guidance уровень: peerDep на react+react-dom, не работает в Vue физически) + 2 строки R10.1 Блок 3 в v2.1 (13.05.2026 day +1 — debug-runtime MCP формализованы retrospective после PR #3 merge) + R0 major rewrite в v3.0 (stack-gate → sub-policy под ruflo Queen-led routing, 15.05.2026 afternoon) + R0 реколлаж в v3.2 (sub-policy → top-of-stack gate — ruflo не entry-point по факту рантайма, 16.05.2026).
|
||||
Снимает запрет CLAUDE.md §5 п.5 на Frontend Design plugin (действовал до v1.77 включительно). Документ — внутренне непротиворечив: 8 первичных конфликтов закрыты в v1.0 + 5 патчей по реальным трениям A–E в v1.1 + 4 новых правила R10–R13 против перекрытий с другими плагинами в v1.2 + 6 уточняющих патчей F–K по найденным трениям второго порядка в v1.3 + 1 новое правило R14 (pipeline внешних UI-генераторов: UPM + 21st Magic MCP) в v1.4 (R15 motion-системы введены в v1.4 и удалены в v2.0) + 5 структурных правок аудита в v1.5 (R10.1 разбит на 3 блока, R0.4.A SoT cross-ref, R10.4/R14.7 tier-метки, R8 +тай-брейкер FD↔21st, R0.1 scope-метка) + 3 правки второго аудита в v1.6 (R0.4.A свёрнут до cross-ref на Pravila §12.3 SoT — устраняет дрейф формулировок; R0.6 hard-стопы пронумерованы 1–11 для надёжности cross-refs «пункт 9/10/11»; R0.1 +Tooling Прил. Н slot уровня 2b) + 1 правка третьего аудита в v1.7 (sync cross-refs на актуальные версии связанных документов после bump'ов CLAUDE.md v1.86 / Tooling v1.15; description-fix описки «уровня 2.5»→«уровня 2b» внутри changelog'а v1.6, сверка с фактическим R0.1) + 1 правка четвёртого аудита в v2.0 (12.05.2026 — снят R15 motion-runtime по решению заказчика; conscious rollback v1.4 audited construction; framer-motion переведён из regulatory hard-запрета в technical-guidance уровень: peerDep на react+react-dom, не работает в Vue физически) + 2 строки R10.1 Блок 3 в v2.1 (13.05.2026 day +1 — debug-runtime MCP формализованы retrospective после PR #3 merge) + R0 major rewrite в v3.0 (stack-gate → sub-policy под ruflo Queen-led routing, 15.05.2026 afternoon) + R0 реколлаж в v3.2 (sub-policy → top-of-stack gate — ruflo не entry-point по факту рантайма, 16.05.2026) + R10.1 Блок 1 +2 строки в v3.3 (adr-kit + architecture-patterns — категория architecture-tooling, раздел A6 карты, 17.05.2026).
|
||||
|
||||
**Принцип-аксиома (v1.2, уточнён в v1.3, расширен в v1.4, актуализирован в v3.2):** **Stack (Superpowers + Frontend Design) — головной** над не-stack плагинами и поведенческими слоями в части плагинов и поведенческих слоёв. Stack-gate (R0) — **первая точка входа** (top-level entry-point среди уровней 3–6). Все остальные плагины (ui-ux-pro-max, 21st Magic MCP, claude-md-management, review, security-review, init, simplify и др.) — **инструменты**, инвокируемые **внутри** stack-flow как подзадачи, не как альтернативы или параллельные решатели. Stack **исполняет** Pravila/CLAUDE.md, а не перебивает их (см. R0.1 для точного scope «головенства»). Другие плагины могут получить работу только по делегированию из stack'а или по явному `/имя-плагина` от пользователя.
|
||||
|
||||
@@ -392,6 +396,12 @@ Stack — **головной**. Все плагины вне stack'а — **ин
|
||||
|---|---|---|---|
|
||||
| **ui-ux-pro-max** *(skill)* | `nextlevelbuilder/ui-ux-pro-max-skill` | резервная UI-библиотека (50+ стилей, 161 палитра, 99 UX-гайдлайнов, 25 типов графиков, 10 стеков). Не источник истины (R11 уровень 5) | (1) когда Frontend Design выдал «не знаю» / не покрывает узкоспецифический вопрос (типичные случаи: chart-стек, экзотические типографические пары, региональные UX-паттерны); **(2) [v1.4]** когда в фазе 1 R2 нужен «третий вариант» для архитектурного решения R12 (см. R11.5). Активируется **внутри** R2 как fallback / источник материала, не параллельно с FD. Категория: **UI-пул** (Pravila §13) |
|
||||
| **claude-md-management** *(skills `claude-md-improver` + `revise-claude-md`)* | `anthropics/claude-plugins-official` | инфраструктурный плагин для CLAUDE.md edits | **обязательно** при любом изменении CLAUDE.md (выполнение CLAUDE.md §5 п.10). Не альтернатива stack'у, а инструмент внутри stack-фазы «реализация». Категория: **инфраструктурная** (вне UI-пула Pravila §13) |
|
||||
| **adr-kit** *(8 skills + агент `adr-generator`)* | `rvdbreemen/adr-kit` | Architecture Decision Records — `/adr-kit:adr` авторинг, `/adr-kit:lint` проверка, `adr-judge` enforcement. Категория: **architecture-tooling** (Tooling #36, вне UI-пула) | при авторинге/ревизии архитектурного решения в `docs/adr/`. `adr-judge` врезан в lefthook job 9 (декларативно, без `--llm`). Не UI → вне R6.0/R6.1/R14 |
|
||||
| **architecture-patterns** *(1 skill)* | `secondsky/claude-skills` | справочник архитектурных паттернов (Clean / Hexagonal / layered / DDD). Категория: **architecture-tooling** (Tooling #38). Knowledge-only, не решатель | при проектировании/рефакторинге подсистемы — справка по паттернам. Не источник истины (R11), как UPM |
|
||||
| **Trail of Bits Skills** *(субсет 8 плагинов)* | `trailofbits/skills` (marketplace `trailofbits`) | аудит безопасности — security-аудит diff, supply-chain риск зависимостей, поиск вариантов уязвимостей. Категория: **audit-security** (Tooling #39, вне UI-пула). CC-BY-SA-4.0, marketplace-плагин (не вендорен) | при глубокой аудит-кампании раздела D3 «Аудит и управление рисками». Не UI → вне R6.0/R6.1/R14 |
|
||||
| **security-guidance** *(1 PreToolUse warn-хук)* | `anthropics/claude-plugins-official` | inline-предупреждения уязвимостей при правке кода (warn-only, не блокирует, 8 категорий). Категория: **audit-security** (Tooling #40) | автоматически — PreToolUse-хук на Write/Edit. Не решатель, не UI → вне R6.0/R6.1/R14 |
|
||||
|
||||
**Блок 1 — note (v3.3):** **mermaid-skill** (Tooling #37, генератор C4/architecture-диаграмм) — вендоренный сторонний скил в `.claude/skills/mermaid/` (`WH-2099/mermaid-skill`, MIT), **не** через marketplace и **не** в `enabledPlugins`. Пассивная утилита (генерация Mermaid-исходника), не решатель — формально вне типологии трёх блоков; регистрируется здесь для полноты. Категория **architecture-tooling**, вне R6/R14.
|
||||
|
||||
**Отмена:** через удаление из `enabledPlugins` в `~/.claude/settings.json` или через live-override `/имя-плагина` (R0.4.B) на одно действие.
|
||||
|
||||
@@ -747,6 +757,10 @@ Pipeline активируется при одновременном выполн
|
||||
|
||||
## История версий
|
||||
|
||||
- **v3.4 (2026-05-17)** — D3 audit-security: R10.1 Блок 1 (`enabledPlugins`) +2 строки (Trail of Bits Skills #39, security-guidance #40) — новая 6-я off-phase подкатегория audit-security. Связано: Tooling v2.4, Pravila v1.18, CLAUDE.md v2.4. План `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`.
|
||||
|
||||
- **v3.3 (2026-05-17)** — A6 architecture-tooling: R10.1 Блок 1 (`enabledPlugins`) +2 строки — **adr-kit** (`rvdbreemen/adr-kit`, 8 skills + агент `adr-generator`; `adr-judge` врезан в lefthook pre-commit job 9 декларативно, без `--llm` → 0 вызовов Claude API) + **architecture-patterns** (`secondsky/claude-skills`, knowledge-only справочник паттернов). Блок 1 +note про **mermaid-skill** (вендоренный сторонний скил `.claude/skills/mermaid/`, генератор C4-диаграмм — пассивная утилита вне типологии 3 блоков). Новая категория **architecture-tooling** (Tooling #36-38, раздел A6 карты «Архитектура систем») — не UI → вне R6.0/R6.1/R14 pipeline, как debug-runtime и infrastructure. Содержательных изменений R0–R9/R11–R14: 0. Связано: Tooling v2.2→v2.3, Pravila v1.16→v1.17, CLAUDE.md v2.2→v2.3. Через manual Edit. План `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md`.
|
||||
|
||||
- **v3.2 (2026-05-16)** — реколлаж R0: sub-policy → top-of-stack gate (ruflo не entry-point по факту рантайма: 0 задач, рой idle). **Изменено:** R0 title → «Stack-gate: paired-stack delegation pattern»; R0.1 таблица — удалена строка уровня −1 (ruflo entry-point), строка уровня 3 (PSR_v1) → «— (PSR_v1 — сам stack-документ, вопрос неприменим)»; R0.1 преамбула — убраны формулировки sub-policy-под-ruflo, stack снова головной над уровнями 4–6; R0.2 абзац перед диаграммой — возвращён к stack-gate формулировке; шапка cross-refs: CLAUDE.md v2.0+ → v2.2+, Pravila v1.15+ → v1.16+, Tooling v2.0+ → v2.2+. ASCII-диаграмма (STACK GATE) и R0.5 не тронуты. **R0.6 п.11 удалён** (ruflo autonomous-routing hard-stop — висячая ссылка на ruflo как маршрутизатор задач; противоречит реколлажу: ruflo не entry-point, рой idle, 0 задач). Связано: Pravila v1.16 / CLAUDE.md v2.2 / Tooling v2.2; spec `docs/superpowers/specs/2026-05-16-ruflo-hierarchy-factual-recollage-design.md`.
|
||||
|
||||
- **v3.0 (2026-05-15)** — major: R0 stack-gate → sub-policy paired-stack delegation pattern под ruflo Queen-led routing. R0.1 +ruflo level −1; R0.2 entry-point shifted ruflo→stack-gate-as-sub-policy; R0.6 +п.11 swarm-pause-without-review (sequential continuation после v2.0 R15 removal, не литерал п.12 как в спеке). Связано: Pravila v1.14 (commit 9c3057b), CLAUDE.md v2.0 (commit 5df88a1), Tooling v2.0 (commit f65a8d7), ruflo v3.7.0-alpha.38 integration via spec/plan 2026-05-15 (commits e55572e/18c4463/9bd1bae).
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
# Правила работы Claude в проекте «Лидерра»
|
||||
|
||||
**Версия:** v1.16 (утверждена заказчиком 16.05.2026)
|
||||
**Дата:** 16.05.2026
|
||||
**Версия:** v1.18 (17.05.2026)
|
||||
**Дата:** 17.05.2026
|
||||
**Назначение:** настройки проекта (Project instructions) — Claude читает этот файл в каждом чате и следует правилам ниже.
|
||||
**Статус документа:** ✅ утверждён. Содержимое скопировано в поле "Project instructions" Claude.ai. Файл хранится в архиве как служебный документ.
|
||||
|
||||
**Что изменилось в v1.16 относительно v1.15:** реколлаж ruflo к фактическому рантайму: §12 sub-policy → hard-rule (title + абзацы), §12.4 первый буллет → «§9 не применяется», §0 priority note убран ruflo уровень −1 (цепочка начинается с §12 explicit hard-rule), §14.6 cross-ref убран «ruflo — уровень −1» → «ruflo как инструмент (хук + MCP), не уровень иерархии», §13.9/§13.10 PSR_v1 cross-refs «v3.0+, R0 → sub-policy» → «v3.2+, R0 — top-of-stack gate». Связано: CLAUDE.md v2.2 / PSR_v1 v3.2 / Tooling v2.2; spec `docs/superpowers/specs/2026-05-16-ruflo-hierarchy-factual-recollage-design.md`.
|
||||
**Что изменилось в v1.18 относительно v1.17:** §13.2 +абзац «Off-phase audit-security» — формализованы 2 инструмента раздела D3 карты «Аудит и управление рисками» (#39 Trail of Bits Skills, #40 Security Guidance) как шестая off-phase категория; §13.2 PSR_v1 cross-ref v3.3+ → v3.4+. Связано: Tooling v2.4 / PSR_v1 v3.4 / CLAUDE.md v2.4; план `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`.
|
||||
|
||||
**Что изменилось в v1.17 относительно v1.16:** §13.2 +абзац «Off-phase architecture-tooling» — формализованы 3 инструмента раздела A6 карты «Архитектура систем» (#36 adr-kit, #37 mermaid-skill, #38 architecture-patterns) как пятая off-phase категория; §13.2 PSR_v1 cross-ref v3.2+ → v3.3+. Связано: Tooling v2.3 / PSR_v1 v3.3 / CLAUDE.md v2.3; план `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md`.
|
||||
|
||||
**Краткое резюме v1.16:** реколлаж ruflo к фактическому рантайму: §12 sub-policy → hard-rule (title + абзацы), §12.4 первый буллет → «§9 не применяется», §0 priority note убран ruflo уровень −1 (цепочка начинается с §12 explicit hard-rule), §14.6 cross-ref убран «ruflo — уровень −1» → «ruflo как инструмент (хук + MCP), не уровень иерархии», §13.9/§13.10 PSR_v1 cross-refs «v3.0+, R0 → sub-policy» → «v3.2+, R0 — top-of-stack gate». Связано: CLAUDE.md v2.2 / PSR_v1 v3.2 / Tooling v2.2; spec `docs/superpowers/specs/2026-05-16-ruflo-hierarchy-factual-recollage-design.md`.
|
||||
|
||||
**Краткое резюме v1.15:** новый §14 «Ruflo Queen routing — hard rule» (триггер queen/королева → безусловный route через ruflo Queen) + §13.6 tier-таблица +строка §14 + §0 priority note.
|
||||
|
||||
@@ -555,6 +559,7 @@ P0 = блокер старта спринта или регуляторного
|
||||
| **v1.14** | **15.05.2026** | Ruflo big-bang sub-policy conversion: §12 hard rule → sub-policy (ruflo routing preference); §5 ПДн +execution-layer note; cross-refs к PSR_v1 v3.0 / CLAUDE.md v2.0 / Tooling v2.0. Связано: ruflo v3.7.0-alpha.38 integration via spec/plan 2026-05-15 (commits e55572e/18c4463/9bd1bae). v1.13 наследие — Task 9 sync after PR #3 (Sentry+Redis MCP). |
|
||||
| **v1.15** | **15.05.2026** | Новый §14 «Ruflo Queen routing — hard rule»: триггер queen/королева → безусловный route через ruflo Queen (`hive-mind spawn --claude`), enforcement-хук `tools/ruflo-queen-hook.mjs`. §13.6 tier-таблица +строка §14 (explicit hard-rule). §0 priority chain +§14 +note. §14.3 — проактивное предложение ruflo-spawn на нетривиальных задачах. Связано: spec/plan 2026-05-15-ruflo-queen-trigger-and-delegation, CLAUDE.md v2.1, PSR_v1 v3.1, Tooling v2.1. Через `superpowers:brainstorming` → `writing-plans` → `subagent-driven-development`. |
|
||||
| **v1.16** | **16.05.2026** | Реколлаж ruflo — приведение декларации к фактическому рантайму: §12 Superpowers переведён из sub-policy обратно в explicit hard-rule; §0 priority note и §14.6 cross-ref — убраны упоминания ruflo как «уровня −1»; §11.5/§13.2/§13.9/§13.10 cross-refs на PSR_v1 v3.2. Связано: CLAUDE.md v2.2 / PSR_v1 v3.2 / Tooling v2.2; spec `docs/superpowers/specs/2026-05-16-ruflo-hierarchy-factual-recollage-design.md`. |
|
||||
| **v1.17** | **17.05.2026** | A6 architecture-tooling: §13.2 +абзац «Off-phase architecture-tooling» — формализованы 3 инструмента раздела A6 карты «Архитектура систем» (#36 adr-kit, #37 mermaid-skill, #38 architecture-patterns) как пятая off-phase категория, отдельная от UI-пула / infrastructure / debug-runtime / orchestration; не UI → вне R6.0/R6.1/R14. §13.2 PSR_v1 cross-ref v3.2+ → v3.3+. Связано: Tooling v2.2→v2.3 (§4.11-4.13 + §0 счётчик 35→38), PSR_v1 v3.2→v3.3 (R10.1 Блок 1 +2 строки + note), CLAUDE.md v2.2→v2.3 (§3.3 +#36-38). Через manual Edit (Pravila/PSR_v1/Tooling) + `/claude-md-management:claude-md-improver` (CLAUDE.md per §5 п.10). План `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md`. Архитектурных изменений в §§1–12 + §§13.1, 13.3–14: 0. |
|
||||
|
||||
---
|
||||
|
||||
@@ -670,7 +675,7 @@ P0 = блокер старта спринта или регуляторного
|
||||
|
||||
### 13.2. Парность со Superpowers + расширенный пул UI-инструментов (v1.8)
|
||||
|
||||
Frontend Design и `obra/superpowers` (v5.1.0, 14 skills) — **парный stack одного приоритетного уровня**. Оба плагина подключены к gate stack'а одновременно, между ними нет иерархии. Координация — через [docs/Plugin_stack_rules_v1.md](Plugin_stack_rules_v1.md) **v3.2+ (R0 — top-of-stack gate; ruflo big-bang 15.05.2026 + реколлаж 16.05.2026; полный детализированный реестр правил в PSR_v1)**.
|
||||
Frontend Design и `obra/superpowers` (v5.1.0, 14 skills) — **парный stack одного приоритетного уровня**. Оба плагина подключены к gate stack'а одновременно, между ними нет иерархии. Координация — через [docs/Plugin_stack_rules_v1.md](Plugin_stack_rules_v1.md) **v3.3+ (R0 — top-of-stack gate; ruflo big-bang 15.05.2026 + реколлаж 16.05.2026; полный детализированный реестр правил в PSR_v1)**.
|
||||
|
||||
**Расширенный пул UI-инструментов (v1.8)** добавляет к paired-stack ядру два внешних плагина в роли **инструментов** (R10.1 PSR_v1, не решателей):
|
||||
|
||||
@@ -687,7 +692,11 @@ Frontend Design и `obra/superpowers` (v5.1.0, 14 skills) — **парный sta
|
||||
|
||||
**Инфраструктурные плагины (вне расширенного UI-пула, v1.9+):** `claude-md-management` (skills `claude-md-improver` + `revise-claude-md`, marketplace `anthropics/claude-plugins-official`) — единственный интерфейс правок CLAUDE.md (CLAUDE.md §5 п.10). Категория **инфраструктурная**, не UI — поэтому не попадает под §13 (расширенный UI-пул) и не проходит R6.0/R6.1 фильтр / R14 pipeline. Регулируется PSR_v1 R10.1 блок 1 (`enabledPlugins`-плагины) как off-pool tool. Аналогичные инфраструктурные категории — built-in skills Claude Code (`review`, `security-review`, `init`, `simplify`, `update-config`, `keybindings-help`, `fewer-permission-prompts`, `loop`, `schedule`, `claude-api`) — активируются по явному `/имя` от пользователя; PSR_v1 R10.1 блок 2.
|
||||
|
||||
**Off-phase MCP debug-runtime (отдельная категория, введена v1.13 Pravila, 13.05.2026 day +1):** `@sentry/mcp-server@0.33.0+` (Tooling #34, server `sentry` в `.mcp.json`) — отладка production errors в self-hosted Sentry (Yandex Cloud per CLAUDE.md §2; pending Б-1 ООО registration); `@modelcontextprotocol/server-redis@2025.4.25` (Tooling #35, server `redis` в `.mcp.json`; deprecated Anthropic source; Memurai PONG verified Task 4) — отладка Redis/Memurai runtime (очереди, кэш, Pest --parallel races per quirk 72/77). **Категория отдельная** от UI-пула (§13.2 paired-stack + UPM + 21st) и от infrastructure (claude-md-management §13.2 paragraph выше) — **не trigger'ит R6.0/R6.1 stack-фильтры** (READ-ONLY, не модифицируют code/UI/CLAUDE.md) и **не входит в R14 pipeline** UI-генераторов. Регулируется PSR_v1 R10.1 Блок 3 (`.mcp.json`-серверы) как debug-runtime off-phase tool. READ-ONLY usage обязателен — никаких mutation операций (DEL/FLUSHDB/SET/LPUSH для Redis; write actions для Sentry). Установлены retrospective на feat/claude-automation `6f7e7d7` (sentry) + `bd4ec48` (redis), merged через PR #3 (`cc5f63b`).
|
||||
**Off-phase MCP debug-runtime (отдельная категория, введена v1.13 Pravila, 13.05.2026 day +1):** `@sentry/mcp-server@0.33.0+` (Tooling #34, server `sentry` в `.mcp.json`) — отладка production errors в self-hosted Sentry (Yandex Cloud per CLAUDE.md §2; pending Б-1 ООО registration); `@modelcontextprotocol/server-redis@2025.4.25` (Tooling #35, server `redis` в `.mcp.json`; deprecated Anthropic source; Memurai PONG verified Task 4) — отладка Redis/Memurai runtime (очереди, кэш, Pest --parallel races per quirk 72/77). **Категория отдельная** от UI-пула (§13.2 paired-stack + UPM + 21st) и от infrastructure (claude-md-management §13.2 paragraph выше) — **не trigger'ит R6.0/R6.1 stack-фильтры** (READ-ONLY, не модифицируют code/UI/CLAUDE.md) и **не входит в R14 pipeline** UI-генераторов. Регулируется PSR_v1 R10.1 Блок 3 (`.mcp.json`-серверы) как debug-runtime off-phase tool. READ-ONLY usage обязателен — никаких mutation операций (DEL/FLUSHDB/SET/LPUSH для Redis; write actions для Sentry). Установлены retrospective на feat/claude-automation `6f7e7d7` (sentry) + `bd4ec48` (redis), merged через PR #3 (`cc5f63b`). PSR_v1 cross-ref: **v3.4+**, R10.1 Блок 3.
|
||||
|
||||
**Off-phase architecture-tooling (отдельная категория, v1.17, 17.05.2026):** три инструмента раздела A6 карты «Архитектура систем» — `adr-kit` (Tooling #36, marketplace `rvdbreemen/adr-kit`; ADR-решения в `docs/adr/`, `adr-judge` врезан в lefthook pre-commit job 9 декларативно, без `--llm`), `mermaid-skill` (Tooling #37, вендоренный сторонний скил `.claude/skills/mermaid/`; C4/architecture-диаграммы), `architecture-patterns` (Tooling #38, marketplace `secondsky/claude-skills`; knowledge-only справочник паттернов). **Категория отдельная** от UI-пула (UPM/21st), infrastructure (claude-md-management) и debug-runtime (Sentry/Redis) — не UI, **не trigger'ит R6.0/R6.1 stack-фильтры и не входит в R14 pipeline**. Регулируется PSR_v1 R10.1 Блок 1 (adr-kit, architecture-patterns) + Блок 1 note (mermaid-skill — вендоренный скил вне типологии трёх блоков). Установлены 17.05.2026 на ветке `feat/a6-architecture-tooling`; план `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md`.
|
||||
|
||||
**Off-phase audit-security (отдельная категория, v1.18, 17.05.2026):** инструменты раздела D3 карты «Аудит и управление рисками» — `Trail of Bits Skills` (Tooling #39, marketplace `trailofbits/skills`; курированный субсет 8 audit-плагинов — security-аудит diff, supply-chain риск зависимостей; CC-BY-SA-4.0, marketplace-плагин не вендорен), `Security Guidance` (Tooling #40, marketplace `anthropics/claude-plugins-official`; один PreToolUse warn-only хук — inline-предупреждения уязвимостей, не блокирует). Дополнительно `/security-review` (Anthropic built-in, customized в `.claude/commands/security-review.md` с проектным FP-фильтром RLS/ПДн/economy-хуки). **Категория отдельная** от UI-пула, infrastructure, debug-runtime, orchestration и architecture-tooling — не UI, **не trigger'ит R6.0/R6.1 stack-фильтры и не входит в R14 pipeline**. Регулируется PSR_v1 R10.1 Блок 1. Установлены 17.05.2026 на ветке `feat/d3-audit-risk-tooling`; план `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`.
|
||||
|
||||
### 13.3. Скоуп
|
||||
|
||||
|
||||
+61
-5
@@ -1,10 +1,10 @@
|
||||
# Приложение Н — Tooling, скиллы и плагины Claude (v8.3)
|
||||
|
||||
**Дата:** 16.05.2026
|
||||
**Версия:** 2.2 (§4.10 реколлаж — ruflo переописан из «entry-point иерархии» в «advisory/automation-подсистему» (декларация приведена к рантайму: рой idle, 0 задач); заголовок §4.10 + «Архитектурная роль» переписаны; §0 table row + «Категории off-phase tools» + «Назначение» обновлены; §13 +v2.2 entry. Связано: Pravila v1.16, PSR_v1 v3.2, CLAUDE.md v2.2; spec `docs/superpowers/specs/2026-05-16-ruflo-hierarchy-factual-recollage-design.md`. **v2.1 наследие:** §4.10 +абзац «Queen trigger»: триггер queen/королева → безусловный route через ruflo Queen (`hive-mind spawn --claude`), explicit hard-rule Pravila §14, enforcement-хук `tools/ruflo-queen-hook.mjs`. Связано: spec/plan `docs/superpowers/{specs,plans}/2026-05-15-ruflo-queen-trigger-and-delegation*`, Pravila v1.15, CLAUDE.md v2.1, PSR_v1 v3.1. **v2.0 наследие:** Ruflo big-bang — major bump: добавлен **orchestration layer (ruflo)** как четвёртая off-phase подкатегория. §0 +ruflo orchestration row: 35 формализованных позиций + 20 ruflo plugins = 55 total; новая §4.10 «Orchestration layer (ruflo)». Связано: spec/plan 2026-05-15, Pravila v1.14, PSR_v1 v3.0, CLAUDE.md v2.0.)
|
||||
**Дата:** 17.05.2026
|
||||
**Версия:** 2.4 (D3 audit-security — формализованы #39 Trail of Bits Skills (субсет 8 audit-плагинов, marketplace `trailofbits`, CC-BY-SA-4.0) + #40 Security Guidance (Anthropic warn-only PreToolUse-хук) как новая 6-я off-phase подкатегория «audit-security» — §4.14/§4.15; §0 счётчик 38→40 (58→60 total); off-phase row +8→+10. Связано: PSR_v1 v3.4, Pravila v1.18, CLAUDE.md v2.4; план `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`. **v2.3 наследие:** A6 architecture-tooling — формализованы 3 инструмента раздела A6 карты «Архитектура систем»: **#36 adr-kit** (ADR-решения + `adr-judge` gate), **#37 mermaid-skill** (C4-диаграммы), **#38 architecture-patterns** (паттерны) — новые §4.11–4.13, новая пятая off-phase подкатегория «architecture-tooling»; §0 счётчик 35→38 формализованных позиций (55→58 total), §0 table row off-phase +5→+8. Связано: PSR_v1 v3.3, Pravila v1.17, CLAUDE.md v2.3; план `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md`. **v2.2 наследие:** §4.10 реколлаж — ruflo переописан из «entry-point иерархии» в «advisory/automation-подсистему» (декларация приведена к рантайму: рой idle, 0 задач); заголовок §4.10 + «Архитектурная роль» переписаны; §0 table row + «Категории off-phase tools» + «Назначение» обновлены; §13 +v2.2 entry. Связано: Pravila v1.16, PSR_v1 v3.2, CLAUDE.md v2.2; spec `docs/superpowers/specs/2026-05-16-ruflo-hierarchy-factual-recollage-design.md`. **v2.1 наследие:** §4.10 +абзац «Queen trigger»: триггер queen/королева → безусловный route через ruflo Queen (`hive-mind spawn --claude`), explicit hard-rule Pravila §14, enforcement-хук `tools/ruflo-queen-hook.mjs`. Связано: spec/plan `docs/superpowers/{specs,plans}/2026-05-15-ruflo-queen-trigger-and-delegation*`, Pravila v1.15, CLAUDE.md v2.1, PSR_v1 v3.1. **v2.0 наследие:** Ruflo big-bang — major bump: добавлен **orchestration layer (ruflo)** как четвёртая off-phase подкатегория. §0 +ruflo orchestration row: 35 формализованных позиций + 20 ruflo plugins = 55 total; новая §4.10 «Orchestration layer (ruflo)». Связано: spec/plan 2026-05-15, Pravila v1.14, PSR_v1 v3.0, CLAUDE.md v2.0.)
|
||||
**Предыдущая версия:** 1.17 (13.05.2026 day +1 — формализация retrospective двух off-phase MCP debug-инструментов установленных на feat/claude-automation `6f7e7d7` + `bd4ec48` после merge PR #3 в main `cc5f63b`: §0 счётчик off-phase 3 → 5, итого 33 → 35; §4.8 новый — #34 Sentry MCP; §4.9 новый — #35 Redis MCP. Категория debug-runtime, отдельная от UI-пула.)
|
||||
**Адресат:** Claude + разработчики проекта Лидерра
|
||||
**Назначение:** единый источник истины по 35 формализованным позициям тулчейна + 20 ruflo orchestration plugins = 55 total (29 «активных» номеров фаз + 5 off-phase инструментов-резерв в категориях UI-пул, инфраструктура, debug-runtime — UPM, 21st, claude-md-management, Sentry MCP, Redis MCP; +1 заменённый PG MCP исторически; +ruflo advisory/automation-подсистема — 20 plugins, см. §4.10), скиллам Claude Code, MCP-серверам и плагинам, используемым в проекте. Зафиксирован выбор, объяснено, что заменяет что, и в какой фазе вводится каждый инструмент.
|
||||
**Назначение:** единый источник истины по 40 формализованным позициям тулчейна + 20 ruflo orchestration plugins = 60 total (29 «активных» номеров фаз + 10 off-phase инструментов-резерв в категориях UI-пул, инфраструктура, debug-runtime, architecture-tooling, audit-security — UPM, 21st, claude-md-management, Sentry MCP, Redis MCP, adr-kit, mermaid-skill, architecture-patterns, Trail of Bits Skills, Security Guidance; +1 заменённый PG MCP исторически; +ruflo advisory/automation-подсистема — 20 plugins, см. §4.10), скиллам Claude Code, MCP-серверам и плагинам, используемым в проекте. Зафиксирован выбор, объяснено, что заменяет что, и в какой фазе вводится каждый инструмент.
|
||||
|
||||
> **Связано:**
|
||||
>
|
||||
@@ -81,10 +81,10 @@
|
||||
| **1 — старт Laravel** | `composer create-project laravel/laravel` | **17** | +9 новых, −1 заменённый (PostgreSQL MCP → Laravel Boost) |
|
||||
| **2 — старт frontend** | первый коммит в `resources/js/` (Vue 3 + Vuetify 3) | **24** | +7 (включая #30 Frontend Design plugin, добавлен post-MVP в v1.10) |
|
||||
| **3 — pre-production** | ~спринт 12, перед публичным релизом | **29** | +5 |
|
||||
| **off-phase tools** | по факту включения в `~/.claude/settings.json` / `~/.claude.json` / `.mcp.json` | **+5** | #31 UPM (UI-резерв), #32 21st Magic MCP (UI-генератор), #33 claude-md-management (инфраструктура CLAUDE.md edits), #34 Sentry MCP (debug self-hosted Sentry в Yandex Cloud), #35 Redis MCP (debug Memurai/Redis runtime) |
|
||||
| **off-phase tools** | по факту включения в `~/.claude/settings.json` / `~/.claude.json` / `.mcp.json` / `.claude/skills/` | **+10** | #31 UPM (UI-резерв), #32 21st Magic MCP (UI-генератор), #33 claude-md-management (инфраструктура CLAUDE.md edits), #34 Sentry MCP (debug self-hosted Sentry в Yandex Cloud), #35 Redis MCP (debug Memurai/Redis runtime), #36 adr-kit (ADR-решения, architecture-tooling), #37 mermaid-skill (C4-диаграммы), #38 architecture-patterns (паттерны), #39 Trail of Bits Skills (8 audit-плагинов, audit-security), #40 Security Guidance (inline security warn-hook) |
|
||||
| **ruflo advisory/automation-подсистема** (off-phase, post-MVP 2026-05-15) | `npx ruflo@latest init` + `.mcp.json` ruflo entry | **+20 plugins** | `ruflo` v3.7.0-alpha.38+ + 20 plugins (`@claude-flow/*`, IPFS-registry) — advisory/automation-подсистема; orchestration подкатегория off-phase (см. §4.10) |
|
||||
|
||||
**Итого формализованных позиций:** 35 (29 активных по фазам + 5 off-phase + 1 заменённый PG MCP исторически) + 20 ruflo orchestration plugins = **55 total**. Полный перечень — §2–§5 (по фазам) + §4.5/§4.6/§4.7/§4.8/§4.9 (off-phase) + §4.10 (ruflo orchestration). Карта «когда что использовать» — §7. Что НЕ ставим и почему — §9.
|
||||
**Итого формализованных позиций:** 40 (29 активных по фазам + 10 off-phase + 1 заменённый PG MCP исторически) + 20 ruflo orchestration plugins = **60 total**. Полный перечень — §2–§5 (по фазам) + §4.5/§4.6/§4.7/§4.8/§4.9/§4.11/§4.12/§4.13/§4.14/§4.15 (off-phase) + §4.10 (ruflo orchestration). Карта «когда что использовать» — §7. Что НЕ ставим и почему — §9.
|
||||
|
||||
**Ключевой принцип фазирования:** не активируем фазу N+1, пока не закрыт триггер фазы N. Без `composer create-project` Boost не работает; без Vuetify-приложения Histoire бесполезен.
|
||||
|
||||
@@ -390,6 +390,56 @@
|
||||
|
||||
---
|
||||
|
||||
### 4.11. adr-kit — Architecture Decision Records (off-phase, architecture-tooling)
|
||||
|
||||
**adr-kit** (Claude Code plugin, marketplace `rvdbreemen/adr-kit`, plugin `adr-kit@rvdbreemen-adr-kit`, **v0.13.1**, MIT). 8 skills (`/adr-kit:{adr,init,judge,lint,migrate,setup,upgrade,install-hooks}`) + агент `adr-generator`. **0 Claude Code lifecycle-хуков** (verified `claude plugin details`).
|
||||
|
||||
**Роль:** инструмент **#36**, раздел A6 карты «Архитектура систем». ADR хранятся в `docs/adr/` (формат Nygard, 7 секций); решения с блоком `## Enforcement` проверяются `adr-judge` на staged-дифе.
|
||||
|
||||
**Категория:** off-phase, **architecture-tooling** — пятая off-phase подкатегория (отдельная от UI-пула UPM/21st, infrastructure claude-md-management, debug-runtime Sentry/Redis, orchestration ruflo). Не UI → **не** проходит R6.0/R6.1/R14 PSR_v1. Регулируется PSR_v1 R10.1 Блок 1.
|
||||
|
||||
**Интеграция (17.05.2026, ветка `feat/a6-architecture-tooling`):** `init`/`install-hooks` **не запускаются** — git-хук adr-kit конфликтовал бы с lefthook (конфликт-аудит AK1); `adr-judge` вендорен в `tools/adr-judge.py` и врезан как **lefthook pre-commit job 9** (`python -X utf8`, **без `--llm`** → declarative regex, 0 вызовов Claude API, 0 стоимости — AK6). `init` пишет в `CLAUDE.md` → не запускается (AK2 — §5 п.10); awareness-stub = строка реестра CLAUDE.md §3.3 #36.
|
||||
|
||||
### 4.12. mermaid-skill — C4 / architecture-диаграммы (off-phase, architecture-tooling)
|
||||
|
||||
**mermaid-skill** (`WH-2099/mermaid-skill`, MIT) — standalone-скил, **вендорен** в `.claude/skills/mermaid/` (`SKILL.md` + 30 references вкл. `c4.md`/`architecture.md` + LICENSE). Генерирует Mermaid-исходник 23 типов диаграмм; рендеринга не требует (`mmdc`/Chromium не нужны — Mermaid-блоки рендерит GitHub). 0 плагинов, 0 хуков, 0 marketplace.
|
||||
|
||||
**Роль:** инструмент **#37**, раздел A6 — визуализация архитектуры. C4-диаграммы → `docs/architecture/`.
|
||||
|
||||
**Категория:** off-phase, architecture-tooling. lefthook-jobs markdownlint+cspell исключают `.claude/skills/mermaid/**` (вендоренные сторонние `.md` — конфликт-аудит MK1; `.markdownlintignore` + `cspell.json` `ignorePaths` для glob-режима `npm run lint:md`/`spell`).
|
||||
|
||||
### 4.13. architecture-patterns — справочник паттернов (off-phase, architecture-tooling)
|
||||
|
||||
**architecture-patterns** (Claude Code plugin, marketplace `secondsky/claude-skills`, plugin `architecture-patterns@claude-skills`, **v3.3.1**, MIT). 1 skill, 0 агентов, **0 хуков** (verified `claude plugin details`).
|
||||
|
||||
**Роль:** инструмент **#38**, раздел A6 — playbook архитектурных паттернов (Clean / Hexagonal / layered architecture, Domain-Driven Design).
|
||||
|
||||
**Категория:** off-phase, architecture-tooling. Knowledge-only скил, без машинерии. Регулируется PSR_v1 R10.1 Блок 1.
|
||||
|
||||
### 4.14. Trail of Bits Skills — аудит безопасности (off-phase, audit-security)
|
||||
|
||||
**Trail of Bits Skills** (Claude Code marketplace `trailofbits/skills`, имя marketplace `trailofbits`, **CC-BY-SA-4.0**, репутабельный AppSec-вендор). Marketplace из 38 плагинов; в `enabledPlugins` включён курированный субсет **8 плагинов** под раздел D3 «Аудит и управление рисками»: `differential-review`, `audit-context-building`, `supply-chain-risk-auditor`, `insecure-defaults`, `sharp-edges`, `static-analysis`, `variant-analysis`, `agentic-actions-auditor`. Все 8 — skill/agent-плагины, **0 Claude Code lifecycle-хуков** (статически верифицировано по репо — ни у одного нет `hooks/`-папки).
|
||||
|
||||
**Роль:** инструмент **#39**, раздел D3 карты «Аудит и управление рисками» — глубокие on-demand аудит-кампании (security-аудит diff, supply-chain риск зависимостей, поиск вариантов уязвимостей по кодбазе).
|
||||
|
||||
**Категория:** off-phase, **audit-security** — шестая off-phase подкатегория (отдельная от UI-пула UPM/21st, infrastructure claude-md-management, debug-runtime Sentry/Redis, orchestration ruflo, architecture-tooling adr-kit/mermaid/architecture-patterns). Не UI → **не** проходит R6.0/R6.1/R14 PSR_v1. Регулируется PSR_v1 R10.1 Блок 1.
|
||||
|
||||
**Конфликт-аудит интеграции:** TB1 — `static-analysis` пересекается с Semgrep MCP (#25): граница — Semgrep MCP = inline SAST в рутине/CI, ToB = глубокие аудит-кампании on-demand. TB4 — CC-BY-SA-4.0 ShareAlike: остаётся marketplace-плагином (кэш вне репо, не вендорен) → ShareAlike-обязательство не триггерится. `fp-check` исключён из субсета — единственный из 9 рассмотренных с `hooks/`-папкой (lifecycle-хук); цепочка хуков проекта держится минимальной. План `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`.
|
||||
|
||||
### 4.15. Security Guidance — inline-предупреждения уязвимостей (off-phase, audit-security)
|
||||
|
||||
**Security Guidance** (Claude Code plugin, marketplace `anthropics/claude-plugins-official`, plugin `security-guidance@claude-plugins-official`, Anthropic Verified). Один PreToolUse `Write|Edit|MultiEdit`-хук — **warn-only**: печатает session-scoped предупреждение об уязвимом паттерне (8 категорий — command/shell injection, `eval`, XSS, pickle-десериализация и т.д.) перед применением правки, **не блокирует**.
|
||||
|
||||
**Роль:** инструмент **#40**, раздел D3 — real-time inline-напоминание об уязвимостях во время редактирования (дополняет on-demand аудит ToB/Semgrep).
|
||||
|
||||
**Категория:** off-phase, audit-security. Регулируется PSR_v1 R10.1 Блок 1.
|
||||
|
||||
**Конфликт-аудит интеграции:** SG1 — добавляет 5-й PreToolUse-хук поверх 4 существующих (skill-marker / skill-check / economy-state-guard в `~/.claude/settings.json` + CLAUDE.md-warn в проектном `.claude/settings.json`); warn-only (не `decision:block`), economy/ruflo-цепочка хуков не нарушается, +~34 мс/правку latency принято. `/security-review` (Anthropic built-in, customized в `.claude/commands/security-review.md` с проектным FP-фильтром — RLS/ПДн/economy-хуки) — D3 #2, не отдельный нумерованный слот (built-in, не installed tool).
|
||||
|
||||
**Категории off-phase tools (v2.4):** шесть подкатегорий — UI-пул (#31 UPM + #32 21st), infrastructure (#33 claude-md-management), debug-runtime (#34 Sentry + #35 Redis), orchestration (ruflo §4.10), **architecture-tooling (#36 adr-kit + #37 mermaid-skill + #38 architecture-patterns)**, **audit-security (#39 Trail of Bits Skills + #40 Security Guidance)**.
|
||||
|
||||
---
|
||||
|
||||
## 5. Фаза 3 — pre-production (+5 новых, итого 29 активных)
|
||||
|
||||
**Триггер:** ~спринт 12, перед публичным релизом MVP.
|
||||
@@ -675,9 +725,15 @@ Vuetify-тема — `liderraLight` и `liderraDark` — определена в
|
||||
| **v2.0** | 15.05.2026 | **Ruflo big-bang:** §0 +ruflo orchestration layer row (35 → 55: 35 формализованных позиций + 20 ruflo plugins); новая §4.10 «Orchestration layer (ruflo)». Major bump reflects architectural inversion — ruflo встаёт entry-point'ом уровня −1 над 8-уровневой иерархией Лидерры (см. CLAUDE.md §1 priority chain). ruflo v3.7.0-alpha.38+ + 20 plugins (`@claude-flow/*`, IPFS-registry — полный CID в §4.10), ~210 MCP tools, 60+ agents (Queen-led: Raft/Byzantine/Gossip), HNSW vector memory, SONA routing. Категория **orchestration** — четвёртая off-phase подкатегория (отдельная от UI-пула, infrastructure, debug-runtime). §4.9 +note «Категории off-phase tools (v2.0)». Runtime state 2026-05-15: scaffold installed + MCP server в `.mcp.json` (7-й MCP); daemon/swarm/memory НЕ активны — opt-in MCP tool, не enforcing overlord. Связано: spec/plan 2026-05-15 (commits `e55572e`/`18c4463`), Pravila v1.14 (`9c3057b`), PSR_v1 v3.0 (`d30cbeb`), CLAUDE.md v2.0 (`5df88a1`). v1.17 наследие — §4.8 Sentry MCP + §4.9 Redis MCP. |
|
||||
| **v2.1** | **15.05.2026** | §4.10 +абзац «Queen trigger»: триггер queen/королева → безусловный route через ruflo Queen (`hive-mind spawn --claude`), explicit hard-rule Pravila §14, enforcement-хук `tools/ruflo-queen-hook.mjs`; footer-колонтитул v2.1. Связано: spec/plan `docs/superpowers/{specs,plans}/2026-05-15-ruflo-queen-trigger-and-delegation*`, Pravila v1.15 / CLAUDE.md v2.1 / PSR_v1 v3.1. |
|
||||
| **v2.2** | **16.05.2026** | **§4.10 реколлаж:** ruflo переописан из «entry-point иерархии» в «advisory/automation-подсистему» (декларация приведена к рантайму: рой idle, 0 задач / 0 раундов консенсуса; Claude-сессии работают напрямую). Заголовок §4.10 изменён («Orchestration layer (ruflo) — entry-point иерархии» → «ruflo — advisory/automation-подсистема»); «Архитектурная роль» переписана; §0 table row обновлён; «Категории off-phase tools» обновлены; «Назначение» обновлено; шапка v2.1 → v2.2, дата 16.05.2026. Связано: Pravila v1.16, PSR_v1 v3.2, CLAUDE.md v2.2; spec `docs/superpowers/specs/2026-05-16-ruflo-hierarchy-factual-recollage-design.md`. |
|
||||
| **v2.3** | **17.05.2026** | **A6 architecture-tooling:** формализованы 3 инструмента раздела A6 карты «Архитектура систем» как новая пятая off-phase подкатегория «architecture-tooling» — **§4.11 #36 adr-kit** (ADR-решения, `adr-judge` lefthook job 9), **§4.12 #37 mermaid-skill** (C4-диаграммы, вендорен в `.claude/skills/mermaid/`), **§4.13 #38 architecture-patterns** (паттерны). §0 счётчик 35→38 формализованных позиций (55→58 total); §0 table row off-phase `+5`→`+8`; «Назначение» обновлено. Конфликт-аудит интеграции — AK1 (git-хук adr-kit не ставится, `adr-judge` через lefthook), AK2 (`init` не пишет CLAUDE.md), AK6 (`adr-judge` без `--llm` → 0 стоимости), MK1 (lefthook exclude вендоренного скила). Связано: PSR_v1 v3.2→v3.3 (R10.1 +3 строки), Pravila v1.16→v1.17 (§13.2 +architecture-tooling абзац), CLAUDE.md v2.2→v2.3 (§3.3 +#36-38). Через manual Edit (Tooling/PSR_v1/Pravila) + `/claude-md-management:claude-md-improver` (CLAUDE.md per §5 п.10). План `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md`. |
|
||||
| **v2.4** | 17.05.2026 | **D3 audit-security:** формализованы #39 Trail of Bits Skills (субсет 8 audit-плагинов, marketplace `trailofbits`, CC-BY-SA-4.0) + #40 Security Guidance (Anthropic warn-only PreToolUse-хук) как новая 6-я off-phase подкатегория «audit-security» — §4.14/§4.15. §0 счётчик 38→40 (58→60 total); off-phase row +8→+10. Конфликт-аудит — TB1 (граница с Semgrep MCP), TB4 (CC-BY-SA не триггерится — не вендорено), SG1 (5-й PreToolUse warn-хук). Связано: PSR_v1 v3.3→v3.4 (R10.1 Блок 1 +2 строки), Pravila v1.17→v1.18 (§13.2 +audit-security абзац), CLAUDE.md v2.3→v2.4 (§3.3 +#39-40). План `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`. |
|
||||
|
||||
---
|
||||
|
||||
*Прил. Н v2.4 от 17.05.2026 — D3 audit-security: формализованы #39 Trail of Bits Skills + #40 Security Guidance (§4.14/§4.15), новая 6-я off-phase подкатегория. 40 формализованных позиций (29 по фазам + 10 off-phase + 1 PG MCP) + 20 ruflo = 60 total. Связано: PSR_v1 v3.4, Pravila v1.18, CLAUDE.md v2.4.*
|
||||
|
||||
*Прил. Н v2.3 от 17.05.2026 — A6 architecture-tooling: формализованы #36 adr-kit + #37 mermaid-skill + #38 architecture-patterns (§4.11–4.13), новая пятая off-phase подкатегория. 38 формализованных позиций (29 по фазам + 8 off-phase + 1 PG MCP) + 20 ruflo = 58 total. Связано: PSR_v1 v3.3, Pravila v1.17, CLAUDE.md v2.3.*
|
||||
|
||||
*Прил. Н v2.2 от 16.05.2026 — §4.10 реколлаж: ruflo переописан из «entry-point иерархии» в «advisory/automation-подсистему» (декларация приведена к рантайму). Связано: Pravila v1.16, PSR_v1 v3.2, CLAUDE.md v2.2.*
|
||||
|
||||
*Прил. Н v2.1 от 15.05.2026 — Ruflo big-bang: добавлен orchestration layer (ruflo) как четвёртая off-phase подкатегория (§4.10). 55 позиций (35 формализованных позиций + 20 ruflo plugins). v2.1 — §4.10 +абзац «Queen trigger» (Pravila §14, хук ruflo-queen-hook.mjs).*
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# ADR-000 Adopt the ADR process and decision-store boundaries
|
||||
|
||||
## Status
|
||||
|
||||
Accepted, 2026-05-17.
|
||||
|
||||
## Context
|
||||
|
||||
Лидерра already records open product, business, and legal questions in the
|
||||
`docs/Открытые_вопросы_v8_3.md` registry (identifiers `Б-`, `CTO-`, `Ю-`,
|
||||
`Диз-`, `DO-`, `OPEN-`), each closed only by an explicit owner decision.
|
||||
|
||||
Integrating the `adr-kit` plugin adds a second store, `docs/adr/`, and the
|
||||
`mermaid` skill adds a third, `docs/architecture/`. Without explicit
|
||||
boundaries the three overlap, and a reader cannot tell where a given decision
|
||||
belongs. This ADR is the first written under the new process; it fixes the
|
||||
boundaries so ADR-001 onward have a clear home.
|
||||
|
||||
## Decision
|
||||
|
||||
- **`docs/adr/`** holds Architecture Decision Records — a *technical or
|
||||
architectural decision that has already been made*: stack choice, structural
|
||||
boundary, data-layer strategy, cross-cutting pattern. One file per decision,
|
||||
named `ADR-NNN-kebab-title.md`, following the seven-section adr-kit template.
|
||||
- **`docs/architecture/`** holds diagrams and models (C4, system context),
|
||||
generated with the `mermaid` skill. Visual, not normative; an ADR may
|
||||
reference a diagram there.
|
||||
- **The `docs/Открытые_вопросы` registry** continues to track *unresolved*
|
||||
product, business, and legal questions. It is not machine-enforced.
|
||||
- A registry question that resolves into a technical choice may graduate into
|
||||
an ADR; the registry entry is then closed by the normal owner process.
|
||||
- An ADR may carry a machine-readable `## Enforcement` block; `adr-judge`
|
||||
applies it to staged diffs at commit time (wired as a `lefthook` job).
|
||||
Enforcement rules target architecture-level constraints only — they do not
|
||||
duplicate `larastan`, `eslint`, or `squawk` rules.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Keep every decision in the Открытые_вопросы registry.** Rejected: the
|
||||
registry is designed for *open* questions awaiting a decision, not for
|
||||
recording *closed* technical choices, and it has no enforcement mechanism.
|
||||
- **Record architecture decisions inside `CLAUDE.md`.** Rejected: `CLAUDE.md`
|
||||
is the operational map, edited only through the `claude-md-management`
|
||||
plugin (`CLAUDE.md` §5 п.10), and would grow without bound if it absorbed
|
||||
every decision rationale.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- A reader can locate any decision by class: open question, closed decision,
|
||||
or diagram.
|
||||
- Architecture decisions become version-controlled, reviewable, and — where an
|
||||
Enforcement block exists — machine-checked at commit time.
|
||||
|
||||
**Negative:**
|
||||
|
||||
- Three stores must be kept disjoint; a contributor has to know the boundary
|
||||
rule above before adding to any of them.
|
||||
- An ADR superseded by a later decision must be marked
|
||||
`Superseded by ADR-MMM` rather than edited in place — process discipline the
|
||||
team has to follow.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- ADR-001 — first technical ADR written under this process.
|
||||
- ADR-002 — first data-layer ADR written under this process.
|
||||
|
||||
## References
|
||||
|
||||
- `docs/superpowers/plans/2026-05-17-a6-architecture-tooling-integration.md` —
|
||||
the integration plan that introduced `docs/adr/`.
|
||||
- `docs/Открытые_вопросы_v8_3.md` — the open-questions registry.
|
||||
- `.claude/adr-kit-guide.md` — the adr-kit authoring guide.
|
||||
@@ -0,0 +1,91 @@
|
||||
# ADR-001 Build the frontend on Vue 3 and Vuetify 3
|
||||
|
||||
## Status
|
||||
|
||||
Accepted, 2026-05-17.
|
||||
|
||||
## Context
|
||||
|
||||
Лидерра's user interface is built with Vue 3 and the Vuetify 3 component
|
||||
library. The project explicitly excludes Tailwind, Inertia, Livewire,
|
||||
Filament, Flux UI, Nova, Folio, Volt, and Wayfinder (`CLAUDE.md` §2 and §5
|
||||
п.2). `framer-motion` cannot be used at all — it is a React-only library and
|
||||
declares a `react` + `react-dom` peer dependency, so it crashes at runtime in
|
||||
a Vue application.
|
||||
|
||||
This decision predates the ADR process; it is recorded retroactively as the
|
||||
first technical ADR (see ADR-000) and given a machine-enforceable rule so the
|
||||
stack boundary cannot be crossed silently in a future change.
|
||||
|
||||
## Decision
|
||||
|
||||
All user-interface code is authored as Vue 3 single-file components rendered
|
||||
with Vuetify 3. No alternative UI framework, and no React-coupled runtime
|
||||
library, may be imported anywhere under `app/resources/js/`.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Inertia.js with a Vue adapter.** Rejected: Inertia couples routing and
|
||||
page rendering to the Laravel backend; the project specification calls for a
|
||||
plain Vue SPA with client-side routing (`vue-router`), already in place.
|
||||
- **Tailwind CSS for styling alongside Vuetify.** Rejected: Vuetify 3 already
|
||||
provides the design system and theme tokens; a second styling system would
|
||||
fragment the visual language and enlarge the bundle for no functional gain.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- Laravel Boost (via Roster auto-detection) serves only stack-relevant
|
||||
guidelines, because no excluded framework appears in `composer.lock` or
|
||||
`package.json`.
|
||||
- A single component library keeps the interface visually consistent and
|
||||
keeps contributor onboarding focused on one toolset.
|
||||
|
||||
**Negative:**
|
||||
|
||||
- The project is committed to Vuetify's component set and theming model; a
|
||||
Vuetify major-version upgrade is coordinated work rather than a drop-in.
|
||||
- A genuinely needed React-only library has no path in; such a need would
|
||||
require a new superseding ADR.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- ADR-000 — defines the ADR process under which this record was written.
|
||||
|
||||
## References
|
||||
|
||||
- `CLAUDE.md` §2 (stack) and §5 п.2 (excluded frameworks).
|
||||
- `liderra_v8_handoff/docs/DEVELOPER_HANDOFF.md` — Vuetify component handoff.
|
||||
- `docs/Tooling_v8_3.md` §9.2 — the `framer-motion` technical-block rationale.
|
||||
|
||||
## Enforcement
|
||||
|
||||
```json
|
||||
{
|
||||
"forbid_import": [
|
||||
{
|
||||
"pattern": "@inertiajs/",
|
||||
"path_glob": "app/resources/js/**",
|
||||
"message": "Inertia is out of stack — frontend is a Vue 3 + Vuetify 3 SPA (ADR-001)."
|
||||
},
|
||||
{
|
||||
"pattern": "from\\s+['\"]react(-dom)?['\"]",
|
||||
"path_glob": "app/resources/js/**",
|
||||
"message": "React is not used — the frontend is Vue 3 (ADR-001)."
|
||||
},
|
||||
{
|
||||
"pattern": "['\"]framer-motion['\"]",
|
||||
"path_glob": "app/resources/js/**",
|
||||
"message": "framer-motion: React-only peer dependency, crashes in Vue (ADR-001)."
|
||||
},
|
||||
{
|
||||
"pattern": "['\"]tailwindcss['\"]",
|
||||
"path_glob": "app/resources/js/**",
|
||||
"message": "Tailwind is out of stack — Vuetify 3 is the design system (ADR-001)."
|
||||
}
|
||||
],
|
||||
"require_pattern": [],
|
||||
"llm_judge": false
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,64 @@
|
||||
# ADR-002 Isolate tenants with PostgreSQL Row-Level Security
|
||||
|
||||
## Status
|
||||
|
||||
Accepted, 2026-05-17.
|
||||
|
||||
## Context
|
||||
|
||||
Лидерра is a multi-tenant SaaS CRM: many tenants share one PostgreSQL
|
||||
database. Tenant isolation is a security boundary — a cross-tenant data leak
|
||||
is a reportable personal-data incident under 152-ФЗ. The boundary has to hold
|
||||
even when application code has a bug.
|
||||
|
||||
This decision predates the ADR process and is recorded retroactively (see
|
||||
ADR-000) because it is the load-bearing data-layer decision the rest of the
|
||||
backend depends on.
|
||||
|
||||
## Decision
|
||||
|
||||
Every tenant-scoped table carries PostgreSQL Row-Level Security policies. The
|
||||
current tenant identifier is set per transaction via
|
||||
`SET LOCAL app.current_tenant_id` from the `SetTenantContext` middleware. The
|
||||
database defines five roles; `crm_supplier_worker` holds `BYPASSRLS`, so any
|
||||
queued job running as that role must filter `tenant_id` explicitly in its
|
||||
queries — Row-Level Security will not do it for that role.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Application-layer scoping only (global Eloquent query scopes).** Rejected:
|
||||
a single forgotten scope, a raw query, or a new developer unaware of the
|
||||
convention leaks cross-tenant rows; defense in depth requires the isolation
|
||||
to live in the database.
|
||||
- **One database per tenant.** Rejected: the operational cost grows linearly
|
||||
with tenant count — every migration runs N times, every backup multiplies —
|
||||
and the project specification targets shared-schema multi-tenancy.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- A cross-tenant read or write is blocked by the database even when the
|
||||
application layer has a bug, a missing scope, or a raw query.
|
||||
- The isolation rule is auditable in one place — the policy set in
|
||||
`db/schema.sql`.
|
||||
|
||||
**Negative:**
|
||||
|
||||
- Every new tenant-scoped table must ship an RLS policy plus a
|
||||
`db/CHANGELOG_schema.md` entry; omitting the policy is a silent security
|
||||
hole until it is caught.
|
||||
- `BYPASSRLS` roles such as `crm_supplier_worker` need careful review — code
|
||||
running as them carries the isolation responsibility the database otherwise
|
||||
enforces.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- ADR-000 — defines the ADR process under which this record was written.
|
||||
|
||||
## References
|
||||
|
||||
- `db/schema.sql` — the RLS policy set and role definitions.
|
||||
- `db/00_create_roles.sql` — the five production database roles.
|
||||
- `app/` Laravel middleware `SetTenantContext` — sets the per-request tenant.
|
||||
- `app/` feature test `RlsSmokeTest` — the RLS isolation smoke test.
|
||||
@@ -0,0 +1,91 @@
|
||||
# ADR-003 Adopt the D3 audit and risk-management toolset
|
||||
|
||||
## Status
|
||||
|
||||
Accepted, 2026-05-17.
|
||||
|
||||
## Context
|
||||
|
||||
The `D3 «Аудит и управление рисками»` section of the automation map
|
||||
(`docs/automation-graph.html`) had no tooling — `NODE_SECTION` tagged zero
|
||||
nodes `D3`. Security audits of the portal (#1, #2, #3) were run ad-hoc with no
|
||||
named toolset, and there was no standing store for closed decisions and their
|
||||
residual risks.
|
||||
|
||||
This ADR records the toolset chosen to populate the section. It is the audit
|
||||
counterpart of ADR-000, which adopted the ADR process itself.
|
||||
|
||||
## Decision
|
||||
|
||||
The D3 audit and risk-management toolset is:
|
||||
|
||||
- **`/security-review`** — the Anthropic command, customized at
|
||||
`.claude/commands/security-review.md` with a project false-positive filter
|
||||
(RLS, ПДн, economy hooks).
|
||||
- **Trail of Bits Skills** — eight plugins from the `trailofbits` marketplace
|
||||
(`differential-review`, `audit-context-building`, `supply-chain-risk-auditor`,
|
||||
`insecure-defaults`, `sharp-edges`, `static-analysis`, `variant-analysis`,
|
||||
`agentic-actions-auditor`) for deep, on-demand audit campaigns.
|
||||
- **Security Guidance** — the Anthropic warn-only `PreToolUse` hook plugin, for
|
||||
inline vulnerability reminders while editing.
|
||||
- **adr-kit** — reused, not re-installed. The decision and risk register is the
|
||||
set of ADRs in `docs/adr/`: each ADR's `## Consequences` records the residual
|
||||
risks of a decision, and the `docs/Открытые_вопросы` registry holds the
|
||||
unresolved ones. D3 adds no separate risk-register tool.
|
||||
- **Manual toolchain attack-surface procedure** — in `docs/audit/`, run on
|
||||
plugin or MCP-server changes; community auto-auditors are deferred
|
||||
(unverified provenance).
|
||||
- **`audit-portal`** — a project skill encoding the repeated 14-phase
|
||||
portal-audit method.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
- **Install a dedicated risk-register tool.** Rejected: an ADR `## Consequences`
|
||||
block plus the Открытые_вопросы registry already cover closed-decision risk
|
||||
and open risk respectively; a third store would violate the "one tool per
|
||||
task" rule (`CLAUDE.md` §5 п.6) and blur the boundaries fixed by ADR-000.
|
||||
- **Enable all 38 Trail of Bits marketplace plugins.** Rejected: most target
|
||||
blockchain, Android, C/C++, or macOS contexts irrelevant to a Laravel + Vue
|
||||
codebase; the eight-plugin subset matches the project's actual audit surface.
|
||||
`fp-check` was additionally dropped — it ships a lifecycle hook, and the
|
||||
project keeps its hook chain minimal.
|
||||
- **Install a community toolchain attack-surface auditor.** Deferred: the
|
||||
candidate plugins have unverified provenance, and installing an unvetted
|
||||
plugin to perform risk management would itself be a risk-management failure.
|
||||
A manual procedure is used until a vetted tool is found.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
|
||||
- The D3 map section is populated; portal audits have a named, repeatable
|
||||
toolset instead of ad-hoc invocation.
|
||||
- Closed decisions and their residual risks are version-controlled in
|
||||
`docs/adr/`; the boundary with the open-questions registry is fixed by
|
||||
ADR-000.
|
||||
|
||||
**Negative:**
|
||||
|
||||
- Trail of Bits and Security Guidance are third-party plugins — a bus-factor
|
||||
and supply-chain risk; mitigated by marketplace-cache pinning and re-checked
|
||||
on plugin upgrades.
|
||||
- Security Guidance adds one `PreToolUse` hook to a chain that already carries
|
||||
four — a small per-edit latency cost, accepted because the hook is warn-only.
|
||||
- The toolchain attack surface still depends on a manual procedure until a
|
||||
vetted auto-auditor exists.
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- ADR-000 — the ADR process and the `docs/adr/` to registry boundary this
|
||||
record relies on.
|
||||
- ADR-002 — tenant isolation via Row-Level Security; its rule drives the
|
||||
`/security-review` project false-positive filter.
|
||||
|
||||
## References
|
||||
|
||||
- `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md` —
|
||||
the D3 integration plan.
|
||||
- `.claude/commands/security-review.md` — the customized security-review
|
||||
command.
|
||||
- `docs/audit/` — the audit procedures and the toolchain attack-surface check.
|
||||
- `docs/Открытые_вопросы_v8_3.md` — the open-questions and open-risk registry.
|
||||
@@ -0,0 +1,30 @@
|
||||
# docs/architecture — system diagrams and models
|
||||
|
||||
C4 and architecture diagrams for Лидерра, written as Mermaid source. Generated
|
||||
and maintained with the `mermaid` skill (`.claude/skills/mermaid/`).
|
||||
|
||||
## Boundaries
|
||||
|
||||
- **`docs/architecture/`** (here) — *visual* models: C4 diagrams, system
|
||||
context, container and component views. Not normative.
|
||||
- **`docs/adr/`** — Architecture Decision Records: the *decisions* and their
|
||||
rationale. An ADR may reference a diagram here.
|
||||
- **`docs/Открытые_вопросы_v8_3.md`** — *open* product/business/legal questions.
|
||||
|
||||
See [ADR-000](../adr/ADR-000-adr-process.md) for the full boundary rule.
|
||||
|
||||
## Diagrams
|
||||
|
||||
| File | View | Mermaid type |
|
||||
|---|---|---|
|
||||
| [c4-context.md](c4-context.md) | System Context — Лидерра and its actors / external systems | `C4Context` |
|
||||
|
||||
## Regenerating
|
||||
|
||||
Mermaid blocks render natively on GitHub. To author or revise a diagram, invoke
|
||||
the `mermaid` skill and describe the view; it reads the matching syntax
|
||||
reference (`.claude/skills/mermaid/references/`) and emits a ` ```mermaid `
|
||||
block. No local renderer (`mmdc`) is required.
|
||||
|
||||
For pattern guidance when shaping a new subsystem, the `architecture-patterns`
|
||||
skill covers Clean / Hexagonal / layered architecture and Domain-Driven Design.
|
||||
@@ -0,0 +1,38 @@
|
||||
# C4 — System Context: Лидерра
|
||||
|
||||
The Level-1 (System Context) view: who uses Лидерра and which external systems
|
||||
it talks to. Authored with the `mermaid` skill; see [README](README.md) for the
|
||||
boundary rule and [ADR-000](../adr/ADR-000-adr-process.md).
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title Лидерра — System Context
|
||||
|
||||
Person(manager, "Менеджер арендатора", "Работает со сделками, лидами и отчётами")
|
||||
Person(saasAdmin, "SaaS-администратор", "Биллинг, инциденты, управление арендаторами")
|
||||
|
||||
System(liderra, "Лидерра CRM", "Multi-tenant CRM: лиды, сделки, биллинг, отчёты")
|
||||
|
||||
System_Ext(supplier, "crm.bp-gr.ru", "Поставщик лидов — источники B1/B2/B3")
|
||||
System_Ext(unisender, "Unisender Go", "SMTP-relay для исходящих писем")
|
||||
System_Ext(yandex360, "Yandex 360", "SSO администраторов")
|
||||
System_Ext(sentry, "Sentry", "Сбор runtime-ошибок, self-hosted в Yandex Cloud")
|
||||
System_Ext(jivosite, "JivoSite", "Виджет онлайн-поддержки")
|
||||
|
||||
Rel(manager, liderra, "Использует", "HTTPS")
|
||||
Rel(saasAdmin, liderra, "Администрирует", "HTTPS")
|
||||
Rel(liderra, supplier, "Импорт лидов и приём webhook", "HTTPS")
|
||||
Rel(liderra, unisender, "Отправка уведомлений", "SMTP")
|
||||
Rel(saasAdmin, yandex360, "Аутентификация", "OAuth")
|
||||
Rel(liderra, sentry, "Отправка ошибок", "HTTPS")
|
||||
Rel(manager, jivosite, "Обращается в поддержку", "Widget")
|
||||
|
||||
UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- This is the Context level only. Container (`C4Container`) and Component
|
||||
(`C4Component`) views can be added as separate files when a subsystem needs
|
||||
one — keep one view per file.
|
||||
- Stack and deployment facts behind this diagram: `CLAUDE.md` §2.
|
||||
@@ -0,0 +1,23 @@
|
||||
# docs/audit — audit procedures and artifacts
|
||||
|
||||
This directory is the home of the `D3 «Аудит и управление рисками»` section of
|
||||
the automation map (`docs/automation-graph.html`). It holds repeatable audit
|
||||
procedures and their artifacts.
|
||||
|
||||
## Toolset
|
||||
|
||||
- `/security-review` — the customized Anthropic security-review command
|
||||
(`.claude/commands/security-review.md`).
|
||||
- Trail of Bits Skills — the `trailofbits` marketplace audit plugins.
|
||||
- Security Guidance — the Anthropic warn-only inline-vulnerability hook.
|
||||
- `audit-portal` — the project skill encoding the 14-phase portal audit.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Closed decisions and their residual risks → `docs/adr/` (see ADR-003).
|
||||
- Open product, business, and legal risks → `docs/Открытые_вопросы_v8_3.md`.
|
||||
|
||||
## Procedures
|
||||
|
||||
- `toolchain-attack-surface.md` — manual audit of the Claude Code plugin and
|
||||
MCP-server attack surface.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Toolchain attack-surface audit (manual procedure)
|
||||
|
||||
Part of the `D3 «Аудит и управление рисками»` section. Run this procedure
|
||||
quarterly, and after any new Claude Code plugin or MCP server is added.
|
||||
|
||||
Motivation: the post-ruflo toolchain is large — about 20 ruflo plugins, ~210
|
||||
MCP tools, and seven MCP servers in `.mcp.json` — and 2026 disclosures (npm
|
||||
`postinstall` MCP-URL rewriting; the ClaudeBleed script-injection class) make
|
||||
the toolchain itself a standing attack surface.
|
||||
|
||||
## 1. MCP servers
|
||||
|
||||
- Review every server in `.mcp.json` — `command`, `args`, `env`. Flag any
|
||||
non-pinned `npx` package and any server reachable over the network.
|
||||
- Confirm no MCP server URL was rewritten by a dependency `postinstall` script.
|
||||
|
||||
## 2. Plugins
|
||||
|
||||
- List `enabledPlugins` in `~/.claude/settings.json`. For each: source repo,
|
||||
license, last commit, and the hooks it contributes.
|
||||
- Flag any plugin that registers a `PreToolUse` hook with `decision: block`.
|
||||
|
||||
## 3. Hooks
|
||||
|
||||
- Diff the `hooks` blocks of `.claude/settings.json` and
|
||||
`~/.claude/settings.json` against the last audited snapshot. Investigate any
|
||||
unexplained change.
|
||||
|
||||
## 4. Permissions
|
||||
|
||||
- Review `permissions.allow` and `permissions.deny` — no broadened wildcard and
|
||||
no new unscoped `Bash(*)` beyond what is already recorded.
|
||||
|
||||
## 5. Secrets
|
||||
|
||||
- Run `gitleaks` over the full history; confirm no token sits in a gitignored
|
||||
cache file.
|
||||
|
||||
## Outcome
|
||||
|
||||
Record findings as P0–P3 items in `docs/Открытые_вопросы_v8_3.md` (via the
|
||||
`q-item-add` skill), or as an ADR in `docs/adr/` if a tooling decision results.
|
||||
|
||||
## Community auto-auditors — evaluated, deferred (2026-05-17)
|
||||
|
||||
The D3 integration evaluated two community plugins that would automate this
|
||||
procedure. Both were deferred:
|
||||
|
||||
- **Claude Code Canary** (`geoffrey-young/anthropic-hackathon-2026`) — a
|
||||
one-off hackathon entry (9 commits, 2 stars); the author explicitly
|
||||
disclaims production use. It registers three broad lifecycle hooks
|
||||
(SessionStart, PreToolUse, PostToolUse) and its design relies on the same
|
||||
stderr-injection class it defends against. Rejected — unfit for a global
|
||||
config and a heavy collision with the project hook chain.
|
||||
- **Plugin Security Auditor** (an mcpmarket aggregator listing) — source
|
||||
repository, author, and license could not be verified. Installing an
|
||||
unverifiable plugin to perform security auditing is itself a risk-management
|
||||
failure. Deferred until a vetted source is found.
|
||||
|
||||
Until a vetted auto-auditor exists, this manual procedure is the D3 control for
|
||||
toolchain attack-surface risk.
|
||||
+105
-3
@@ -233,7 +233,7 @@ const NODES = [
|
||||
{ id: 'psr_v1', label: 'PSR_v1 v3.2', group: 'rules', size: 32, ring: 1, ...pos(1, 150) },
|
||||
{ id: 'tooling', label: 'Tooling v2.2', group: 'rules', size: 30, ring: 1, ...pos(1, 270) },
|
||||
|
||||
// ── ПЛАГИНЫ (9) ── второе кольцо ───────────────
|
||||
// ── ПЛАГИНЫ (13) ── второе кольцо ──────────────
|
||||
{ id: 'superpowers', label: 'Superpowers v5.1', group: 'plugins', size: 30, ring: 2, ...pos(2, 45) },
|
||||
{ id: 'fd_plugin', label: 'Frontend Design', group: 'plugins', size: 26, ring: 2, ...pos(2, 135) },
|
||||
{ id: 'upm', label: 'UI UX Pro Max', group: 'plugins', size: 22, ring: 2, ...pos(2, 165) },
|
||||
@@ -243,6 +243,12 @@ const NODES = [
|
||||
{ id: 'claude_setup', label: 'claude-code-setup', group: 'plugins', size: 22, ring: 2, ...pos(2, 90) },
|
||||
{ id: 'plugin_dev', label: 'plugin-dev', group: 'plugins', size: 22, ring: 2, ...pos(2, 290) },
|
||||
{ id: 'context7', label: 'context7 (docs MCP)', group: 'plugins', size: 20, ring: 2, ...pos(2, 315) },
|
||||
// A6 architecture-tooling (17.05.2026) — 2 плагина раздела «Архитектура систем»
|
||||
{ id: 'adr_kit', label: 'adr-kit', group: 'plugins', size: 22, ring: 2, ...pos(2, 240) },
|
||||
{ id: 'arch_patterns', label: 'architecture-patterns',group: 'plugins', size: 20, ring: 2, ...pos(2, 250) },
|
||||
// D3 audit-security (17.05.2026) — 2 плагина раздела «Аудит и управление рисками»
|
||||
{ id: 'tob_skills', label: 'Trail of Bits\nskills', group: 'plugins', size: 22, ring: 2, ...pos(2, 330) },
|
||||
{ id: 'sec_guidance', label: 'Security\nGuidance', group: 'plugins', size: 20, ring: 2, ...pos(2, 345) },
|
||||
|
||||
// ── СКИЛЫ SUPERPOWERS (14) — N sector (0–90) ────
|
||||
{ id: 'sk_brainstorm', label: 'brainstorming', group: 'skills_sp', size: 18, ring: 3, ...pos(3, 5) },
|
||||
@@ -260,10 +266,15 @@ const NODES = [
|
||||
{ id: 'sk_wskills', label: 'writing-skills', group: 'skills_sp', size: 16, ring: 3, ...pos(3, 85) },
|
||||
{ id: 'sk_elements', label: 'elements-of-style', group: 'skills_sp', size: 16, ring: 3, ...pos(3, 92) },
|
||||
|
||||
// ── СКИЛЫ ПРОЕКТА (3) — W sector (RLS) ─────────
|
||||
// ── СКИЛЫ ПРОЕКТА (6) — W sector (RLS/arch/audit) ────
|
||||
{ id: 'sk_rls', label: 'rls-check', group: 'skills_proj', size: 20, ring: 3, ...pos(3, 305) },
|
||||
{ id: 'sk_qitem', label: 'q-item-add', group: 'skills_proj', size: 20, ring: 3, ...pos(3, 220) },
|
||||
{ id: 'sk_regression', label: 'regression', group: 'skills_proj', size: 20, ring: 3, ...pos(3, 260) },
|
||||
// A6 architecture-tooling (17.05.2026) — вендоренный скил диаграмм
|
||||
{ id: 'mermaid_skill', label: 'mermaid (skill)', group: 'skills_proj', size: 18, ring: 3, ...pos(3, 280) },
|
||||
// D3 audit-security (17.05.2026) — скилы раздела «Аудит и управление рисками»
|
||||
{ id: 'sk_security_review', label: 'security-review', group: 'skills_proj', size: 20, ring: 3, ...pos(3, 315) },
|
||||
{ id: 'sk_audit_portal', label: 'audit-portal', group: 'skills_proj', size: 20, ring: 3, ...pos(3, 325) },
|
||||
|
||||
// ── ХУКИ (12) — S+infra + E (economy/skill) ───
|
||||
{ id: 'hk_session', label: 'SessionStart:\ncontext-inject', group: 'hooks', size: 24, ring: 4, ...pos(4, 100) },
|
||||
@@ -491,6 +502,21 @@ const EDGES = [
|
||||
E('hk_ruflo_queen', 'ruflo_queen', '§14: маршрут\nqueen-задач'),
|
||||
E('sk_regression', 'ag_pest', 'передаёт разбор\nпадений Pest --parallel'),
|
||||
|
||||
// ── A6 ARCHITECTURE-TOOLING 17.05.2026 — связи новых узлов ──
|
||||
E('psr_v1', 'adr_kit', 'R10.1 блок 1:\narchitecture-tooling'),
|
||||
E('psr_v1', 'arch_patterns', 'R10.1 блок 1:\narchitecture-tooling'),
|
||||
E('tooling', 'mermaid_skill', '§4.12: реестр\n(вендоренный скил)'),
|
||||
|
||||
// ── D3 AUDIT-SECURITY 17.05.2026 — связи новых узлов ──
|
||||
E('psr_v1', 'tob_skills', 'R10.1 блок 1:\naudit-security'),
|
||||
E('psr_v1', 'sec_guidance', 'R10.1 блок 1:\naudit-security'),
|
||||
E('tooling', 'tob_skills', '§4.14 #39 — реестр'),
|
||||
E('tooling', 'sec_guidance', '§4.15 #40 — реестр'),
|
||||
E('sk_audit_portal', 'sk_security_review', 'оркеструет\nкак фазу аудита'),
|
||||
E('sk_audit_portal', 'tob_skills', 'оркеструет\nглубокие кампании'),
|
||||
E('sk_audit_portal', 'sk_regression', 'использует\nна фазе тестов'),
|
||||
CONFLICT('tob_skills', 'mcp_semgrep', 'TB1: граница разграничена — Semgrep = inline SAST, Trail of Bits = глубокие on-demand аудит-кампании. Параллельное использование разрешено при разных сценариях.', 'GREEN'),
|
||||
|
||||
// ══════════════════════════════════════════════════
|
||||
// КОНФЛИКТЫ — 3-color classification (iter2 §4)
|
||||
// 🔴 не закрыт правилом / ⚫ возник на практике / 🟢 закрыт правилом
|
||||
@@ -669,6 +695,67 @@ const NODE_DETAILS = {
|
||||
]
|
||||
),
|
||||
|
||||
// ── A6 ARCHITECTURE-TOOLING (17.05.2026) ─────────
|
||||
adr_kit: nd(
|
||||
'Плагин ADR (Architecture Decision Records) — фиксация закрытых архитектурных решений в docs/adr/ (формат Nygard, 7 секций) + декларативный энфорсер adr-judge.',
|
||||
'При фиксации архитектурного решения — стек, паттерн, граница слоёв; ADR-NNN в docs/adr/. Открытые вопросы — не сюда, они в реестре Открытые_вопросы.',
|
||||
'Правило PSR_v1 R10.1 блок 1 (architecture-tooling, off-phase). adr-judge врезан в lefthook pre-commit job 9 — декларативный regex без --llm, 0 вызовов Claude API. init/install-hooks НЕ запускаются (конфликт-аудит AK1/AK2). Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.11, CLAUDE.md §3.3 #36.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: architecture-tooling' }, { name: 'Tooling', cond: '§4.11 #36 — реестр' }],
|
||||
[{ name: 'lefthook job 9 (adr-judge)', cond: 'врезан как pre-commit проверка Enforcement-блоков' }],
|
||||
[{ name: 'docs/adr/', cond: 'хранилище ADR — ADR-000/001/002' }]
|
||||
),
|
||||
arch_patterns: nd(
|
||||
'Скил-плагин — справочник архитектурных паттернов (Clean / Hexagonal / layered architecture, Domain-Driven Design). Knowledge-only, не решатель.',
|
||||
'При архитектурном вопросе «какой паттерн подходит» — playbook паттернов; код не генерирует, файлы не правит.',
|
||||
'Правило PSR_v1 R10.1 блок 1 (architecture-tooling, off-phase). Knowledge-only — без хуков и машинерии. Не UI → вне фильтров R6.0/R6.1/R14. Tooling §4.13, CLAUDE.md §3.3 #38.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: architecture-tooling' }, { name: 'Tooling', cond: '§4.13 #38 — реестр' }],
|
||||
[],
|
||||
[]
|
||||
),
|
||||
mermaid_skill: nd(
|
||||
'Вендоренный standalone-скил (.claude/skills/mermaid/) — генерирует исходник Mermaid-диаграмм (23 типа, включая C4/architecture). Рендера mmdc/Chromium не требует.',
|
||||
'При построении C4/архитектурной диаграммы — результат в docs/architecture/ (Mermaid рендерится на GitHub нативно).',
|
||||
'Вендорен — не плагин, не marketplace, не подсистема (конфликт-аудит CC1: иммунен к потере апстрима). lefthook markdownlint+cspell исключают .claude/skills/mermaid/** (MK1). Tooling §4.12, CLAUDE.md §3.3 #37.',
|
||||
[{ name: 'Tooling', cond: '§4.12 #37 — реестр' }],
|
||||
[],
|
||||
[{ name: 'docs/architecture/', cond: 'C4-диаграммы → c4-context.md' }]
|
||||
),
|
||||
|
||||
// ── D3 AUDIT-SECURITY (17.05.2026) ───────────────
|
||||
tob_skills: nd(
|
||||
'Marketplace-плагин Trail of Bits (`trailofbits/skills`) — курированный субсет 8 audit-плагинов: `differential-review`, `audit-context-building`, `supply-chain-risk-auditor`, `insecure-defaults`, `sharp-edges`, `static-analysis`, `variant-analysis`, `agentic-actions-auditor`. Глубокие on-demand аудит-кампании. Раздел D3. Tooling #39, off-phase audit-security.',
|
||||
'При глубоком аудите безопасности: diff-ревью, supply-chain риски зависимостей, поиск вариантов уязвимостей, статический анализ (SARIF). Глубокие кампании — не inline SAST.',
|
||||
'Правило PSR_v1 R10.1 блок 1 (audit-security, off-phase). Граница TB1: Semgrep MCP (#25) = inline SAST, ToB = глубокие on-demand кампании. CC-BY-SA-4.0 — не вендорено, лицензионный триггер TB4 не применяется. Не UI → вне R6.0/R6.1/R14. Tooling §4.14, CLAUDE.md §3.3 #39.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: audit-security' }, { name: 'Tooling', cond: '§4.14 #39 — реестр' }],
|
||||
[],
|
||||
[{ name: 'MCP: semgrep', cond: 'TB1: граница — Semgrep inline SAST, ToB глубокие кампании' }],
|
||||
[{ name: 'MCP: semgrep', desc: 'TB1: граница разграничена регламентом — Semgrep = inline SAST в процессе работы, Trail of Bits = глубокие аудит-кампании по запросу. Параллельное использование разрешено при разных сценариях.', type: 'GREEN' }]
|
||||
),
|
||||
sec_guidance: nd(
|
||||
'Anthropic-плагин (`security-guidance@claude-plugins-official`, Anthropic Verified) — один warn-only PreToolUse-хук, inline-предупреждения уязвимостей при правке кода (8 категорий). Не блокирует, только предупреждает. Раздел D3. Tooling #40.',
|
||||
'Активен автоматически при каждом Write/Edit/MultiEdit — печатает предупреждение об уязвимом паттерне перед правкой файла.',
|
||||
'Правило PSR_v1 R10.1 блок 1 (audit-security, off-phase). SG1: 5-й PreToolUse-хук, +~34 мс/правку, economy/ruflo-цепочка не нарушается. Warn-only — не блокирует. Не UI → вне R6.0/R6.1/R14. Tooling §4.15, CLAUDE.md §3.3 #40.',
|
||||
[{ name: 'PSR_v1', cond: 'R10.1 блок 1: audit-security' }, { name: 'Tooling', cond: '§4.15 #40 — реестр' }],
|
||||
[],
|
||||
[{ name: 'скил security-review', cond: 'оба — D3 audit-security; sec_guidance inline, sk_security_review ручной' }]
|
||||
),
|
||||
sk_security_review: nd(
|
||||
'Кастомизированная Anthropic-команда `/security-review` — копия в `.claude/commands/security-review.md` с проектным FP-фильтром (RLS / ПДн / economy-хуки). Раздел D3. D3 #2.',
|
||||
'При ручном security-review кода или PR — запуск `/security-review` с проектным контекстом для фильтрации ложных срабатываний.',
|
||||
'Проектный FP-фильтр: RLS-политики / ПДн-поля / economy-хуки — не считаются уязвимостями. Раздел D3.',
|
||||
[{ name: 'Tooling', cond: 'D3 #2 — проектная кастомизация' }],
|
||||
[],
|
||||
[{ name: 'sec_guidance', cond: 'оба D3 audit-security; sec_guidance — inline warn, security-review — ручной аудит' }, { name: 'audit-portal', cond: 'sk_audit_portal оркеструет security-review как фазу аудита' }]
|
||||
),
|
||||
sk_audit_portal: nd(
|
||||
'Проектный скил — 14-фазный портальный аудит (дистилляция аудитов #1/#2/#3). Покрывает: PHP/Vue статический анализ, RLS, a11y, coverage, зависимости, secrets. Раздел D3.',
|
||||
'При проведении полного аудита портала — запуск 14 фаз последовательно с документированием находок P0/P1/P2/P3.',
|
||||
'Раздел D3. Оркеструет несколько инструментов: sk_security_review, Trail of Bits Skills, regression-скил. Находки фиксируются в docs/superpowers/audits/.',
|
||||
[{ name: 'Tooling', cond: 'D3 — проектный аудит-скил' }],
|
||||
[{ name: 'скил security-review', cond: 'оркеструет как security-фазу аудита' }, { name: 'Trail of Bits Skills', cond: 'оркеструет для глубоких кампаний' }, { name: 'скил regression', cond: 'использует на фазе тестов' }],
|
||||
[{ name: 'скил security-review', cond: 'пара в D3 audit-security' }]
|
||||
),
|
||||
|
||||
// ── СКИЛЫ SUPERPOWERS ────────────────────────────
|
||||
sk_brainstorm: nd(
|
||||
'Продумывает задачу вместе с заказчиком, формулирует варианты A/B/C и согласует дизайн до написания кода.',
|
||||
@@ -1741,6 +1828,17 @@ const NODE_META = {
|
||||
mem_sprint1: { since: '15.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
mem_sprint2: { since: '15.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
mem_sprint3: { since: '16.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
|
||||
// ── A6 ARCHITECTURE-TOOLING 17.05.2026 ──
|
||||
adr_kit: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
arch_patterns: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
mermaid_skill: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
|
||||
// ── D3 AUDIT-SECURITY 17.05.2026 ──
|
||||
tob_skills: { since: '17.05.2026', changed: '—', uses: null, usesSrc: '—' },
|
||||
sec_guidance: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'хук' },
|
||||
sk_security_review: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'скил' },
|
||||
sk_audit_portal: { since: '17.05.2026', changed: '—', uses: null, usesSrc: 'скил' },
|
||||
};
|
||||
|
||||
// Явные парные дубли (Фича 3) — попадают в кнопку «⧉ Дубли».
|
||||
@@ -1823,7 +1921,7 @@ const SECTIONS = [
|
||||
{ id: 'E7', bucket: 'E', label: 'Исследования' },
|
||||
{ id: 'E8', bucket: 'E', label: 'Самообучение Claude' },
|
||||
];
|
||||
// Узел -> раздел. Покрывает все 83 узла карты.
|
||||
// Узел -> раздел. Покрывает все 110 узлов карты.
|
||||
const NODE_SECTION = {
|
||||
// правила (4)
|
||||
pravila: 'E1', claude_md: 'E1', psr_v1: 'E1', tooling: 'E1',
|
||||
@@ -1865,6 +1963,10 @@ const NODE_SECTION = {
|
||||
sk_regression: 'A5',
|
||||
mem_audit_b: 'E4', mem_audit_c: 'E4', mem_suppliercrm: 'E4', mem_audit12: 'E4',
|
||||
mem_audit14: 'E4', mem_sprint1: 'E4', mem_sprint2: 'E4', mem_sprint3: 'E4',
|
||||
// A6 architecture-tooling 17.05.2026 — раздел «Архитектура систем» наполнен
|
||||
adr_kit: 'A6', arch_patterns: 'A6', mermaid_skill: 'A6',
|
||||
// D3 audit-security 17.05.2026 — раздел «Аудит и управление рисками» наполнен
|
||||
tob_skills: 'D3', sec_guidance: 'D3', sk_security_review: 'D3', sk_audit_portal: 'D3',
|
||||
};
|
||||
// Производные индексы для рендера панели и Паспорта.
|
||||
const SECTION_BY_ID = new Map(SECTIONS.map(s => [s.id, s]));
|
||||
|
||||
@@ -0,0 +1,659 @@
|
||||
# A6 Architecture Tooling Integration 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:** Install three tools from the A6 top-5 — #1 `adr-kit` (architecture decisions + enforcement), #3 `mermaid-skill` (C4 / architecture diagrams), #4 `architecture-patterns` (patterns playbook) — and integrate them into Лидерра so the `A6 «Архитектура систем»` map section becomes a populated, working playbook.
|
||||
|
||||
**Architecture:** Three tools, three install modes. **adr-kit** = marketplace plugin; its git pre-commit hook and `init` CLAUDE.md write are bypassed (lefthook job 9 + manual bootstrap). **architecture-patterns** = marketplace plugin, knowledge-only skill, zero machinery. **mermaid-skill** = standalone skill vendored into `.claude/skills/mermaid/` (no plugin, no marketplace, no hooks). All three are non-UI → architecture-tooling category, outside the PSR_v1 UI-pool. ADRs live in `docs/adr/`; C4 diagrams in `docs/architecture/`.
|
||||
|
||||
**Tech Stack:** adr-kit v0.13.1 (marketplace `rvdbreemen/adr-kit`, MIT); mermaid-skill (`WH-2099/mermaid-skill`, standalone skill, MIT); architecture-patterns (`secondsky/claude-skills`, marketplace plugin, MIT); lefthook 2.x; project normative docs; `docs/automation-graph.html` (vis.js).
|
||||
|
||||
---
|
||||
|
||||
## Tool Identity (verified 2026-05-17)
|
||||
|
||||
| # | Tool | Install mode | Source | Hooks? |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **adr-kit** v0.13.1 | Marketplace plugin — `rvdbreemen/adr-kit` → `adr-kit@rvdbreemen-adr-kit` | GitHub `rvdbreemen/adr-kit`, MIT | No CC lifecycle hooks; ships a git pre-commit hook (not used) |
|
||||
| 3 | **mermaid-skill** | Standalone skill — vendored copy into `.claude/skills/mermaid/` | GitHub `WH-2099/mermaid-skill`, MIT | None (skill files only) |
|
||||
| 4 | **architecture-patterns** | Marketplace plugin — `secondsky/claude-skills` → `architecture-patterns@claude-skills` | GitHub `secondsky/claude-skills`, MIT | None disclosed (verify on install) |
|
||||
|
||||
Dropped from the top-5 by user: #2 `wshobson/agents` (agent sprawl, UI-pool overlap), #5 Anthropic baseline (narrow).
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions & Conflict Audit
|
||||
|
||||
Pattern follows the K1–K8 audit used for claude-mem. Verified against the three repos, project `.claude/settings.json`, `~/.claude/settings.json`, `lefthook.yml`, `cspell.json`, `.markdownlintignore`.
|
||||
|
||||
| # | Tool | Sev | Conflict | Resolution (locked) |
|
||||
|---|---|---|---|---|
|
||||
| AK1 | adr-kit | 🔴 | `/adr-kit:install-hooks`/`init` writes `.git/hooks/pre-commit`; lefthook owns that file → mutual clobber. | Never run `install-hooks`. Vendor `adr-judge`, wire as **lefthook pre-commit job 9** (Task 5). |
|
||||
| AK2 | adr-kit | 🔴 | `/adr-kit:init` modifies root `CLAUDE.md` — violates CLAUDE.md §5 п.10 (edits only via `claude-md-management`). | Never run full `init`. Bootstrap manually (Task 4). The CLAUDE.md stub = the `§3.3 #36` row via `claude-md-management` (Task 7). |
|
||||
| AK3 | adr-kit | 🟡 | `docs/adr/` overlaps the `Открытые_вопросы` registry. | Boundary in ADR-000 (Task 4): ADR = *closed technical/architecture* decision; registry = *open* product/business/legal questions. |
|
||||
| AK4 | adr-kit | 🟡 | `adr-judge` `## Enforcement` rules could duplicate larastan/eslint/squawk. | Enforcement = architecture-level rules only ("no Inertia/Tailwind/React imports"); never re-encode lint/style rules. |
|
||||
| AK5 | adr-kit | 🟢 | Lifecycle-hook collision with the 6 economy + 2 ruflo + skill-discipline hooks. | None — adr-kit registers zero CC lifecycle hooks. Re-verified Task 2 Step 5. |
|
||||
| AK6 | adr-kit | 🟢 | `adr-judge` cost vs economy. | None — declarative regex, no LLM. No ADR will be flagged `llm_judge: true`. |
|
||||
| AK7 | adr-kit | 🟡 | Plugin #10, Tooling #36 — unregistered = PSR_v1 R0.2/R10 violation on use. | Register in 4 normative homes (Task 7). |
|
||||
| AK8 | adr-kit | 🟢 | `/adr-kit:judge` vs `superpowers:requesting-code-review` (§12). | None — different scope (ADR-compliance vs general review); §12.2 map unchanged. |
|
||||
| MK1 | mermaid | 🟡 | Vendored `.claude/skills/mermaid/**/*.md` (third-party English refs) gets caught by the cspell + markdownlint pre-commit jobs. | Add `.claude/skills/mermaid/**` to `cspell.json` `ignorePaths` and `.markdownlintignore` (Task 3). Project's own skills stay linted. |
|
||||
| MK2 | mermaid | 🟢 | Plugin/marketplace/hook footprint. | None — vendored skill = SKILL.md + references only. No `enabledPlugins` entry, no hooks. Re-verify Task 3 Step 4. |
|
||||
| MK3 | mermaid | 🟢 | Overlap with the vis.js automation-graph. | None — different purpose: vis.js = the live "карта"; Mermaid = static C4/architecture diagrams in docs. Additive. |
|
||||
| AP1 | arch-patterns | 🟢 | Hook/overlap footprint. | Knowledge-only skill; no overlap with Superpowers (no architecture skill) or Frontend Design (UI only). Verify hooks unchanged Task 2 Step 5. |
|
||||
| AP2 | arch-patterns | 🟡 | Plugin #11, Tooling #38 — registration. | Register in 4 normative homes (Task 7). |
|
||||
| CC1 | all 3 | 🟡 | Bus-factor — three small community projects (adr-kit pre-1.0; mermaid/arch-patterns single-maintainer). | mermaid is **vendored** (immune to upstream loss). adr-kit + arch-patterns are marketplace-cache-pinned. Note in Tooling §4.x as a known risk. No alpha-substrate spike needed — none has a known-broken core (unlike ruflo K7). |
|
||||
|
||||
**Severable scope:** Task 5 (lefthook enforcement) is the only part touching the commit pipeline. **Knowledge-only** integration = skip Task 5 entirely — Tasks 1-4, 6-9 still deliver a working ADR store + diagram skill + patterns skill + populated map. Primary path below = full integration.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Created / Modified | Responsibility |
|
||||
|---|---|---|
|
||||
| `docs/adr/` | Create dir | ADR document store, `ADR-XXX-kebab-title.md` |
|
||||
| `docs/adr/ADR-000-adr-process.md` | Create | Meta-ADR: ADR vs registry vs `docs/architecture/` boundaries |
|
||||
| `docs/adr/ADR-001-frontend-stack-vue-vuetify.md` | Create | Seed ADR with an `Enforcement` block (testable rule for Task 5) |
|
||||
| `docs/adr/ADR-002-multitenancy-postgres-rls.md` | Create | Seed ADR, documentation-only |
|
||||
| `docs/architecture/` | Create dir | C4 / system diagrams (Mermaid), models |
|
||||
| `docs/architecture/README.md` | Create | Index — defines `docs/architecture/` purpose, links A6 |
|
||||
| `docs/architecture/c4-context.md` | Create | Seed C4 Context diagram of Лидерра (proof + initial A6 content) |
|
||||
| `.claude/skills/mermaid/` | Create (vendored) | The mermaid-skill — `SKILL.md` + `references/` |
|
||||
| `.claude/adr-kit-guide.md` | Create | adr-kit canonical guide (manual copy from plugin cache) |
|
||||
| `tools/adr-judge.mjs` | Create (vendored) | Project-local `adr-judge` runner — decouples lefthook from plugin cache |
|
||||
| `lefthook.yml` | Modify | Add pre-commit job 9 `adr-judge` |
|
||||
| `cspell.json` | Modify | `ignorePaths` += `.claude/skills/mermaid/**` |
|
||||
| `.markdownlintignore` | Modify | += `.claude/skills/mermaid/` |
|
||||
| `cspell-words.txt` | Modify (conditional) | New ADR/architecture vocabulary |
|
||||
| `~/.claude/settings.json` | Modify | `enabledPlugins` += adr-kit + architecture-patterns; `extraKnownMarketplaces` += 2 |
|
||||
| `docs/Tooling_v8_3.md` | Modify | Прил. Н — new §4.11/§4.12/§4.13 + §0 counter 35 → 38 |
|
||||
| `docs/Plugin_stack_rules_v1.md` | Modify | R10.1 — 3 new rows |
|
||||
| `docs/Pravila_raboty_Claude_v1_1.md` | Modify | §13.2 — architecture-tooling note |
|
||||
| `CLAUDE.md` | Modify (**via claude-md-management only**) | §3 title count, §1 row 2b count, §3.3 rows #36/#37/#38 |
|
||||
| `docs/CHANGELOG_claude_md.md` | Modify | CLAUDE.md version-bump entry |
|
||||
| `docs/automation-graph.html` | Modify | 3 new nodes (`adr_kit`, `mermaid_skill`, `arch_patterns`) → `NODE_SECTION` A6 |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Pre-flight — baseline, snapshot, fact-check
|
||||
|
||||
**Files:** none modified (read-only)
|
||||
|
||||
- [ ] **Step 1: Confirm clean tree and HEAD**
|
||||
|
||||
```bash
|
||||
cd "c:/моя/проекты/портал crm/Документация"
|
||||
git status --short
|
||||
git rev-parse --short HEAD
|
||||
git branch --show-current
|
||||
```
|
||||
|
||||
Expected: working tree shows only known untracked items; record HEAD SHA as the regression baseline.
|
||||
|
||||
- [ ] **Step 2: Snapshot the git-hook owner**
|
||||
|
||||
```bash
|
||||
git config core.hooksPath
|
||||
head -5 .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
Expected: pre-commit references `lefthook`. Task 5 must NOT change `.git/hooks/`.
|
||||
|
||||
- [ ] **Step 3: Baseline the pre-commit chain**
|
||||
|
||||
```bash
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: all 8 jobs (gitleaks, markdownlint, cspell, stylelint, pint, larastan, squawk, eslint-vue) green / "no staged files".
|
||||
|
||||
- [ ] **Step 4: Fact-check the 3 tools**
|
||||
|
||||
Open and confirm assumptions still hold:
|
||||
|
||||
- `https://github.com/rvdbreemen/adr-kit` — v0.13.x, no CC lifecycle hooks, marketplace `rvdbreemen/adr-kit`.
|
||||
- `https://github.com/WH-2099/mermaid-skill` — standalone skill, files under `.claude/skills/mermaid/`, supports C4 + Architecture diagram types, MIT.
|
||||
- `https://github.com/secondsky/claude-skills` — `architecture-patterns` installable as `architecture-patterns@claude-skills`, MIT.
|
||||
|
||||
If any tool now registers CC lifecycle hooks → **stop**, re-audit AK5/MK2/AP1 before continuing.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Install the two marketplace plugins (adr-kit, architecture-patterns)
|
||||
|
||||
**Files:** Modify `~/.claude/settings.json` (the `Edit` triggers the `ask` permission — expected)
|
||||
|
||||
- [ ] **Step 1: Add both marketplaces**
|
||||
|
||||
```
|
||||
/plugin marketplace add rvdbreemen/adr-kit
|
||||
/plugin marketplace add https://github.com/secondsky/claude-skills
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Install both plugins**
|
||||
|
||||
```
|
||||
/plugin install adr-kit@rvdbreemen-adr-kit
|
||||
/plugin install architecture-patterns@claude-skills
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Reload**
|
||||
|
||||
```
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify `enabledPlugins`**
|
||||
|
||||
Read `~/.claude/settings.json` — `enabledPlugins` must now contain `adr-kit@rvdbreemen-adr-kit: true` and `architecture-patterns@claude-skills: true`, alongside the existing 9 → **11 plugins total**.
|
||||
|
||||
- [ ] **Step 5: Verify NO lifecycle hooks were added (AK5 / AP1)**
|
||||
|
||||
Read the `hooks` block of `~/.claude/settings.json` AND project `.claude/settings.json`. Both must be **unchanged** — still only the economy/skill-discipline/ruflo entries. If either plugin injected a `hooks` entry → stop and re-audit.
|
||||
|
||||
- [ ] **Step 6: Verify adr-kit skills loaded**
|
||||
|
||||
```bash
|
||||
ls ~/.claude/plugins/cache/rvdbreemen-adr-kit/*/skills/
|
||||
```
|
||||
|
||||
Expected: 8 skill directories (`adr`, `init`, `judge`, `install-hooks`, `upgrade`, `setup`, `lint`, `migrate`).
|
||||
|
||||
- [ ] **Step 7: Confirm economy/ruflo chain intact**
|
||||
|
||||
Submit a trivial prompt; confirm the economy marker still appears, no hook errors. No repo files changed in Task 2 → no commit.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Install the mermaid-skill (standalone, vendored)
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `.claude/skills/mermaid/` (vendored skill tree)
|
||||
- Modify: `cspell.json`, `.markdownlintignore`
|
||||
|
||||
- [ ] **Step 1: Clone the source to a temp location**
|
||||
|
||||
```bash
|
||||
git clone --depth 1 https://github.com/WH-2099/mermaid-skill.git /tmp/mermaid-skill-src
|
||||
ls /tmp/mermaid-skill-src/.claude/skills/mermaid/
|
||||
```
|
||||
|
||||
Expected: `SKILL.md` + a `references/` directory.
|
||||
|
||||
- [ ] **Step 2: Vendor the skill into the project**
|
||||
|
||||
```bash
|
||||
mkdir -p ".claude/skills/mermaid"
|
||||
cp -r /tmp/mermaid-skill-src/.claude/skills/mermaid/. ".claude/skills/mermaid/"
|
||||
ls -R ".claude/skills/mermaid/"
|
||||
rm -rf /tmp/mermaid-skill-src
|
||||
```
|
||||
|
||||
Expected: `.claude/skills/mermaid/SKILL.md` + `references/*.md` present. (Vendoring — not a submodule — keeps it on Windows+Cyrillic paths and immune to upstream loss, CC1.)
|
||||
|
||||
- [ ] **Step 3: Exclude the vendored skill from the doc-lint chain (MK1)**
|
||||
|
||||
Edit `.markdownlintignore` — append:
|
||||
|
||||
```
|
||||
.claude/skills/mermaid/
|
||||
```
|
||||
|
||||
Edit `cspell.json` — add `.claude/skills/mermaid/**` to the `ignorePaths` array (create the array if absent). Do **not** ignore `.claude/skills/` wholesale — the project's own skills (`q-item-add`, `rls-check`, `regression`) stay linted.
|
||||
|
||||
- [ ] **Step 4: Reload and verify the skill is discoverable**
|
||||
|
||||
```
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
Confirm a `mermaid` skill is now listed among available skills (project `.claude/skills/` is auto-discovered, like the existing 3 project skills). Confirm neither `settings.json` `hooks` block changed (MK2).
|
||||
|
||||
- [ ] **Step 5: Verify lint exclusion works**
|
||||
|
||||
```bash
|
||||
git add .claude/skills/mermaid/ cspell.json .markdownlintignore
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: cspell + markdownlint jobs do NOT report errors from `.claude/skills/mermaid/**`; all 8 jobs green.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "feat(arch): vendor mermaid-skill into .claude/skills + lint-ignore (MK1)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: adr-kit controlled bootstrap (no `init`, no CLAUDE.md write)
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `docs/adr/ADR-000-adr-process.md`, `ADR-001-frontend-stack-vue-vuetify.md`, `ADR-002-multitenancy-postgres-rls.md`
|
||||
- Create: `.claude/adr-kit-guide.md`
|
||||
- Modify (conditional): `cspell-words.txt`
|
||||
|
||||
- [ ] **Step 1: Create the ADR directory**
|
||||
|
||||
```bash
|
||||
mkdir -p "docs/adr"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Copy the adr-kit canonical guide**
|
||||
|
||||
```bash
|
||||
cp ~/.claude/plugins/cache/rvdbreemen-adr-kit/*/skills/adr/SKILL.md ".claude/adr-kit-guide.md"
|
||||
```
|
||||
|
||||
If the guide lives elsewhere (e.g. `references/guide.md`), copy that. Reference-only — this replaces what `init` would write, without touching `CLAUDE.md` (AK2).
|
||||
|
||||
- [ ] **Step 3: Write ADR-000 — process / boundary meta-ADR**
|
||||
|
||||
Create `docs/adr/ADR-000-adr-process.md`:
|
||||
|
||||
```markdown
|
||||
# ADR-000: ADR process and boundaries
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
Лидерра tracks open product/business/legal questions in
|
||||
`docs/Открытые_вопросы_v8_3.md`. Adding adr-kit introduces `docs/adr/`, and the
|
||||
mermaid-skill introduces `docs/architecture/`. Without boundaries they overlap.
|
||||
|
||||
## Decision
|
||||
- **ADR (`docs/adr/`)** — a *closed technical/architecture decision*: stack,
|
||||
patterns, structural boundaries, data-layer strategy. One file per decision.
|
||||
- **`docs/architecture/`** — diagrams and models (C4, system context). Visual,
|
||||
not normative; an ADR may embed or reference a diagram.
|
||||
- **Открытые_вопросы registry** — *unresolved* product/business/legal questions
|
||||
awaiting an explicit "закрываем". Not machine-enforced.
|
||||
- A registry question resolving into a technical choice MAY graduate into an ADR.
|
||||
|
||||
## Consequences
|
||||
- No ADR for an item still open in the registry.
|
||||
- ADR `## Enforcement` blocks target architecture-level constraints only — never
|
||||
re-encode larastan / eslint / squawk rules (AK4).
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Write ADR-001 — frontend stack (with Enforcement)**
|
||||
|
||||
Preferred: run `/adr-kit:adr "frontend stack Vue Vuetify"` and let the `adr-generator` agent emit the `## Enforcement` block in the exact v0.13 schema; paste the prose below. Verify key names (`forbid_import`/`forbid_pattern`/`require_pattern`) against `.claude/adr-kit-guide.md`. Create `docs/adr/ADR-001-frontend-stack-vue-vuetify.md`:
|
||||
|
||||
```markdown
|
||||
# ADR-001: Frontend stack — Vue 3 + Vuetify 3
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
Лидерра's UI is Vue 3 + Vuetify 3. Tailwind, Inertia, Livewire, Filament, Flux
|
||||
UI, Nova, Folio, Volt, Wayfinder are excluded (CLAUDE.md §5 п.2). framer-motion
|
||||
is a technical block — React-only peerDep, crashes in Vue.
|
||||
|
||||
## Decision
|
||||
All UI is Vue 3 SFC + Vuetify 3. No alternative UI framework or React-coupled
|
||||
runtime library may be imported into `app/resources/js/`.
|
||||
|
||||
## Consequences
|
||||
- Laravel Boost (Roster auto-detect) serves only relevant guidelines.
|
||||
- A dependency pulling React / Tailwind / Inertia is rejected at pre-commit.
|
||||
|
||||
## Enforcement
|
||||
```json
|
||||
{
|
||||
"forbid_import": ["@inertiajs/vue3", "tailwindcss", "framer-motion", "react", "react-dom"],
|
||||
"applies_to": ["app/resources/js/**"]
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
(Adjust `applies_to` / key names to the schema in `.claude/adr-kit-guide.md`.)
|
||||
|
||||
- [ ] **Step 5: Write ADR-002 — multi-tenancy via RLS (documentation-only)**
|
||||
|
||||
Create `docs/adr/ADR-002-multitenancy-postgres-rls.md` — no Enforcement block:
|
||||
```markdown
|
||||
# ADR-002: Multi-tenancy via PostgreSQL Row-Level Security
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
Лидерра is a multi-tenant SaaS CRM. Tenant isolation must be enforced at the
|
||||
database layer, not only in application code.
|
||||
|
||||
## Decision
|
||||
Every tenant-scoped table carries RLS policies. Tenant id is set per transaction
|
||||
via `SET LOCAL app.current_tenant_id` (`SetTenantContext` middleware). Five DB
|
||||
roles; `crm_supplier_worker` is `BYPASSRLS` — queued jobs running as it MUST
|
||||
filter `tenant_id` explicitly.
|
||||
|
||||
## Consequences
|
||||
- New tenant-scoped tables require an RLS policy + a `db/CHANGELOG_schema.md`
|
||||
entry (CLAUDE.md §5 п.8).
|
||||
- Verified by RLS smoke tests and the `rls-reviewer` agent — not by a regex gate.
|
||||
|
||||
## Enforcement
|
||||
None — verified by tests and code review.
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Lint and validate**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/adr/*.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/adr/*.md"
|
||||
```
|
||||
|
||||
Add valid project terms (`Vuetify`, `BYPASSRLS`, `Лидерра`, etc.) to `cspell-words.txt` if flagged. Then:
|
||||
|
||||
```
|
||||
/adr-kit:lint docs/adr/
|
||||
```
|
||||
|
||||
Expected: all three ADRs pass adr-kit's structural checks.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/adr/ .claude/adr-kit-guide.md cspell-words.txt
|
||||
git commit -m "feat(adr): bootstrap docs/adr — ADR-000/001/002 + adr-kit guide"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Wire `adr-judge` into lefthook (pre-commit job 9)
|
||||
|
||||
**Files:** Create `tools/adr-judge.mjs`; Modify `lefthook.yml`
|
||||
|
||||
> Skip this entire task for **knowledge-only** integration (see "Severable scope").
|
||||
|
||||
- [ ] **Step 1: Inspect `adr-judge` and choose the invocation**
|
||||
|
||||
```bash
|
||||
ls -la ~/.claude/plugins/cache/rvdbreemen-adr-kit/*/bin/
|
||||
head -20 ~/.claude/plugins/cache/rvdbreemen-adr-kit/*/bin/adr-judge
|
||||
```
|
||||
|
||||
Decision:
|
||||
|
||||
- **(a) self-contained** node/`.mjs` script, no plugin-local `require` → vendor: `cp <plugin>/bin/adr-judge tools/adr-judge.mjs`. (Re-vendor after `/adr-kit:upgrade`.)
|
||||
- **(b) has plugin-local deps** → write a 3-line `tools/adr-judge.mjs` wrapper that globs the newest `~/.claude/plugins/cache/rvdbreemen-adr-kit/*/bin/adr-judge` and `spawnSync`s it.
|
||||
|
||||
Primary path = (a). Record the branch taken.
|
||||
|
||||
- [ ] **Step 2: Add the lefthook job**
|
||||
|
||||
Edit `lefthook.yml` — append after the `eslint-vue` job (job 8), inside `pre-commit:` → `jobs:`:
|
||||
|
||||
```yaml
|
||||
# 9. adr-judge — declarative ADR Enforcement-block checker (Прил. Н #36).
|
||||
# Blocks the commit if a staged diff violates a forbid_*/require_* rule
|
||||
# in an `## Enforcement` block of any docs/adr/ADR-*.md.
|
||||
# Declarative regex only — no LLM calls, zero economy cost.
|
||||
- name: adr-judge
|
||||
glob: "*.{php,vue,ts,js,sql}"
|
||||
run: node tools/adr-judge.mjs {staged_files}
|
||||
fail_text: |
|
||||
adr-judge: a staged change violates a documented Architecture Decision.
|
||||
See the file:line citation above and the referenced docs/adr/ADR-*.md.
|
||||
If the ADR is wrong, update it (and its Enforcement block) first.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the job runs clean**
|
||||
|
||||
```bash
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: 9 jobs now; `adr-judge` reports success.
|
||||
|
||||
- [ ] **Step 4: Red test — confirm the gate blocks**
|
||||
|
||||
```bash
|
||||
echo "import { router } from '@inertiajs/vue3';" > app/resources/js/__adr_probe.ts
|
||||
git add app/resources/js/__adr_probe.ts
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: **FAIL** — `adr-judge` blocks on the `@inertiajs/vue3` import, citing `ADR-001` with file:line.
|
||||
|
||||
- [ ] **Step 5: Green test — clean up**
|
||||
|
||||
```bash
|
||||
git restore --staged app/resources/js/__adr_probe.ts
|
||||
rm app/resources/js/__adr_probe.ts
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: PASS — all 9 jobs green, no probe file remaining.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add lefthook.yml tools/adr-judge.mjs
|
||||
git commit -m "feat(adr): wire adr-judge as lefthook pre-commit job 9 (AK1)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Smoke #3 + #4, seed `docs/architecture/`
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `docs/architecture/README.md`, `docs/architecture/c4-context.md`
|
||||
|
||||
- [ ] **Step 1: Create the architecture-diagrams directory + index**
|
||||
|
||||
```bash
|
||||
mkdir -p "docs/architecture"
|
||||
```
|
||||
|
||||
Create `docs/architecture/README.md`:
|
||||
|
||||
```markdown
|
||||
# docs/architecture — system diagrams & models
|
||||
|
||||
C4 and architecture diagrams for Лидерра (Mermaid source). Generated with the
|
||||
`mermaid` skill (`.claude/skills/mermaid/`). Decisions live in `docs/adr/`;
|
||||
this directory holds the *visual* models. Map section: A6 «Архитектура систем».
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Smoke-test the mermaid skill — generate the seed C4 diagram**
|
||||
|
||||
Invoke the `mermaid` skill to author a C4 Context diagram. Create `docs/architecture/c4-context.md`:
|
||||
|
||||
````markdown
|
||||
# C4 — System Context: Лидерра
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title Лидерра — System Context
|
||||
Person(manager, "Менеджер арендатора", "Работает со сделками и лидами")
|
||||
Person(saasAdmin, "SaaS-админ", "Биллинг, инциденты, арендаторы")
|
||||
System(liderra, "Лидерра CRM", "Multi-tenant CRM: лиды, сделки, биллинг")
|
||||
System_Ext(supplier, "crm.bp-gr.ru", "Поставщик лидов (B1/B2/B3)")
|
||||
System_Ext(unisender, "Unisender Go", "SMTP-relay для email")
|
||||
System_Ext(yandex360, "Yandex 360", "SSO администраторов")
|
||||
Rel(manager, liderra, "Использует", "HTTPS")
|
||||
Rel(saasAdmin, liderra, "Администрирует", "HTTPS")
|
||||
Rel(liderra, supplier, "Импорт лидов / webhook", "HTTPS")
|
||||
Rel(liderra, unisender, "Отправка email", "SMTP")
|
||||
Rel(saasAdmin, yandex360, "Аутентификация", "OAuth")
|
||||
```
|
||||
````
|
||||
|
||||
Expected: the skill loads, the Mermaid `C4Context` block is syntactically valid (renders on GitHub natively — no `mmdc`/Chromium needed).
|
||||
|
||||
- [ ] **Step 3: Smoke-test the architecture-patterns skill**
|
||||
|
||||
Ask a sample architecture question (e.g. "what pattern fits the supplier-lead import pipeline?") and confirm the `architecture-patterns` skill activates and returns pattern guidance (layered / hexagonal / event-driven etc.). This is a functional smoke test — no file output required.
|
||||
|
||||
- [ ] **Step 4: Lint the new docs**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/architecture/*.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/architecture/*.md"
|
||||
```
|
||||
|
||||
Add flagged valid terms to `cspell-words.txt`.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/architecture/ cspell-words.txt
|
||||
git commit -m "feat(arch): seed docs/architecture — C4 Context diagram + index"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Unified normative registry sync (AK7 / AP2)
|
||||
|
||||
**Files:** Modify `docs/Tooling_v8_3.md`, `docs/Plugin_stack_rules_v1.md`, `docs/Pravila_raboty_Claude_v1_1.md`, `CLAUDE.md`, `docs/CHANGELOG_claude_md.md`
|
||||
|
||||
- [ ] **Step 1: Read the registry homes**
|
||||
|
||||
Read for exact insertion points and counters: `docs/Tooling_v8_3.md` Прил. Н §0 (counter "35") + §4.8/§4.9/§4.10; `docs/Plugin_stack_rules_v1.md` R10.1; `docs/Pravila_raboty_Claude_v1_1.md` §13.2.
|
||||
|
||||
- [ ] **Step 2: Add Tooling Прил. Н §4.11–§4.13**
|
||||
|
||||
Edit `docs/Tooling_v8_3.md`: add three subsections — `§4.11 #36 adr-kit`, `§4.12 #37 mermaid-skill`, `§4.13 #38 architecture-patterns` — all category **architecture-tooling** (off-phase). Note per tool: adr-kit (marketplace `rvdbreemen/adr-kit` v0.13.1 MIT; `adr-judge` via lefthook job 9, NOT adr-kit's git hook — AK1; `init`/`install-hooks` unused — AK1/AK2); mermaid-skill (`WH-2099/mermaid-skill` MIT, vendored standalone skill in `.claude/skills/mermaid/`); architecture-patterns (`secondsky/claude-skills` MIT, marketplace plugin). Add the bus-factor note (CC1). Bump §0 counter `35 → 38`; bump the Прил. Н version header.
|
||||
|
||||
- [ ] **Step 3: Add 3 PSR_v1 R10.1 rows**
|
||||
|
||||
Edit `docs/Plugin_stack_rules_v1.md`: add adr-kit, mermaid-skill, architecture-patterns rows to R10.1, category **architecture-tooling** (off-phase) — explicitly *outside* the UI-pool → no R6.0/R6.1 stack-filter, no R14 pipeline (same treatment as `claude-md-management`). Bump the PSR_v1 version header.
|
||||
|
||||
- [ ] **Step 4: Add the Pravila §13.2 note**
|
||||
|
||||
Edit `docs/Pravila_raboty_Claude_v1_1.md` §13.2: add a one-line architecture-tooling note covering the three tools, alongside the existing infrastructure note. Re-read Pravila §0/§13 first to keep section numbering consistent. Bump the Pravila version header.
|
||||
|
||||
- [ ] **Step 5: Update CLAUDE.md via the governed channel**
|
||||
|
||||
Invoke `/claude-md-management:claude-md-improver`. Apply: §3 title count (`35` → `38`), §1 priority-chain row 2b count (`35` → `38`), three new §3.3 rows `#36 adr-kit` / `#37 mermaid-skill` / `#38 architecture-patterns` (architecture-tooling, off-phase). The plugin also writes the `docs/CHANGELOG_claude_md.md` entry and bumps §0 cross-ref versions (Tooling / PSR_v1 / Pravila).
|
||||
|
||||
- [ ] **Step 6: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/Tooling_v8_3.md" "docs/Plugin_stack_rules_v1.md" "docs/Pravila_raboty_Claude_v1_1.md" "docs/CHANGELOG_claude_md.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/Tooling_v8_3.md" "docs/Plugin_stack_rules_v1.md" "docs/Pravila_raboty_Claude_v1_1.md"
|
||||
git add docs/Tooling_v8_3.md docs/Plugin_stack_rules_v1.md docs/Pravila_raboty_Claude_v1_1.md CLAUDE.md docs/CHANGELOG_claude_md.md cspell-words.txt
|
||||
git commit -m "docs(arch): register adr-kit/mermaid/architecture-patterns #36-38 (AK7/AP2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Reflect the 3 tools on the map (close A6)
|
||||
|
||||
**Files:** Modify `docs/automation-graph.html`
|
||||
|
||||
- [ ] **Step 1: Read the structures to replicate**
|
||||
|
||||
In `docs/automation-graph.html` read, as templates, an existing plugin node — `claude_md_mgmt` — across `NODES`, `NODE_DETAILS`, `NODE_SECTION`, and the "Паспорт узла" date fields. Read an existing skill node for the mermaid-skill template. Note the current node count (~103-104) and the plugins-group size.
|
||||
|
||||
- [ ] **Step 2: Add three nodes**
|
||||
|
||||
Add to `NODES`, replicating the template shapes:
|
||||
|
||||
- `adr_kit` — label `adr-kit`, plugins group.
|
||||
- `arch_patterns` — label `architecture-patterns`, plugins group.
|
||||
- `mermaid_skill` — label `mermaid (skill)`, skills group.
|
||||
|
||||
Add matching `NODE_DETAILS` entries: adr_kit — "Плагин ADR. docs/adr/ + adr-judge в lefthook job 9."; arch_patterns — "Скил-плагин: справочник архитектурных паттернов."; mermaid_skill — "Vendored-скил: C4/architecture-диаграммы (Mermaid) → docs/architecture/." Паспорт: дата внедрения `2026-05-17` for all three.
|
||||
|
||||
- [ ] **Step 3: Map all three to section A6**
|
||||
|
||||
In `NODE_SECTION` add:
|
||||
|
||||
```js
|
||||
adr_kit: 'A6', arch_patterns: 'A6', mermaid_skill: 'A6',
|
||||
```
|
||||
|
||||
A6 «Архитектура систем» goes from 0 → 3 nodes — the section is no longer empty.
|
||||
|
||||
- [ ] **Step 4: Update header metrics**
|
||||
|
||||
Bump the node count in the map header/legend by 3 (e.g. `103 → 106`). Update the plugins-group and skills-group count comments in `NODE_SECTION`.
|
||||
|
||||
- [ ] **Step 5: Smoke-test the map**
|
||||
|
||||
```bash
|
||||
npx stylelint docs/automation-graph.html
|
||||
```
|
||||
|
||||
Open `docs/automation-graph.html` in a browser (Playwright MCP or local `http.server`): 0 JS console errors; the 3 new nodes render; clicking section `A6` highlights all three.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/automation-graph.html
|
||||
git commit -m "feat(map): add adr_kit/mermaid/arch_patterns nodes — closes section A6"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Final regression & branch finish
|
||||
|
||||
**Files:** none modified
|
||||
|
||||
- [ ] **Step 1: Full pre-commit chain**
|
||||
|
||||
```bash
|
||||
npx lefthook run pre-commit
|
||||
```
|
||||
|
||||
Expected: all **9** jobs green (8 if knowledge-only).
|
||||
|
||||
- [ ] **Step 2: Confirm app code untouched — run the suites**
|
||||
|
||||
These tools change no `app/` code → suites must match the Task 1 baseline:
|
||||
|
||||
```bash
|
||||
cd app && php artisan test --parallel
|
||||
npm run test:vue
|
||||
```
|
||||
|
||||
Expected: Pest and Vitest counts unchanged vs Task 1 baseline (0 regressions). Record exact counts.
|
||||
|
||||
- [ ] **Step 3: Confirm the economy/ruflo hook chain is intact**
|
||||
|
||||
Economy marker still appears; Stop verifier still runs; no plugin leaked a `hooks` entry into either `settings.json`.
|
||||
|
||||
- [ ] **Step 4: Pre-push checks**
|
||||
|
||||
```bash
|
||||
./bin/gitleaks.exe detect --source . --no-banner --config .gitleaks.toml --redact
|
||||
./bin/lychee.exe --config .lychee.toml "docs/**/*.md" "db/**/*.md" "*.md"
|
||||
```
|
||||
|
||||
Expected: gitleaks 0 leaks; lychee 0 broken (new `docs/adr/*.md` + `docs/architecture/*.md` are scanned — fix or `.lychee.toml`-exclude any link).
|
||||
|
||||
- [ ] **Step 5: Finish the branch**
|
||||
|
||||
Invoke `superpowers:finishing-a-development-branch` — present the standard options. Do **not** push without an explicit user choice.
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**1. Spec coverage** — adr-kit: install (Task 2), bootstrap (Task 4), enforcement (Task 5). mermaid-skill: install+lint-ignore (Task 3), smoke+seed (Task 6). architecture-patterns: install (Task 2), smoke (Task 6). Conflict audit: AK1→T5, AK2→T4+T7.5, AK3→T4.3 (ADR-000), AK4→T4.4, AK5/AP1→T2.5, AK6→no task, AK7/AP2→T7, AK8→no task, MK1→T3.3, MK2→T3.4, MK3→no task, CC1→T7.2. Map A6 closure→T8. No gaps.
|
||||
|
||||
**2. Placeholder scan** — Task 5 Step 1 and Task 8 Step 1 are *decision/read-template* steps with concrete criteria, not "TBD" (the adr-judge dep shape and the live `claude_md_mgmt` node shape are not knowable without install / without reading the 2400-line map). All commands exact; ADR + C4 + README content shown in full.
|
||||
|
||||
**3. Consistency** — `adr-judge` invoked uniformly (`node tools/adr-judge.mjs`); paths consistent (`docs/adr/`, `docs/architecture/`, `.claude/skills/mermaid/`); tool counter `35 → 38` consistent T7↔T8; node ids `adr_kit`/`mermaid_skill`/`arch_patterns` consistent T8 Steps 2-3; plugin ids `adr-kit@rvdbreemen-adr-kit` / `architecture-patterns@claude-skills` consistent.
|
||||
|
||||
---
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
Plan complete. Two execution options:
|
||||
|
||||
1. **Subagent-Driven** — fresh subagent per task, two-stage review. *Caveat:* Tasks 2, 3 (slash commands `/plugin …`, `/reload-plugins`), Task 4 Step 6 (`/adr-kit:lint`), Task 6 Steps 2-3 (skill invocations) and Task 7 Step 5 (`claude-md-management`) are main-session-bound — those steps stay with the controller.
|
||||
2. **Inline Execution** — `superpowers:executing-plans`, batch with checkpoints. **Recommended here** — the integration is install/config/docs-heavy with many interactive main-session steps.
|
||||
|
||||
Open question for the user: **full integration** (incl. Task 5 — adr-judge pre-commit enforcement) or **knowledge-only** (skip Task 5 — ADR store + diagrams + patterns skill, no commit gate)?
|
||||
@@ -0,0 +1,578 @@
|
||||
# D3 Audit & Risk-Management Tooling Integration 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:** Integrate the D3 top-5 (variant «а») — install #1 Trail of Bits Skills, formalize #2 `/security-review`, install #4 Anthropic Security Guidance, reuse #3 adr-kit (from the A6 plan) for the decision/risk register, spike #5 toolchain-auditor — and distill the project's 14-phase portal-audit method into a project skill, so the `D3 «Аудит и управление рисками»` map section becomes a populated, working playbook.
|
||||
|
||||
**Architecture:** Audit & risk-management is currently an **empty** functional section — `NODE_SECTION` in `docs/automation-graph.html` tags zero nodes `D3`. Two third-party tools are added: **Trail of Bits Skills** (marketplace plugin — 16 audit/vuln/supply-chain skills) and **Anthropic Security Guidance** (marketplace plugin — a warn-only PreToolUse hook). `/security-review` (Anthropic built-in) is customized via a project copy in `.claude/commands/`. The decision/risk register is **not** a new tool — it reuses **adr-kit** introduced by the A6 plan (`2026-05-17-a6-architecture-tooling-integration.md`); D3 only adds the risk-register convention. #5 (toolchain attack-surface auditor) is a **provenance spike** — community plugins are unvetted, so the default outcome is a documented manual procedure, not an install. Finally the project's own repeated 14-phase audit (#1/#2/#3) is distilled into an `audit-portal` project skill. All tools are non-UI → **audit-security** category, outside the PSR_v1 UI-pool. D3 artifacts live in `docs/audit/`.
|
||||
|
||||
**Tech Stack:** Trail of Bits Skills (marketplace `trailofbits/skills`, CC-BY-SA-4.0); `anthropics/claude-code-security-review` (Anthropic, the `/security-review` command); Anthropic Security Guidance (Anthropic-verified marketplace plugin, PreToolUse hook); adr-kit v0.13.1 (reused — see A6 plan); project normative docs; `docs/automation-graph.html` (vis.js); `skill-creator` (for the `audit-portal` skill).
|
||||
|
||||
**Sequencing (locked 2026-05-17):** the A6 plan (`2026-05-17-a6-architecture-tooling-integration.md`) runs to completion and pushes **first**; D3 Task 1 then forks from the updated `origin/main`. Rationale: D3 Task 5 reuses A6's adr-kit (AD1) and builds on its `docs/adr/ADR-000`; A6-first means D3 completes in one pass with no self-skip and no tool-number rework (NUM1). The two epics touch shared files (the map, 4 normative docs) → run strictly sequentially, never interleaved.
|
||||
|
||||
---
|
||||
|
||||
## Tool Identity (verified 2026-05-17 via WebFetch/WebSearch)
|
||||
|
||||
| # | Tool | Install mode | Source / License | Hooks? |
|
||||
|---|---|---|---|---|
|
||||
| 1 | **Trail of Bits Skills** (16 skills: `differential-review`, `audit-context-building`, `supply-chain-risk-auditor`, `insecure-defaults`, `sharp-edges`, `fp-check`, `static-analysis`, `c-review`, `variant-analysis`, `semgrep-rule-creator`, …) | Marketplace plugin — `/plugin marketplace add trailofbits/skills` | GitHub `trailofbits/skills`, **CC-BY-SA-4.0**, 113 commits, reputable AppSec vendor | Verify on install — expected none (skills only) |
|
||||
| 2 | **`/security-review`** | Built-in Claude Code command; customized via a project copy in `.claude/commands/security-review.md` | GitHub `anthropics/claude-code-security-review`, Anthropic | None (slash command) |
|
||||
| 4 | **Security Guidance** | Marketplace plugin (Anthropic) | `claude.com/plugins/security-guidance`, **Anthropic Verified**, ~153k installs | **YES** — one PreToolUse `Write\|Edit\|MultiEdit` hook, **warn-only** (8 vuln categories, session-scoped, does not block) |
|
||||
| 3 | **adr-kit** | **NOT installed by D3** — reused from the A6 plan | (see A6 plan) | (see A6 plan) |
|
||||
| 5 | **toolchain attack-surface auditor** | **NOT installed by default** — Task 6 is a provenance spike | community (`geoffrey-young/anthropic-hackathon-2026` "Claude Code Canary"; "Plugin Security Auditor" on mcpmarket) — **provenance unverified** | TBD by spike |
|
||||
|
||||
**Verification status:** #1 and #4 — confirmed via WebFetch (skill list, license, install command / publisher, hook behaviour). #2 — present in this environment as the `/security-review` command; repo confirmed via search. #3 — defined by the A6 plan. #5 — **search-level only, provenance NOT verified** (this is by design — Task 6).
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions & Conflict Audit
|
||||
|
||||
Pattern follows the K1–K8 / AK1–CC1 audits used for claude-mem and the A6 plan. Verified against the three sources, project `.claude/settings.json`, `.mcp.json`, and the A6 plan.
|
||||
|
||||
| # | Tool | Sev | Conflict | Resolution (locked) |
|
||||
|---|---|---|---|---|
|
||||
| TB1 | ToB Skills | 🟡 | `static-analysis` / `semgrep-rule-creator` overlap the existing **Semgrep MCP** (#25, section A8) → CLAUDE.md §5 п.6 "не два инструмента на одну задачу". | Boundary, documented in the Tooling entry (Task 8): **Semgrep MCP** = inline SAST during routine dev / CI; **ToB Skills** = deep on-demand *audit campaigns*. Different cadence, not the same task. |
|
||||
| TB2 | ToB Skills | 🟡 | `differential-review` / `c-review` overlap `superpowers:requesting-code-review` (§12) and `/security-review`. | Different depth/scope: `/security-review` = quick diff scan; `requesting-code-review` = general review; ToB `differential-review` = security-focused *deep* audit of a change-set. §12.2 task-map **unchanged** — ToB skills are not added to it. |
|
||||
| TB3 | ToB Skills | 🟢 | Could the plugin register CC lifecycle hooks? | Verify on install (Task 3 Step 4). Skills-only plugins register none; if it does → stop and re-audit. |
|
||||
| TB4 | ToB Skills | 🟢 | CC-BY-SA-4.0 (ShareAlike) — could obligate the project. | None — ToB stays a **marketplace plugin** (cache outside the repo, not vendored, not modified). ShareAlike triggers only on redistribution/derivative works. Do **not** vendor ToB skill files into the repo. Noted in the Tooling entry. |
|
||||
| SG1 | Security Guidance | 🟡 | Adds a PreToolUse `Write\|Edit\|MultiEdit` hook → coexists with the project `CLAUDE.md`-direct-edit-warn hook (`.claude/settings.json`) and the economy/skill-discipline PreToolUse hooks (in `.claude/settings.local.json` / `~/.claude/settings.json`). | Verified **warn-only** — it prints session-scoped warnings, never blocks (WebFetch). Task 4 Steps 3-4 verify: the SG hook fires AND the economy + ruflo + CLAUDE-warn chain still fire, no `decision:block`. Accept +~34 ms/edit latency (memory: per-hook median). |
|
||||
| SG2 | Security Guidance | 🟢 | Overlaps ToB `insecure-defaults` / Semgrep. | None — SG = real-time inline warn *during the edit* (8 fixed categories); ToB/Semgrep = on-demand audit. Different timing. Additive. |
|
||||
| SG3 | Security Guidance | 🟡 | Anthropic-verified marketplace plugin, unregistered = PSR_v1 R0.2/R10 violation on use. | Register in 4 normative homes (Task 8). |
|
||||
| SR1 | `/security-review` | 🟢 | A customized `.claude/commands/security-review.md` shadows the built-in command. | Intended — this is the documented Anthropic customization path. Note in the Tooling entry that the project copy replaces the built-in. |
|
||||
| AD1 | ADR / risk register | 🟡 | D3 needs a decision/risk register. Installing a *second* ADR tool collides with **adr-kit** (A6 plan, Tooling #36) → §5 п.6. | D3 **reuses adr-kit** — installs nothing. The risk register = adr-kit `## Consequences` (risks + mitigations) + the `Открытые_вопросы` registry (open risks). Task 5 only adds the convention. |
|
||||
| AD2 | ADR / risk register | 🟢 | D3 Task 5 depends on the A6 plan having installed adr-kit. | **Locked sequencing** — A6 runs to completion first; D3 Task 1 forks from the updated `origin/main`. Task 5 therefore always finds adr-kit present; there is no self-skip path. |
|
||||
| TA1 | #5 toolchain auditor | 🟡 | Community plugin-auditors are unvetted. Installing an unverified plugin to *do risk management* is itself a D3 risk-management failure. | Task 6 = provenance spike (`superpowers:systematic-debugging` style). **Default outcome = defer** + a documented manual `docs/audit/toolchain-attack-surface.md` procedure. Install only if provenance clears the bar in the spike. |
|
||||
| AP1 | `audit-portal` skill | 🟢 | A new project skill duplicating `superpowers` or ToB skills. | None — `audit-portal` encodes the project's *own* 14-phase methodology (run 3× as audits #1/#2/#3), distilled per the E8 triada policy (workflow ≥3× → skill). It *orchestrates* `/security-review` + ToB skills, it does not reimplement them. |
|
||||
| CC1 | all | 🟡 | Bus-factor. | ToB = reputable firm (low risk); Security Guidance = Anthropic (low risk); #5 = community (high risk → spike, default defer). Noted in the Tooling entry. |
|
||||
| NUM1 | normative sync | 🟡 | The A6 plan (untracked, not yet executed) claims Tooling slots **#36-#38**, PSR_v1 R10.1 rows, and plugin slots #10/#11. D3 must not collide. | Task 8 Step 1 reads the **live** `docs/Tooling_v8_3.md` §0 counter and assigns D3's entries sequentially after whatever is current (≥#36 if A6 has not landed, ≥#39 if it has). Never hard-code a number before reading. |
|
||||
|
||||
**Severable scope.** Minimal D3 = Tasks 1-3 + 8-10 (formalize `/security-review`, install ToB, normative sync, map, finish) — already populates the section. Independently severable: **Task 4** (Security Guidance hook — env change), **Task 5** (ADR risk register — depends on A6), **Task 6** (#5 spike), **Task 7** (`audit-portal` skill). Primary path below = everything. **Selected 2026-05-17: full integration — all 10 tasks, no task severed.**
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Created / Modified | Responsibility |
|
||||
|---|---|---|
|
||||
| `.claude/commands/` | Create dir | Project slash-command overrides |
|
||||
| `.claude/commands/security-review.md` | Create | Customized `/security-review` — project false-positive filter |
|
||||
| `docs/audit/` | Create dir | D3 home — audit procedures & artifacts |
|
||||
| `docs/audit/README.md` | Create | Index — defines `docs/audit/` purpose, links D3 + the toolset |
|
||||
| `docs/audit/toolchain-attack-surface.md` | Create | #5 deliverable — manual toolchain attack-surface audit procedure |
|
||||
| `.claude/skills/audit-portal/` | Create | The distilled 14-phase portal-audit project skill (`SKILL.md`) |
|
||||
| `docs/adr/ADR-003-audit-risk-tooling.md` | Create (conditional — AD2) | Seed risk-ADR documenting the D3 tooling decision; only if adr-kit present |
|
||||
| `~/.claude/settings.json` | Modify | `enabledPlugins` += Trail of Bits + Security Guidance; `extraKnownMarketplaces` += 1-2 |
|
||||
| `docs/Tooling_v8_3.md` | Modify | Прил. Н — new audit-security subsection(s) + §0 counter bump |
|
||||
| `docs/Plugin_stack_rules_v1.md` | Modify | R10.1 — new audit-security rows |
|
||||
| `docs/Pravila_raboty_Claude_v1_1.md` | Modify | §13.2 — audit-security category note |
|
||||
| `CLAUDE.md` | Modify (**via claude-md-management only**) | §3 title count, §1 row 2b count, new §3.3 audit-security rows |
|
||||
| `docs/CHANGELOG_claude_md.md` | Modify | CLAUDE.md version-bump entry |
|
||||
| `docs/automation-graph.html` | Modify | New D3 nodes → `NODE_SECTION` D3; header metrics |
|
||||
| `cspell-words.txt` | Modify (conditional) | New audit/security vocabulary |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Pre-flight — baseline, snapshot, fact-check
|
||||
|
||||
**Files:** none modified (read-only) except a new branch
|
||||
|
||||
- [ ] **Step 1: Confirm tree state and create the working branch**
|
||||
|
||||
```bash
|
||||
cd "c:/моя/проекты/портал crm/Документация"
|
||||
git status --short
|
||||
git rev-parse --short HEAD
|
||||
git fetch origin && git checkout -b feat/d3-audit-risk-tooling origin/main
|
||||
```
|
||||
|
||||
Expected: record `origin/main` HEAD SHA as the regression baseline; new branch `feat/d3-audit-risk-tooling` created off it. (Push pattern at the end: `git push origin feat/d3-audit-risk-tooling:main`.)
|
||||
|
||||
- [ ] **Step 2: Snapshot the hook chain**
|
||||
|
||||
Read `.claude/settings.json`, `.claude/settings.local.json` (if present), and `~/.claude/settings.json`. Record every hook on `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop`. This is the SG1 baseline — Task 4 compares against it.
|
||||
Expected (project `.claude/settings.json`): 2× UserPromptSubmit (ruflo-recall, ruflo-queen), 1× PreToolUse (CLAUDE.md-warn), 2× PostToolUse (markdownlint-fix, schema-CHANGELOG-reminder). Economy/skill-discipline/Stop-verifier hooks live in the local/global file — record them too.
|
||||
|
||||
- [ ] **Step 3: Baseline regression**
|
||||
|
||||
```
|
||||
/regression quick
|
||||
```
|
||||
|
||||
Expected: GREEN. Also record current Pest / Vitest counts from the last green run (memory `project_state.md`: Pest 891/888/3sk/0, Vitest 102f/849/3sk/0) — D3 touches no `app/` code, so the final run must match.
|
||||
|
||||
- [ ] **Step 4: Check whether the A6 plan has landed (AD2 / NUM1)**
|
||||
|
||||
```bash
|
||||
git log --oneline origin/main | grep -iE "adr-kit|architecture-patterns|mermaid" | head
|
||||
ls ~/.claude/plugins/cache/ | grep -i adr-kit
|
||||
```
|
||||
|
||||
Read `docs/Tooling_v8_3.md` Прил. Н §0 — record the **live** tool counter.
|
||||
Record: **A6 landed?** yes/no — drives Task 5 (conditional) and Task 8 numbering.
|
||||
|
||||
- [ ] **Step 5: Fact-check the tools**
|
||||
|
||||
Confirm assumptions still hold:
|
||||
|
||||
- `https://github.com/trailofbits/skills` — marketplace `trailofbits/skills`, CC-BY-SA-4.0, skills-only (no CC lifecycle hooks).
|
||||
- `https://claude.com/plugins/security-guidance` — Anthropic-verified; confirm the exact `/plugin marketplace add …` + `/plugin install …` strings (the marketplace org/name).
|
||||
- `https://github.com/anthropics/claude-code-security-review` — confirm the path of `security-review.md` in the repo.
|
||||
|
||||
If ToB Skills or Security Guidance now differs materially (e.g. ToB registers lifecycle hooks) → **stop**, re-audit TB3/SG1.
|
||||
|
||||
No repo files changed → no commit.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Formalize `/security-review` — customized project copy (#2)
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `.claude/commands/` (dir), `.claude/commands/security-review.md`
|
||||
|
||||
- [ ] **Step 1: Create the commands directory**
|
||||
|
||||
```bash
|
||||
mkdir -p ".claude/commands"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Fetch the upstream command file**
|
||||
|
||||
```bash
|
||||
git clone --depth 1 https://github.com/anthropics/claude-code-security-review.git /tmp/ccsr-src
|
||||
cp /tmp/ccsr-src/security-review.md ".claude/commands/security-review.md"
|
||||
rm -rf /tmp/ccsr-src
|
||||
```
|
||||
|
||||
(If `security-review.md` lives in a subfolder — confirmed in Task 1 Step 5 — copy from that path.)
|
||||
|
||||
- [ ] **Step 3: Add the project false-positive filter**
|
||||
|
||||
Append to `.claude/commands/security-review.md` a `## Project false-positive guidance (Лидерра)` section with these concrete directions:
|
||||
|
||||
```markdown
|
||||
## Project false-positive guidance (Лидерра)
|
||||
|
||||
When filtering findings for this repository, treat the following as expected, not findings:
|
||||
- Missing application-layer tenant checks where the table has PostgreSQL RLS — tenant
|
||||
isolation is enforced at the DB layer (`SET LOCAL app.current_tenant_id`, 5 roles,
|
||||
39 policies; see ADR-002 / CLAUDE.md §2). DO still flag queued jobs running as
|
||||
`crm_supplier_worker` (BYPASSRLS) that lack an explicit `where('tenant_id', …)`.
|
||||
- `tools/*.mjs` economy/ruflo hook scripts using `child_process.spawnSync` / `process.env`
|
||||
— these are intentional local CLI hooks, not user-facing code paths.
|
||||
- Secrets: gitleaks already gates secrets at pre-commit + pre-push; do not re-report
|
||||
unless a NEW hardcoded credential appears in the diff.
|
||||
- Test factories / seeders using `Faker` and predictable values — test-only.
|
||||
|
||||
DO prioritise: HMAC/webhook verification gaps, signed-URL handling, auth/tenant
|
||||
middleware on `/api/*` routes, ПДн handling (152-ФЗ), and mass-assignment on Eloquent models.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Smoke-test the customized command**
|
||||
|
||||
Stage the new file, then run the command:
|
||||
|
||||
```bash
|
||||
git add .claude/commands/security-review.md
|
||||
```
|
||||
|
||||
Invoke `/security-review`. Expected: it runs, reads the customized `.claude/commands/security-review.md`, and produces a structured findings report (a docs/command change → expected result "no security-relevant findings"). Functional smoke only.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git commit -m "feat(audit): customize /security-review with project FP-filter (D3 #2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Install Trail of Bits Skills (#1)
|
||||
|
||||
**Files:** Modify `~/.claude/settings.json` (the `Edit` triggers the `ask` permission — expected). No repo file changed.
|
||||
|
||||
- [ ] **Step 1: Add the marketplace and install**
|
||||
|
||||
```
|
||||
/plugin marketplace add trailofbits/skills
|
||||
/plugin install <plugin-name>@trailofbits-skills
|
||||
```
|
||||
|
||||
(Resolve the exact plugin name from the marketplace listing shown after `add` — likely `skills@trailofbits-skills` or per the repo's `plugin.json`.)
|
||||
|
||||
- [ ] **Step 2: Reload**
|
||||
|
||||
```
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify the skills loaded**
|
||||
|
||||
```bash
|
||||
ls ~/.claude/plugins/cache/trailofbits-skills/*/skills/
|
||||
```
|
||||
|
||||
Expected: ~16 skill directories including `differential-review`, `supply-chain-risk-auditor`, `audit-context-building`, `insecure-defaults`, `fp-check`, `static-analysis`. Read `~/.claude/settings.json` — `enabledPlugins` contains the ToB entry.
|
||||
|
||||
- [ ] **Step 4: Verify NO lifecycle hooks were added (TB3)**
|
||||
|
||||
Read the `hooks` block of `~/.claude/settings.json` AND project `.claude/settings.json`. Both must be **unchanged** vs the Task 1 Step 2 snapshot. If ToB injected a `hooks` entry → stop and re-audit TB3.
|
||||
|
||||
- [ ] **Step 5: Smoke-test a ToB audit skill**
|
||||
|
||||
Invoke the `differential-review` skill against the last commit's diff (`git show HEAD`). Expected: the skill activates and returns a security-focused review of the change-set. Functional smoke — no file output, no findings expected on a docs change.
|
||||
|
||||
- [ ] **Step 6: Confirm economy/ruflo chain intact**
|
||||
|
||||
Submit a trivial prompt; the economy marker still appears, no hook errors. No repo files changed in Task 3 → no commit.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Install Security Guidance (#4) — *severable*
|
||||
|
||||
**Files:** Modify `~/.claude/settings.json`. No repo file changed.
|
||||
|
||||
> Skip this task for the minimal D3 scope (see "Severable scope"). It is the only env-changing step that adds a hook.
|
||||
|
||||
- [ ] **Step 1: Install the plugin**
|
||||
|
||||
Run the exact strings confirmed in Task 1 Step 5, e.g.:
|
||||
|
||||
```
|
||||
/plugin marketplace add <anthropic-marketplace>
|
||||
/plugin install security-guidance@<anthropic-marketplace>
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the plugin and its hook**
|
||||
|
||||
Read `~/.claude/settings.json` — `enabledPlugins` contains `security-guidance…: true`. Confirm the plugin contributes exactly one PreToolUse `Write|Edit|MultiEdit` hook (via the plugin's own `hooks/`, merged at runtime — it may not appear literally in `settings.json`).
|
||||
|
||||
- [ ] **Step 3: Verify warn-only behaviour + chain integrity (SG1)**
|
||||
|
||||
Make a trivial edit to a scratch file containing an obvious pattern (e.g. `eval("x")` in a `.js` temp file). Expected:
|
||||
|
||||
- The Security Guidance warning prints to stderr.
|
||||
- The edit **still applies** — the hook does NOT block (`decision:block` absent).
|
||||
- The project `CLAUDE.md`-warn hook, the economy hooks, and the ruflo hooks **all still fire** — compare to the Task 1 Step 2 snapshot.
|
||||
|
||||
If the SG hook blocks an edit, or any pre-existing hook stops firing → **stop**, document, and treat Task 4 as deferred.
|
||||
|
||||
- [ ] **Step 4: Clean up the scratch file**
|
||||
|
||||
```bash
|
||||
rm <scratch-file>
|
||||
```
|
||||
|
||||
No repo files changed in Task 4 → no commit.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: ADR risk-register convention — reuse adr-kit (#3)
|
||||
|
||||
**Files:** Create `docs/adr/ADR-003-audit-risk-tooling.md`
|
||||
|
||||
> Sequencing is locked — A6 runs first, so adr-kit is present when D3 starts. Step 1 is a confirmation, not a branch. If adr-kit is somehow absent, the locked sequencing was violated → stop and run A6 to completion before continuing.
|
||||
|
||||
- [ ] **Step 1: Confirm adr-kit is available**
|
||||
|
||||
```bash
|
||||
ls ~/.claude/plugins/cache/*adr-kit*/
|
||||
ls docs/adr/
|
||||
```
|
||||
|
||||
Expected: the adr-kit plugin cache exists and `docs/adr/ADR-000-adr-process.md` exists (created by the A6 plan). If absent → the locked sequencing was violated; stop and run A6 to completion first.
|
||||
|
||||
- [ ] **Step 2: Write the D3 risk-ADR**
|
||||
|
||||
Create `docs/adr/ADR-003-audit-risk-tooling.md`:
|
||||
|
||||
```markdown
|
||||
# ADR-003: Audit & risk-management tooling (D3)
|
||||
|
||||
- **Status:** Accepted
|
||||
- **Date:** 2026-05-17
|
||||
- **Deciders:** Дмитрий
|
||||
|
||||
## Context
|
||||
The `D3 «Аудит и управление рисками»` map section had zero tooling. Audits #1/#2/#3
|
||||
were run ad-hoc. There was no standing decision/risk register.
|
||||
|
||||
## Decision
|
||||
- Security/code audit — `/security-review` (customized) + Trail of Bits Skills.
|
||||
- Inline risk warnings — Anthropic Security Guidance.
|
||||
- Decision/risk register — **adr-kit ADRs** (this directory). An ADR's
|
||||
`## Consequences` section records risks + mitigations; the `Открытые_вопросы`
|
||||
registry holds *open, unresolved* risks. No separate risk-register tool.
|
||||
- Toolchain attack-surface — manual procedure `docs/audit/toolchain-attack-surface.md`
|
||||
(community auto-auditors deferred — unverified provenance).
|
||||
- The 14-phase portal-audit method is encoded as the `audit-portal` project skill.
|
||||
|
||||
## Consequences
|
||||
- Positive: D3 section populated; audits repeatable.
|
||||
- Risk: ToB / Security Guidance are third-party — bus-factor; mitigated by
|
||||
marketplace-cache pinning. Mitigation: re-verify on `/plugin` upgrades.
|
||||
- Risk: +1 PreToolUse hook latency (~34 ms/edit) — accepted.
|
||||
|
||||
## Enforcement
|
||||
None — D3 tools are advisory; verified by audits and code review.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/adr/ADR-003-audit-risk-tooling.md"
|
||||
git add docs/adr/ADR-003-audit-risk-tooling.md
|
||||
git commit -m "feat(adr): ADR-003 — D3 audit & risk-management tooling decision"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 6: #5 toolchain attack-surface — provenance spike + manual procedure (TA1) — *severable*
|
||||
|
||||
**Files:** Create `docs/audit/` (dir), `docs/audit/README.md`, `docs/audit/toolchain-attack-surface.md`
|
||||
|
||||
- [ ] **Step 1: Provenance spike on the community auto-auditors**
|
||||
|
||||
Use `superpowers:systematic-debugging` discipline — 3 hypotheses, falsify each:
|
||||
|
||||
- H1 "Claude Code Canary (`geoffrey-young/anthropic-hackathon-2026`) is install-safe" — check: stars/maintenance, hackathon-only?, does it register hooks / read secrets?
|
||||
- H2 "`Plugin Security Auditor` (mcpmarket) is install-safe" — check: real GitHub source, license, maintainer.
|
||||
- H3 "neither clears the bar; a manual procedure is sufficient for D3 now."
|
||||
Record the verdict. **Default expectation: H3** — defer the install.
|
||||
|
||||
- [ ] **Step 2: Create the audit directory + index**
|
||||
|
||||
```bash
|
||||
mkdir -p "docs/audit"
|
||||
```
|
||||
|
||||
Create `docs/audit/README.md`:
|
||||
|
||||
```markdown
|
||||
# docs/audit — audit procedures & artifacts
|
||||
|
||||
Home of the `D3 «Аудит и управление рисками»` section. Holds repeatable audit
|
||||
procedures. Toolset: `/security-review` (customized), Trail of Bits Skills,
|
||||
Security Guidance, the `audit-portal` skill. Decisions/risks → `docs/adr/`.
|
||||
Open product/business/legal risks → `docs/Открытые_вопросы_v8_3.md`.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Write the manual toolchain attack-surface procedure (#5 deliverable)**
|
||||
|
||||
Create `docs/audit/toolchain-attack-surface.md` — a checklist covering the post-ruflo attack surface (20 ruflo plugins + ~210 MCP tools + 7 `.mcp.json` servers), motivated by the May-2026 MCP-MITM / ClaudeBleed disclosures:
|
||||
|
||||
```markdown
|
||||
# Toolchain attack-surface audit (manual procedure)
|
||||
|
||||
Run quarterly, or after any new plugin / MCP server is added.
|
||||
|
||||
## 1. MCP servers
|
||||
- Review every server in `.mcp.json` — command, args, env. Flag any non-pinned
|
||||
`npx` package and any server reachable over the network.
|
||||
- Confirm no MCP URL was rewritten (npm postinstall MITM vector).
|
||||
|
||||
## 2. Plugins
|
||||
- List `enabledPlugins` in `~/.claude/settings.json`. For each: source repo,
|
||||
license, last commit, hooks contributed.
|
||||
- Flag plugins that register `PreToolUse` hooks with `decision:block`.
|
||||
|
||||
## 3. Hooks
|
||||
- Diff `.claude/settings.json` + `.claude/settings.local.json` hook blocks vs
|
||||
the last audited snapshot. Any unexplained change → investigate.
|
||||
|
||||
## 4. Permissions
|
||||
- Review `permissions.allow` / `deny` — no broadened wildcard, no new `Bash(*)`.
|
||||
|
||||
## 5. Secrets
|
||||
- `gitleaks detect` full history; confirm no token in a gitignored cache file.
|
||||
|
||||
## Outcome
|
||||
Record findings as P0-P3 in `docs/Открытые_вопросы_v8_3.md` (via `q-item-add`)
|
||||
or as an ADR if a tooling decision results.
|
||||
```
|
||||
|
||||
If Step 1 cleared a community auditor, additionally note it here as an optional accelerator.
|
||||
|
||||
- [ ] **Step 4: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/audit/*.md"
|
||||
git add docs/audit/
|
||||
git commit -m "docs(audit): toolchain attack-surface procedure + audit/ home (D3 #5)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Distill the 14-phase portal audit into the `audit-portal` skill — *severable*
|
||||
|
||||
**Files:** Create `.claude/skills/audit-portal/SKILL.md`
|
||||
|
||||
- [ ] **Step 1: Gather the source material**
|
||||
|
||||
Read the three prior audit plans/findings as the methodology source:
|
||||
`docs/superpowers/plans/2026-05-12-portal-full-audit.md`, `2026-05-13-portal-full-audit-2.md`, `2026-05-14-portal-full-audit-3.md` (and their `docs/superpowers/audits/` findings). Extract the stable 14-phase structure and the P0-P3 severity convention.
|
||||
|
||||
- [ ] **Step 2: Author the skill via skill-creator**
|
||||
|
||||
Invoke `skill-creator`. Create `.claude/skills/audit-portal/SKILL.md` — a project skill (auto-discovered like `rls-check` / `regression`) whose body:
|
||||
|
||||
- Lists the 14 audit phases in order, each with its concrete check.
|
||||
- Maps each phase to the tool that runs it (`/security-review`, ToB `audit-context-building` / `static-analysis` / `supply-chain-risk-auditor`, `regression` skill, Pa11y, `rls-reviewer`).
|
||||
- States the P0-P3 severity rubric and that findings are filed via `q-item-add`.
|
||||
- `description:` frontmatter triggers on "audit the portal" / "full audit" / "портальный аудит".
|
||||
|
||||
- [ ] **Step 3: Verify the skill is discoverable**
|
||||
|
||||
```
|
||||
/reload-plugins
|
||||
```
|
||||
|
||||
Confirm `audit-portal` appears among available skills. Confirm no `hooks` block changed.
|
||||
|
||||
- [ ] **Step 4: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 ".claude/skills/audit-portal/SKILL.md"
|
||||
git add .claude/skills/audit-portal/
|
||||
git commit -m "feat(audit): distill 14-phase portal audit into audit-portal skill (D3)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 8: Normative registry sync (TB1/SG3/NUM1)
|
||||
|
||||
**Files:** Modify `docs/Tooling_v8_3.md`, `docs/Plugin_stack_rules_v1.md`, `docs/Pravila_raboty_Claude_v1_1.md`, `CLAUDE.md`, `docs/CHANGELOG_claude_md.md`
|
||||
|
||||
- [ ] **Step 1: Read the registry homes and the live counter (NUM1)**
|
||||
|
||||
Read for exact insertion points and the **current** counter: `docs/Tooling_v8_3.md` Прил. Н §0 + the last §4.x subsection; `docs/Plugin_stack_rules_v1.md` R10.1; `docs/Pravila_raboty_Claude_v1_1.md` §13.2. Assign D3's numbers sequentially from `counter + 1` — **after** any A6 entries already present. Record the two assigned numbers as `#N` (Trail of Bits Skills) and `#N+1` (Security Guidance).
|
||||
|
||||
- [ ] **Step 2: Add the Tooling Прил. Н audit-security subsection(s)**
|
||||
|
||||
Edit `docs/Tooling_v8_3.md`: add a new subsection for `#N Trail of Bits Skills` and `#N+1 Security Guidance`, category **audit-security** (off-phase). Per tool record: ToB Skills (marketplace `trailofbits/skills`, CC-BY-SA-4.0; **not vendored** — TB4; boundary vs Semgrep MCP — TB1); Security Guidance (Anthropic-verified marketplace plugin, one warn-only PreToolUse hook — SG1). Also document `/security-review` (Anthropic built-in, customized at `.claude/commands/security-review.md`) as a sub-bullet of the same subsection — it is a built-in, not an installed tool, so no numbered slot. Add the bus-factor note (CC1). Bump §0 counter and the Прил. Н version header.
|
||||
|
||||
- [ ] **Step 3: Add PSR_v1 R10.1 rows**
|
||||
|
||||
Edit `docs/Plugin_stack_rules_v1.md`: add Trail of Bits Skills + Security Guidance rows to R10.1, category **audit-security** (off-phase) — explicitly *outside* the UI-pool → no R6.0/R6.1 stack-filter, no R14 pipeline (same treatment as `claude-md-management` and the A6 architecture-tooling category). Bump the PSR_v1 version header.
|
||||
|
||||
- [ ] **Step 4: Add the Pravila §13.2 note**
|
||||
|
||||
Edit `docs/Pravila_raboty_Claude_v1_1.md` §13.2: add a one-line audit-security category note covering the two tools + `/security-review`, alongside the existing infrastructure / debug-runtime notes. Re-read Pravila §0/§13 first to keep numbering consistent. Bump the Pravila version header.
|
||||
|
||||
- [ ] **Step 5: Update CLAUDE.md via the governed channel**
|
||||
|
||||
Invoke `/claude-md-management:claude-md-improver`. Apply: §3 title count bump, §1 priority-chain row 2b count bump, new §3.3 audit-security rows for `#N`/`#N+1` + the `/security-review` sub-note. The plugin also writes the `docs/CHANGELOG_claude_md.md` entry and bumps §0 cross-ref versions (Tooling / PSR_v1 / Pravila). **Do not** edit `CLAUDE.md` directly (§5 п.10).
|
||||
|
||||
- [ ] **Step 6: Lint + commit**
|
||||
|
||||
```bash
|
||||
npx markdownlint-cli2 "docs/Tooling_v8_3.md" "docs/Plugin_stack_rules_v1.md" "docs/Pravila_raboty_Claude_v1_1.md" "docs/CHANGELOG_claude_md.md"
|
||||
npx cspell --no-progress --no-summary --no-gitignore "docs/Tooling_v8_3.md" "docs/Plugin_stack_rules_v1.md" "docs/Pravila_raboty_Claude_v1_1.md"
|
||||
git add docs/Tooling_v8_3.md docs/Plugin_stack_rules_v1.md docs/Pravila_raboty_Claude_v1_1.md CLAUDE.md docs/CHANGELOG_claude_md.md cspell-words.txt
|
||||
git commit -m "docs(audit): register Trail of Bits + Security Guidance (D3 audit-security)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 9: Reflect D3 on the map — close the section
|
||||
|
||||
**Files:** Modify `docs/automation-graph.html`
|
||||
|
||||
- [ ] **Step 1: Read the structures to replicate**
|
||||
|
||||
In `docs/automation-graph.html` read, as templates: an existing plugin node (`claude_md_mgmt`) and a project-skill node (`sk_rls`) across `NODES`, `NODE_DETAILS`/`nd(...)`, `NODE_SECTION`, and the "Паспорт узла" date fields. Record the current node/edge counts from the header (memory: 103 nodes / 106 edges).
|
||||
|
||||
- [ ] **Step 2: Add the D3 nodes**
|
||||
|
||||
Add to `NODES`, replicating the template shapes:
|
||||
|
||||
- `tob_skills` — label `Trail of Bits\nskills`, plugins group.
|
||||
- `sec_guidance` — label `Security\nGuidance`, plugins group. *(omit if Task 4 was severed)*
|
||||
- `sk_security_review` — label `security-review`, skills group (the customized Anthropic command).
|
||||
- `sk_audit_portal` — label `audit-portal`, project-skills group. *(omit if Task 7 was severed)*
|
||||
|
||||
Add matching `nd(...)` / `NODE_DETAILS` entries (Russian, per the file's convention) with Паспорт `since: '2026-05-17'`. Add edges where real: `sk_audit_portal → sk_security_review` / `→ tob_skills` ("оркеструет"); `sec_guidance` as a PreToolUse-hook node if the file models hooks that way.
|
||||
|
||||
- [ ] **Step 3: Map the new nodes to section D3**
|
||||
|
||||
In `NODE_SECTION` add (omit any severed node):
|
||||
|
||||
```js
|
||||
tob_skills: 'D3', sec_guidance: 'D3', sk_security_review: 'D3', sk_audit_portal: 'D3',
|
||||
```
|
||||
|
||||
`D3 «Аудит и управление рисками»` goes from 0 → up to 4 nodes — the section is no longer empty.
|
||||
|
||||
- [ ] **Step 4: Update header metrics + group-count comments**
|
||||
|
||||
Bump the node count (and edge count if edges were added) in the map header/legend. Update the `NODE_SECTION` group-count comments (plugins, skills).
|
||||
|
||||
- [ ] **Step 5: Smoke-test the map**
|
||||
|
||||
```bash
|
||||
npx stylelint docs/automation-graph.html
|
||||
```
|
||||
|
||||
Open `docs/automation-graph.html` (Playwright MCP or local `http.server` — quirk 90: `file://` rejected). Expected: 0 JS console errors; the new nodes render; clicking section `D3` highlights them.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/automation-graph.html
|
||||
git commit -m "feat(map): D3 nodes — closes section «Аудит и управление рисками»"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 10: Final regression & branch finish
|
||||
|
||||
**Files:** none modified
|
||||
|
||||
- [ ] **Step 1: Full regression**
|
||||
|
||||
```
|
||||
/regression full
|
||||
```
|
||||
|
||||
Expected: GREEN. D3 changed no `app/` code → Pest / Vitest counts must match the Task 1 Step 3 baseline (0 regressions). Record exact counts; write out any failure with file:line.
|
||||
|
||||
- [ ] **Step 2: Confirm the hook chain is intact**
|
||||
|
||||
Economy marker still appears; Stop verifier still runs; ruflo + `CLAUDE.md`-warn hooks fire. If Task 4 ran, the Security Guidance hook is present and warn-only. Compare to the Task 1 Step 2 snapshot.
|
||||
|
||||
- [ ] **Step 3: Pre-push checks**
|
||||
|
||||
```bash
|
||||
./bin/gitleaks.exe detect --source . --no-banner --redact
|
||||
./bin/lychee.exe --config .lychee.toml "docs/**/*.md" "*.md"
|
||||
```
|
||||
|
||||
Expected: gitleaks 0 leaks; lychee 0 broken (new `docs/audit/*.md`, `docs/adr/ADR-003*.md`, `.claude/commands/security-review.md` are scanned — fix or `.lychee.toml`-exclude any link).
|
||||
|
||||
- [ ] **Step 4: Finish the branch**
|
||||
|
||||
Invoke `superpowers:finishing-a-development-branch` — present the standard options. Do **not** push without an explicit user choice. Push pattern: `git push origin feat/d3-audit-risk-tooling:main`.
|
||||
|
||||
---
|
||||
|
||||
## Self-Review
|
||||
|
||||
**1. Spec coverage (D3 top-5, variant «а»).** #1 ToB Skills — install (Task 3). #2 `/security-review` — formalize (Task 2). #3 ADR/risk register — reuse adr-kit (Task 5, conditional). #4 Security Guidance — install (Task 4). #5 toolchain auditor — spike + manual procedure (Task 6). Bonus `audit-portal` skill (Task 7). Section closure: normative (Task 8), map (Task 9), regression/finish (Task 10). Conflict audit: TB1→T8.2, TB2→no task (map note), TB3→T3.4, TB4→T8.2, SG1→T4.3, SG2→no task, SG3→T8, SR1→T8.2, AD1→T5, AD2→T5 Step 1 (conditional), TA1→T6, AP1→no task, CC1→T8.2, NUM1→T8.1. No gaps.
|
||||
|
||||
**2. Placeholder scan.** `#N`/`#N+1` (Task 8) and the exact `/plugin install` plugin names (Tasks 3-4) are **runtime-resolved by design** — the live Tooling counter and the marketplace listing are not knowable before Task 1 Step 5 / Task 8 Step 1, which carry concrete resolution criteria (matches the A6 plan's "read the current node count" pattern). All file contents (security-review FP-filter, ADR-003, audit READMEs, the toolchain procedure) are shown in full. No "TBD"/"handle edge cases".
|
||||
|
||||
**3. Consistency.** Branch `feat/d3-audit-risk-tooling` consistent T1↔T10. Node ids `tob_skills` / `sec_guidance` / `sk_security_review` / `sk_audit_portal` consistent T9 Steps 2-3. Category name **audit-security** consistent T8 Steps 2-4. Severable tasks (4, 5, 6, 7) flagged at the header, in "Severable scope", and inline. `docs/audit/` / `docs/adr/` paths consistent.
|
||||
|
||||
---
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
Plan complete and saved to `docs/superpowers/plans/2026-05-17-d3-audit-risk-tooling-integration.md`. Two execution options:
|
||||
|
||||
1. **Subagent-Driven (recommended for the doc-heavy tasks)** — fresh subagent per task, two-stage review. *Caveat:* Tasks 3, 4, 7 (`/plugin …`, `/reload-plugins`, `skill-creator`), Task 2 Step 4 + Task 3 Step 5 (skill/command invocations) and Task 8 Step 5 (`claude-md-management`) are main-session-bound — those steps stay with the controller.
|
||||
2. **Inline Execution** — `superpowers:executing-plans`, batch with checkpoints. **Recommended here** — install/config/docs-heavy with many interactive main-session steps (same as the A6 plan).
|
||||
|
||||
**Decided 2026-05-17:** scope = **full integration** (all 10 tasks); sequencing = **A6 plan first, then D3** (locked — see "Sequencing" in the header).
|
||||
|
||||
One open item before execution: execution method — **1** (Subagent-Driven) or **2** (Inline, recommended here). And: D3 cannot start until the A6 epic is complete and pushed — confirm who runs A6.
|
||||
@@ -0,0 +1,669 @@
|
||||
# Sprint 5C — Billing/Admin Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to
|
||||
> implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Закрыть 5 P2-эпиков подсистемы Billing/Admin портального аудита — E2, E4, G3, G7, G10.
|
||||
|
||||
**Architecture:** Frontend Vue 3.5 + Vuetify 3.12 + TypeScript (Composition API, `<script setup>`);
|
||||
backend Laravel 13. Изменения локализованы в 4 view/компонентах биллинга/админки + 1 контроллере +
|
||||
1 api-модуле. БД не трогаем (schema без изменений). Decide-эпики E2/E4 разрешены заказчиком
|
||||
2026-05-17: E2 → `disabled` + tooltip (паттерн 5A A1); E4 → убрать mock-баннер целиком.
|
||||
|
||||
**Tech Stack:** Vue 3.5, Vuetify 3.12, TypeScript, Pinia, vue-router 4, Pest 4, Vitest 4, Laravel 13.
|
||||
|
||||
**Порядок задач:** T1 (E2) и T2 (E4) независимы. T3→T4→T5 (G3→G7→G10) — последовательны, все три
|
||||
правят `AdminPricingTiersView.vue`; T3 — фундамент (вынос API), T4 и T5 строятся поверх.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: E2 — BalanceCard «Автопополнение» + «Сменить тариф» → `disabled` + tooltip
|
||||
|
||||
**Контекст:** `BalanceCard.vue` — три wallet-карты в `BillingView`. Кнопка «Пополнить» подвязана
|
||||
(`@click="$emit('topup')"`). Кнопки «Автопополнение» (рекуррентный биллинг) и «Сменить тариф»
|
||||
(self-service смена тарифа) — без обработчиков; backend-endpoint'ов под них нет, реализация вне
|
||||
scope P2. Решение заказчика — `disabled` + tooltip (как 5A A1 Yandex SSO). Disabled `v-btn` не
|
||||
ловит pointer-события → активатор tooltip навешивается на оборачивающий `<span>`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/components/billing/BalanceCard.vue`
|
||||
- Create: `app/tests/Frontend/BalanceCard.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Написать падающий тест** — `app/tests/Frontend/BalanceCard.spec.ts`
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import { createVuetify } from 'vuetify';
|
||||
import BalanceCard from '../../resources/js/components/billing/BalanceCard.vue';
|
||||
|
||||
const vuetify = createVuetify();
|
||||
|
||||
function factory() {
|
||||
return mount(BalanceCard, {
|
||||
global: { plugins: [vuetify] },
|
||||
props: {
|
||||
walletRub: 14250,
|
||||
leadsBalance: 285,
|
||||
tariffName: 'Про',
|
||||
tariffPrice: '990.00',
|
||||
tariffFeatures: ['Webhook', 'Канбан'],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
describe('BalanceCard.vue', () => {
|
||||
it('кнопка «Пополнить» активна и эмитит topup', async () => {
|
||||
const wrapper = factory();
|
||||
const btn = wrapper.findAll('button').find((b) => b.text().includes('Пополнить'));
|
||||
expect(btn).toBeDefined();
|
||||
expect(btn!.attributes('disabled')).toBeUndefined();
|
||||
await btn!.trigger('click');
|
||||
expect(wrapper.emitted('topup')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('кнопка «Автопополнение» disabled (E2 — нет backend)', () => {
|
||||
const wrapper = factory();
|
||||
const btn = wrapper.findAll('button').find((b) => b.text().includes('Автопополнение'));
|
||||
expect(btn).toBeDefined();
|
||||
expect(btn!.attributes('disabled')).toBeDefined();
|
||||
});
|
||||
|
||||
it('кнопка «Сменить тариф» disabled (E2 — нет backend)', () => {
|
||||
const wrapper = factory();
|
||||
const btn = wrapper.findAll('button').find((b) => b.text().includes('Сменить тариф'));
|
||||
expect(btn).toBeDefined();
|
||||
expect(btn!.attributes('disabled')).toBeDefined();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Прогнать тест — убедиться, что падает**
|
||||
|
||||
Run: `cd app && npx vitest run tests/Frontend/BalanceCard.spec.ts`
|
||||
Expected: FAIL — «Автопополнение» и «Сменить тариф» сейчас не disabled.
|
||||
|
||||
- [ ] **Step 3: Обернуть обе кнопки в `v-tooltip` с `disabled`**
|
||||
|
||||
В `BalanceCard.vue` заменить кнопку «Автопополнение» (сейчас `<v-btn variant="outlined"
|
||||
prepend-icon="mdi-autorenew" size="small"> Автопополнение </v-btn>`) на:
|
||||
|
||||
```vue
|
||||
<v-tooltip text="Автопополнение будет доступно после подключения платёжного шлюза.">
|
||||
<template #activator="{ props: tipProps }">
|
||||
<span v-bind="tipProps" class="d-inline-flex">
|
||||
<v-btn variant="outlined" prepend-icon="mdi-autorenew" size="small" disabled>
|
||||
Автопополнение
|
||||
</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
```
|
||||
|
||||
Заменить кнопку «Сменить тариф →» (сейчас `<v-btn variant="outlined" size="small"
|
||||
class="mt-auto">Сменить тариф →</v-btn>`) на:
|
||||
|
||||
```vue
|
||||
<v-tooltip text="Самостоятельная смена тарифа появится после запуска биллинга.">
|
||||
<template #activator="{ props: tipProps }">
|
||||
<span v-bind="tipProps" class="mt-auto d-inline-flex">
|
||||
<v-btn variant="outlined" size="small" disabled>Сменить тариф →</v-btn>
|
||||
</span>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
```
|
||||
|
||||
Примечание: класс `mt-auto` перенесён с кнопки на `<span>`-обёртку — обёртка теперь flex-ребёнок
|
||||
карты, ей нужен `mt-auto` для прижатия книзу.
|
||||
|
||||
- [ ] **Step 4: Прогнать тест — убедиться, что проходит**
|
||||
|
||||
Run: `cd app && npx vitest run tests/Frontend/BalanceCard.spec.ts`
|
||||
Expected: PASS (3/3).
|
||||
|
||||
- [ ] **Step 5: Lint + type-check**
|
||||
|
||||
Run: `cd app && npx vue-tsc --noEmit -p tsconfig.json && npm run lint:vue`
|
||||
Expected: 0 ошибок.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/components/billing/BalanceCard.vue app/tests/Frontend/BalanceCard.spec.ts
|
||||
git commit -m "feat(billing): E2 — disabled+tooltip на кнопках Автопополнение/Сменить тариф"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: E4 — убрать mock pending-баннер в BillingView + удалить mockBilling.ts
|
||||
|
||||
**Контекст:** `BillingView.vue` рисует `v-alert` «1 платёж в обработке» по хардкоду `MOCK_PENDING`
|
||||
из `composables/mockBilling.ts`. Платёжного шлюза нет (заблокирован Б-1), `POST /api/billing/topup`
|
||||
кредитует баланс мгновенно — состояния «платёж в обработке» в БД не существует и не появится до
|
||||
Б-1. Хардкод-баннер с фейковым «TX-89421 ЮKassa 14:21» вводит в заблуждение в проде. Решение
|
||||
заказчика — убрать баннер и файл `mockBilling.ts` целиком.
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/views/BillingView.vue`
|
||||
- Delete: `app/resources/js/composables/mockBilling.ts`
|
||||
- Modify: `app/tests/Frontend/BillingView.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Проверить, что `mockBilling` нигде больше не используется**
|
||||
|
||||
Run: `cd app && npx --yes grep -rn "mockBilling\|MOCK_PENDING" resources tests` (либо Grep-тул)
|
||||
Expected: совпадения только в `views/BillingView.vue` и `composables/mockBilling.ts`. Если есть
|
||||
другие — остановиться и эскалировать контроллеру.
|
||||
|
||||
- [ ] **Step 2: Написать падающий тест** — добавить в `app/tests/Frontend/BillingView.spec.ts`
|
||||
внутрь `describe('BillingView.vue', ...)`:
|
||||
|
||||
```ts
|
||||
it('не показывает pending-баннер (E4 — mock убран)', async () => {
|
||||
const wrapper = factory();
|
||||
await flushPromises();
|
||||
expect(wrapper.text()).not.toContain('в обработке');
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Прогнать тест — убедиться, что падает**
|
||||
|
||||
Run: `cd app && npx vitest run tests/Frontend/BillingView.spec.ts`
|
||||
Expected: FAIL — баннер «1 платёж в обработке» сейчас рендерится (`MOCK_PENDING` truthy).
|
||||
|
||||
- [ ] **Step 4: Убрать баннер и импорт из `BillingView.vue`**
|
||||
|
||||
1. Удалить строку импорта: `import { MOCK_PENDING } from '../composables/mockBilling';`
|
||||
2. Удалить блок `v-alert` (`<v-alert v-if="MOCK_PENDING" ...>...</v-alert>` — целиком, ~12 строк
|
||||
внутри `<template v-else-if="wallet">` перед `<BalanceCard ...>`).
|
||||
3. В doc-комментарии (строки 8–12) убрать абзац про «Pending-баннер остаётся mock (MOCK_PENDING) —
|
||||
это отдельный эпик E4». Заменить на одну строку:
|
||||
`Sprint 5C (E4): pending-баннер убран — платёжного шлюза нет (Б-1), реального состояния «платёж в обработке» в БД не существует.`
|
||||
|
||||
- [ ] **Step 5: Удалить файл `mockBilling.ts`**
|
||||
|
||||
```bash
|
||||
git rm app/resources/js/composables/mockBilling.ts
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Прогнать тесты — убедиться, что проходят**
|
||||
|
||||
Run: `cd app && npx vitest run tests/Frontend/BillingView.spec.ts`
|
||||
Expected: PASS (все, включая новый тест). `formatPlain`/`featureLabel` импорты остаются — они из
|
||||
`billingFormatters`, не из `mockBilling`.
|
||||
|
||||
- [ ] **Step 7: Lint + type-check**
|
||||
|
||||
Run: `cd app && npx vue-tsc --noEmit -p tsconfig.json && npm run lint:vue`
|
||||
Expected: 0 ошибок.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/views/BillingView.vue app/tests/Frontend/BillingView.spec.ts
|
||||
git commit -m "feat(billing): E4 — убрать mock pending-баннер (нет платёжного шлюза до Б-1)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: G3 — AdminPricingTiersView + AdminSupplierPricesView → типизированный api/admin.ts
|
||||
|
||||
**Контекст:** Обе админ-вьюхи уже на `<script setup lang="ts">` с интерфейсами (находка аудита
|
||||
«plain JS» устарела — Sprint 1 уже типизировал). Реальный остаток G3 — вьюхи дёргают сырой
|
||||
`import axios from 'axios'` напрямую, минуя `apiClient` (без `withCredentials`/`withXSRFToken`/
|
||||
CSRF — латентный баг для прода). Остальные админ-вьюхи (`AdminBillingView`) ходят через типизо-
|
||||
ванный `api/admin.ts` + `apiClient`. Задача — вынести вызовы pricing-tiers/suppliers в `api/admin.ts`.
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/api/admin.ts`
|
||||
- Modify: `app/resources/js/views/admin/AdminPricingTiersView.vue`
|
||||
- Modify: `app/resources/js/views/admin/AdminSupplierPricesView.vue`
|
||||
- Modify: `app/tests/Frontend/AdminPricingTiersView.spec.ts`
|
||||
- Modify: `app/tests/Frontend/AdminSupplierPricesView.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Добавить функции и типы в `api/admin.ts`** (в конец файла)
|
||||
|
||||
```ts
|
||||
// === SaaS-admin → Тарифная сетка (Plan 4 / Sprint 5C G3) ===
|
||||
|
||||
export interface AdminPricingTier {
|
||||
tier_no: number;
|
||||
leads_in_tier: number | null;
|
||||
price_per_lead_kopecks: number;
|
||||
effective_from: string;
|
||||
}
|
||||
|
||||
export interface PricingTiersResponse {
|
||||
active: AdminPricingTier[];
|
||||
scheduled: Record<string, AdminPricingTier[]>;
|
||||
}
|
||||
|
||||
export interface PricingTierEditorRow {
|
||||
tier_no: number;
|
||||
leads_in_tier: number | null;
|
||||
price_rub: string;
|
||||
}
|
||||
|
||||
export async function getPricingTiers(): Promise<PricingTiersResponse> {
|
||||
const { data } = await apiClient.get<{ data: PricingTiersResponse }>('/api/admin/pricing-tiers');
|
||||
return { active: data.data.active, scheduled: data.data.scheduled ?? {} };
|
||||
}
|
||||
|
||||
export async function createPricingTiers(tiers: PricingTierEditorRow[]): Promise<{ effective_from: string }> {
|
||||
await ensureCsrfCookie();
|
||||
const { data } = await apiClient.post<{ effective_from: string }>('/api/admin/pricing-tiers', { tiers });
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteScheduledPricingTier(effectiveFrom: string): Promise<void> {
|
||||
await ensureCsrfCookie();
|
||||
await apiClient.delete(`/api/admin/pricing-tiers/scheduled/${effectiveFrom}`);
|
||||
}
|
||||
|
||||
// === SaaS-admin → Цены поставщиков (Plan 4 / Sprint 5C G3) ===
|
||||
|
||||
export interface AdminSupplier {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
cost_rub: string;
|
||||
quality_score: string;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
export async function getAdminSuppliers(): Promise<AdminSupplier[]> {
|
||||
const { data } = await apiClient.get<{ data: AdminSupplier[] }>('/api/admin/suppliers');
|
||||
return data.data;
|
||||
}
|
||||
|
||||
export async function updateAdminSupplier(
|
||||
id: number,
|
||||
payload: { cost_rub: string; quality_score: string; is_active: boolean },
|
||||
): Promise<AdminSupplier> {
|
||||
await ensureCsrfCookie();
|
||||
const { data } = await apiClient.patch<{ data: AdminSupplier }>(`/api/admin/suppliers/${id}`, payload);
|
||||
return data.data;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Переписать `AdminPricingTiersView.vue` на api/admin**
|
||||
|
||||
1. Удалить `import axios from 'axios';`.
|
||||
2. Добавить:
|
||||
`import { getPricingTiers, createPricingTiers, deleteScheduledPricingTier, type AdminPricingTier, type PricingTierEditorRow } from '../../api/admin';`
|
||||
3. Удалить локальные интерфейсы `Tier` и `EditorRow`; заменить их использования на `AdminPricingTier`
|
||||
и `PricingTierEditorRow` соответственно (`active: ref<AdminPricingTier[]>([])`,
|
||||
`scheduled: ref<Record<string, AdminPricingTier[]>>({})`, `editor: ref<PricingTierEditorRow[]>(...)`,
|
||||
`defaultEditor: PricingTierEditorRow[]`).
|
||||
4. `load()` — заменить тело:
|
||||
```ts
|
||||
const data = await getPricingTiers();
|
||||
active.value = data.active;
|
||||
scheduled.value = data.scheduled;
|
||||
```
|
||||
5. `submit()` — заменить `await axios.post('/api/admin/pricing-tiers', { tiers: editor.value });` на
|
||||
`await createPricingTiers(editor.value);`.
|
||||
6. `confirmDelete()` — заменить `await axios.delete(\`/api/admin/pricing-tiers/scheduled/${effectiveFrom}\`);`
|
||||
на `await deleteScheduledPricingTier(effectiveFrom);`.
|
||||
7. `extractErrorMessage` остаётся (импорт из `../../api/client`).
|
||||
|
||||
- [ ] **Step 3: Переписать `AdminSupplierPricesView.vue` на api/admin**
|
||||
|
||||
1. Удалить `import axios from 'axios';`.
|
||||
2. Добавить: `import { getAdminSuppliers, updateAdminSupplier, type AdminSupplier } from '../../api/admin';`
|
||||
3. Удалить локальный интерфейс `SupplierRow`; заменить использования на `AdminSupplier`
|
||||
(`suppliers: ref<AdminSupplier[]>([])`, параметр `save(s: AdminSupplier)`).
|
||||
4. `load()` — заменить тело: `suppliers.value = await getAdminSuppliers();`.
|
||||
5. `save()` — заменить `axios.patch(...)` на:
|
||||
`await updateAdminSupplier(s.id, { cost_rub: s.cost_rub, quality_score: s.quality_score, is_active: s.is_active });`
|
||||
|
||||
- [ ] **Step 4: Переписать `AdminPricingTiersView.spec.ts` на мок api/admin**
|
||||
|
||||
Эталон паттерна — `app/tests/Frontend/AdminBillingViewActions.spec.ts`. Ключевые правки:
|
||||
1. Убрать `import axios from 'axios';` и `vi.mock('axios');`.
|
||||
2. Добавить partial-мок:
|
||||
```ts
|
||||
vi.mock('../../resources/js/api/admin', async (importOriginal) => {
|
||||
const orig = await importOriginal<typeof import('../../resources/js/api/admin')>();
|
||||
return { ...orig, getPricingTiers: vi.fn(), createPricingTiers: vi.fn(), deleteScheduledPricingTier: vi.fn() };
|
||||
});
|
||||
const adminApi = await import('../../resources/js/api/admin');
|
||||
```
|
||||
3. Добавить хелпер ошибки (копия из эталона):
|
||||
```ts
|
||||
function makeAxiosError(message: string, status = 422): unknown {
|
||||
return Object.assign(new Error(message), { isAxiosError: true, response: { status, data: { message } } });
|
||||
}
|
||||
```
|
||||
4. `mockTiers` — оставить (это `AdminPricingTier[]`).
|
||||
5. Первый `describe` `beforeEach`:
|
||||
```ts
|
||||
vi.mocked(adminApi.getPricingTiers).mockResolvedValue({ active: mockTiers, scheduled: {} });
|
||||
vi.mocked(adminApi.createPricingTiers).mockResolvedValue({ effective_from: '2026-06-01' });
|
||||
vi.mocked(adminApi.deleteScheduledPricingTier).mockResolvedValue(undefined);
|
||||
```
|
||||
6. Тест `submits POST ...` → `expect(adminApi.createPricingTiers).toHaveBeenCalledWith(expect.arrayContaining([expect.objectContaining({ tier_no: 7, leads_in_tier: null })]));`
|
||||
7. Тест `confirmDelete triggers DELETE ...` → `expect(adminApi.deleteScheduledPricingTier).toHaveBeenCalledWith('2026-06-01');` (`window.confirm = vi.fn(() => true)` — оставить, T5 уберёт).
|
||||
8. `describe` error handling — убрать `axios.isAxiosError` блок; в каждом тесте заменить
|
||||
`(axios.X as any).mockRejectedValue({response:...})` на `vi.mocked(adminApi.fn).mockRejectedValue(makeAxiosError('...', status))`,
|
||||
а `(axios.get as any).mockResolvedValue(...)` на `vi.mocked(adminApi.getPricingTiers).mockResolvedValue({ active: mockTiers, scheduled: {} })`.
|
||||
`afterEach(() => vi.clearAllMocks())` — оставить.
|
||||
|
||||
- [ ] **Step 5: Переписать `AdminSupplierPricesView.spec.ts` на мок api/admin**
|
||||
|
||||
Аналогично Step 4:
|
||||
1. Убрать axios; `vi.mock('../../resources/js/api/admin', ...)` с `getAdminSuppliers`/`updateAdminSupplier` как `vi.fn()`.
|
||||
2. `makeAxiosError` хелпер.
|
||||
3. `beforeEach`: `vi.mocked(adminApi.getAdminSuppliers).mockResolvedValue(mockSuppliers);`
|
||||
`vi.mocked(adminApi.updateAdminSupplier).mockResolvedValue(mockSuppliers[0]);`
|
||||
4. Тест `save() fires PATCH ...` → `expect(adminApi.updateAdminSupplier).toHaveBeenCalledWith(1, { cost_rub: '2.00', quality_score: '1.00', is_active: true });`
|
||||
5. Error-тесты → `mockRejectedValue(makeAxiosError(...))`; `load() ... rejects` → `vi.mocked(adminApi.getAdminSuppliers).mockRejectedValue(makeAxiosError('Database connection lost', 500))`.
|
||||
|
||||
- [ ] **Step 6: Прогнать оба spec-файла**
|
||||
|
||||
Run: `cd app && npx vitest run tests/Frontend/AdminPricingTiersView.spec.ts tests/Frontend/AdminSupplierPricesView.spec.ts`
|
||||
Expected: PASS (все тесты обоих файлов).
|
||||
|
||||
- [ ] **Step 7: Lint + type-check**
|
||||
|
||||
Run: `cd app && npx vue-tsc --noEmit -p tsconfig.json && npm run lint:vue`
|
||||
Expected: 0 ошибок (в т.ч. `import axios` удалён из обеих вьюх).
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/api/admin.ts app/resources/js/views/admin/AdminPricingTiersView.vue \
|
||||
app/resources/js/views/admin/AdminSupplierPricesView.vue \
|
||||
app/tests/Frontend/AdminPricingTiersView.spec.ts app/tests/Frontend/AdminSupplierPricesView.spec.ts
|
||||
git commit -m "refactor(admin): G3 — pricing-tiers/suppliers вьюхи на типизированный api/admin.ts"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: G7 — AdminPricingTiers effective_from через date-picker
|
||||
|
||||
**Контекст:** Сейчас `effective_from` новой сетки жёстко = 1-е число следующего месяца (МСК):
|
||||
backend `AdminPricingTiersController@store:92` хардкодит `startOfMonth()->addMonth()`, frontend
|
||||
показывает `nextMonthStart` в кнопке и заголовке диалога. G7 — дать админу выбрать дату.
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/app/Http/Controllers/Api/AdminPricingTiersController.php`
|
||||
- Modify: `app/resources/js/api/admin.ts`
|
||||
- Modify: `app/resources/js/views/admin/AdminPricingTiersView.vue`
|
||||
- Modify: `app/tests/Feature/Admin/AdminPricingTiersControllerTest.php`
|
||||
- Modify: `app/tests/Frontend/AdminPricingTiersView.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Написать падающий Pest-тест** — добавить в `AdminPricingTiersControllerTest.php`
|
||||
|
||||
```php
|
||||
it('store accepts a custom effective_from date', function (): void {
|
||||
$custom = \Illuminate\Support\Carbon::now('Europe/Moscow')->addMonths(3)->toDateString();
|
||||
|
||||
$response = $this->postJson('/api/admin/pricing-tiers', [
|
||||
'tiers' => validTiersPayload(),
|
||||
'effective_from' => $custom,
|
||||
]);
|
||||
|
||||
$response->assertCreated()->assertJson(['effective_from' => $custom]);
|
||||
expect(\App\Models\PricingTier::where('effective_from', $custom)->count())->toBe(7);
|
||||
});
|
||||
|
||||
it('store rejects effective_from in the past', function (): void {
|
||||
$past = \Illuminate\Support\Carbon::now('Europe/Moscow')->subDay()->toDateString();
|
||||
|
||||
$this->postJson('/api/admin/pricing-tiers', [
|
||||
'tiers' => validTiersPayload(),
|
||||
'effective_from' => $past,
|
||||
])->assertStatus(422);
|
||||
});
|
||||
```
|
||||
|
||||
Примечание: если в файле нет хелпера `validTiersPayload()` — переиспользовать массив тиров из
|
||||
существующего теста store (7 строк `tier_no`/`leads_in_tier`/`price_rub`); вынести в локальную
|
||||
функцию-хелпер в начале файла либо инлайнить массив в обоих новых тестах.
|
||||
|
||||
- [ ] **Step 2: Прогнать — убедиться, что падает**
|
||||
|
||||
Run: `cd app && php artisan test --filter=AdminPricingTiersControllerTest`
|
||||
Expected: FAIL — `effective_from` сейчас игнорируется (первый тест: дата = next-month, не custom;
|
||||
второй: 201 вместо 422).
|
||||
|
||||
- [ ] **Step 3: Backend — принять `effective_from` в `store()`**
|
||||
|
||||
В `AdminPricingTiersController@store`:
|
||||
1. Перед `$request->validate([...])` вычислить `$todayMsk = Carbon::now('Europe/Moscow')->toDateString();`
|
||||
2. В массив правил добавить:
|
||||
`'effective_from' => ['sometimes', 'date_format:Y-m-d', 'after:'.$todayMsk],`
|
||||
3. Заменить строку `$effectiveFrom = Carbon::now('Europe/Moscow')->startOfMonth()->addMonth()->toDateString();` на:
|
||||
```php
|
||||
$effectiveFrom = $request->input('effective_from')
|
||||
?? Carbon::now('Europe/Moscow')->startOfMonth()->addMonth()->toDateString();
|
||||
```
|
||||
(`$todayMsk` из шага 1 переиспользуется правилом валидации; вычислять до `validate`.)
|
||||
|
||||
- [ ] **Step 4: Прогнать Pest — убедиться, что проходит**
|
||||
|
||||
Run: `cd app && php artisan test --filter=AdminPricingTiersControllerTest`
|
||||
Expected: PASS (старые тесты store без `effective_from` → дефолт next-month; 2 новых → custom/422).
|
||||
|
||||
- [ ] **Step 5: api/admin.ts — `createPricingTiers` принимает `effectiveFrom`**
|
||||
|
||||
Изменить сигнатуру (расширение T3-функции):
|
||||
|
||||
```ts
|
||||
export async function createPricingTiers(
|
||||
tiers: PricingTierEditorRow[],
|
||||
effectiveFrom?: string,
|
||||
): Promise<{ effective_from: string }> {
|
||||
await ensureCsrfCookie();
|
||||
const payload: { tiers: PricingTierEditorRow[]; effective_from?: string } = { tiers };
|
||||
if (effectiveFrom) payload.effective_from = effectiveFrom;
|
||||
const { data } = await apiClient.post<{ effective_from: string }>('/api/admin/pricing-tiers', payload);
|
||||
return data;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Frontend — date-picker в редакторе сетки**
|
||||
|
||||
В `AdminPricingTiersView.vue`:
|
||||
1. Добавить ref после `nextMonthStart` computed:
|
||||
`const effectiveFrom = ref<string>(nextMonthStart.value);`
|
||||
2. Добавить computed для `min` (завтра):
|
||||
```ts
|
||||
const minEffectiveFrom = computed(() => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() + 1);
|
||||
return d.toISOString().slice(0, 10);
|
||||
});
|
||||
```
|
||||
3. В диалоге-редакторе перед `<table class="editor-table">` добавить поле:
|
||||
```vue
|
||||
<v-text-field
|
||||
v-model="effectiveFrom"
|
||||
type="date"
|
||||
label="Дата вступления в силу"
|
||||
:min="minEffectiveFrom"
|
||||
density="compact"
|
||||
class="mb-3"
|
||||
style="max-width: 240px"
|
||||
data-testid="effective-from-input"
|
||||
/>
|
||||
```
|
||||
4. Заголовок диалога: `Новая сетка (effective_from = {{ effectiveFrom }})` (вместо `nextMonthStart`).
|
||||
Кнопку открытия редактора `Редактировать сетку (с {{ nextMonthStart }})` — оставить
|
||||
`nextMonthStart` (это дефолтная подсказка до открытия диалога).
|
||||
5. `submit()` — передать дату: `await createPricingTiers(editor.value, effectiveFrom.value);`.
|
||||
6. `successMessage` в `submit()` — использовать `effectiveFrom.value` вместо `nextMonthStart.value`.
|
||||
7. `defineExpose` — добавить `effectiveFrom`.
|
||||
|
||||
- [ ] **Step 7: Написать Vitest для date-picker** — добавить в первый `describe` `AdminPricingTiersView.spec.ts`:
|
||||
|
||||
```ts
|
||||
it('редактор содержит поле даты effective_from с дефолтом = след. месяц', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(wrapper.vm as any).editorOpen = true;
|
||||
await wrapper.vm.$nextTick();
|
||||
expect(wrapper.find('[data-testid="effective-from-input"]').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('submit передаёт выбранную effective_from в createPricingTiers', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(wrapper.vm as any).effectiveFrom = '2026-09-01';
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await (wrapper.vm as any).submit();
|
||||
expect(adminApi.createPricingTiers).toHaveBeenCalledWith(expect.any(Array), '2026-09-01');
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 8: Прогнать FE-тест**
|
||||
|
||||
Run: `cd app && npx vitest run tests/Frontend/AdminPricingTiersView.spec.ts`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 9: Lint + type-check + Pint**
|
||||
|
||||
Run: `cd app && npx vue-tsc --noEmit -p tsconfig.json && npm run lint:vue && composer pint -- --dirty`
|
||||
Expected: 0 ошибок.
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
|
||||
```bash
|
||||
git add app/app/Http/Controllers/Api/AdminPricingTiersController.php app/resources/js/api/admin.ts \
|
||||
app/resources/js/views/admin/AdminPricingTiersView.vue \
|
||||
app/tests/Feature/Admin/AdminPricingTiersControllerTest.php app/tests/Frontend/AdminPricingTiersView.spec.ts
|
||||
git commit -m "feat(admin): G7 — выбор effective_from тарифной сетки через date-picker"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: G10 — AdminPricingTiers `window.confirm` → `v-dialog`
|
||||
|
||||
**Контекст:** `AdminPricingTiersView@confirmDelete` использует браузерный `window.confirm()` для
|
||||
подтверждения удаления запланированного набора тиров. Браузерный `confirm` блокирует UI и не
|
||||
доступен ассистивным технологиям — заменить на `v-dialog`. (Аудит назвал эпик «AdminBilling
|
||||
confirm()», но в `AdminBillingView` `confirm()` уже нет — Sprint 3D G4 заменил row-actions на
|
||||
`v-dialog`'и; фактический оставшийся браузерный confirm в админ-биллинге — здесь, в pricing-tiers.)
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/views/admin/AdminPricingTiersView.vue`
|
||||
- Modify: `app/tests/Frontend/AdminPricingTiersView.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Написать падающие тесты** — заменить в первом `describe` тест
|
||||
`confirmDelete triggers DELETE to /scheduled/{date}` на два теста:
|
||||
|
||||
```ts
|
||||
it('confirmDelete открывает диалог подтверждения, DELETE не вызывается сразу', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const vm = wrapper.vm as any;
|
||||
vm.confirmDelete('2026-06-01');
|
||||
expect(vm.deleteDialogOpen).toBe(true);
|
||||
expect(vm.deleteTarget).toBe('2026-06-01');
|
||||
expect(adminApi.deleteScheduledPricingTier).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('performDelete вызывает deleteScheduledPricingTier для выбранной даты', async () => {
|
||||
const wrapper = mount(AdminPricingTiersView, { global: { plugins: [vuetify] } });
|
||||
await new Promise((r) => setTimeout(r, 50));
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const vm = wrapper.vm as any;
|
||||
vm.confirmDelete('2026-06-01');
|
||||
await vm.performDelete();
|
||||
expect(adminApi.deleteScheduledPricingTier).toHaveBeenCalledWith('2026-06-01');
|
||||
expect(vm.deleteDialogOpen).toBe(false);
|
||||
});
|
||||
```
|
||||
|
||||
В error-handling `describe` тест `confirmDelete() shows errorMessage when ... rejects` —
|
||||
переименовать вызов: после `vm.confirmDelete('2026-06-01')` вызывать `await vm.performDelete()`
|
||||
(ошибку проверять после `performDelete`). Убрать `window.confirm = vi.fn(() => true)` из всех
|
||||
тестов этого файла (больше не нужен).
|
||||
|
||||
- [ ] **Step 2: Прогнать — убедиться, что падает**
|
||||
|
||||
Run: `cd app && npx vitest run tests/Frontend/AdminPricingTiersView.spec.ts`
|
||||
Expected: FAIL — `deleteDialogOpen`/`deleteTarget`/`performDelete` ещё не существуют.
|
||||
|
||||
- [ ] **Step 3: Заменить `window.confirm` на `v-dialog`-flow**
|
||||
|
||||
В `AdminPricingTiersView.vue`:
|
||||
1. Добавить state: `const deleteDialogOpen = ref(false);` и `const deleteTarget = ref<string | null>(null);`
|
||||
2. Заменить функцию `confirmDelete` — теперь только открывает диалог:
|
||||
```ts
|
||||
function confirmDelete(effectiveFrom: string): void {
|
||||
deleteTarget.value = effectiveFrom;
|
||||
deleteDialogOpen.value = true;
|
||||
}
|
||||
```
|
||||
3. Добавить `performDelete` — фактическое удаление (тело — бывший `confirmDelete` без `window.confirm`):
|
||||
```ts
|
||||
async function performDelete(): Promise<void> {
|
||||
const effectiveFrom = deleteTarget.value;
|
||||
if (effectiveFrom === null) return;
|
||||
deleteDialogOpen.value = false;
|
||||
errorMessage.value = null;
|
||||
successMessage.value = null;
|
||||
try {
|
||||
await deleteScheduledPricingTier(effectiveFrom);
|
||||
successMessage.value = `Удалено: запланированный набор с ${effectiveFrom}.`;
|
||||
successToastOpen.value = true;
|
||||
await load();
|
||||
} catch (err) {
|
||||
errorMessage.value = extractErrorMessage(err, 'Не удалось удалить запланированный набор.');
|
||||
} finally {
|
||||
deleteTarget.value = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
4. В `<template>` после диалога-редактора добавить confirm-диалог:
|
||||
```vue
|
||||
<v-dialog v-model="deleteDialogOpen" max-width="440">
|
||||
<v-card>
|
||||
<v-card-title>Удалить запланированный набор?</v-card-title>
|
||||
<v-card-text>
|
||||
Запланированная сетка с <strong>{{ deleteTarget }}</strong> будет удалена.
|
||||
Действие необратимо.
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="deleteDialogOpen = false">Отмена</v-btn>
|
||||
<v-btn color="error" data-testid="confirm-delete-btn" @click="performDelete">Удалить</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
```
|
||||
5. `defineExpose` — добавить `deleteDialogOpen`, `deleteTarget`, `performDelete`.
|
||||
|
||||
- [ ] **Step 4: Прогнать FE-тест — убедиться, что проходит**
|
||||
|
||||
Run: `cd app && npx vitest run tests/Frontend/AdminPricingTiersView.spec.ts`
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Lint + type-check**
|
||||
|
||||
Run: `cd app && npx vue-tsc --noEmit -p tsconfig.json && npm run lint:vue`
|
||||
Expected: 0 ошибок (в т.ч. `window.confirm` удалён из вьюхи).
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/views/admin/AdminPricingTiersView.vue app/tests/Frontend/AdminPricingTiersView.spec.ts
|
||||
git commit -m "feat(admin): G10 — браузерный confirm() удаления сетки → v-dialog"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Финал
|
||||
|
||||
После всех 5 задач — финальный holistic review всей реализации, затем полная регрессия
|
||||
(`/regression full`: Pest --parallel, Vitest, Vite build, vue-tsc, ESLint, Pint, Larastan,
|
||||
markdownlint, cspell, lychee, gitleaks) и `superpowers:finishing-a-development-branch`.
|
||||
|
||||
**Ожидаемые изменения относительно базы `345d14d`:** 5 feat/refactor-коммитов + этот plan-коммит.
|
||||
Файлы: `BalanceCard.vue`, `BillingView.vue`, `mockBilling.ts` (удалён), `api/admin.ts`,
|
||||
`AdminPricingTiersView.vue`, `AdminSupplierPricesView.vue`, `AdminPricingTiersController.php`,
|
||||
+ 5 spec-файлов (1 новый `BalanceCard.spec.ts`). БД/schema — без изменений.
|
||||
@@ -0,0 +1,423 @@
|
||||
# Sprint 5D — Cleanup dev-артефактов (I3 + I4) Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Убрать production fake-data fallback (вьюхи рендерят выдуманные сделки/тенантов/инциденты при сбое API) и DEV-gate баннер `_dev_plain_code` в impersonation-диалоге.
|
||||
|
||||
**Architecture:** Аудит портала, находки I3 + I4 (под-план Sprint 5D). 8 production-файлов инициализируют reactive-state mock-данными из `composables/mock*.ts` и держат их как fallback при ошибке API — в проде юзер при 500/network видит фейковые данные. Фикс: state инициализируется пустым (`[]` / нули); при ошибке показывается error-alert (уже есть `v-alert v-if="fetchError"` в каждом файле — меняется только текст). Файлы `mock*.ts` **остаются на месте** (типы + UI-константы там реальные; mock-массивы используются только тестами как фикстуры) — решение заказчика «убрать fake-fallback», без переезда файлов. I4: баннер `_dev_plain_code` гейтится за `import.meta.env.DEV`.
|
||||
|
||||
**Tech Stack:** Vue 3.5 + Vuetify 3.12 + TypeScript, Vitest 4 (`@vue/test-utils` + jsdom).
|
||||
|
||||
**Scope (I1 — отложен):** I1 (DevIndexBadge/DevIndexOverlay removal) решением заказчика отложен до заморозки UI — не в этом плане.
|
||||
|
||||
**НЕ в scope (discovered minor):** `DealsView.vue` строка `const newToday = 3; // mock` — инлайн-литерал (не `mock*.ts`-композабл), показывает фейковое «+3 новых лида с утра». Требует реальной backend-метрики — отдельная находка, в 5D не трогается.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
Production (8 файлов — убрать mock-fallback):
|
||||
|
||||
- `app/resources/js/views/DealsView.vue` — `dealsState` init из `MOCK_DEALS`.
|
||||
- `app/resources/js/views/KanbanView.vue` — `dealsByStatus` + `totalDeals` init из `MOCK_DEALS`.
|
||||
- `app/resources/js/components/deals/NewDealDialog.vue` — `projectOptions`/`managerOptions` init из `MOCK_PROJECTS`/`MOCK_MANAGERS`.
|
||||
- `app/resources/js/components/deals/DealDetailDrawer.vue` — `events` init из `MOCK_EVENTS`, fallback на 2 catch-путях.
|
||||
- `app/resources/js/views/admin/AdminBillingView.vue` — `rowsState`/`summary` init из `ADMIN_BILLING_TENANTS`/`ADMIN_BILLING_SUMMARY`.
|
||||
- `app/resources/js/views/admin/AdminIncidentsView.vue` — `rowsState` + initial `stats` init из `ADMIN_INCIDENTS`.
|
||||
- `app/resources/js/views/admin/AdminSystemView.vue` — `settingsState` init из `ADMIN_SYSTEM_SETTINGS` (тип `AdminSystemSetting` — оставить).
|
||||
- `app/resources/js/views/admin/AdminTenantsView.vue` — `tenantsState`/`stats` init из `MOCK_TENANTS`/`MOCK_STATS`.
|
||||
|
||||
Production (1 файл — I4):
|
||||
|
||||
- `app/resources/js/components/admin/ImpersonationDialog.vue` — баннер `dev-code-banner` за `import.meta.env.DEV`.
|
||||
|
||||
Не трогаются: `composables/mock*.ts` (типы/константы реальны, mock-массивы — тест-фикстуры).
|
||||
|
||||
Tests (обновить — спеки писались вокруг mock-initial-state):
|
||||
|
||||
- `app/tests/Frontend/DealsView.spec.ts`, `DealsViewRedesign.spec.ts`, `KanbanView.spec.ts`
|
||||
- `app/tests/Frontend/NewDealDialog*.spec.ts`, `DealDetailDrawer*.spec.ts` (имена уточнить через `ls`)
|
||||
- `app/tests/Frontend/AdminBillingView.spec.ts`, `AdminBillingViewApi.spec.ts`
|
||||
- `app/tests/Frontend/AdminIncidentsView.spec.ts`, `AdminIncidentsViewApi.spec.ts`
|
||||
- `app/tests/Frontend/AdminSystemView.spec.ts`, `AdminTenantsView.spec.ts`, `AdminTenantsViewApi.spec.ts`
|
||||
|
||||
---
|
||||
|
||||
## Общий тест-принцип (для всех задач T1–T4)
|
||||
|
||||
Init state → `[]` ломает существующие тесты двух типов. Стратегия:
|
||||
|
||||
1. **Тесты, использующие mock как фикстуру** (рендер строк, bulk-actions, фильтры). Mock-композабл импортируется **в spec-файле** как фикстура (это разрешено — тесты могут использовать mock-данные). Два варианта вернуть данные в state — выбрать минимально-инвазивный для конкретного спека:
|
||||
- **Вариант A (предпочтительно для `*Api.spec.ts` и где API уже `vi.mock`'нут):** мок data-API резолвится фикстурой → success-путь `loadX()` наполняет state, `fetchError=false`.
|
||||
- **Вариант B (для smoke-спеков без `vi.mock`):** после `mount` + `flushPromises()` засеять exposed-state: `vm.<stateRef>.push(...<MOCK_FIXTURE>.map(clone))`. Для seed может потребоваться расширить `defineExpose` (см. задачи).
|
||||
2. **Тесты, явно ассертящие fake-fallback как поведение** (напр. `AdminBillingViewApi.spec.ts:96-106` — `expect(vm.rowsState.length).toBeGreaterThan(0)` после reject, заголовок «MOCK fallback остаётся»). **Инвертировать**: `toBe(0)`, заголовок → «state пустой».
|
||||
3. **Новый regression-тест на каждый production-файл:** API/loadX отклоняется → state пустой (`length === 0`) **и** error-видим (`fetchError`/alert). См. red-green в шагах задач.
|
||||
|
||||
Каждая задача завершается полным зелёным прогоном `npm run test:vue` (0 fail) + `npm run lint:vue` + `npm run type-check`.
|
||||
|
||||
---
|
||||
|
||||
## Task 1: DealsView + KanbanView — убрать MOCK_DEALS fallback
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/views/DealsView.vue`
|
||||
- Modify: `app/resources/js/views/KanbanView.vue`
|
||||
- Test: `app/tests/Frontend/DealsView.spec.ts`, `DealsViewRedesign.spec.ts`, `KanbanView.spec.ts`
|
||||
|
||||
- [ ] **Step 1: Regression-тест на пустой state при ошибке (DealsView) — должен упасть**
|
||||
|
||||
В `DealsView.spec.ts` добавить (рядом с C3-тестами в конце файла):
|
||||
|
||||
```ts
|
||||
test('I3: loadDeals reject → dealsState пустой + fetchError', async () => {
|
||||
vi.spyOn(dealsApi, 'listDeals').mockRejectedValue(new Error('500'));
|
||||
const wrapper = await mountDeals();
|
||||
const auth = useAuthStore();
|
||||
auth.user = { id: 1, tenant_id: 42, email: 't@t.io' } as AuthUser;
|
||||
const vm = wrapper.vm as unknown as { loadDeals: () => Promise<void>; dealsState: unknown[]; fetchError: boolean };
|
||||
await vm.loadDeals();
|
||||
await flushPromises();
|
||||
expect(vm.dealsState.length).toBe(0);
|
||||
expect(vm.fetchError).toBe(true);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Прогнать — убедиться, что падает**
|
||||
|
||||
Run: `cd app && npm run test:vue -- DealsView.spec.ts`
|
||||
Expected: новый тест FAIL (`dealsState.length` = длина `MOCK_DEALS`, не 0).
|
||||
|
||||
- [ ] **Step 3: DealsView.vue — init пустой**
|
||||
|
||||
Импорт (строка 18) — убрать `MOCK_DEALS`:
|
||||
```ts
|
||||
import { DEALS_TABS, type MockDeal } from '../composables/mockDeals';
|
||||
```
|
||||
Init `dealsState` (строка 113):
|
||||
```ts
|
||||
// Локальная reactive-копия. Наполняется через API (см. loadDeals/onMounted).
|
||||
// До загрузки и при ошибке — пустой массив; ошибка показывается через fetchError.
|
||||
const dealsState = reactive<MockDeal[]>([]);
|
||||
```
|
||||
Catch в `loadDeals` (строка 133) — комментарий:
|
||||
```ts
|
||||
} catch {
|
||||
fetchError.value = true; // state остаётся пустым — показываем error-alert
|
||||
}
|
||||
```
|
||||
Шаблон, alert `fetch-error-alert` (строки ~714-724) — текст:
|
||||
```
|
||||
Не удалось загрузить сделки. Попробуйте обновить.
|
||||
```
|
||||
Doc-комментарий вверху файла — строку `MVP: page-head + chiprow со срезами + поиск + v-data-table с mock'ами.` поправить на `... + v-data-table (данные из API).`
|
||||
|
||||
- [ ] **Step 4: KanbanView.vue — init пустой**
|
||||
|
||||
Импорт (строка 23):
|
||||
```ts
|
||||
import { type MockDeal } from '../composables/mockDeals';
|
||||
```
|
||||
Init `dealsByStatus` (строки 49-54):
|
||||
```ts
|
||||
const dealsByStatus = reactive<Record<string, MockDeal[]>>(
|
||||
LEAD_STATUSES.reduce<Record<string, MockDeal[]>>((acc, s) => {
|
||||
acc[s.slug] = [];
|
||||
return acc;
|
||||
}, {}),
|
||||
);
|
||||
```
|
||||
`totalDeals` (строка 111):
|
||||
```ts
|
||||
const totalDeals = ref(0);
|
||||
```
|
||||
Catch в `loadDeals` (строка 142) — комментарий: `fetchError.value = true; // state остаётся пустым — показываем error-alert`
|
||||
Alert `fetch-error-alert` (строки ~199-209) — текст: `Не удалось загрузить сделки. Попробуйте обновить.`
|
||||
|
||||
- [ ] **Step 5: Аналогичный regression-тест для KanbanView**
|
||||
|
||||
В `KanbanView.spec.ts` добавить тест: замокать `dealsApi.listDeals` reject, выставить `auth.user` с `tenant_id`, вызвать `vm.loadDeals()`, проверить что все массивы `dealsByStatus` пусты (`Object.values(vm.dealsByStatus).every(c => c.length === 0)`) и `vm.fetchError === true`.
|
||||
|
||||
- [ ] **Step 6: Починить существующие тесты (DealsView.spec.ts, DealsViewRedesign.spec.ts, KanbanView.spec.ts)**
|
||||
|
||||
Многие тесты используют `MOCK_DEALS` как фикстуру (рендер строк, `applyBulkStatus`, фильтры «Окна Москва»/«Иван П.», `route.query.openId=MOCK_DEALS[0].id`). Применить **Вариант B**: в mount-хелперах (`mountDeals`, `mountDealsViewAt`, аналог в KanbanView) после `mount` засеять state из mock-фикстуры:
|
||||
```ts
|
||||
const wrapper = mount(DealsView, { /* ... */ });
|
||||
await flushPromises();
|
||||
const vm = wrapper.vm as unknown as { dealsState: MockDeal[] };
|
||||
vm.dealsState.push(...MOCK_DEALS.map((d) => ({ ...d, manager: { ...d.manager } })));
|
||||
await flushPromises();
|
||||
return wrapper;
|
||||
```
|
||||
`MOCK_DEALS` уже импортируется в `DealsView.spec.ts`. Для KanbanView — засеять `dealsByStatus` по slug'ам. Тесты на новый-deal/bulk/фильтры/openId после seed работают как раньше. Прогонять после правки каждого спека.
|
||||
|
||||
- [ ] **Step 7: Полный прогон + линт**
|
||||
|
||||
Run: `cd app && npm run test:vue -- DealsView KanbanView && npm run lint:vue && npm run type-check`
|
||||
Expected: все DealsView/KanbanView спеки PASS (0 fail), ESLint 0, vue-tsc 0.
|
||||
|
||||
- [ ] **Step 8: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/views/DealsView.vue app/resources/js/views/KanbanView.vue app/tests/Frontend/DealsView.spec.ts app/tests/Frontend/DealsViewRedesign.spec.ts app/tests/Frontend/KanbanView.spec.ts
|
||||
git commit -m "fix(deals): I3 — убрать MOCK_DEALS fallback в DealsView/KanbanView"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 2: NewDealDialog + DealDetailDrawer — убрать mock-fallback
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/components/deals/NewDealDialog.vue`
|
||||
- Modify: `app/resources/js/components/deals/DealDetailDrawer.vue`
|
||||
- Test: spec-файлы NewDealDialog / DealDetailDrawer (уточнить `ls tests/Frontend | grep -E "NewDeal|DealDetailDrawer"`)
|
||||
|
||||
- [ ] **Step 1: NewDealDialog.vue — пустые опции**
|
||||
|
||||
Импорт (строка 17):
|
||||
```ts
|
||||
import { type MockDeal, type MockManager } from '../../composables/mockDeals';
|
||||
```
|
||||
`projectOptions`/`managerOptions` (строки 24-25):
|
||||
```ts
|
||||
const projectOptions = ref<string[]>([]);
|
||||
const managerOptions = ref<MockManager[]>([]);
|
||||
```
|
||||
Doc-комментарий блока (строки 19-23) — переписать без «fallback на MOCK»:
|
||||
```ts
|
||||
/**
|
||||
* Списки проектов и менеджеров грузятся с backend через GET /api/projects,
|
||||
* /api/managers при открытии диалога (если передан tenantId). На fail —
|
||||
* списки пустые + degradation-alter (lookupsFailed), создание блокируется
|
||||
* до повторной успешной загрузки.
|
||||
*/
|
||||
```
|
||||
Комментарий строки 80 → `// Audit C6: loadLookups упал → показываем degradation-alert (списки пусты).`
|
||||
Alert `lookups-error-alert` (строки ~207-210) — текст:
|
||||
```
|
||||
Не удалось загрузить списки проектов и менеджеров — попробуйте позже.
|
||||
```
|
||||
`defineExpose` (строка 178) — добавить `projectOptions`, `managerOptions` для seed в тестах:
|
||||
```ts
|
||||
defineExpose({ lookupsFailed, projectOptions, managerOptions });
|
||||
```
|
||||
|
||||
- [ ] **Step 2: DealDetailDrawer.vue — пустой timeline**
|
||||
|
||||
Импорт (строка 23):
|
||||
```ts
|
||||
import { type DealEvent } from '../../composables/mockDealEvents';
|
||||
```
|
||||
`events` init (строка 60):
|
||||
```ts
|
||||
const events = ref<DealEvent[]>([]);
|
||||
```
|
||||
`loadEvents` — путь без deal/tenantId (строка 119): `events.value = [];`
|
||||
`loadEvents` — catch (строка 131): `events.value = [];`
|
||||
Комментарий строки 59 → `// показываем реальные events. На fail / без tenant_id — events пуст + eventsFetchError.`
|
||||
|
||||
- [ ] **Step 3: Regression-тесты (red-green)**
|
||||
|
||||
NewDealDialog spec: тест — открыть диалог **без** `tenantId`, проверить `vm.projectOptions.length === 0` и `vm.managerOptions.length === 0` (раньше были mock). DealDetailDrawer spec: тест — `loadEvents` без tenantId / при reject `getDeal` → `vm.events.length === 0`. Сначала прогнать (увидеть fail на старом коде — но код уже правится в Step 1-2, поэтому: написать тест → если spec'и проверяют наличие mock-данных, они упадут до правки; порядок — допустимо написать тест и правку вместе, ключ: после правки тест зелёный и проверяет именно пустоту).
|
||||
|
||||
- [ ] **Step 4: Починить существующие тесты**
|
||||
|
||||
NewDealDialog: тесты submit-flow выбирают проект/менеджера — засеять `vm.projectOptions`/`vm.managerOptions` после mount (Вариант B), либо замокать `dealsApi.listProjects`/`listManagers` резолвом с фикстурой (Вариант A) при открытии с `tenantId`. DealDetailDrawer: тесты timeline — замокать `dealsApi.getDeal` резолвом с events, либо засеять `vm.events`. Тесты на `events-fetch-error-alert` (Sprint 5B C7) — events уже пуст при ошибке, проверить что alert по-прежнему рендерится.
|
||||
|
||||
- [ ] **Step 5: Полный прогон + линт**
|
||||
|
||||
Run: `cd app && npm run test:vue -- NewDealDialog DealDetailDrawer && npm run lint:vue && npm run type-check`
|
||||
Expected: PASS 0 fail, ESLint 0, vue-tsc 0.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/components/deals/NewDealDialog.vue app/resources/js/components/deals/DealDetailDrawer.vue app/tests/Frontend/
|
||||
git commit -m "fix(deals): I3 — убрать mock-fallback в NewDealDialog/DealDetailDrawer"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 3: AdminBillingView + AdminIncidentsView — убрать mockAdmin fallback
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/views/admin/AdminBillingView.vue`
|
||||
- Modify: `app/resources/js/views/admin/AdminIncidentsView.vue`
|
||||
- Test: `app/tests/Frontend/AdminBillingView.spec.ts`, `AdminBillingViewApi.spec.ts`, `AdminIncidentsView.spec.ts`, `AdminIncidentsViewApi.spec.ts`
|
||||
|
||||
- [ ] **Step 1: AdminBillingView.vue — init пустой**
|
||||
|
||||
Удалить импорт (строка 11) `import { ADMIN_BILLING_SUMMARY as MOCK_SUMMARY, ADMIN_BILLING_TENANTS } from '../../composables/mockAdmin';` целиком.
|
||||
`rowsState` (строки 37-49):
|
||||
```ts
|
||||
const rowsState = reactive<BillingRow[]>([]);
|
||||
```
|
||||
`summary` (строки 51-56):
|
||||
```ts
|
||||
const summary = reactive({
|
||||
total_mrr_rub: 0,
|
||||
monthly_revenue_rub: 0,
|
||||
overdue_count: 0,
|
||||
refunds_count_30d: 0,
|
||||
});
|
||||
```
|
||||
Doc-комментарий вверху (строки 8-9): `MVP — только display-вьюха с mock-данными.` → `Данные грузятся с backend GET /api/admin/billing.`
|
||||
Комментарий строки 20-24 (над `BillingRow`) — убрать упоминание «initial = MOCK».
|
||||
Alert `fetch-error-alert` (строки ~249-259) — текст: `Не удалось загрузить биллинг. Попробуйте обновить.`
|
||||
|
||||
- [ ] **Step 2: AdminIncidentsView.vue — init пустой**
|
||||
|
||||
Удалить импорт (строка 12) `import { ADMIN_INCIDENTS } from '../../composables/mockAdmin';`.
|
||||
`rowsState` (строки 77-90):
|
||||
```ts
|
||||
const rowsState = reactive<IncidentRow[]>([]);
|
||||
```
|
||||
Удалить блок initial-stats из mock (строки 96-100 — `stats.open = rowsState.filter(...)` ×3). `stats` остаётся инициализированным нулями на строке 91.
|
||||
Комментарий строки 76 (`// Reactive — initial = MOCK; replace на API на mount.`) → `// Reactive — наполняется через loadIncidents (API).`
|
||||
Комментарий строки 95 (`// Initial stats из mock ...`) — удалить вместе с блоком.
|
||||
Doc-комментарий (строки 8-9): `MVP — display + фильтр ...` → `Display + фильтр по статусу/severity. Данные с backend GET /api/admin/incidents.`
|
||||
Alert `fetch-error-alert` (строки ~170-180) — текст: `Не удалось загрузить инциденты. Попробуйте обновить.`
|
||||
|
||||
- [ ] **Step 3: Тесты — инвертировать fake-fallback ассерты + regression**
|
||||
|
||||
`AdminBillingViewApi.spec.ts:96-106` — тест `'reject → fetchError=true + alert виден + MOCK fallback остаётся'`:
|
||||
- заголовок → `'reject → fetchError=true + alert виден + rowsState пустой'`
|
||||
- `expect(vm.rowsState.length).toBeGreaterThan(0);` → `expect(vm.rowsState.length).toBe(0);`
|
||||
Аналогично проверить `AdminIncidentsViewApi.spec.ts` на наличие «MOCK fallback»-ассертов — инвертировать.
|
||||
Smoke-спеки `AdminBillingView.spec.ts` / `AdminIncidentsView.spec.ts`: если рендерят строки из mock-init — применить Вариант A (замокать `adminApi.listAdminBilling`/`listAdminIncidents` резолвом фикстуры — фикстуру взять из `composables/mockAdmin` импортом в спеке) или Вариант B (seed `vm.rowsState`). Добавить regression-тест где ещё нет: reject → `rowsState.length === 0` + `fetchError`.
|
||||
|
||||
- [ ] **Step 4: Полный прогон + линт**
|
||||
|
||||
Run: `cd app && npm run test:vue -- AdminBilling AdminIncidents && npm run lint:vue && npm run type-check`
|
||||
Expected: PASS 0 fail, ESLint 0, vue-tsc 0.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/views/admin/AdminBillingView.vue app/resources/js/views/admin/AdminIncidentsView.vue app/tests/Frontend/
|
||||
git commit -m "fix(admin): I3 — убрать mockAdmin fallback в Billing/Incidents"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: AdminSystemView + AdminTenantsView — убрать mock fallback
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/views/admin/AdminSystemView.vue`
|
||||
- Modify: `app/resources/js/views/admin/AdminTenantsView.vue`
|
||||
- Test: `app/tests/Frontend/AdminSystemView.spec.ts`, `AdminTenantsView.spec.ts`, `AdminTenantsViewApi.spec.ts`
|
||||
|
||||
- [ ] **Step 1: AdminSystemView.vue — init пустой**
|
||||
|
||||
Удалить импорт mock-данных (строка 11) `import { ADMIN_SYSTEM_SETTINGS } from '../../composables/mockAdmin';`.
|
||||
**Оставить** импорт типа (строка 12) `import type { AdminSystemSetting } from '../../composables/mockAdmin';` — тип используется.
|
||||
`settingsState` (строка 30):
|
||||
```ts
|
||||
const settingsState = reactive<AdminSystemSetting[]>([]);
|
||||
```
|
||||
Комментарий строки 23-29 (над `settingsState`) — убрать «Инициируется mock-данными (fallback...)»:
|
||||
```ts
|
||||
/**
|
||||
* Settings-state. Наполняется на mount через `adminApi.listSystemSettings()`.
|
||||
* До загрузки и при ошибке — пустой; ошибка показывается через fetchError-banner.
|
||||
*/
|
||||
```
|
||||
Catch в `loadSettings` (строка 41) — текст fallback в `extractErrorMessage`:
|
||||
```ts
|
||||
fetchError.value = extractErrorMessage(err, 'Не удалось загрузить настройки с сервера. Попробуйте обновить.');
|
||||
```
|
||||
Комментарий строки 39-40 (`// На fail оставляем mock ...`) → `// На fail — settingsState пустой, показываем error-banner.`
|
||||
Doc-комментарий (строки 8-9): `MVP — display + read-only edit-режим.` → `Display + edit-режим. Данные с backend GET /api/admin/system-settings.`
|
||||
|
||||
- [ ] **Step 2: AdminTenantsView.vue — init пустой**
|
||||
|
||||
Импорт (строка 18) — убрать `MOCK_STATS`, `MOCK_TENANTS`:
|
||||
```ts
|
||||
import { type AdminTenant, type TenantStatus } from '../../composables/mockTenants';
|
||||
```
|
||||
`tenantsState` (строка 32):
|
||||
```ts
|
||||
const tenantsState = reactive<AdminTenant[]>([]);
|
||||
```
|
||||
`stats` (строка 33) — заменить `{ ...MOCK_STATS }` объектом с теми же ключами в нулях. **Сверить точную форму `MOCK_STATS` в `composables/mockTenants.ts`** (`loadTenants` пишет `total/active/trial/overdue`):
|
||||
```ts
|
||||
const stats = reactive({ total: 0, active: 0, trial: 0, overdue: 0 });
|
||||
```
|
||||
Alert `fetch-error-alert` (строки ~117-127) — текст: `Не удалось загрузить тенантов. Попробуйте обновить.`
|
||||
|
||||
- [ ] **Step 3: Тесты — починить + regression**
|
||||
|
||||
`AdminTenantsViewApi.spec.ts` — проверить на «MOCK fallback»-ассерты после reject, инвертировать на `length === 0`.
|
||||
Smoke-спеки `AdminSystemView.spec.ts` / `AdminTenantsView.spec.ts` — рендер строк из mock-init: Вариант A (мок `adminApi.listSystemSettings`/`listAdminTenants` резолвом фикстуры) или B (seed `vm.settingsState`/`vm.tenantsState`). Regression-тест: reject → state пустой + ошибка видна (`AdminSystemView.fetchError` — это `string|null`, при ошибке непустая строка; `AdminTenantsView.fetchError` — boolean).
|
||||
|
||||
- [ ] **Step 4: Полный прогон + линт**
|
||||
|
||||
Run: `cd app && npm run test:vue -- AdminSystem AdminTenants && npm run lint:vue && npm run type-check`
|
||||
Expected: PASS 0 fail, ESLint 0, vue-tsc 0.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/views/admin/AdminSystemView.vue app/resources/js/views/admin/AdminTenantsView.vue app/tests/Frontend/
|
||||
git commit -m "fix(admin): I3 — убрать mock fallback в System/Tenants"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 5: I4 — ImpersonationDialog devPlainCode за DEV-gate
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/resources/js/components/admin/ImpersonationDialog.vue`
|
||||
- Test: `app/tests/Frontend/ImpersonationDialog*.spec.ts` (уточнить `ls`)
|
||||
|
||||
Контекст: баннер `data-testid="dev-code-banner"` (строки ~218-228) показывает `_dev_plain_code` (плейн-код impersonation) при `v-if="devPlainCode"`. Сейчас гейт — только наличие данных (backend на prod не отдаёт `_dev_plain_code`). Аудит I4: добавить явный frontend DEV-gate, чтобы баннер не отрисовывался в prod-сборке даже если бэк случайно отдаст код.
|
||||
|
||||
- [ ] **Step 1: Regression-тест (red) — баннер скрыт в prod**
|
||||
|
||||
В spec ImpersonationDialog добавить тест: `vi.stubEnv('DEV', false)` **до** mount → пройти flow до step `verify` с непустым `devPlainCode` (замокать `adminApi.impersonationInit` резолвом с `_dev_plain_code: '123456'`) → `expect(wrapper.find('[data-testid="dev-code-banner"]').exists()).toBe(false)`. `afterEach(() => vi.unstubAllEnvs())`.
|
||||
|
||||
- [ ] **Step 2: Прогон — упадёт**
|
||||
|
||||
Run: `cd app && npm run test:vue -- ImpersonationDialog`
|
||||
Expected: новый тест FAIL (баннер рендерится — гейт только по `devPlainCode`).
|
||||
|
||||
- [ ] **Step 3: ImpersonationDialog.vue — DEV-gate**
|
||||
|
||||
В `<script setup>` после `const devPlainCode = ref<string | null>(null);` (строка 49) добавить:
|
||||
```ts
|
||||
// I4: явный frontend DEV-gate. import.meta.env.DEV статически заменяется Vite —
|
||||
// в prod-сборке = false, баннер с плейн-кодом tree-shake'ится.
|
||||
const isDevEnv = import.meta.env.DEV;
|
||||
```
|
||||
`defineExpose` отсутствует — не добавлять (тест проверяет через DOM).
|
||||
Шаблон, баннер (строка 219) — гейт:
|
||||
```html
|
||||
<v-alert
|
||||
v-if="isDevEnv && devPlainCode"
|
||||
```
|
||||
Doc-комментарий (строка 8) — уточнить: `На dev показывается _dev_plain_code (за import.meta.env.DEV; на prod — баннер не рендерится).`
|
||||
|
||||
- [ ] **Step 4: Прогон — зелёный**
|
||||
|
||||
Run: `cd app && npm run test:vue -- ImpersonationDialog`
|
||||
Expected: новый тест PASS. Существующие тесты (в Vitest `import.meta.env.DEV === true`) — баннер по-прежнему виден при `devPlainCode`, PASS.
|
||||
|
||||
- [ ] **Step 5: Линт + type-check**
|
||||
|
||||
Run: `cd app && npm run lint:vue && npm run type-check`
|
||||
Expected: ESLint 0, vue-tsc 0.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add app/resources/js/components/admin/ImpersonationDialog.vue app/tests/Frontend/
|
||||
git commit -m "fix(admin): I4 — devPlainCode-баннер за import.meta.env.DEV"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Self-Review (контроллер, после всех задач)
|
||||
|
||||
- **Покрытие спека:** I3 — 8 production-файлов init→пусто + error-текст (✓ T1-T4). I4 — DEV-gate (✓ T5). I1 — отложен (вне scope).
|
||||
- **Нет fake-data в prod-путях:** grep `MOCK_|ADMIN_` по `resources/js/views` + `resources/js/components/deals` + `ImpersonationDialog` — 0 совпадений в production-импортах (только типы). `mock*.ts` не удалены — типы/константы (`MockDeal`, `DEALS_TABS`, `AdminTenant`...) живы.
|
||||
- **Тесты:** полный `npm run test:vue` зелёный, 0 fail; новые regression-тесты на каждый файл; инвертированы явные «MOCK fallback» ассерты.
|
||||
- **Регрессия:** `npm run lint:vue` 0, `npm run type-check` 0, Pest не затронут (только frontend).
|
||||
@@ -31,6 +31,10 @@ pre-commit:
|
||||
# 2. markdownlint — стиль Markdown с авто-fix
|
||||
- name: markdownlint
|
||||
glob: "*.md"
|
||||
# Вендоренный сторонний скил .claude/skills/mermaid/ — не линтуем (его .md
|
||||
# не наши; markdownlint-cli2 игнорирует .markdownlintignore при явных путях).
|
||||
exclude:
|
||||
- ".claude/skills/mermaid/**"
|
||||
run: npx markdownlint-cli2 --fix {staged_files}
|
||||
stage_fixed: true
|
||||
fail_text: |
|
||||
@@ -42,6 +46,9 @@ pre-commit:
|
||||
# (useGitignore:true) игнорирует worktree-коммиты под gitignored .claude/worktrees/.
|
||||
- name: cspell
|
||||
glob: "*.md"
|
||||
# Вендоренный сторонний скил .claude/skills/mermaid/ — не проверяем орфографию.
|
||||
exclude:
|
||||
- ".claude/skills/mermaid/**"
|
||||
run: npx cspell --no-progress --no-summary --no-gitignore {staged_files}
|
||||
fail_text: |
|
||||
cspell нашёл слова, отсутствующие в словаре.
|
||||
@@ -109,6 +116,20 @@ pre-commit:
|
||||
Запусти `cd app && npm run lint:vue` локально и поправь.
|
||||
Авто-форматирование: `cd app && npm run format`.
|
||||
|
||||
# 9. adr-judge — декларативная проверка ADR Enforcement-блоков (Прил. Н #36).
|
||||
# Читает `## Enforcement` блоки docs/adr/ADR-*.md, применяет forbid_*/require_*
|
||||
# правила к staged-дифу. Вендорен из adr-kit v0.13.1 (MIT) → tools/adr-judge.py;
|
||||
# пере-вендорить после `/adr-kit:upgrade`. Без --llm → только regex, без вызова
|
||||
# Claude API, нулевая стоимость (AK6). Без glob — adr-judge нужен весь staged-диф.
|
||||
# `-X utf8` обязателен: stdin Python на Windows = cp1251; диф с кириллицей иначе
|
||||
# ломается на surrogate → UnicodeEncodeError (crash, не ADR-violation).
|
||||
- name: adr-judge
|
||||
run: git diff --cached --unified=0 | python -X utf8 tools/adr-judge.py --diff - --adr-dir docs/adr/
|
||||
fail_text: |
|
||||
adr-judge: staged-изменение нарушает задокументированное архитектурное
|
||||
решение (ADR). Смотри file:line выше и docs/adr/ADR-*.md.
|
||||
Если ADR устарел — сначала обнови ADR (и его Enforcement-блок).
|
||||
|
||||
# Pre-push: проверки перед git push (медленнее, но реже запускаются)
|
||||
pre-push:
|
||||
parallel: false
|
||||
|
||||
@@ -0,0 +1,819 @@
|
||||
#!/usr/bin/env python3
|
||||
"""adr-judge: diff-vs-ADR engine for adr-kit (v0.12.0+).
|
||||
|
||||
Pairs with the /adr-kit:judge Claude Code skill. Two evaluation paths run
|
||||
on every commit when invoked from the pre-commit hook:
|
||||
|
||||
1. Declarative pass — fast, regex-only, no LLM round-trip. Reads the
|
||||
fenced JSON Enforcement block of each Accepted ADR and applies
|
||||
forbid_pattern / forbid_import / require_pattern rules to the staged
|
||||
git diff.
|
||||
|
||||
2. LLM pass (v0.13.0+, opt-out via ADR_KIT_NO_LLM=1) — for ADRs with
|
||||
`llm_judge: true`, batches all of them into ONE Claude Sonnet call
|
||||
(default: claude-sonnet-4-6 via `claude -p`). Sonnet returns a JSON
|
||||
verdict object {ADR-NNN: {verdict: OK | VIOLATION, reason: ...}}.
|
||||
The LLM pass requires the `claude` CLI on PATH; if missing or auth
|
||||
fails, adr-judge prints a warning and falls back to declarative-only
|
||||
so a missing CLI never blocks legitimate commits.
|
||||
|
||||
ADRs without an Enforcement block are skipped silently regardless of mode.
|
||||
|
||||
Exit codes (mirror bin/adr-lint):
|
||||
0 no violations (advisory entries may exist)
|
||||
1 at least one violation (declarative or LLM)
|
||||
2 config or input error
|
||||
|
||||
Usage:
|
||||
adr-judge # diff from stdin, ADRs from docs/adr/
|
||||
adr-judge --diff <file> # read diff from a file (use - for stdin)
|
||||
adr-judge --llm # also run the LLM pass for llm_judge:true ADRs
|
||||
adr-judge --llm-cmd "claude -p ..." # override the LLM invocation (tests, custom models)
|
||||
adr-judge --json # machine-readable output
|
||||
adr-judge --config <path> # override .adr-kit.json location
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import fnmatch
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
# Default LLM invocation. Overridable via --llm-cmd, ADR_KIT_LLM_CMD env, or
|
||||
# .adr-kit.json's judge.llm_cmd. Tests inject a fake binary here.
|
||||
DEFAULT_LLM_CMD = ["claude", "-p", "--model", "claude-sonnet-4-6"]
|
||||
DEFAULT_LLM_TIMEOUT_S = 120
|
||||
|
||||
# ---------- ADR / diff parsing ----------
|
||||
|
||||
ADR_FILENAME_RE = re.compile(r"(?i)^ADR-(\d{1,4})-.*\.md$")
|
||||
STATUS_LINE_RE = re.compile(r"^Status\s*:?\s*(\w+)", re.IGNORECASE | re.MULTILINE)
|
||||
STATUS_HEADING_RE = re.compile(
|
||||
r"^##\s+Status\s*$\n+([^\n]+)", re.IGNORECASE | re.MULTILINE
|
||||
)
|
||||
# Legacy bold-inline Status format used by many pre-canonical ADR sets:
|
||||
# **Status:** Accepted
|
||||
# **Status**: Proposed
|
||||
# **Status: Accepted**
|
||||
# adr-lint flags these on Completeness (no '## Status' heading), which is
|
||||
# correct — but adr-judge only needs the *value* to decide whether to enforce.
|
||||
# Recognising the bold-inline form here means a project mid-migration still
|
||||
# gets diff-vs-Enforcement coverage on its Accepted ADRs without first having
|
||||
# to run /adr-kit:migrate. See v0.12.1 changelog.
|
||||
STATUS_BOLD_INLINE_RE = re.compile(
|
||||
r"^\s*\*\*\s*Status\s*:?\s*\*\*\s*:?\s*([A-Za-z]+)|^\s*\*\*\s*Status\s*:?\s*([A-Za-z]+)\s*\*\*",
|
||||
re.IGNORECASE | re.MULTILINE,
|
||||
)
|
||||
ENFORCEMENT_BLOCK_RE = re.compile(
|
||||
r"^##\s+Enforcement\s*$\n+(?:.*?\n)*?```json\s*\n(.*?)\n```",
|
||||
re.IGNORECASE | re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
HUNK_HEADER_RE = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@")
|
||||
|
||||
|
||||
class JudgeError(Exception):
|
||||
"""Raised on configuration / input errors (exit code 2)."""
|
||||
|
||||
|
||||
def adr_status(text: str) -> Optional[str]:
|
||||
"""Return the ADR's status (Accepted/Proposed/Deprecated/Superseded) or None.
|
||||
|
||||
Handles all of these (case-insensitive):
|
||||
Status: Accepted, 2026-04-25. (single-line plain, anywhere)
|
||||
## Status\n\nAccepted, 2026-04-25. (heading + body, comma form)
|
||||
## Status\n\nAccepted. Date: 2026-04-25. (heading + body, period form)
|
||||
## Status\n\nSuperseded by ADR-099, 2026-05-01.
|
||||
**Status:** Accepted (bold-inline, since v0.12.1)
|
||||
**Status**: Proposed (bold-inline, alt punctuation)
|
||||
**Status: Accepted** (bold-inline, fully bracketed)
|
||||
|
||||
Returns the first alphabetic word it finds in the status line. Trailing
|
||||
punctuation is stripped so 'Accepted.' becomes 'Accepted'.
|
||||
"""
|
||||
m = STATUS_HEADING_RE.search(text)
|
||||
if m:
|
||||
line = m.group(1).strip()
|
||||
wm = re.match(r"\s*([A-Za-z]+)", line)
|
||||
return wm.group(1) if wm else None
|
||||
m = STATUS_BOLD_INLINE_RE.search(text)
|
||||
if m:
|
||||
return m.group(1) or m.group(2)
|
||||
m = STATUS_LINE_RE.search(text)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def parse_enforcement(adr_text: str, adr_path: Path) -> Optional[Dict]:
|
||||
"""Extract and parse the JSON inside an ADR's ## Enforcement section.
|
||||
|
||||
Returns None when there is no Enforcement section. Raises JudgeError when
|
||||
the section exists but the JSON is malformed.
|
||||
"""
|
||||
m = ENFORCEMENT_BLOCK_RE.search(adr_text)
|
||||
if not m:
|
||||
return None
|
||||
raw = m.group(1)
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except json.JSONDecodeError as e:
|
||||
raise JudgeError(
|
||||
f"{adr_path}: malformed JSON in ## Enforcement block "
|
||||
f"({e.msg} at line {e.lineno})"
|
||||
)
|
||||
if not isinstance(data, dict):
|
||||
raise JudgeError(
|
||||
f"{adr_path}: ## Enforcement JSON must be an object, got {type(data).__name__}"
|
||||
)
|
||||
|
||||
# Basic shape validation. Optional jsonschema deeper check below.
|
||||
for key in ("forbid_pattern", "require_pattern", "forbid_import"):
|
||||
if key in data and not isinstance(data[key], list):
|
||||
raise JudgeError(f"{adr_path}: Enforcement.{key} must be an array")
|
||||
if "llm_judge" in data and not isinstance(data["llm_judge"], bool):
|
||||
raise JudgeError(f"{adr_path}: Enforcement.llm_judge must be a boolean")
|
||||
|
||||
try:
|
||||
import jsonschema # type: ignore
|
||||
schema_path = (
|
||||
Path(__file__).resolve().parent.parent
|
||||
/ "schemas"
|
||||
/ "adr-enforcement.schema.json"
|
||||
)
|
||||
if schema_path.exists():
|
||||
schema = json.loads(schema_path.read_text(encoding="utf-8"))
|
||||
jsonschema.validate(data, schema)
|
||||
except ImportError:
|
||||
pass
|
||||
except Exception as e:
|
||||
raise JudgeError(f"{adr_path}: Enforcement block fails schema validation: {e}")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def parse_diff(text: str) -> Dict[str, List[Tuple[int, str]]]:
|
||||
"""Extract (lineno, content) tuples per added line, keyed by post-diff path.
|
||||
|
||||
Skips deleted files (+++ /dev/null) and binary diffs. Tracks the new-file
|
||||
line counter via the @@ hunk header so reporting can cite file:line.
|
||||
"""
|
||||
files: Dict[str, List[Tuple[int, str]]] = {}
|
||||
current: Optional[str] = None
|
||||
lineno = 0
|
||||
for line in text.splitlines():
|
||||
if line.startswith("+++ "):
|
||||
target = line[4:].strip()
|
||||
if target == "/dev/null" or target.startswith("/dev/null"):
|
||||
current = None
|
||||
else:
|
||||
# Strip leading "b/" if present (git default)
|
||||
current = target[2:] if target.startswith("b/") else target
|
||||
files.setdefault(current, [])
|
||||
elif line.startswith("@@ "):
|
||||
m = HUNK_HEADER_RE.match(line)
|
||||
if m:
|
||||
lineno = int(m.group(1))
|
||||
elif current and line.startswith("+") and not line.startswith("+++"):
|
||||
files[current].append((lineno, line[1:]))
|
||||
lineno += 1
|
||||
elif line.startswith(" "):
|
||||
lineno += 1
|
||||
# diff --git, index, ---, --- /dev/null, removed lines: ignored
|
||||
return files
|
||||
|
||||
|
||||
# ---------- glob & rule application ----------
|
||||
|
||||
|
||||
def glob_to_regex(glob: str) -> re.Pattern:
|
||||
"""Translate a shell-style glob (with ** for recursive descent) to a regex.
|
||||
|
||||
Examples:
|
||||
*.py → only top-level .py
|
||||
**/*.py → any .py at any depth (including top-level)
|
||||
src/**/*.py → any .py under src/ at any depth
|
||||
src/** → anything under src/
|
||||
src/**/*.{ino,cpp,h} → .ino, .cpp, or .h files anywhere under src/ (v0.12.2+)
|
||||
src/{a,b,c}.ino → exactly src/a.ino, src/b.ino, or src/c.ino (v0.12.2+)
|
||||
|
||||
Brace expansion ({a,b,c}) was added in v0.12.2 — without it, real-world
|
||||
Enforcement-block path_globs that scope to a list of source files
|
||||
silently match nothing (regressed from common shell-glob expectations).
|
||||
"""
|
||||
out: List[str] = []
|
||||
i = 0
|
||||
while i < len(glob):
|
||||
c = glob[i]
|
||||
if c == "*":
|
||||
if i + 1 < len(glob) and glob[i + 1] == "*":
|
||||
# consume **, then optional trailing slash
|
||||
if i + 2 < len(glob) and glob[i + 2] == "/":
|
||||
out.append("(?:.*/)?")
|
||||
i += 3
|
||||
else:
|
||||
out.append(".*")
|
||||
i += 2
|
||||
else:
|
||||
out.append("[^/]*")
|
||||
i += 1
|
||||
elif c == "?":
|
||||
out.append("[^/]")
|
||||
i += 1
|
||||
elif c == "{":
|
||||
# Brace expansion: {a,b,c} -> (?:a|b|c). Find matching closing brace.
|
||||
# Nested braces are not supported; if the user needs them they should
|
||||
# restructure the glob. An unclosed brace is treated literally.
|
||||
close = glob.find("}", i + 1)
|
||||
if close == -1 or "{" in glob[i + 1:close]:
|
||||
out.append(re.escape(c))
|
||||
i += 1
|
||||
else:
|
||||
inner = glob[i + 1:close]
|
||||
alts = inner.split(",")
|
||||
# Each alternative is a sub-glob, recursively translated. Wrap in
|
||||
# a non-capturing group with anchored sub-patterns stripped of the
|
||||
# surrounding ^...$ that glob_to_regex would otherwise add.
|
||||
alt_patterns = [
|
||||
glob_to_regex(a).pattern.lstrip("^").rstrip("$") if a else ""
|
||||
for a in alts
|
||||
]
|
||||
out.append("(?:" + "|".join(alt_patterns) + ")")
|
||||
i = close + 1
|
||||
else:
|
||||
out.append(re.escape(c))
|
||||
i += 1
|
||||
return re.compile("^" + "".join(out) + "$")
|
||||
|
||||
|
||||
def path_matches(path: str, glob: Optional[str]) -> bool:
|
||||
"""True when path matches the glob, or no glob is set."""
|
||||
if not glob:
|
||||
return True
|
||||
return bool(glob_to_regex(glob).match(path))
|
||||
|
||||
|
||||
def any_skip_match(path: str, skip_globs: List[str]) -> bool:
|
||||
return any(path_matches(path, g) for g in skip_globs)
|
||||
|
||||
|
||||
def apply_rules_to_diff(
|
||||
adr_id: str,
|
||||
enforcement: Dict,
|
||||
diff_files: Dict[str, List[Tuple[int, str]]],
|
||||
repo_root: Path,
|
||||
skip_files: List[str],
|
||||
llm_mode_active: bool = False,
|
||||
) -> List[Dict]:
|
||||
"""Apply one ADR's Enforcement block to the parsed diff. Returns findings.
|
||||
|
||||
When ``llm_mode_active`` is True (added in v0.13.0), pure-llm_judge ADRs
|
||||
(those with no declarative rules) are NOT emitted as advisories here —
|
||||
they are batched into the LLM pass instead. When False, the v0.12.x
|
||||
advisory behaviour is preserved so existing hooks that don't pass
|
||||
--llm continue working unchanged.
|
||||
"""
|
||||
findings: List[Dict] = []
|
||||
for kind in ("forbid_pattern", "forbid_import"):
|
||||
for rule in enforcement.get(kind, []):
|
||||
pattern = rule.get("pattern")
|
||||
path_glob = rule.get("path_glob")
|
||||
message = rule.get("message") or f"{kind}: {pattern}"
|
||||
try:
|
||||
regex = re.compile(pattern)
|
||||
except re.error as e:
|
||||
raise JudgeError(
|
||||
f"{adr_id}: invalid regex in {kind} rule ({pattern!r}): {e}"
|
||||
)
|
||||
for path, added in diff_files.items():
|
||||
if any_skip_match(path, skip_files):
|
||||
continue
|
||||
if not path_matches(path, path_glob):
|
||||
continue
|
||||
for lineno, content in added:
|
||||
if regex.search(content):
|
||||
findings.append(
|
||||
{
|
||||
"adr": adr_id,
|
||||
"rule": kind,
|
||||
"pattern": pattern,
|
||||
"path": path,
|
||||
"line": lineno,
|
||||
"snippet": content.rstrip("\n")[:200],
|
||||
"message": message,
|
||||
"severity": "violation",
|
||||
}
|
||||
)
|
||||
|
||||
for rule in enforcement.get("require_pattern", []):
|
||||
pattern = rule.get("pattern")
|
||||
path_glob = rule.get("path_glob")
|
||||
message = rule.get("message") or f"require_pattern: {pattern}"
|
||||
try:
|
||||
regex = re.compile(pattern, re.MULTILINE)
|
||||
except re.error as e:
|
||||
raise JudgeError(
|
||||
f"{adr_id}: invalid regex in require_pattern rule ({pattern!r}): {e}"
|
||||
)
|
||||
for path in diff_files:
|
||||
if any_skip_match(path, skip_files):
|
||||
continue
|
||||
if not path_matches(path, path_glob):
|
||||
continue
|
||||
file_path = repo_root / path
|
||||
if not file_path.is_file():
|
||||
continue
|
||||
try:
|
||||
content = file_path.read_text(encoding="utf-8", errors="replace")
|
||||
except OSError:
|
||||
continue
|
||||
if not regex.search(content):
|
||||
findings.append(
|
||||
{
|
||||
"adr": adr_id,
|
||||
"rule": "require_pattern",
|
||||
"pattern": pattern,
|
||||
"path": path,
|
||||
"line": None,
|
||||
"snippet": None,
|
||||
"message": message,
|
||||
"severity": "violation",
|
||||
}
|
||||
)
|
||||
|
||||
if enforcement.get("llm_judge") and not (
|
||||
enforcement.get("forbid_pattern")
|
||||
or enforcement.get("forbid_import")
|
||||
or enforcement.get("require_pattern")
|
||||
):
|
||||
if not llm_mode_active:
|
||||
# v0.12.x behaviour: hook stays advisory; user runs /adr-kit:judge.
|
||||
findings.append(
|
||||
{
|
||||
"adr": adr_id,
|
||||
"rule": "llm_judge",
|
||||
"pattern": None,
|
||||
"path": None,
|
||||
"line": None,
|
||||
"snippet": None,
|
||||
"message": (
|
||||
"ADR has llm_judge:true and no declarative rules; "
|
||||
"run /adr-kit:judge in your Claude Code session for full coverage."
|
||||
),
|
||||
"severity": "advisory",
|
||||
}
|
||||
)
|
||||
# else: handled by run_llm_batch — produces "violation" or nothing per ADR.
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
# ---------- LLM judge pass (v0.13.0+) ----------
|
||||
|
||||
DECISION_SECTION_RE = re.compile(
|
||||
r"^##\s+Decision\s*$\n+(.*?)(?=^##\s|\Z)",
|
||||
re.IGNORECASE | re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
TITLE_RE = re.compile(r"^#\s+(.+?)\s*$", re.MULTILINE)
|
||||
|
||||
|
||||
def extract_title(body: str) -> str:
|
||||
"""Return the ADR's `# ADR-NNN Title` line text, or '' if absent."""
|
||||
m = TITLE_RE.search(body)
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
|
||||
def extract_decision(body: str) -> str:
|
||||
"""Return the body of the ## Decision section, or '' if absent.
|
||||
|
||||
The Decision section is the rule the LLM judge needs to evaluate against
|
||||
the diff. Sending it (rather than the full ADR body) keeps the prompt
|
||||
compact and the judge focused.
|
||||
"""
|
||||
m = DECISION_SECTION_RE.search(body)
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
|
||||
def collect_llm_targets(adrs: List[Tuple[str, Path, str]]) -> List[Dict]:
|
||||
"""Return [{adr_id, title, decision}] for Accepted ADRs with llm_judge:true."""
|
||||
out: List[Dict] = []
|
||||
for adr_id, adr_path, body in adrs:
|
||||
status = adr_status(body)
|
||||
if status is None or status.lower() != "accepted":
|
||||
continue
|
||||
enforcement = parse_enforcement(body, adr_path)
|
||||
if enforcement is None:
|
||||
continue
|
||||
if not enforcement.get("llm_judge"):
|
||||
continue
|
||||
title = extract_title(body)
|
||||
decision = extract_decision(body)
|
||||
if not decision:
|
||||
# Skip ADRs without a Decision section — the LLM can't reason
|
||||
# about them. The Completeness gate would already have flagged
|
||||
# this; the judge silently skips.
|
||||
continue
|
||||
out.append({"adr_id": adr_id, "title": title, "decision": decision})
|
||||
return out
|
||||
|
||||
|
||||
def build_llm_prompt(targets: List[Dict], diff_text: str) -> str:
|
||||
"""Build the single-call batch prompt for `claude -p`.
|
||||
|
||||
ADR set goes BEFORE the diff so prompt-cache hits across commits when
|
||||
the ADR set is stable. Diff is the only varying input per commit.
|
||||
"""
|
||||
parts = [
|
||||
"You are evaluating whether a staged git diff violates documented "
|
||||
"Architecture Decision Records (ADRs).",
|
||||
"",
|
||||
"For each ADR below, decide whether the diff introduces something "
|
||||
"that conflicts with the ADR's stated decision. Be conservative: "
|
||||
"only flag CLEAR violations. If the diff is unrelated to the ADR's "
|
||||
"subject area, the verdict is OK. Do not flag stylistic issues, "
|
||||
"minor refactors, or anything ambiguous.",
|
||||
"",
|
||||
"Return ONLY a single JSON object — no preamble, no commentary, no "
|
||||
"code fences. The object maps each ADR id to a verdict:",
|
||||
"",
|
||||
' {"ADR-NNN": {"verdict": "OK"}}',
|
||||
' {"ADR-NNN": {"verdict": "VIOLATION", "reason": "<one sentence '
|
||||
'citing the file and what conflicts>"}}',
|
||||
"",
|
||||
"=== ADRS TO EVALUATE ===",
|
||||
"",
|
||||
]
|
||||
for t in targets:
|
||||
parts.append(f"{t['adr_id']} — {t['title']}")
|
||||
parts.append("Decision:")
|
||||
parts.append(t["decision"])
|
||||
parts.append("")
|
||||
parts.append("=== STAGED DIFF ===")
|
||||
parts.append("")
|
||||
parts.append(diff_text if diff_text.strip() else "(empty diff)")
|
||||
parts.append("")
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def parse_llm_response(raw: str) -> Dict[str, Dict]:
|
||||
"""Extract the JSON verdict object from Claude's response.
|
||||
|
||||
Robust to: direct JSON, fenced code block, leading/trailing prose.
|
||||
Raises JudgeError when no JSON is recoverable.
|
||||
"""
|
||||
raw = raw.strip()
|
||||
if not raw:
|
||||
raise JudgeError("empty LLM response")
|
||||
# Try direct
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
# Try fenced code block
|
||||
m = re.search(r"```(?:json)?\s*\n(.*?)\n```", raw, re.DOTALL)
|
||||
if m:
|
||||
try:
|
||||
data = json.loads(m.group(1))
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
# Try greedy first {...} block
|
||||
m = re.search(r"\{.*\}", raw, re.DOTALL)
|
||||
if m:
|
||||
try:
|
||||
data = json.loads(m.group(0))
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
raise JudgeError(
|
||||
f"could not extract JSON from LLM response (first 200 chars): {raw[:200]!r}"
|
||||
)
|
||||
|
||||
|
||||
def run_llm_batch(
|
||||
targets: List[Dict],
|
||||
diff_text: str,
|
||||
llm_cmd: List[str],
|
||||
timeout_s: int,
|
||||
) -> Optional[List[Dict]]:
|
||||
"""Run the LLM judge over all `llm_judge: true` targets in one call.
|
||||
|
||||
Returns a list of findings (only VIOLATION entries; OK is the silent
|
||||
default). Returns None when the LLM CLI is missing, errors, or returns
|
||||
unparseable output — caller should fall back to declarative-only without
|
||||
blocking the commit.
|
||||
"""
|
||||
if not targets:
|
||||
return []
|
||||
binary = llm_cmd[0]
|
||||
if shutil.which(binary) is None:
|
||||
print(
|
||||
f"[adr-judge] WARN: LLM judge requested but {binary!r} not on PATH; "
|
||||
f"skipping LLM pass (declarative checks unaffected). "
|
||||
f"To enable, install Claude Code or set --llm-cmd.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return None
|
||||
prompt = build_llm_prompt(targets, diff_text)
|
||||
try:
|
||||
result = subprocess.run(
|
||||
llm_cmd,
|
||||
input=prompt,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout_s,
|
||||
)
|
||||
except subprocess.TimeoutExpired:
|
||||
print(
|
||||
f"[adr-judge] WARN: LLM judge timed out after {timeout_s}s; "
|
||||
f"skipping LLM pass. Increase judge.llm_timeout_seconds in "
|
||||
f".adr-kit.json if commits routinely exceed this.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return None
|
||||
if result.returncode != 0:
|
||||
print(
|
||||
f"[adr-judge] WARN: LLM judge command exited {result.returncode}: "
|
||||
f"{result.stderr.strip()[:200]!r}; skipping LLM pass.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return None
|
||||
try:
|
||||
verdicts = parse_llm_response(result.stdout)
|
||||
except JudgeError as e:
|
||||
print(f"[adr-judge] WARN: {e}; skipping LLM pass.", file=sys.stderr)
|
||||
return None
|
||||
findings: List[Dict] = []
|
||||
for t in targets:
|
||||
adr_id = t["adr_id"]
|
||||
v = verdicts.get(adr_id)
|
||||
if not isinstance(v, dict):
|
||||
continue
|
||||
if str(v.get("verdict", "")).upper() != "VIOLATION":
|
||||
continue
|
||||
reason = str(v.get("reason") or "LLM judge flagged a violation.")
|
||||
findings.append(
|
||||
{
|
||||
"adr": adr_id,
|
||||
"rule": "llm_judge",
|
||||
"pattern": None,
|
||||
"path": None,
|
||||
"line": None,
|
||||
"snippet": None,
|
||||
"message": reason[:500],
|
||||
"severity": "violation",
|
||||
}
|
||||
)
|
||||
return findings
|
||||
|
||||
|
||||
# ---------- config & top-level orchestration ----------
|
||||
|
||||
|
||||
def load_config(path: Optional[Path]) -> Dict:
|
||||
"""Read .adr-kit.json (if present). Returns {} when missing."""
|
||||
if path is None or not path.exists():
|
||||
return {}
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as e:
|
||||
raise JudgeError(f"{path}: invalid JSON ({e.msg} at line {e.lineno})")
|
||||
|
||||
|
||||
def adr_id_from_filename(name: str) -> Optional[str]:
|
||||
m = ADR_FILENAME_RE.match(name)
|
||||
if not m:
|
||||
return None
|
||||
return f"ADR-{int(m.group(1)):03d}"
|
||||
|
||||
|
||||
def collect_adrs(adr_dir: Path) -> List[Tuple[str, Path, str]]:
|
||||
"""Return [(adr_id, path, body)] for every ADR-*.md file in adr_dir."""
|
||||
out: List[Tuple[str, Path, str]] = []
|
||||
if not adr_dir.is_dir():
|
||||
return out
|
||||
for p in sorted(adr_dir.glob("ADR-*.md")):
|
||||
adr_id = adr_id_from_filename(p.name)
|
||||
if not adr_id:
|
||||
continue
|
||||
try:
|
||||
body = p.read_text(encoding="utf-8")
|
||||
except OSError:
|
||||
continue
|
||||
out.append((adr_id, p, body))
|
||||
return out
|
||||
|
||||
|
||||
def read_diff(diff_arg: str) -> str:
|
||||
if diff_arg == "-" or diff_arg == "":
|
||||
return sys.stdin.read()
|
||||
return Path(diff_arg).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def emit_text(findings: List[Dict], adr_count: int, advisory_only: bool) -> None:
|
||||
violations = [f for f in findings if f["severity"] == "violation"]
|
||||
advisories = [f for f in findings if f["severity"] == "advisory"]
|
||||
print(f"[adr-judge] checked {adr_count} ADR(s) with Enforcement blocks", file=sys.stderr)
|
||||
for f in violations:
|
||||
loc = f["path"] if f["line"] is None else f"{f['path']}:{f['line']}"
|
||||
print(f" VIOLATION {f['adr']} {f['rule']} {loc}", file=sys.stderr)
|
||||
print(f" {f['message']}", file=sys.stderr)
|
||||
if f["snippet"]:
|
||||
print(f" > {f['snippet']}", file=sys.stderr)
|
||||
for f in advisories:
|
||||
print(f" ADVISORY {f['adr']} {f['rule']}", file=sys.stderr)
|
||||
print(f" {f['message']}", file=sys.stderr)
|
||||
if violations and advisory_only:
|
||||
print(
|
||||
f"[adr-judge] {len(violations)} violation(s), {len(advisories)} advisory; "
|
||||
f"advisory_only=true → exiting 0",
|
||||
file=sys.stderr,
|
||||
)
|
||||
elif violations:
|
||||
print(
|
||||
f"[adr-judge] {len(violations)} violation(s), {len(advisories)} advisory",
|
||||
file=sys.stderr,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
f"[adr-judge] OK — 0 violations, {len(advisories)} advisory",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
|
||||
def emit_json(findings: List[Dict], adr_count: int) -> None:
|
||||
payload = {
|
||||
"summary": {
|
||||
"adrs_checked": adr_count,
|
||||
"violations": sum(1 for f in findings if f["severity"] == "violation"),
|
||||
"advisories": sum(1 for f in findings if f["severity"] == "advisory"),
|
||||
},
|
||||
"findings": findings,
|
||||
}
|
||||
json.dump(payload, sys.stdout, indent=2)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
p = argparse.ArgumentParser(
|
||||
prog="adr-judge",
|
||||
description="Apply ADR Enforcement blocks to a staged git diff.",
|
||||
)
|
||||
p.add_argument(
|
||||
"--diff",
|
||||
default="-",
|
||||
help="Path to a unified diff file (use '-' for stdin). Default: stdin.",
|
||||
)
|
||||
p.add_argument(
|
||||
"--adr-dir",
|
||||
default="docs/adr",
|
||||
help="Directory containing ADR-*.md files. Default: docs/adr.",
|
||||
)
|
||||
p.add_argument(
|
||||
"--config",
|
||||
default=None,
|
||||
help="Path to .adr-kit.json. Default: <adr-dir>/.adr-kit.json.",
|
||||
)
|
||||
p.add_argument("--json", action="store_true", help="Emit JSON to stdout.")
|
||||
p.add_argument(
|
||||
"--repo-root",
|
||||
default=None,
|
||||
help="Repo root for resolving file paths in require_pattern rules. "
|
||||
"Default: current working directory.",
|
||||
)
|
||||
p.add_argument(
|
||||
"--llm",
|
||||
action="store_true",
|
||||
help="Also run the LLM pass: batch all llm_judge:true ADRs into one "
|
||||
"Claude Sonnet call. Requires the `claude` CLI on PATH (or override "
|
||||
"via --llm-cmd). Falls back to declarative-only when the CLI is "
|
||||
"unavailable. Default off; the pre-commit hook template enables it.",
|
||||
)
|
||||
p.add_argument(
|
||||
"--llm-cmd",
|
||||
default=None,
|
||||
help="Override the LLM invocation. Default: 'claude -p --model "
|
||||
"claude-sonnet-4-6'. Tests inject a fake binary here; users may "
|
||||
"switch model via this flag or via .adr-kit.json's judge.llm_model.",
|
||||
)
|
||||
p.add_argument(
|
||||
"--llm-timeout",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Per-call timeout for the LLM pass in seconds. Default 120 "
|
||||
"(or judge.llm_timeout_seconds in .adr-kit.json).",
|
||||
)
|
||||
args = p.parse_args()
|
||||
|
||||
try:
|
||||
adr_dir = Path(args.adr_dir).resolve()
|
||||
config_path = Path(args.config) if args.config else (adr_dir / ".adr-kit.json")
|
||||
cfg = load_config(config_path)
|
||||
judge_cfg = cfg.get("judge") or {}
|
||||
skip_files = judge_cfg.get("skip_files") or []
|
||||
advisory_only = bool(judge_cfg.get("advisory_only", False))
|
||||
max_diff_bytes = int(judge_cfg.get("max_diff_bytes", 1048576))
|
||||
|
||||
# LLM mode resolution. Precedence: ADR_KIT_NO_LLM env (highest),
|
||||
# --llm flag, judge.llm_default in config (lowest).
|
||||
env_no_llm = os.environ.get("ADR_KIT_NO_LLM", "0") == "1"
|
||||
llm_mode_active = (
|
||||
args.llm or bool(judge_cfg.get("llm_default", False))
|
||||
) and not env_no_llm
|
||||
|
||||
# LLM command resolution.
|
||||
if args.llm_cmd:
|
||||
llm_cmd = shlex.split(args.llm_cmd)
|
||||
elif os.environ.get("ADR_KIT_LLM_CMD"):
|
||||
llm_cmd = shlex.split(os.environ["ADR_KIT_LLM_CMD"])
|
||||
elif judge_cfg.get("llm_cmd"):
|
||||
llm_cmd = list(judge_cfg["llm_cmd"]) if isinstance(judge_cfg["llm_cmd"], list) else shlex.split(judge_cfg["llm_cmd"])
|
||||
elif judge_cfg.get("llm_model"):
|
||||
# User specified just the model — keep the default `claude -p` shape.
|
||||
llm_cmd = ["claude", "-p", "--model", str(judge_cfg["llm_model"])]
|
||||
else:
|
||||
llm_cmd = list(DEFAULT_LLM_CMD)
|
||||
|
||||
llm_timeout_s = int(
|
||||
args.llm_timeout
|
||||
if args.llm_timeout is not None
|
||||
else judge_cfg.get("llm_timeout_seconds", DEFAULT_LLM_TIMEOUT_S)
|
||||
)
|
||||
|
||||
repo_root = Path(args.repo_root).resolve() if args.repo_root else Path.cwd()
|
||||
|
||||
diff_text = read_diff(args.diff)
|
||||
if max_diff_bytes and len(diff_text.encode("utf-8")) > max_diff_bytes:
|
||||
print(
|
||||
f"[adr-judge] diff exceeds max_diff_bytes={max_diff_bytes}; skipping",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 0
|
||||
diff_files = parse_diff(diff_text)
|
||||
|
||||
adrs = collect_adrs(adr_dir)
|
||||
all_findings: List[Dict] = []
|
||||
adrs_with_enforcement = 0
|
||||
for adr_id, adr_path, body in adrs:
|
||||
status = adr_status(body)
|
||||
if status is None or status.lower() != "accepted":
|
||||
continue
|
||||
enforcement = parse_enforcement(body, adr_path)
|
||||
if enforcement is None:
|
||||
continue
|
||||
adrs_with_enforcement += 1
|
||||
all_findings.extend(
|
||||
apply_rules_to_diff(
|
||||
adr_id, enforcement, diff_files, repo_root, skip_files,
|
||||
llm_mode_active=llm_mode_active,
|
||||
)
|
||||
)
|
||||
|
||||
# LLM pass — only when the user opted in via --llm (or judge.llm_default).
|
||||
# Failures here log a warning and fall through; they NEVER block the
|
||||
# commit, because a missing CLI or transient API hiccup must not break
|
||||
# legitimate work.
|
||||
llm_findings_emitted = 0
|
||||
if llm_mode_active:
|
||||
targets = collect_llm_targets(adrs)
|
||||
if targets:
|
||||
print(
|
||||
f"[adr-judge] running LLM pass over {len(targets)} "
|
||||
f"llm_judge ADR(s) with {llm_cmd[0]}...",
|
||||
file=sys.stderr,
|
||||
)
|
||||
llm_findings = run_llm_batch(targets, diff_text, llm_cmd, llm_timeout_s)
|
||||
if llm_findings is not None:
|
||||
all_findings.extend(llm_findings)
|
||||
llm_findings_emitted = len(llm_findings)
|
||||
|
||||
if args.json:
|
||||
emit_json(all_findings, adrs_with_enforcement)
|
||||
else:
|
||||
emit_text(all_findings, adrs_with_enforcement, advisory_only)
|
||||
|
||||
violations = [f for f in all_findings if f["severity"] == "violation"]
|
||||
if violations and not advisory_only:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
except JudgeError as e:
|
||||
print(f"[adr-judge] ERROR: {e}", file=sys.stderr)
|
||||
return 2
|
||||
except KeyboardInterrupt:
|
||||
return 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user