From f8c6470edeafec4d18e0add6617268b2772b7b1d Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 8 Oct 2026 02:50:11 +0800 Subject: [PATCH] docs: add bilingual showcases and synchronize translated READMEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加中英文 Jeepay 交互展示和复杂时序图展示 - 在安装指南前增加五条快速入门示例 - 以中文版为底稿同步六种语言 README - 补齐十一类图说明、架构总览及演示录制脚本 --- README.md | 108 +- docs/readme/README.de.md | 124 +- docs/readme/README.es.md | 124 +- docs/readme/README.ja.md | 124 +- docs/readme/README.pt.md | 124 +- docs/readme/README.ru.md | 124 +- docs/readme/README.zh-CN.md | 68 +- .../showcase/checkout-sequence.en.graph.json | 598 +++++++++ .../showcase/checkout-sequence.graph.json | 598 +++++++++ examples/showcase/checkout-sequence.md | 28 + examples/showcase/jeepay.en.hero.graph.json | 1133 +++++++++++++++++ scripts/record-sequence-drawing.mjs | 181 +++ scripts/record-showcase-hero.mjs | 241 ++++ scripts/sequence-drawing-player.js | 296 +++++ tests/jeepay-pages.test.mjs | 3 +- tests/open-source-hygiene.test.mjs | 7 +- 16 files changed, 3739 insertions(+), 142 deletions(-) create mode 100644 examples/showcase/checkout-sequence.en.graph.json create mode 100644 examples/showcase/checkout-sequence.graph.json create mode 100644 examples/showcase/checkout-sequence.md create mode 100644 examples/showcase/jeepay.en.hero.graph.json create mode 100644 scripts/record-sequence-drawing.mjs create mode 100644 scripts/record-showcase-hero.mjs create mode 100644 scripts/sequence-drawing-player.js diff --git a/README.md b/README.md index 3e37a29..9ba5579 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ Follow the path. Inspect the evidence. Share one offline file. [English](README.md) · [中文](docs/readme/README.zh-CN.md) · [Русский](docs/readme/README.ru.md) · [Português](docs/readme/README.pt.md) · [日本語](docs/readme/README.ja.md) · [Deutsch](docs/readme/README.de.md) · [Español](docs/readme/README.es.md) -[Live demo](https://supermax92.github.io/qgraphflow/) · [Client installation](#installation-guide) · [Report an issue](https://github.com/supermax92/qgraphflow/issues) · [MIT](LICENSE) +[Live demo](https://supermax92.github.io/qgraphflow/) · [Getting started](#getting-started) · [Client installation](#installation-guide) · [Report an issue](https://github.com/supermax92/qgraphflow/issues) · [MIT](LICENSE) @@ -20,6 +20,16 @@ QGraphFlow turns source code, schemas, configuration and requirements into inter **What sets it apart:** eleven diagram types from one skill, an evidence kind on every relationship and the source line behind each code-backed one, automatic layout, editing in the page, and no network requests from the plugin scripts or the Viewer itself. +![Jeepay multi-view interaction demo: component relationship architecture, sequence and ER diagrams](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +Using real Jeepay source code, switch between component relationship architecture, sequence and ER diagrams to explore components and call relationships. [View full-size GIF](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +**Complex sequence diagram showcase** + +![Complex sequence diagram drawn step by step: participants, lifelines, messages, activation bars and nested combined fragments](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + +A fictional e-commerce scenario contains 9 participants, 29 messages and 6 combined fragments, covering stock retries, nested branches, parallel processing, failure compensation and asynchronous callbacks. The animation progressively reveals the generated diagram to show its structure and details. [View full-size GIF](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + ```bash npx skills add supermax92/qgraphflow ``` @@ -36,6 +46,60 @@ One command installs the skill for Claude Code, Codex, Cursor and Qoder; the [in The real-source [Jeepay corpus](examples/jeepay) contains all eleven views used by CI and the live demo. +## Getting started + +After installation, open your application project in the client and select the `q-flow` skill. These examples use Claude Code's `/q-flow`; in Codex, use the `$q-flow` or `$qgraphflow:q-flow` entry actually provided by your client. Not installed yet? Read the [installation guide](#installation-guide) first. + +### 1. Empty input: Not sure where to start + +Invoke the skill without adding a request: + +```text +/q-flow +``` + +The skill guides you to choose the part to analyze and the question the diagram should answer. Drawing starts once the necessary information is clear. + +### 2. Ask about capabilities: Learn what it can draw + +```text +/q-flow What types of diagrams can you draw? What questions does each type answer? I have just taken over a project; introduce your capabilities and suggest a starting point. +``` + +Learn what the eleven diagram types are for, then decide whether to explore project structure, call order, data relationships or something else. + +### 3. Vague input: Only a general goal + +```text +/q-flow Help me draw this project. I want to understand it as quickly as possible. +``` + +You do not need to specify a diagram type first. The skill selects a suitable view based on the project and your goal, and asks follow-up questions when necessary information is missing. + +### 4. Precise input: Define the scope and request drawing details + +Replace the business names and steps below with flows that actually exist in your project: + +```text +/q-flow Analyze the order creation flow in the current project and generate a sequence diagram in Chinese. +Cover the request entry point, pricing, stock reservation, payment authorization and order persistence. +Retain the synchronous calls, asynchronous messages, paired returns, activation bars, conditional branches, retries and failure compensation that actually exist in the source. Do not omit details for brevity. +Mark the source files and line numbers for components and calls, and save the result to docs/qgraphflow/order-sequence/. +``` + +State the subject, question, level of detail and output location clearly to start directly. The diagram retains only facts supported by evidence. + +### 5. Refine further: Expand part of the previous diagram + +After the result is generated, continue in the same conversation: + +```text +/q-flow Expand the stock reservation step in the previous sequence diagram into a separate flowchart in Chinese. +Show all branches for stock validation, successful reservation, retryable failures, the retry limit and stock release. Follow the source code and add no steps absent from it. +``` + +Start with the overall picture, then explore one step in depth. You can also request more detail in an existing diagram or verify its relationships. + ## Installation guide You need Node.js 22 or later and a plugin-capable client with model access configured. @@ -113,11 +177,11 @@ Confirm that `.cursor-plugin/plugin.json` exists there, reload the window, and f ### 3. Start using it -Open your project in the client, start a new session, and select the skill. Describe your task using the [Quick start](#quick-start) examples below. Open the generated HTML in your browser. +Open your application project in the client, start a new session and select the skill. Describe your request using the [Getting started](#getting-started) examples. Open the generated HTML in your browser. Building it yourself? See the [source build instructions](https://github.com/supermax92/qgraphflow/blob/main/docs/distribution.md#prepare-locally). -## Quick start +## Quick usage These examples use `$qgraphflow:q-flow` in Codex. If your client shows `$q-flow`, select that entry instead. For other clients, use the skill entry described above. @@ -132,7 +196,7 @@ $qgraphflow:q-flow ### Example 1: Understand the architecture ```text -$qgraphflow:q-flow Analyze this project and create an architecture diagram in English showing module responsibilities, dependencies and system boundaries. +$qgraphflow:q-flow Analyze the current project and generate an architecture diagram in Chinese, showing the responsibilities of the main modules, dependencies and system boundaries. ``` Useful when joining a project and learning its overall structure. @@ -140,13 +204,13 @@ Useful when joining a project and learning its overall structure. ### Example 2: Trace a business flow ```text -$qgraphflow:q-flow Analyze order creation and create a sequence diagram in English showing pricing, stock reservation, payment and order persistence, including failure branches. +$qgraphflow:q-flow Analyze order creation and generate a sequence diagram in Chinese, showing the call order for pricing, stock reservation, payment and order persistence, and mark failure branches. ``` Replace order creation and its steps with your project's actual flow. Continue in the same conversation: ```text -$qgraphflow:q-flow Expand stock reservation from the previous diagram into a separate flowchart in English, showing success and failure handling. +$qgraphflow:q-flow Expand the stock reservation step in the previous diagram into a separate flowchart in Chinese, showing success and failure handling. ``` Results go under `docs/qgraphflow/` by default. Open `index.html` to explore, edit and export; `graph.json` retains the graph data. Each view is also written as an SVG (`diagram.svg`, or `diagram--.svg` for several views) that you can embed as an image in a README, pull request or wiki. @@ -198,21 +262,21 @@ $qgraphflow:q-flow CI says docs/qgraphflow/order-sequence is out of date. Refres The skill moves anchors whose symbol it finds once, corrects only the anchors still reported, and regenerates the page and SVGs with your edited positions and text kept. It does not redraw the diagram. -## What each of the nine views answers +## What each of the eleven diagram types answers | View · PNG | Main question | Example scope | | --- | --- | --- | -| Platform capability architecture | What capabilities does the platform provide? | Capability bands and matrices | -| Engineering layer architecture | How is the project organized? | Layers and parallel support | -| Component relationship architecture | Which responsibilities collaborate? | Channels, checkout, pricing, risk, stock, payment, orders, events and fulfillment | -| Flowchart | Where does the process branch and converge? | Stock shortage, risk rejection, payment compensation and successful commit | -| Sequence | In what order do calls and returns occur? | Successful checkout and asynchronous OrderPaid | -| ER | How does core data relate? | Cart, orders, items, payments, reservations and parcels | -| Deployment | Where do runtime units run and connect? | Edge, Kubernetes, data services, payments and logistics networks | -| Class | How do domain objects and contracts depend on each other? | Checkout service, Order and four ports | +| Platform capability architecture | What capabilities does the platform provide? | Capability zones and matrices | +| Engineering layer architecture | How is the engineering code organized? | Engineering layers and shared support | +| Component relationship architecture | Which responsibility boundaries collaborate in the system? | Channels, transaction orchestration, pricing, risk, stock, payment, orders, events and fulfillment | +| Flowchart | How does each decision point branch and converge? | Stock shortage, risk rejection, compensation for payment failure and successful commit | +| Sequence | In what order does a request make calls and receive returns? | Successful checkout main flow and asynchronous OrderPaid | +| ER | How does core data relate? | Cart, orders, items, payments, stock reservations and parcels | +| Deployment | Where are runtime units placed and how are they connected? | Edge, Kubernetes, data services, payment and warehouse/logistics networks | +| Class | How do domain objects and code contracts depend on each other? | Checkout application service, Order and four ports | | State | Which events and guards advance an order? | Payment, fulfillment, cancellation, refund and closure | -| Use case | What can each actor do? | Buyer, merchant, warehouse and support | -| Data flow | How is data transformed and stored? | Cart, transaction decisions, events, warehouse and delivery receipts | +| Use case | What capabilities does each actor have? | Buyer, merchant, warehouse and customer support | +| Data flow | What transformations and stores do data assets pass through? | Cart, transaction decisions, order events, warehouse/logistics and delivery receipts | This is a concept model demonstrating QGraphFlow, not a particular e-commerce repository. The example `graph.json` invents no source paths and marks relationship evidence as `inference`. Real project diagrams need traceable source, DDL, configuration, tests and accepted requirements. @@ -226,21 +290,21 @@ node --test tests/*.test.mjs skills/q-flow/scripts/*.test.mjs Development needs Node.js 22 or later, npm, tar, zip and unzip. Include a minimal redacted graph, client/browser versions and reproduction steps in issue reports. -[Evidence sources](skills/q-flow/references/evidence-sources.md) · [Graph format](skills/q-flow/references/graph-schema.md) · [Guided intake](skills/q-flow/references/guided-intake.md) · [Viewer development](skills/q-flow/references/viewer-development.md) · [Diagram composition](skills/q-flow/references/visual-contract.md) +Reference documentation (English): [Evidence sources](skills/q-flow/references/evidence-sources.md) · [Graph data format](skills/q-flow/references/graph-schema.md) · [Guided intake](skills/q-flow/references/guided-intake.md) · [Viewer development and acceptance](skills/q-flow/references/viewer-development.md) · [Visual conventions](skills/q-flow/references/visual-contract.md) ## License and attribution [MIT](LICENSE) · [Third-party notices](THIRD_PARTY_NOTICES.md) -QGraphFlow is an independent MIT-licensed project. The scenarios in this document are conceptual and do not represent any company's production architecture; no affiliation, sponsorship or endorsement is implied. +QGraphFlow is an independent MIT-licensed project. The scenarios in this document are conceptual examples and do not represent any real company's production architecture. -## Architecture overviews / 架构总览 +## Architecture overviews Architecture now includes component relations, platform capabilities and engineering layers. Describe the subject and question; the skill chooses the template. Requested collections can contain multiple architecture views with independent edits. ```text -$qgraphflow:q-flow 分析当前项目的平台能力和业务接入方式,生成中文平台能力总览。 -$qgraphflow:q-flow 分析当前工程组织和组件分层,生成工程整体与一个组件剖面的中文总览。 +$qgraphflow:q-flow Analyze this project's platform capabilities and business integration methods, and generate a platform capability overview in Chinese. +$qgraphflow:q-flow Analyze the organization and component layers of the current project, and generate Chinese overviews of the whole project and a cross-section of one component. $qgraphflow:q-flow Generate an English platform capability overview of this project and show how application modules integrate. ``` diff --git a/docs/readme/README.de.md b/docs/readme/README.de.md index 0acd952..daac308 100644 --- a/docs/readme/README.de.md +++ b/docs/readme/README.de.md @@ -10,16 +10,26 @@ Pfade verfolgen. Belege prüfen. Eine Offline-Datei teilen. [English](../../README.md) · [中文](../../docs/readme/README.zh-CN.md) · [Русский](../../docs/readme/README.ru.md) · [Português](../../docs/readme/README.pt.md) · [日本語](../../docs/readme/README.ja.md) · [Deutsch](../../docs/readme/README.de.md) · [Español](../../docs/readme/README.es.md) -[Online-Demo](https://supermax92.github.io/qgraphflow/) · [Client-Installation](#installationsanleitung) · [Problem melden](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) +[Online-Demo](https://supermax92.github.io/qgraphflow/) · [Erste Schritte](#erste-schritte) · [Client-Installation](#installationsanleitung) · [Problem melden](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) -*Elf Diagrammarten: Plattformfähigkeitsarchitektur, Schichtenarchitektur, Komponentenarchitektur, Flussdiagramm, Sequenz, ER, Bereitstellung, Klasse, Zustand, Anwendungsfall und Datenfluss.* +*Elf Diagrammarten: Plattformfähigkeitsarchitektur, Architektur der Engineering-Schichten, Komponentenbeziehungsarchitektur, Flussdiagramm, Sequenz, ER, Bereitstellung, Klasse, Zustand, Anwendungsfall und Datenfluss.* QGraphFlow erzeugt interaktive Softwarediagramme aus Quellcode, Datenstrukturen, Konfiguration und Anforderungen. Beziehungen bleiben überprüfbar; das Ergebnis lässt sich als Offline-HTML teilen. **Was es auszeichnet:** elf Diagrammarten in einem Skill, eine Belegart für jede Beziehung und die Quellzeile hinter jeder durch Code gestützten, automatisches Layout, Bearbeiten direkt auf der Seite und keine Netzwerkanfragen von den Plugin-Skripten oder dem Viewer selbst. +![Interaktive Jeepay-Demo mit mehreren Ansichten: Komponentenbeziehungsarchitektur, Sequenz- und ER-Diagramme](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +Wechsle anhand des echten Jeepay-Quellcodes zwischen Komponentenbeziehungsarchitektur, Sequenz- und ER-Diagrammen, um Komponenten und Aufrufbeziehungen zu erkunden. [GIF in Originalgröße ansehen](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +**Präsentation eines komplexen Sequenzdiagramms** + +![Schrittweises Zeichnen eines komplexen Sequenzdiagramms: Teilnehmer, Lebenslinien, Nachrichten, Aktivierungsbalken und verschachtelte kombinierte Fragmente](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + +Ein fiktives E-Commerce-Szenario enthält 9 Teilnehmer, 29 Nachrichten und 6 kombinierte Fragmente mit Bestandswiederholungen, verschachtelten Zweigen, paralleler Verarbeitung, Fehlerkompensation und asynchronen Rückrufen. Die Animation zeigt das erzeugte Diagramm schrittweise und macht seine Struktur und Details sichtbar. [GIF in Originalgröße ansehen](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + ```bash npx skills add supermax92/qgraphflow ``` @@ -34,7 +44,61 @@ Ein Befehl installiert den Skill für Claude Code, Codex, Cursor und Qoder; die - **Teilen:** Offline-HTML öffnen oder das vollständige Diagramm als SVG / PNG exportieren. -The real-source [Jeepay corpus](../../examples/jeepay) contains all eleven views used by CI and the live demo. +Der auf echtem Quellcode beruhende [Jeepay-Korpus](../../examples/jeepay) enthält alle elf Ansichten, die CI und die Online-Demo verwenden. + +## Erste Schritte + +Öffne nach der Installation dein Geschäftsprojekt im Client und wähle den Skill `q-flow`. Die Beispiele verwenden `/q-flow` in Claude Code; in Codex verwendest du den Eintrag `$q-flow` oder `$qgraphflow:q-flow`, den dein Client tatsächlich anbietet. Noch nicht installiert? Lies zuerst die [Installationsanleitung](#installationsanleitung). + +### 1. Leere Eingabe: Du weißt nicht, wo du anfangen sollst + +Rufe den Skill ohne zusätzliche Anforderungen auf: + +```text +/q-flow +``` + +Der Skill hilft dir, den zu analysierenden Teil und die Frage auszuwählen, die das Diagramm beantworten soll. Sobald die nötigen Informationen geklärt sind, beginnt das Zeichnen. + +### 2. Nach Fähigkeiten fragen: Erfahren, was er zeichnen kann + +```text +/q-flow Welche Diagrammarten kannst du zeichnen? Welche Fragen beantwortet jede Art? Ich habe gerade ein Projekt übernommen; stelle deine Fähigkeiten vor und schlage einen Einstieg vor. +``` + +Lerne zuerst die Einsatzgebiete der elf Diagrammarten kennen. Entscheide dann, ob du Projektstruktur, Aufrufreihenfolge, Datenbeziehungen oder etwas anderes betrachten möchtest. + +### 3. Vage Eingabe: Nur ein grobes Ziel + +```text +/q-flow Hilf mir, dieses Projekt zu zeichnen. Ich möchte es möglichst schnell verstehen. +``` + +Du musst vorher keine Diagrammart angeben. Der Skill bestimmt anhand des Projekts und deines Ziels eine passende Ansicht und fragt nach, wenn notwendige Informationen fehlen. + +### 4. Präzise Eingabe: Umfang festlegen und Zeichnungsdetails anfordern + +Ersetze die folgenden Geschäftsbegriffe und Schritte durch Abläufe, die tatsächlich in deinem Projekt vorkommen: + +```text +/q-flow Analysiere die Bestellerstellung im aktuellen Projekt und erzeuge ein Sequenzdiagramm auf Chinesisch. +Berücksichtige den Anfrageeingang, die Preisberechnung, Bestandsreservierung, Zahlungsautorisierung und Speicherung der Bestellung. +Erhalte die synchronen Aufrufe, asynchronen Nachrichten, gepaarten Rückgaben, Aktivierungsbalken, bedingten Zweige, Wiederholungen und Fehlerkompensationen, die tatsächlich im Quellcode vorkommen. Lasse keine Details zugunsten der Kürze weg. +Kennzeichne Quelldateien und Zeilennummern der Komponenten und Aufrufe und speichere das Ergebnis unter docs/qgraphflow/order-sequence/. +``` + +Benenne Gegenstand, Frage, Detailgrad und Ausgabeort klar, damit es direkt losgehen kann. Das Diagramm enthält nur durch Belege gestützte Fakten. + +### 5. Weiter verfeinern: Einen Teil des letzten Diagramms vertiefen + +Fahre nach der Erzeugung des Ergebnisses im selben Gespräch fort: + +```text +/q-flow Vertiefe die Bestandsreservierung aus dem letzten Sequenzdiagramm als separates Flussdiagramm auf Chinesisch. +Zeige sämtliche Zweige für Bestandsprüfung, erfolgreiche Reservierung, wiederholbare Fehler, Wiederholungslimit und Bestandsfreigabe. Halte dich an den Quellcode und füge keine dort fehlenden Schritte hinzu. +``` + +Betrachte zuerst das Ganze und vertiefe dann einen Schritt. Du kannst auch weitere Details für ein vorhandenes Diagramm anfordern oder seine Beziehungen prüfen. ## Installationsanleitung @@ -113,11 +177,11 @@ Prüfen, ob dort `.cursor-plugin/plugin.json` vorhanden ist, das Fenster neu lad ### 3. Loslegen -Das eigene Projekt im Client öffnen, eine neue Sitzung starten und den Skill wählen. Die Aufgabe anhand der Beispiele unter [Schnellstart](#schnellstart) beschreiben. Das erzeugte HTML im Browser öffnen. +Öffne dein Geschäftsprojekt im Client, starte eine neue Sitzung und wähle den Skill. Beschreibe deine Anforderungen anhand der Beispiele unter [Erste Schritte](#erste-schritte). Öffne das erzeugte HTML im Browser. Selbst bauen? Siehe [Anleitung zum Bauen aus dem Quellcode](https://github.com/supermax92/qgraphflow/blob/main/docs/distribution.md#prepare-locally). -## Schnellstart +## Schnell verwenden Die Beispiele verwenden `$qgraphflow:q-flow` in Codex. Zeigt der Client `$q-flow`, diesen Eintrag wählen. In anderen Clients den oben beschriebenen Skill-Aufruf verwenden. @@ -132,7 +196,7 @@ $qgraphflow:q-flow ### Beispiel 1: Projektarchitektur verstehen ```text -$qgraphflow:q-flow Analysiere dieses Projekt und erstelle ein deutsches Architekturdiagramm mit Verantwortlichkeiten der Module, Abhängigkeiten und Systemgrenzen. +$qgraphflow:q-flow Analysiere dieses Projekt und erstelle ein chinesisches Architekturdiagramm mit Verantwortlichkeiten der Module, Abhängigkeiten und Systemgrenzen. ``` Geeignet, um sich erstmals einen Überblick über ein Projekt zu verschaffen. @@ -140,13 +204,13 @@ Geeignet, um sich erstmals einen Überblick über ein Projekt zu verschaffen. ### Beispiel 2: Einen Geschäftsablauf verfolgen ```text -$qgraphflow:q-flow Analysiere die Bestellerstellung und erstelle ein deutsches Sequenzdiagramm für Preisberechnung, Bestandsreservierung, Zahlung und Speicherung der Bestellung, einschließlich Fehlerzweigen. +$qgraphflow:q-flow Analysiere die Bestellerstellung und erstelle ein chinesisches Sequenzdiagramm für Preisberechnung, Bestandsreservierung, Zahlung und Speicherung der Bestellung, einschließlich Fehlerzweigen. ``` Bestellerstellung und Schritte durch den tatsächlichen Projektablauf ersetzen. Im selben Gespräch weiterfragen: ```text -$qgraphflow:q-flow Vertiefe die Bestandsreservierung aus dem letzten Diagramm als separates deutsches Flussdiagramm mit Erfolgs- und Fehlerbehandlung. +$qgraphflow:q-flow Vertiefe die Bestandsreservierung aus dem letzten Diagramm als separates chinesisches Flussdiagramm mit Erfolgs- und Fehlerbehandlung. ``` Ergebnisse landen standardmäßig unter `docs/qgraphflow/`. `index.html` zum Erkunden, Bearbeiten und Exportieren öffnen; `graph.json` enthält die Diagrammdaten. Jede Ansicht wird außerdem als SVG geschrieben (`diagram.svg`, bei mehreren Ansichten `diagram--.svg`), das sich als Bild in eine README, einen Pull Request oder ein Wiki einbinden lässt. @@ -154,9 +218,9 @@ Ergebnisse landen standardmäßig unter `docs/qgraphflow/`. `index.html` zum Erk Nach Änderungen auf der Seite schreibt **Mehr → Änderungen speichern** in Chrome oder Edge Seite, `graph.json` und SVGs direkt zurück, sobald der Diagrammordner einmal gewählt ist. Andere Browser speichern nur `graph.json`: die Datei in den Ordner legen und Seite und SVGs mit `npx -y qgraphflow generate docs/qgraphflow//graph.json docs/qgraphflow/ --layout preserve --force` neu erzeugen.
-Run the eleven-view Jeepay source example +Das Jeepay-Quellcodebeispiel mit elf Ansichten ausführen -Select your Jeepay source checkout to verify the evidence: +Wähle deine lokale Jeepay-Quellcodekopie, um die Belege zu prüfen: ```bash export JEEPAY_REPO_ROOT="" @@ -164,7 +228,7 @@ node skills/q-flow/scripts/validate-graph.mjs examples/jeepay/collection.graph.j node skills/q-flow/scripts/generate-viewer.mjs examples/jeepay/collection.graph.json output/jeepay --repo-root "$JEEPAY_REPO_ROOT" ``` -Open `output/jeepay/index.html`; its eleven SVGs are in the same directory. See the [corpus README](../../examples/jeepay) for the source revision and refresh procedure. +Öffne `output/jeepay/index.html`; die elf SVGs liegen im selben Verzeichnis. Die Quellcoderevision und das Aktualisierungsverfahren stehen in der [Korpus-README](../../examples/jeepay).
@@ -198,19 +262,21 @@ $qgraphflow:q-flow Laut CI ist docs/qgraphflow/order-sequence veraltet. Bitte ak Der Skill verschiebt Anker, deren Symbol er genau einmal in der Datei findet, korrigiert nur die weiterhin gemeldeten Anker und erzeugt Seite und SVGs neu, wobei bearbeitete Positionen und Texte erhalten bleiben. Er zeichnet das Diagramm nicht neu. -## Welche Fragen die neun Ansichten beantworten +## Welche Fragen die elf Diagrammarten beantworten | Ansicht · PNG | Hauptfrage | Umfang des Beispiels | | --- | --- | --- | -| Architektur | Welche Verantwortlichkeiten arbeiten zusammen? | Kanäle, Kaufabschluss, Preise, Risiko, Bestand, Zahlung, Bestellung, Ereignisse und Versand | -| Flussdiagramm | Wo verzweigt und vereinigt sich der Ablauf? | Fehlbestand, Risikoablehnung, Zahlungskompensation und erfolgreicher Commit | -| Sequenz | In welcher Reihenfolge erfolgen Aufrufe und Antworten? | Erfolgreicher Kauf und asynchrones OrderPaid | -| ER | Wie hängen die Kerndaten zusammen? | Warenkorb, Bestellungen, Positionen, Zahlungen, Reservierungen und Pakete | -| Bereitstellung | Wo laufen Einheiten und wie sind sie verbunden? | Randnetz, Kubernetes, Datendienste, Zahlung und Logistiknetz | -| Klasse | Wie hängen Domänenobjekte und Schnittstellen ab? | Kaufdienst, Order und vier Ports | -| Zustand | Welche Ereignisse und Bedingungen steuern eine Bestellung? | Zahlung, Versand, Stornierung, Erstattung und Abschluss | -| Anwendungsfall | Was kann jeder Akteur tun? | Käufer, Händler, Lager und Betreuung | -| Datenfluss | Wie werden Daten umgewandelt und gespeichert? | Warenkorb, Transaktionsentscheidungen, Ereignisse, Lager und Empfangsbelege | +| Plattformfähigkeitsarchitektur | Welche Fähigkeiten bietet die Plattform? | Fähigkeitsbereiche und Matrizen | +| Architektur der Engineering-Schichten | Wie ist der Projektcode organisiert? | Engineering-Schichten und gemeinsame Unterstützung | +| Komponentenbeziehungsarchitektur | Welche Verantwortungsgrenzen arbeiten im System zusammen? | Kanäle, Transaktionsorchestrierung, Preise, Risiko, Bestand, Zahlung, Bestellungen, Ereignisse und Auftragsabwicklung | +| Flussdiagramm | Wie verzweigt und vereinigt sich jeder Entscheidungspunkt? | Fehlbestand, Risikoablehnung, Kompensation bei Zahlungsfehlern und erfolgreicher Commit | +| Sequenz | In welcher Reihenfolge führt eine Anfrage Aufrufe aus und erhält Rückgaben? | Hauptpfad eines erfolgreichen Kaufabschlusses und asynchrones OrderPaid | +| ER | Wie hängen die Kerndaten zusammen? | Warenkorb, Bestellungen, Positionen, Zahlungen, Bestandsreservierungen und Pakete | +| Bereitstellung | Wo werden Laufzeiteinheiten platziert und wie sind sie verbunden? | Randnetz, Kubernetes, Datendienste, Zahlung und Lager-/Logistiknetze | +| Klasse | Wie hängen Domänenobjekte und Codeverträge voneinander ab? | Checkout-Anwendungsdienst, Order und vier Ports | +| Zustand | Welche Ereignisse und Wächterbedingungen treiben eine Bestellung voran? | Zahlung, Auftragsabwicklung, Stornierung, Erstattung und Abschluss | +| Anwendungsfall | Welche Fähigkeiten besitzt jeder Akteur? | Käufer, Händler, Lager und Kundendienst | +| Datenfluss | Welche Transformationen und Speicher durchlaufen Datenbestände? | Warenkorb, Transaktionsentscheidungen, Bestellereignisse, Lager/Logistik und Lieferbelege | Dies ist ein Konzeptmodell zur Demonstration von QGraphFlow, kein konkretes E-Commerce-Repository. Die Beispieldatei `graph.json` erfindet keine Quellpfade und kennzeichnet Beziehungen mit `inference`. Echte Projektdiagramme benötigen nachvollziehbaren Quellcode, DDL, Konfiguration, Tests und akzeptierte Anforderungen. @@ -224,10 +290,22 @@ node --test tests/*.test.mjs skills/q-flow/scripts/*.test.mjs Erforderlich sind Node.js 22 oder neuer, npm, tar, zip und unzip. Fehlerberichte sollten ein minimales anonymisiertes Diagramm, Client- und Browserversion sowie Reproduktionsschritte enthalten. -Referenzdokumentation (Englisch): [Belegquellen](../../skills/q-flow/references/evidence-sources.md) · [Diagrammformat](../../skills/q-flow/references/graph-schema.md) · [Geführte Bedarfsklärung](../../skills/q-flow/references/guided-intake.md) · [Viewer-Entwicklung](../../skills/q-flow/references/viewer-development.md) · [Diagrammgestaltung](../../skills/q-flow/references/visual-contract.md) +Referenzdokumentation (Englisch): [Belegquellen](../../skills/q-flow/references/evidence-sources.md) · [Diagrammformat](../../skills/q-flow/references/graph-schema.md) · [Geführte Bedarfsklärung](../../skills/q-flow/references/guided-intake.md) · [Viewer-Entwicklung und Abnahme](../../skills/q-flow/references/viewer-development.md) · [Diagrammgestaltung](../../skills/q-flow/references/visual-contract.md) ## Lizenz und Zuordnung [MIT](../../LICENSE) · [Drittanbieterhinweise](../../THIRD_PARTY_NOTICES.md) -QGraphFlow ist ein unabhängiges Projekt unter MIT-Lizenz. Die Szenarien in diesem Dokument sind konzeptionell und stellen keine Produktionsarchitektur eines Unternehmens dar. Eine Zugehörigkeit, Förderung oder Empfehlung wird nicht behauptet. +QGraphFlow ist ein unabhängiges Projekt unter MIT-Lizenz. Die Szenarien in diesem Dokument sind konzeptionelle Beispiele und stellen keine Produktionsarchitektur eines realen Unternehmens dar. + +## Architekturübersichten + +Architektur umfasst jetzt Komponentenbeziehungen, Plattformfähigkeiten und Engineering-Schichten. Beschreibe den Gegenstand und die Frage; der Skill wählt die Vorlage. Angeforderte Sammlungen können mehrere Architekturansichten mit unabhängigen Bearbeitungen enthalten. + +```text +$qgraphflow:q-flow Analysiere die Plattformfähigkeiten und die geschäftlichen Integrationswege des aktuellen Projekts und erzeuge eine Übersicht der Plattformfähigkeiten auf Chinesisch. +$qgraphflow:q-flow Analysiere die Organisation des aktuellen Projekts und seine Komponentenschichten und erzeuge chinesische Übersichten des Gesamtprojekts und eines Komponentenquerschnitts. +$qgraphflow:q-flow Erzeuge eine englische Übersicht der Plattformfähigkeiten dieses Projekts und zeige, wie sich Anwendungsmodule integrieren. +``` + +Siehe [examples/jeepay](../../examples/jeepay) für quellenbasierte Architekturansichten zu Plattform, Engineering und Komponentenbeziehungen. Entsperre eine Übersicht, um Karten innerhalb einer Schicht neu anzuordnen oder Texte zu bearbeiten. Speichern erhält alle Ansichten; Zurücksetzen stellt nur die aktuelle wieder her. diff --git a/docs/readme/README.es.md b/docs/readme/README.es.md index 028d2c4..0ee215c 100644 --- a/docs/readme/README.es.md +++ b/docs/readme/README.es.md @@ -10,16 +10,26 @@ Sigue el recorrido. Comprueba las evidencias. Comparte un archivo sin conexión. [English](../../README.md) · [中文](../../docs/readme/README.zh-CN.md) · [Русский](../../docs/readme/README.ru.md) · [Português](../../docs/readme/README.pt.md) · [日本語](../../docs/readme/README.ja.md) · [Deutsch](../../docs/readme/README.de.md) · [Español](../../docs/readme/README.es.md) -[Demo en línea](https://supermax92.github.io/qgraphflow/) · [Instalación por cliente](#guía-de-instalación) · [Informar de un problema](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) +[Demo en línea](https://supermax92.github.io/qgraphflow/) · [Primeros pasos](#primeros-pasos) · [Instalación por cliente](#guía-de-instalación) · [Informar de un problema](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) -*Once tipos: arquitectura de capacidades, arquitectura por capas, arquitectura de componentes, flujo, secuencia, ER, despliegue, clases, estados, casos de uso y flujo de datos.* +*Once tipos de diagramas: arquitectura de capacidades de la plataforma, arquitectura por capas de ingeniería, arquitectura de relaciones entre componentes, diagrama de flujo, secuencia, ER, despliegue, clases, estados, casos de uso y flujo de datos.* QGraphFlow genera diagramas de software interactivos a partir del código, los esquemas, la configuración y los requisitos. Permite comprobar las relaciones y compartir el resultado como HTML sin conexión. **Qué lo distingue:** once tipos de diagrama en una sola habilidad, un tipo de evidencia en cada relación y la línea de código detrás de cada una respaldada por código, diseño automático, edición en la propia página y ninguna solicitud de red de los scripts del complemento ni del propio Viewer. +![Demostración interactiva de varias vistas de Jeepay: arquitectura de relaciones entre componentes, secuencia y ER](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +Con el código fuente real de Jeepay, alterna entre diagramas de arquitectura de relaciones entre componentes, secuencia y ER para explorar componentes y relaciones de llamadas. [Ver GIF a tamaño original](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +**Presentación de un diagrama de secuencia complejo** + +![Dibujo gradual de un diagrama de secuencia complejo: participantes, líneas de vida, mensajes, barras de activación y fragmentos combinados anidados](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + +Un escenario ficticio de comercio electrónico contiene 9 participantes, 29 mensajes y 6 fragmentos combinados, con reintentos de inventario, ramas anidadas, procesamiento paralelo, compensación de fallos y callbacks asíncronos. La animación muestra gradualmente el diagrama generado para presentar su estructura y sus detalles. [Ver GIF a tamaño original](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + ```bash npx skills add supermax92/qgraphflow ``` @@ -34,7 +44,61 @@ Un solo comando instala la habilidad para Claude Code, Codex, Cursor y Qoder; la - **Compartir:** abrir el HTML sin conexión o exportar el diagrama completo a SVG / PNG. -The real-source [Jeepay corpus](../../examples/jeepay) contains all eleven views used by CI and the live demo. +El [corpus de Jeepay](../../examples/jeepay), basado en código fuente real, contiene las once vistas utilizadas por CI y la demostración online. + +## Primeros pasos + +Tras la instalación, abre tu proyecto de negocio en el cliente y selecciona la habilidad `q-flow`. Los ejemplos usan `/q-flow` de Claude Code; en Codex, usa la entrada `$q-flow` o `$qgraphflow:q-flow` que ofrezca tu cliente. ¿Todavía no lo has instalado? Consulta primero la [guía de instalación](#guía-de-instalación). + +### 1. Entrada vacía: No sabes por dónde empezar + +Invoca la habilidad sin añadir una petición: + +```text +/q-flow +``` + +La habilidad te guía para elegir la parte que analizar y la pregunta que debe responder el diagrama. Empieza a dibujar cuando la información necesaria está clara. + +### 2. Preguntar por las capacidades: Saber qué puede dibujar + +```text +/q-flow ¿Qué tipos de diagramas puedes dibujar? ¿Qué preguntas responde cada tipo? Acabo de hacerme cargo de un proyecto; presenta tus capacidades y sugiere un punto de partida. +``` + +Conoce primero los usos de los once tipos de diagramas y decide después si quieres ver la estructura del proyecto, el orden de llamadas, las relaciones de datos u otro contenido. + +### 3. Entrada vaga: Solo tienes un objetivo general + +```text +/q-flow Ayúdame a dibujar este proyecto. Quiero entenderlo lo antes posible. +``` + +No necesitas indicar primero el tipo de diagrama. La habilidad determina una vista adecuada según el proyecto y tu objetivo, y pregunta cuando falta información necesaria. + +### 4. Entrada precisa: Definir el alcance y pedir detalles del dibujo + +Sustituye los nombres de negocio y los pasos siguientes por procesos que existan realmente en tu proyecto: + +```text +/q-flow Analiza el proceso de creación de pedidos del proyecto actual y genera un diagrama de secuencia en chino. +Incluye la entrada de solicitudes, el cálculo de precios, la reserva de inventario, la autorización de pago y la persistencia del pedido. +Conserva las llamadas síncronas, mensajes asíncronos, retornos emparejados, barras de activación, ramas condicionales, reintentos y compensaciones de fallos que existan realmente en el código. No omitas detalles por brevedad. +Indica los archivos y números de línea del código de los componentes y llamadas, y guarda el resultado en docs/qgraphflow/order-sequence/. +``` + +Define claramente el objeto, la pregunta, el nivel de detalle y la ubicación de salida para empezar directamente. El diagrama conserva solo hechos respaldados por evidencia. + +### 5. Seguir refinando: Ampliar una parte del diagrama anterior + +Una vez generado el resultado, continúa en la misma conversación: + +```text +/q-flow Amplía el paso de reserva de inventario del diagrama de secuencia anterior en un diagrama de flujo independiente en chino. +Muestra todas las ramas de validación de inventario, reserva exitosa, fallos reintentables, límite de reintentos y liberación de inventario. Sigue el código fuente y no añadas pasos que no existan en él. +``` + +Mira primero el conjunto y profundiza después en un paso. También puedes pedir más detalles de un diagrama existente o verificar sus relaciones. ## Guía de instalación @@ -113,11 +177,11 @@ Comprobar que allí existe `.cursor-plugin/plugin.json`, recargar la ventana y b ### 3. Empezar a usarlo -Abrir el proyecto en el cliente, iniciar una sesión nueva y seleccionar la habilidad. Describir la tarea siguiendo los ejemplos de [Inicio rápido](#inicio-rápido) más abajo. Abrir el HTML generado en el navegador. +Abre tu proyecto de negocio en el cliente, inicia una sesión nueva y selecciona la habilidad. Describe tu petición siguiendo los ejemplos de [Primeros pasos](#primeros-pasos). Abre el HTML generado en el navegador. ¿Prefieres compilarlo? Consulta las [instrucciones de compilación desde el código fuente](https://github.com/supermax92/qgraphflow/blob/main/docs/distribution.md#prepare-locally). -## Inicio rápido +## Uso rápido Estos ejemplos usan `$qgraphflow:q-flow` en Codex. Si el cliente muestra `$q-flow`, seleccionar esa entrada. En los demás clientes, usar la forma de invocación indicada arriba. @@ -132,7 +196,7 @@ $qgraphflow:q-flow ### Ejemplo 1: Entender la arquitectura ```text -$qgraphflow:q-flow Analiza este proyecto y crea un diagrama de arquitectura en español que muestre responsabilidades de los módulos, dependencias y límites del sistema. +$qgraphflow:q-flow Analiza este proyecto y crea un diagrama de arquitectura en chino que muestre responsabilidades de los módulos, dependencias y límites del sistema. ``` Útil para conocer la estructura general al llegar a un proyecto. @@ -140,13 +204,13 @@ $qgraphflow:q-flow Analiza este proyecto y crea un diagrama de arquitectura en e ### Ejemplo 2: Seguir un flujo de negocio ```text -$qgraphflow:q-flow Analiza la creación de pedidos y genera un diagrama de secuencia en español con cálculo de precios, reserva de inventario, pago y persistencia del pedido, incluidas las ramas de fallo. +$qgraphflow:q-flow Analiza la creación de pedidos y genera un diagrama de secuencia en chino con cálculo de precios, reserva de inventario, pago y persistencia del pedido, incluidas las ramas de fallo. ``` Sustituir la creación de pedidos y sus pasos por el flujo real del proyecto. Continuar en la misma conversación: ```text -$qgraphflow:q-flow Amplía la reserva de inventario del diagrama anterior en un diagrama de flujo independiente en español, con el tratamiento de éxitos y fallos. +$qgraphflow:q-flow Amplía la reserva de inventario del diagrama anterior en un diagrama de flujo independiente en chino, con el tratamiento de éxitos y fallos. ``` Los resultados se guardan bajo `docs/qgraphflow/` de forma predeterminada. Abrir `index.html` para explorar, editar y exportar; `graph.json` conserva los datos. Cada vista también se escribe como SVG (`diagram.svg`, o `diagram--.svg` cuando hay varias), que puede incrustarse como imagen en un README, una pull request o una wiki. @@ -154,9 +218,9 @@ Los resultados se guardan bajo `docs/qgraphflow/` de forma predeterminada. Abrir Después de editar en la página, **Más → Guardar cambios** en Chrome o Edge reescribe en su sitio la página, `graph.json` y los SVG, tras elegir una vez la carpeta del diagrama. Otros navegadores solo guardan `graph.json`: colocarlo en la carpeta y volver a generar la página y los SVG con `npx -y qgraphflow generate docs/qgraphflow//graph.json docs/qgraphflow/ --layout preserve --force`.
-Run the eleven-view Jeepay source example +Ejecutar el ejemplo de Jeepay con once vistas basado en código fuente -Select your Jeepay source checkout to verify the evidence: +Selecciona tu copia local del código fuente de Jeepay para verificar la evidencia: ```bash export JEEPAY_REPO_ROOT="" @@ -164,7 +228,7 @@ node skills/q-flow/scripts/validate-graph.mjs examples/jeepay/collection.graph.j node skills/q-flow/scripts/generate-viewer.mjs examples/jeepay/collection.graph.json output/jeepay --repo-root "$JEEPAY_REPO_ROOT" ``` -Open `output/jeepay/index.html`; its eleven SVGs are in the same directory. See the [corpus README](../../examples/jeepay) for the source revision and refresh procedure. +Abre `output/jeepay/index.html`; sus once SVG están en el mismo directorio. Consulta el [README del corpus](../../examples/jeepay) para conocer la revisión del código fuente y el procedimiento de actualización.
@@ -198,19 +262,21 @@ $qgraphflow:q-flow La CI dice que docs/qgraphflow/order-sequence está desactual La habilidad mueve los anclajes cuyo símbolo encuentra una sola vez en el archivo, corrige solo los que siguen fallando y vuelve a generar la página y los SVG conservando las posiciones y los textos editados. No redibuja el diagrama. -## Qué responde cada una de las nueve vistas +## Qué responde cada uno de los once tipos de diagramas | Vista · PNG | Pregunta principal | Alcance del ejemplo | | --- | --- | --- | -| Arquitectura | ¿Qué responsabilidades colaboran? | Canales, compra, precios, riesgo, inventario, pago, pedidos, eventos y entrega | -| Flujo | ¿Dónde se bifurca y converge el proceso? | Falta de inventario, rechazo de riesgo, compensación del pago y confirmación exitosa | -| Secuencia | ¿En qué orden ocurren llamadas y retornos? | Compra exitosa y OrderPaid asíncrono | -| ER | ¿Cómo se relacionan los datos principales? | Carrito, pedidos, líneas, pagos, reservas y paquetes | -| Despliegue | ¿Dónde se ejecutan y conectan las unidades? | Borde, Kubernetes, servicios de datos, pagos y redes logísticas | -| Clases | ¿Cómo dependen los objetos de dominio y los contratos? | Servicio de compra, Order y cuatro puertos | -| Estados | ¿Qué eventos y condiciones hacen avanzar un pedido? | Pago, entrega, cancelación, reembolso y cierre | -| Casos de uso | ¿Qué puede hacer cada actor? | Comprador, comercio, almacén y atención al cliente | -| Flujo de datos | ¿Cómo se transforman y almacenan los datos? | Carrito, decisiones, eventos, almacén y comprobantes de entrega | +| Arquitectura de capacidades de la plataforma | ¿Qué capacidades ofrece la plataforma? | Zonas de capacidades y matrices | +| Arquitectura por capas de ingeniería | ¿Cómo se organiza el código del proyecto? | Capas de ingeniería y soporte compartido | +| Arquitectura de relaciones entre componentes | ¿Qué límites de responsabilidad colaboran en el sistema? | Canales, orquestación de transacciones, precios, riesgo, inventario, pago, pedidos, eventos y ejecución de pedidos | +| Diagrama de flujo | ¿Cómo se bifurca y converge cada decisión? | Falta de inventario, rechazo de riesgo, compensación de fallos de pago y confirmación exitosa | +| Secuencia | ¿En qué orden hace llamadas y recibe retornos una solicitud? | Flujo principal de compra exitosa y OrderPaid asíncrono | +| ER | ¿Cómo se relacionan los datos principales? | Carrito, pedidos, líneas, pagos, reservas de inventario y paquetes | +| Despliegue | ¿Dónde se ubican las unidades de ejecución y cómo se conectan? | Borde, Kubernetes, servicios de datos, pago y redes de almacén y logística | +| Clases | ¿Cómo dependen los objetos de dominio y los contratos del código? | Servicio de aplicación Checkout, Order y cuatro puertos | +| Estados | ¿Qué eventos y condiciones de guarda hacen avanzar un pedido? | Pago, ejecución del pedido, cancelación, reembolso y cierre | +| Casos de uso | ¿Qué capacidades tiene cada actor? | Comprador, comercio, almacén y atención al cliente | +| Flujo de datos | ¿Por qué transformaciones y almacenes pasan los activos de datos? | Carrito, decisiones de transacción, eventos de pedidos, almacén y logística y comprobantes de entrega | Este modelo conceptual demuestra QGraphFlow y no corresponde a un repositorio de comercio concreto. El ejemplo `graph.json` no inventa rutas de código y marca las evidencias de las relaciones como `inference`. Los diagramas reales necesitan código, DDL, configuración, pruebas y requisitos aceptados que se puedan rastrear. @@ -224,10 +290,22 @@ node --test tests/*.test.mjs skills/q-flow/scripts/*.test.mjs Se necesitan Node.js 22 o posterior, npm, tar, zip y unzip. Al informar de un problema, incluir un gráfico mínimo sin datos sensibles, versiones del cliente y navegador y pasos de reproducción. -Documentación de referencia (en inglés): [Fuentes de evidencia](../../skills/q-flow/references/evidence-sources.md) · [Formato de gráficos](../../skills/q-flow/references/graph-schema.md) · [Consulta guiada](../../skills/q-flow/references/guided-intake.md) · [Desarrollo del Viewer](../../skills/q-flow/references/viewer-development.md) · [Composición de diagramas](../../skills/q-flow/references/visual-contract.md) +Documentación de referencia (en inglés): [Fuentes de evidencia](../../skills/q-flow/references/evidence-sources.md) · [Formato de gráficos](../../skills/q-flow/references/graph-schema.md) · [Consulta guiada](../../skills/q-flow/references/guided-intake.md) · [Desarrollo y aceptación del Viewer](../../skills/q-flow/references/viewer-development.md) · [Composición de diagramas](../../skills/q-flow/references/visual-contract.md) ## Licencia y atribución [MIT](../../LICENSE) · [Avisos de terceros](../../THIRD_PARTY_NOTICES.md) -QGraphFlow es un proyecto independiente con licencia MIT. Los escenarios de este documento son conceptuales y no representan la arquitectura de producción de ninguna empresa; no implica afiliación, patrocinio ni respaldo. +QGraphFlow es un proyecto independiente con licencia MIT. Los escenarios de este documento son ejemplos conceptuales y no representan la arquitectura de producción de ninguna empresa real. + +## Vistas generales de arquitectura + +La arquitectura incluye ahora relaciones entre componentes, capacidades de la plataforma y capas de ingeniería. Describe el objeto y la pregunta; la habilidad elige la plantilla. Las colecciones solicitadas pueden contener varias vistas de arquitectura con ediciones independientes. + +```text +$qgraphflow:q-flow Analiza las capacidades de la plataforma y las formas de integración de negocio del proyecto actual, y genera una vista general de capacidades de la plataforma en chino. +$qgraphflow:q-flow Analiza la organización del proyecto actual y sus capas de componentes, y genera vistas generales en chino del proyecto completo y de una sección de un componente. +$qgraphflow:q-flow Genera una vista general en inglés de las capacidades de la plataforma de este proyecto y muestra cómo se integran los módulos de aplicación. +``` + +Consulta [examples/jeepay](../../examples/jeepay) para ver las vistas de arquitectura de plataforma, ingeniería y relaciones entre componentes basadas en código fuente real. Desbloquea una vista general para reordenar tarjetas dentro de una capa o editar texto. Guardar conserva todas las vistas; restablecer restaura solo la actual. diff --git a/docs/readme/README.ja.md b/docs/readme/README.ja.md index 2881cca..b122259 100644 --- a/docs/readme/README.ja.md +++ b/docs/readme/README.ja.md @@ -10,16 +10,26 @@ [English](../../README.md) · [中文](../../docs/readme/README.zh-CN.md) · [Русский](../../docs/readme/README.ru.md) · [Português](../../docs/readme/README.pt.md) · [日本語](../../docs/readme/README.ja.md) · [Deutsch](../../docs/readme/README.de.md) · [Español](../../docs/readme/README.es.md) -[オンラインデモ](https://supermax92.github.io/qgraphflow/) · [クライアントへの導入](#インストールガイド) · [問題を報告](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) +[オンラインデモ](https://supermax92.github.io/qgraphflow/) · [はじめに](#はじめに) · [クライアントへの導入](#インストールガイド) · [問題を報告](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) -*11種類の図に対応:プラットフォーム機能アーキテクチャ、工程階層アーキテクチャ、コンポーネント関係アーキテクチャ、フローチャート、シーケンス、ER、配置、クラス、状態、ユースケース、データフロー。* +*11 種類の図に対応:プラットフォーム機能アーキテクチャ図、エンジニアリング層アーキテクチャ図、コンポーネント関係アーキテクチャ図、フローチャート、シーケンス図、ER 図、配置図、クラス図、状態図、ユースケース図、データフロー図。* QGraphFlow はソースコード、データ構造、設定、要件から対話型のソフトウェア図を生成します。関係の根拠を確認し、共有可能なオフライン HTML として届けます。 **ここが違う:** 1 つのスキルで 11 種類の図、すべての関係に根拠の種別、コードで裏付けられた関係にはその根拠となるソース行、自動レイアウト、ページ上での編集。プラグインのスクリプトと Viewer 自体はネットワーク通信を一切行いません。 +![Jeepay の複数ビュー操作デモ:コンポーネント関係アーキテクチャ図、シーケンス図、ER 図](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +Jeepay の実際のソースを例に、コンポーネント関係アーキテクチャ図、シーケンス図、ER 図を切り替えて、コンポーネントと呼び出し関係を探索します。 [原寸の GIF を見る](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +**複雑なシーケンス図の紹介** + +![複雑なシーケンス図の段階的な描画:参加者、ライフライン、メッセージ、活性区間、入れ子の結合フラグメント](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + +架空の EC シナリオに 9 人の参加者、29 件のメッセージ、6 個の結合フラグメントを含め、在庫の再試行、入れ子の分岐、並行処理、失敗時の補償、非同期コールバックを扱います。生成済みの図をアニメーションで段階的に表示し、複雑なシーケンス図の構造と詳細を紹介します。 [原寸の GIF を見る](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + ```bash npx skills add supermax92/qgraphflow ``` @@ -34,7 +44,61 @@ npx skills add supermax92/qgraphflow - **共有:** オフライン HTML を開くか、図全体を SVG / PNG に出力。 -The real-source [Jeepay corpus](../../examples/jeepay) contains all eleven views used by CI and the live demo. +実際のソースに基づく [Jeepay コーパス](../../examples/jeepay)には、CI とオンラインデモで使う 11 種類のビューがすべて含まれます。 + +## はじめに + +インストール後、クライアントで業務プロジェクトを開き、`q-flow` スキルを選択します。以下は Claude Code の `/q-flow` を使う例です。Codex ではクライアントが実際に提供する `$q-flow` または `$qgraphflow:q-flow` を使ってください。まだインストールしていない場合は、先に[インストールガイド](#インストールガイド)を参照してください。 + +### 1. 空の入力:どこから始めるか分からない + +要件を付けずにスキルだけを呼び出します。 + +```text +/q-flow +``` + +スキルが、分析する部分と図で答えてほしい問いの選択を案内します。必要な情報が明確になったら描画を始めます。 + +### 2. 機能を尋ねる:何を描けるか知る + +```text +/q-flow どの種類の図を描けますか?各種類はどのような問いに答えるのに適していますか?プロジェクトを引き継いだばかりなので、まず機能を紹介し、始める場所を提案してください。 +``` + +まず 11 種類の図の用途を知り、それからプロジェクト構造、呼び出し順序、データ関係など、何を見るかを決めます。 + +### 3. 曖昧な入力:大まかな目的だけがある + +```text +/q-flow このプロジェクトを図にしてください。できるだけ早く理解したいです。 +``` + +先に図の種類を指定する必要はありません。スキルがプロジェクトと目的に応じて適したビューを決め、必要な情報が不足している場合に質問します。 + +### 4. 正確な入力:範囲を明示して描画の詳細を求める + +次の業務名と手順を、プロジェクトに実際に存在するフローに置き換えてください。 + +```text +/q-flow 現在のプロジェクトの注文作成フローを分析し、中国語のシーケンス図を生成してください。 +リクエスト入口、価格計算、在庫引当、支払い承認、注文の永続化を含めてください。 +ソースに実際に存在する同期呼び出し、非同期メッセージ、対応する戻り、活性区間、条件分岐、再試行、失敗時の補償を残し、簡潔さのために詳細を省略しないでください。 +コンポーネントと呼び出しのソースファイルと行番号を示し、結果を docs/qgraphflow/order-sequence/ に保存してください。 +``` + +対象、問い、詳細度、出力先を明確に伝えると、そのまま開始できます。図には根拠のある事実だけを残します。 + +### 5. さらに詳しく:前の図の一部分を展開する + +結果の生成後、同じ会話で続けます。 + +```text +/q-flow 前のシーケンス図の在庫引当ステップを展開し、独立した中国語のフローチャートを生成してください。 +在庫確認、引当成功、再試行可能な失敗、再試行上限、在庫解放の分岐をすべて示してください。ソースコードに従い、コードにない手順は追加しないでください。 +``` + +まず全体を見てから、ひとつの手順を掘り下げます。既存の図の詳細追加や、関係の検証を続けて依頼することもできます。 ## インストールガイド @@ -113,7 +177,7 @@ qodercli plugins install . ### 3. 使い始める -クライアントで対象のプロジェクトを開き、新しいセッションでスキルを選択します。下の[すぐに使う](#すぐに使う)の例を参考に依頼し、生成された HTML をブラウザで開いてください。 +クライアントで業務プロジェクトを開き、新しいセッションでスキルを選択します。[はじめに](#はじめに)の例を参考に要件を伝えてください。生成後、出力された HTML をブラウザで開きます。 自分でビルドする場合は、[ソースからのビルド手順](https://github.com/supermax92/qgraphflow/blob/main/docs/distribution.md#prepare-locally)を参照してください。 @@ -132,7 +196,7 @@ $qgraphflow:q-flow ### 例1:プロジェクト構造を理解する ```text -$qgraphflow:q-flow 現在のプロジェクトを分析し、主要モジュールの責務、依存関係、システム境界を示す日本語のアーキテクチャ図を作成してください。 +$qgraphflow:q-flow 現在のプロジェクトを分析し、主要モジュールの責務、依存関係、システム境界を示す中国語のアーキテクチャ図を作成してください。 ``` 初めて触れるプロジェクトの全体像をつかむのに適しています。 @@ -140,13 +204,13 @@ $qgraphflow:q-flow 現在のプロジェクトを分析し、主要モジュー ### 例2:業務の呼び出しを追う ```text -$qgraphflow:q-flow 注文作成フローを分析し、価格計算、在庫引当、支払い、注文保存の呼び出し順と失敗分岐を示す日本語のシーケンス図を作成してください。 +$qgraphflow:q-flow 注文作成フローを分析し、価格計算、在庫引当、支払い、注文保存の呼び出し順と失敗分岐を示す中国語のシーケンス図を作成してください。 ``` 「注文作成」と各手順を実際の業務フローに置き換えてください。同じ会話で続けて依頼できます。 ```text -$qgraphflow:q-flow 前の図の在庫引当を掘り下げ、成功時と失敗時の処理を示す日本語のフローチャートを別に作成してください。 +$qgraphflow:q-flow 前の図の在庫引当を掘り下げ、成功時と失敗時の処理を示す中国語のフローチャートを別に作成してください。 ``` 既定の保存先は `docs/qgraphflow/` 配下です。`index.html` を開いて探索・編集・出力でき、`graph.json` に図データが残ります。各ビューは SVG(`diagram.svg`、複数ビューでは `diagram--.svg`)としても書き出され、README、プルリクエスト、Wiki に画像として埋め込めます。 @@ -154,9 +218,9 @@ $qgraphflow:q-flow 前の図の在庫引当を掘り下げ、成功時と失敗 ページで編集したあと、Chrome または Edge で「その他 → 変更を保存」を実行し、図のフォルダーを一度選ぶと、ページ、`graph.json`、SVG がその場で書き換わります。ほかのブラウザーは `graph.json` だけを保存します。そのフォルダーに置いてから `npx -y qgraphflow generate docs/qgraphflow//graph.json docs/qgraphflow/ --layout preserve --force` でページと SVG を再生成してください。
-Run the eleven-view Jeepay source example +Jeepay のソースに基づく 11 ビューの例を実行する -Select your Jeepay source checkout to verify the evidence: +根拠を検証するため、ローカルの Jeepay ソースチェックアウトを指定してください。 ```bash export JEEPAY_REPO_ROOT="" @@ -164,7 +228,7 @@ node skills/q-flow/scripts/validate-graph.mjs examples/jeepay/collection.graph.j node skills/q-flow/scripts/generate-viewer.mjs examples/jeepay/collection.graph.json output/jeepay --repo-root "$JEEPAY_REPO_ROOT" ``` -Open `output/jeepay/index.html`; its eleven SVGs are in the same directory. See the [corpus README](../../examples/jeepay) for the source revision and refresh procedure. +`output/jeepay/index.html` を開いてください。11 個の SVG も同じディレクトリにあります。ソースのリビジョンと更新手順は[コーパスの README](../../examples/jeepay)を参照してください。
@@ -198,19 +262,21 @@ $qgraphflow:q-flow CI で docs/qgraphflow/order-sequence の図が古いと出 スキルは、ファイル内で 1 か所だけ見つかったシンボルのアンカーを移し、なお失敗するアンカーだけを修正し、編集した位置と文字を保ったままページと SVG を再生成します。図を描き直すことはしません。 -## 9種類の図が答えること +## 11 種類の図がそれぞれ答えること -| 図 · PNG | 主な問い | サンプルの範囲 | +| ビュー · PNG | 主な問い | 本サンプルの範囲 | | --- | --- | --- | -| アーキテクチャ | どの責務が協調するか? | チャネル、決済、価格、リスク、在庫、支払い、注文、イベント、出荷 | -| フローチャート | 判断はどこで分岐・合流するか? | 欠品、リスク拒否、支払い補償、正常コミット | -| シーケンス | 呼び出しと戻りの順序は? | 決済成功経路と非同期 OrderPaid | -| ER | 主要データはどう関連するか? | カート、注文、明細、支払い、引当、荷物 | -| 配置 | 実行単位をどこに置き、どう接続するか? | エッジ、Kubernetes、データサービス、支払い、倉庫配送ネットワーク | -| クラス | ドメインオブジェクトと契約はどう依存するか? | 決済サービス、Order、4つのポート | -| 状態 | どのイベントとガードが注文を進めるか? | 支払い、出荷、取消、返金、終了 | -| ユースケース | 各利用者に何ができるか? | 購入者、店舗、倉庫、サポート | -| データフロー | データをどう変換・保存するか? | カート、取引判断、注文イベント、倉庫、配送受領記録 | +| プラットフォーム機能アーキテクチャ図 | プラットフォームにはどんな機能があるか? | 機能の区分とマトリクス | +| エンジニアリング層アーキテクチャ図 | プロジェクトのコードはどう構成されているか? | エンジニアリング層と共通の支援機能 | +| コンポーネント関係アーキテクチャ図 | システムのどの責務境界が協調するか? | チャネル、取引のオーケストレーション、価格、リスク、在庫、支払い、注文、イベント、注文履行 | +| フローチャート | 各判断点はどう分岐・合流するか? | 欠品、リスク拒否、支払い失敗時の補償、正常コミット | +| シーケンス図 | ひとつのリクエストはどの順序で呼び出しと戻りを行うか? | 決済成功の主経路と非同期 OrderPaid | +| ER 図 | 主要データはどう関連するか? | カート、注文、明細、支払い、在庫引当、荷物 | +| 配置図 | 実行単位をどこに置き、どう接続するか? | エッジ、Kubernetes、データサービス、支払い、倉庫配送ネットワーク | +| クラス図 | ドメインオブジェクトとコードの契約はどう依存するか? | Checkout アプリケーションサービス、Order、4 つのポート | +| 状態図 | どのイベントとガード条件が注文を進めるか? | 支払い、注文履行、取消、返金、終了 | +| ユースケース図 | 各参加者にはどんな機能があるか? | 購入者、店舗、倉庫、カスタマーサポート | +| データフロー図 | データ資産はどの変換と保存を経るか? | カート、取引判断、注文イベント、倉庫配送、配送受領記録 | これは QGraphFlow の機能を示す概念モデルで、特定の EC リポジトリに対応しません。サンプルの `graph.json` に架空のソースパスは入れず、関係の証拠を `inference` に統一しています。実際のプロジェクトでは、追跡可能なソース、DDL、設定、テスト、合意済み要件を使ってください。 @@ -224,10 +290,22 @@ node --test tests/*.test.mjs skills/q-flow/scripts/*.test.mjs 開発には Node.js 22 以降、npm、tar、zip、unzip が必要です。問題報告には、機密情報を除いた最小限の図データ、クライアントとブラウザーのバージョン、再現手順を添えてください。 -リファレンス(英語):[証拠の出典](../../skills/q-flow/references/evidence-sources.md) · [図データ形式](../../skills/q-flow/references/graph-schema.md) · [対話による要件確認](../../skills/q-flow/references/guided-intake.md) · [Viewer の開発](../../skills/q-flow/references/viewer-development.md) · [図の構成](../../skills/q-flow/references/visual-contract.md) +リファレンス(英語):[証拠の出典](../../skills/q-flow/references/evidence-sources.md) · [図データ形式](../../skills/q-flow/references/graph-schema.md) · [対話による要件確認](../../skills/q-flow/references/guided-intake.md) · [Viewer の開発と受け入れ検証](../../skills/q-flow/references/viewer-development.md) · [図の構成](../../skills/q-flow/references/visual-contract.md) ## ライセンスと帰属 [MIT](../../LICENSE) · [第三者の表示](../../THIRD_PARTY_NOTICES.md) -QGraphFlow は MIT ライセンスの独立プロジェクトです。本文のシナリオは概念例で、実在する企業の本番構成を表しません。提携、後援、推奨を意味するものでもありません。 +QGraphFlow は MIT ライセンスを採用する独立したプロジェクトです。本文のシナリオは概念例で、実在する企業の本番アーキテクチャを表しません。 + +## アーキテクチャの全体像 + +アーキテクチャには、コンポーネント関係、プラットフォーム機能、エンジニアリング層が含まれます。対象と問いを伝えると、スキルがテンプレートを選びます。依頼したコレクションには、個別に編集できる複数のアーキテクチャビューを含められます。 + +```text +$qgraphflow:q-flow 現在のプロジェクトのプラットフォーム機能と業務の接続方法を分析し、中国語のプラットフォーム機能全体図を生成してください。 +$qgraphflow:q-flow 現在のプロジェクト構成とコンポーネントの階層を分析し、プロジェクト全体とひとつのコンポーネント断面の中国語の全体図を生成してください。 +$qgraphflow:q-flow このプロジェクトのプラットフォーム機能の全体図を英語で生成し、アプリケーションモジュールがどのように統合されるかを示してください。 +``` + +実際のソースに基づくプラットフォーム、エンジニアリング、コンポーネント関係のアーキテクチャビューは [examples/jeepay](../../examples/jeepay)を参照してください。全体図のロックを解除すると、同じ層のカードの並べ替えや文字の編集ができます。保存は全ビューを保持し、リセットは現在のビューだけを戻します。 diff --git a/docs/readme/README.pt.md b/docs/readme/README.pt.md index 604257d..1bc62a6 100644 --- a/docs/readme/README.pt.md +++ b/docs/readme/README.pt.md @@ -10,16 +10,26 @@ Siga o caminho. Confira as evidências. Compartilhe um arquivo offline. [English](../../README.md) · [中文](../../docs/readme/README.zh-CN.md) · [Русский](../../docs/readme/README.ru.md) · [Português](../../docs/readme/README.pt.md) · [日本語](../../docs/readme/README.ja.md) · [Deutsch](../../docs/readme/README.de.md) · [Español](../../docs/readme/README.es.md) -[Demonstração online](https://supermax92.github.io/qgraphflow/) · [Instalação por cliente](#guia-de-instalação) · [Relatar problema](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) +[Demonstração online](https://supermax92.github.io/qgraphflow/) · [Primeiros passos](#primeiros-passos) · [Instalação por cliente](#guia-de-instalação) · [Relatar problema](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) -*Onze tipos: arquitetura de capacidades, arquitetura em camadas, arquitetura de componentes, fluxograma, sequência, ER, implantação, classes, estados, casos de uso e fluxo de dados.* +*Onze tipos de diagrama: arquitetura de capacidades da plataforma, arquitetura em camadas de engenharia, arquitetura de relações entre componentes, fluxograma, sequência, ER, implantação, classes, estados, casos de uso e fluxo de dados.* QGraphFlow gera diagramas de software interativos a partir de código, esquemas, configuração e requisitos. As relações podem ser verificadas e o resultado é um HTML offline compartilhável. **O que o diferencia:** onze tipos de diagrama em uma só habilidade, o tipo de evidência de cada relação e a linha de código por trás de cada uma que o código sustenta, layout automático, edição na própria página e nenhuma requisição de rede dos scripts do plugin nem do próprio Viewer. +![Demonstração interativa de várias vistas do Jeepay: arquitetura de relações entre componentes, sequência e ER](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +Com o código-fonte real do Jeepay, alterne entre diagramas de arquitetura de relações entre componentes, sequência e ER para explorar componentes e relações de chamadas. [Ver GIF no tamanho original](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +**Apresentação de um diagrama de sequência complexo** + +![Desenho gradual de um diagrama de sequência complexo: participantes, linhas de vida, mensagens, barras de ativação e fragmentos combinados aninhados](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + +Um cenário fictício de comércio eletrônico contém 9 participantes, 29 mensagens e 6 fragmentos combinados, com novas tentativas de estoque, ramos aninhados, processamento paralelo, compensação de falhas e callbacks assíncronos. A animação revela gradualmente o diagrama gerado para mostrar sua estrutura e seus detalhes. [Ver GIF no tamanho original](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + ```bash npx skills add supermax92/qgraphflow ``` @@ -34,7 +44,61 @@ Um só comando instala a habilidade para Claude Code, Codex, Cursor e Qoder; a i - **Compartilhar:** abrir o HTML offline ou exportar o diagrama completo em SVG / PNG. -The real-source [Jeepay corpus](../../examples/jeepay) contains all eleven views used by CI and the live demo. +O [corpus do Jeepay](../../examples/jeepay), baseado em código-fonte real, contém as onze vistas usadas pelo CI e pela demonstração online. + +## Primeiros passos + +Após a instalação, abra seu projeto de negócio no cliente e selecione a habilidade `q-flow`. Os exemplos usam `/q-flow` do Claude Code; no Codex, use a entrada `$q-flow` ou `$qgraphflow:q-flow` que seu cliente realmente oferece. Ainda não instalou? Consulte primeiro o [guia de instalação](#guia-de-instalação). + +### 1. Entrada vazia: Não sabe por onde começar + +Invoque a habilidade sem acrescentar uma solicitação: + +```text +/q-flow +``` + +A habilidade orienta você a escolher a parte a analisar e a pergunta que o diagrama deve responder. O desenho começa quando as informações necessárias estão claras. + +### 2. Perguntar sobre capacidades: Saber o que pode desenhar + +```text +/q-flow Quais tipos de diagrama você pode desenhar? Que perguntas cada tipo responde? Acabei de assumir um projeto; apresente suas capacidades e sugira um ponto de partida. +``` + +Conheça primeiro os usos dos onze tipos de diagrama e depois decida se quer explorar a estrutura do projeto, a ordem das chamadas, as relações de dados ou outro conteúdo. + +### 3. Entrada vaga: Apenas um objetivo geral + +```text +/q-flow Ajude-me a desenhar este projeto. Quero entendê-lo o mais rápido possível. +``` + +Não é necessário indicar primeiro o tipo de diagrama. A habilidade determina uma vista adequada com base no projeto e no seu objetivo, e pergunta quando faltam informações necessárias. + +### 4. Entrada precisa: Definir o escopo e pedir detalhes do desenho + +Substitua os nomes de negócio e os passos abaixo por fluxos que realmente existam no seu projeto: + +```text +/q-flow Analise o fluxo de criação de pedidos do projeto atual e gere um diagrama de sequência em chinês. +Inclua a entrada da solicitação, o cálculo de preços, a reserva de estoque, a autorização de pagamento e a persistência do pedido. +Mantenha as chamadas síncronas, mensagens assíncronas, retornos emparelhados, barras de ativação, ramificações condicionais, tentativas e compensações de falha que realmente existam no código. Não omita detalhes por brevidade. +Indique os arquivos e números de linha do código dos componentes e chamadas, e salve o resultado em docs/qgraphflow/order-sequence/. +``` + +Defina claramente o objeto, a pergunta, o nível de detalhe e o local de saída para começar diretamente. O diagrama mantém apenas fatos sustentados por evidências. + +### 5. Refinar mais: Expandir parte do diagrama anterior + +Após a geração do resultado, continue na mesma conversa: + +```text +/q-flow Expanda a etapa de reserva de estoque do diagrama de sequência anterior em um fluxograma separado em chinês. +Mostre todos os ramos de validação do estoque, reserva bem-sucedida, falhas que permitem nova tentativa, limite de tentativas e liberação de estoque. Siga o código-fonte e não adicione etapas ausentes dele. +``` + +Veja primeiro o todo e depois aprofunde uma etapa. Você também pode pedir mais detalhes de um diagrama existente ou verificar suas relações. ## Guia de instalação @@ -113,11 +177,11 @@ Confirme que `.cursor-plugin/plugin.json` existe nesse local, recarregue a janel ### 3. Comece a usar -Abra seu projeto no cliente, inicie uma nova sessão e selecione a habilidade. Descreva a tarefa seguindo os exemplos de [Início rápido](#início-rápido) abaixo. Abra o HTML gerado no navegador. +Abra seu projeto de negócio no cliente, inicie uma nova sessão e selecione a habilidade. Descreva sua solicitação seguindo os exemplos de [Primeiros passos](#primeiros-passos). Abra o HTML gerado no navegador. Quer compilar por conta própria? Consulte as [instruções de compilação do código-fonte](https://github.com/supermax92/qgraphflow/blob/main/docs/distribution.md#prepare-locally). -## Início rápido +## Uso rápido Os exemplos usam `$qgraphflow:q-flow` no Codex. Se o cliente mostrar `$q-flow`, selecione essa entrada. Nos demais clientes, use a forma de chamada da habilidade indicada acima. @@ -132,7 +196,7 @@ $qgraphflow:q-flow ### Exemplo 1: Entender a arquitetura ```text -$qgraphflow:q-flow Analise este projeto e crie um diagrama de arquitetura em português com responsabilidades dos módulos, dependências e limites do sistema. +$qgraphflow:q-flow Analise este projeto e crie um diagrama de arquitetura em chinês com responsabilidades dos módulos, dependências e limites do sistema. ``` Útil para conhecer a estrutura geral ao entrar em um projeto. @@ -140,13 +204,13 @@ $qgraphflow:q-flow Analise este projeto e crie um diagrama de arquitetura em por ### Exemplo 2: Acompanhar um fluxo de negócio ```text -$qgraphflow:q-flow Analise a criação de pedidos e gere um diagrama de sequência em português com cálculo de preços, reserva de estoque, pagamento e persistência do pedido, incluindo os ramos de falha. +$qgraphflow:q-flow Analise a criação de pedidos e gere um diagrama de sequência em chinês com cálculo de preços, reserva de estoque, pagamento e persistência do pedido, incluindo os ramos de falha. ``` Substitua a criação de pedidos e suas etapas pelo fluxo real do projeto. Continue na mesma conversa: ```text -$qgraphflow:q-flow Expanda a reserva de estoque do diagrama anterior em um fluxograma separado em português, mostrando o tratamento de sucesso e falha. +$qgraphflow:q-flow Expanda a reserva de estoque do diagrama anterior em um fluxograma separado em chinês, mostrando o tratamento de sucesso e falha. ``` Os resultados ficam em `docs/qgraphflow/` por padrão. Abra `index.html` para explorar, editar e exportar; `graph.json` mantém os dados do grafo. Cada vista também é gravada como SVG (`diagram.svg`, ou `diagram--.svg` quando há várias vistas), que pode ser incorporado como imagem em um README, pull request ou wiki. @@ -154,9 +218,9 @@ Os resultados ficam em `docs/qgraphflow/` por padrão. Abra `index.html` para ex Depois de editar na página, **Mais → Salvar alterações** no Chrome ou no Edge regrava a página, o `graph.json` e os SVG no lugar, depois que você escolhe a pasta do diagrama uma vez. Outros navegadores salvam só o `graph.json`: coloque-o na pasta e gere de novo a página e os SVG com `npx -y qgraphflow generate docs/qgraphflow//graph.json docs/qgraphflow/ --layout preserve --force`.
-Run the eleven-view Jeepay source example +Executar o exemplo do Jeepay com onze vistas baseado em código-fonte -Select your Jeepay source checkout to verify the evidence: +Selecione sua cópia local do código-fonte do Jeepay para verificar as evidências: ```bash export JEEPAY_REPO_ROOT="" @@ -164,7 +228,7 @@ node skills/q-flow/scripts/validate-graph.mjs examples/jeepay/collection.graph.j node skills/q-flow/scripts/generate-viewer.mjs examples/jeepay/collection.graph.json output/jeepay --repo-root "$JEEPAY_REPO_ROOT" ``` -Open `output/jeepay/index.html`; its eleven SVGs are in the same directory. See the [corpus README](../../examples/jeepay) for the source revision and refresh procedure. +Abra `output/jeepay/index.html`; os onze SVG estão no mesmo diretório. Consulte o [README do corpus](../../examples/jeepay) para conhecer a revisão do código-fonte e o procedimento de atualização.
@@ -198,19 +262,21 @@ $qgraphflow:q-flow O CI diz que docs/qgraphflow/order-sequence está desatualiza A habilidade move as âncoras cujo símbolo aparece uma única vez no arquivo, corrige apenas as âncoras que ainda falham e gera de novo a página e os SVG, mantendo as posições e os textos que você editou. Ela não redesenha o diagrama. -## O que cada uma das nove vistas responde +## O que cada um dos onze tipos de diagrama responde | Vista · PNG | Pergunta principal | Escopo do exemplo | | --- | --- | --- | -| Arquitetura | Quais responsabilidades colaboram? | Canais, compra, preços, risco, estoque, pagamento, pedidos, eventos e entrega | -| Fluxograma | Onde o processo se ramifica e converge? | Falta de estoque, recusa de risco, compensação de pagamento e confirmação bem-sucedida | -| Sequência | Em que ordem ocorrem chamadas e retornos? | Compra bem-sucedida e OrderPaid assíncrono | -| ER | Como os dados centrais se relacionam? | Carrinho, pedidos, itens, pagamentos, reservas e pacotes | -| Implantação | Onde as unidades executam e se conectam? | Borda, Kubernetes, serviços de dados, pagamentos e redes logísticas | -| Classes | Como objetos de domínio e contratos dependem entre si? | Serviço de compra, Order e quatro portas | -| Estados | Quais eventos e condições avançam um pedido? | Pagamento, entrega, cancelamento, reembolso e encerramento | -| Casos de uso | O que cada ator pode fazer? | Comprador, lojista, depósito e atendimento | -| Fluxo de dados | Como os dados são transformados e armazenados? | Carrinho, decisões, eventos, depósito e comprovantes de entrega | +| Arquitetura de capacidades da plataforma | Quais capacidades a plataforma oferece? | Zonas de capacidades e matrizes | +| Arquitetura em camadas de engenharia | Como o código do projeto é organizado? | Camadas de engenharia e suporte compartilhado | +| Arquitetura de relações entre componentes | Quais limites de responsabilidade colaboram no sistema? | Canais, orquestração de transações, preços, risco, estoque, pagamento, pedidos, eventos e execução de pedidos | +| Fluxograma | Como cada decisão se ramifica e converge? | Falta de estoque, recusa de risco, compensação de falha de pagamento e confirmação bem-sucedida | +| Sequência | Em que ordem uma solicitação faz chamadas e recebe retornos? | Fluxo principal de compra bem-sucedida e OrderPaid assíncrono | +| ER | Como os dados centrais se relacionam? | Carrinho, pedidos, itens, pagamentos, reservas de estoque e pacotes | +| Implantação | Onde ficam as unidades de execução e como se conectam? | Borda, Kubernetes, serviços de dados, pagamento e redes de armazém e logística | +| Classes | Como objetos de domínio e contratos do código dependem entre si? | Serviço de aplicação Checkout, Order e quatro portas | +| Estados | Quais eventos e condições de guarda fazem um pedido avançar? | Pagamento, execução do pedido, cancelamento, reembolso e encerramento | +| Casos de uso | Quais capacidades cada ator possui? | Comprador, lojista, depósito e atendimento | +| Fluxo de dados | Por quais transformações e armazenamentos os ativos de dados passam? | Carrinho, decisões de transação, eventos de pedidos, armazém e logística e comprovantes de entrega | Este modelo conceitual demonstra o QGraphFlow e não corresponde a um repositório de comércio específico. O exemplo `graph.json` não inventa caminhos de código e marca as evidências das relações como `inference`. Diagramas reais precisam de código, DDL, configuração, testes e requisitos aceitos rastreáveis. @@ -224,10 +290,22 @@ node --test tests/*.test.mjs skills/q-flow/scripts/*.test.mjs São necessários Node.js 22 ou posterior, npm, tar, zip e unzip. Ao relatar problemas, inclua um grafo mínimo sem informações sensíveis, versões do cliente e navegador e passos de reprodução. -Documentação de referência (em inglês): [Fontes de evidência](../../skills/q-flow/references/evidence-sources.md) · [Formato dos grafos](../../skills/q-flow/references/graph-schema.md) · [Consulta guiada](../../skills/q-flow/references/guided-intake.md) · [Desenvolvimento do Viewer](../../skills/q-flow/references/viewer-development.md) · [Composição de diagramas](../../skills/q-flow/references/visual-contract.md) +Documentação de referência (em inglês): [Fontes de evidência](../../skills/q-flow/references/evidence-sources.md) · [Formato dos grafos](../../skills/q-flow/references/graph-schema.md) · [Consulta guiada](../../skills/q-flow/references/guided-intake.md) · [Desenvolvimento e aceitação do Viewer](../../skills/q-flow/references/viewer-development.md) · [Composição de diagramas](../../skills/q-flow/references/visual-contract.md) ## Licença e atribuição [MIT](../../LICENSE) · [Avisos de terceiros](../../THIRD_PARTY_NOTICES.md) -QGraphFlow é um projeto independente sob a licença MIT. Os cenários deste documento são conceituais e não representam a arquitetura de produção de nenhuma empresa; não implica afiliação, patrocínio ou endosso. +QGraphFlow é um projeto independente sob a licença MIT. Os cenários deste documento são exemplos conceituais e não representam a arquitetura de produção de nenhuma empresa real. + +## Visões gerais de arquitetura + +A arquitetura agora inclui relações entre componentes, capacidades da plataforma e camadas de engenharia. Descreva o objeto e a pergunta; a habilidade escolhe o modelo. As coleções solicitadas podem conter várias vistas de arquitetura com edições independentes. + +```text +$qgraphflow:q-flow Analise as capacidades da plataforma e as formas de integração de negócio do projeto atual e gere uma visão geral de capacidades da plataforma em chinês. +$qgraphflow:q-flow Analise a organização do projeto atual e suas camadas de componentes e gere visões gerais em chinês do projeto inteiro e de uma seção de um componente. +$qgraphflow:q-flow Gere uma visão geral em inglês das capacidades da plataforma deste projeto e mostre como os módulos de aplicação se integram. +``` + +Consulte [examples/jeepay](../../examples/jeepay) para ver as vistas de arquitetura de plataforma, engenharia e relações entre componentes baseadas em código-fonte real. Desbloqueie uma visão geral para reordenar cartões dentro de uma camada ou editar textos. Salvar mantém todas as vistas; redefinir restaura apenas a atual. diff --git a/docs/readme/README.ru.md b/docs/readme/README.ru.md index 141f3e7..39c7a23 100644 --- a/docs/readme/README.ru.md +++ b/docs/readme/README.ru.md @@ -10,16 +10,26 @@ [English](../../README.md) · [中文](../../docs/readme/README.zh-CN.md) · [Русский](../../docs/readme/README.ru.md) · [Português](../../docs/readme/README.pt.md) · [日本語](../../docs/readme/README.ja.md) · [Deutsch](../../docs/readme/README.de.md) · [Español](../../docs/readme/README.es.md) -[Онлайн-демо](https://supermax92.github.io/qgraphflow/) · [Установка для клиентов](#установка) · [Сообщить о проблеме](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) +[Онлайн-демо](https://supermax92.github.io/qgraphflow/) · [Первые шаги](#первые-шаги) · [Установка для клиентов](#установка) · [Сообщить о проблеме](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) -*Одиннадцать типов: архитектура возможностей платформы, слоистая архитектура, архитектура компонентов, блок-схема, последовательность, ER, развёртывание, классы, состояния, варианты использования и поток данных.* +*Поддерживаются одиннадцать типов диаграмм: архитектура возможностей платформы, архитектура инженерных слоёв, архитектура связей компонентов, блок-схема, последовательность, ER, развёртывание, классы, состояния, варианты использования и поток данных.* QGraphFlow создаёт интерактивные диаграммы программных систем из кода, схем данных, конфигурации и требований. Основания связей можно проверить, а результат — передать как автономный HTML. **Чем отличается:** одиннадцать типов диаграмм в одном навыке, тип доказательства для каждой связи и строка кода за каждой связью, подтверждённой кодом, автоматическая раскладка, редактирование прямо на странице и никаких сетевых запросов ни от скриптов плагина, ни от самого Viewer. +![Интерактивная демонстрация нескольких видов Jeepay: архитектура связей компонентов, последовательность и ER](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +На примере реального исходного кода Jeepay переключайтесь между архитектурой связей компонентов, диаграммой последовательности и ER, изучая компоненты и связи вызовов. [Посмотреть GIF в исходном размере](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.en.hero.gif) + +**Демонстрация сложной диаграммы последовательности** + +![Пошаговое рисование сложной диаграммы последовательности: участники, линии жизни, сообщения, полосы активации и вложенные комбинированные фрагменты](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + +Вымышленный сценарий электронной торговли содержит 9 участников, 29 сообщений и 6 комбинированных фрагментов: повторные попытки резервирования, вложенные ветви, параллельную обработку, компенсацию сбоев и асинхронные обратные вызовы. Анимация постепенно показывает готовую диаграмму, раскрывая её структуру и детали. [Посмотреть GIF в исходном размере](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.en.sequence-drawing.gif) + ```bash npx skills add supermax92/qgraphflow ``` @@ -34,7 +44,61 @@ npx skills add supermax92/qgraphflow - **Обмен:** автономный HTML или экспорт всей диаграммы в SVG / PNG. -The real-source [Jeepay corpus](../../examples/jeepay) contains all eleven views used by CI and the live demo. +Основанный на реальном коде [корпус Jeepay](../../examples/jeepay) содержит все одиннадцать видов, используемых CI и онлайн-демонстрацией. + +## Первые шаги + +После установки откройте свой рабочий проект в клиенте и выберите навык `q-flow`. Примеры используют `/q-flow` в Claude Code; в Codex используйте пункт `$q-flow` или `$qgraphflow:q-flow`, который действительно предлагает ваш клиент. Ещё не установили? Сначала прочитайте раздел [Установка](#установка). + +### 1. Пустой ввод: Не знаете, с чего начать + +Вызовите навык без дополнительных требований: + +```text +/q-flow +``` + +Навык поможет выбрать часть для анализа и вопрос, на который должна ответить диаграмма. Рисование начнётся, когда необходимая информация будет ясна. + +### 2. Вопрос о возможностях: Узнать, что можно нарисовать + +```text +/q-flow Какие типы диаграмм ты можешь рисовать? На какие вопросы отвечает каждый тип? Я только что принял проект; сначала расскажи о своих возможностях и предложи, с чего начать. +``` + +Сначала узнайте назначение одиннадцати типов диаграмм, затем решите, что изучать: структуру проекта, порядок вызовов, связи данных или что-то другое. + +### 3. Неопределённый ввод: Есть только общая цель + +```text +/q-flow Помоги нарисовать этот проект. Я хочу как можно быстрее его понять. +``` + +Не нужно заранее указывать тип диаграммы. Навык определит подходящий вид по проекту и вашей цели и задаст вопросы, если необходимой информации не хватает. + +### 4. Точный ввод: Задать границы и запросить детали рисунка + +Замените названия и шаги ниже процессами, которые действительно есть в вашем проекте: + +```text +/q-flow Проанализируй создание заказа в текущем проекте и создай диаграмму последовательности на китайском языке. +Включи точку входа запроса, расчёт цены, резервирование запасов, авторизацию платежа и сохранение заказа. +Сохрани синхронные вызовы, асинхронные сообщения, парные возвраты, полосы активации, условные ветви, повторные попытки и компенсацию сбоев, которые действительно есть в исходниках. Не опускай детали ради краткости. +Укажи исходные файлы и номера строк компонентов и вызовов и сохрани результат в docs/qgraphflow/order-sequence/. +``` + +Чётко задайте предмет, вопрос, уровень детализации и место вывода, чтобы начать сразу. Диаграмма сохраняет только факты, подтверждённые доказательствами. + +### 5. Дальнейшее уточнение: Раскрыть часть предыдущей диаграммы + +После получения результата продолжите в том же разговоре: + +```text +/q-flow Раскрой резервирование запасов из предыдущей диаграммы последовательности отдельной блок-схемой на китайском языке. +Покажи все ветви проверки запасов, успешного резервирования, повторяемых сбоев, предела повторных попыток и освобождения запасов. Следуй исходному коду и не добавляй отсутствующих в нём шагов. +``` + +Сначала рассмотрите целое, затем углубитесь в один шаг. Можно также запросить дополнительные детали существующей диаграммы или проверить её связи. ## Установка @@ -113,11 +177,11 @@ qodercli plugins install . ### 3. Начните работу -Откройте свой проект в клиенте, начните новую сессию и выберите навык. Опишите задачу по примерам в разделе [Быстрый старт](#быстрый-старт) ниже. Откройте полученный HTML в браузере. +Откройте свой рабочий проект в клиенте, начните новую сессию и выберите навык. Опишите требования по примерам в разделе [Первые шаги](#первые-шаги). После генерации откройте выходной HTML в браузере. Хотите собрать самостоятельно? См. [инструкции по сборке из исходников](https://github.com/supermax92/qgraphflow/blob/main/docs/distribution.md#prepare-locally). -## Быстрый старт +## Быстрое использование Примеры используют `$qgraphflow:q-flow` в Codex. Если клиент показывает `$q-flow`, выберите этот пункт. Для других клиентов используйте способ вызова навыка, указанный выше. @@ -132,7 +196,7 @@ $qgraphflow:q-flow ### Пример 1: Понять архитектуру проекта ```text -$qgraphflow:q-flow Проанализируй текущий проект и создай диаграмму архитектуры на русском языке: обязанности модулей, зависимости и границы системы. +$qgraphflow:q-flow Проанализируй текущий проект и создай диаграмму архитектуры на китайском языке: обязанности модулей, зависимости и границы системы. ``` Подходит для первого знакомства с общей структурой проекта. @@ -140,13 +204,13 @@ $qgraphflow:q-flow Проанализируй текущий проект и с ### Пример 2: Проследить бизнес-процесс ```text -$qgraphflow:q-flow Проанализируй создание заказа и создай диаграмму последовательности на русском языке: расчёт цены, резерв запасов, оплата и сохранение заказа, включая ветки сбоев. +$qgraphflow:q-flow Проанализируй создание заказа и создай диаграмму последовательности на китайском языке: расчёт цены, резерв запасов, оплата и сохранение заказа, включая ветки сбоев. ``` Замените создание заказа и его шаги реальным процессом проекта. Продолжите в том же разговоре: ```text -$qgraphflow:q-flow Подробно раскрой резерв запасов из предыдущей диаграммы отдельной блок-схемой на русском языке с обработкой успеха и сбоев. +$qgraphflow:q-flow Подробно раскрой резерв запасов из предыдущей диаграммы отдельной блок-схемой на китайском языке с обработкой успеха и сбоев. ``` По умолчанию результаты находятся в `docs/qgraphflow/`. Откройте `index.html` для исследования, редактирования и экспорта; `graph.json` хранит данные графа. Каждый вид также записывается как SVG (`diagram.svg`, а для нескольких видов — `diagram--.svg`), который можно встроить как изображение в README, pull request или вики. @@ -154,9 +218,9 @@ $qgraphflow:q-flow Подробно раскрой резерв запасов После правок на странице команда **Ещё → Сохранить изменения** в Chrome или Edge перезаписывает страницу, `graph.json` и SVG на месте, когда вы один раз выберете каталог диаграммы. Другие браузеры сохраняют только `graph.json`: положите его в этот каталог и пересоздайте страницу и SVG командой `npx -y qgraphflow generate docs/qgraphflow//graph.json docs/qgraphflow/ --layout preserve --force`.
-Run the eleven-view Jeepay source example +Запустить пример Jeepay с одиннадцатью видами на основе исходного кода -Select your Jeepay source checkout to verify the evidence: +Выберите локальную копию исходников Jeepay для проверки доказательств: ```bash export JEEPAY_REPO_ROOT="" @@ -164,7 +228,7 @@ node skills/q-flow/scripts/validate-graph.mjs examples/jeepay/collection.graph.j node skills/q-flow/scripts/generate-viewer.mjs examples/jeepay/collection.graph.json output/jeepay --repo-root "$JEEPAY_REPO_ROOT" ``` -Open `output/jeepay/index.html`; its eleven SVGs are in the same directory. See the [corpus README](../../examples/jeepay) for the source revision and refresh procedure. +Откройте `output/jeepay/index.html`; одиннадцать SVG находятся в том же каталоге. Версия исходников и процедура обновления описаны в [README корпуса](../../examples/jeepay).
@@ -198,19 +262,21 @@ $qgraphflow:q-flow CI сообщает, что диаграмма docs/qgraphflo Навык переносит якоря, символ которых встречается в файле один раз, исправляет только якоря с оставшимися ошибками и пересоздаёт страницу и SVG, сохраняя ваши позиции и тексты. Заново диаграмму он не рисует. -## На какие вопросы отвечают девять видов +## На какие вопросы отвечает каждый из одиннадцати типов диаграмм | Вид · PNG | Главный вопрос | Область примера | | --- | --- | --- | -| Архитектура | Какие зоны ответственности взаимодействуют? | Каналы, покупка, цены, риски, запасы, оплата, заказы, события и доставка | -| Блок-схема | Где процесс ветвится и сходится? | Нехватка запасов, отказ по риску, компенсация оплаты и успешная фиксация | -| Последовательность | Каков порядок вызовов и ответов? | Успешная покупка и асинхронный OrderPaid | -| ER | Как связаны основные данные? | Корзина, заказы, позиции, платежи, резервы и посылки | -| Развёртывание | Где работают и как соединены единицы исполнения? | Периметр, Kubernetes, данные, платежи и логистические сети | -| Классы | Как зависят объекты домена и контракты? | Сервис покупки, Order и четыре порта | -| Состояния | Какие события и условия продвигают заказ? | Оплата, доставка, отмена, возврат денег и закрытие | -| Варианты использования | Что может каждый участник? | Покупатель, продавец, склад и поддержка | -| Поток данных | Как данные преобразуются и сохраняются? | Корзина, решения, события, склад и подтверждения доставки | +| Архитектура возможностей платформы | Какие возможности есть у платформы? | Зоны возможностей и матрицы | +| Архитектура инженерных слоёв | Как организован код проекта? | Инженерные слои и общая поддержка | +| Архитектура связей компонентов | Какие границы ответственности взаимодействуют в системе? | Каналы, оркестрация транзакций, цены, риски, запасы, платежи, заказы, события и исполнение заказов | +| Блок-схема | Как ветвится и сходится каждая точка решения? | Нехватка запасов, отказ по риску, компенсация сбоя оплаты и успешная фиксация | +| Последовательность | В каком порядке запрос выполняет вызовы и получает возвраты? | Основной путь успешной покупки и асинхронный OrderPaid | +| ER | Как связаны основные данные? | Корзина, заказы, позиции, платежи, резервы запасов и посылки | +| Развёртывание | Где размещены единицы исполнения и как они соединены? | Периметр, Kubernetes, сервисы данных, платежи и складские/логистические сети | +| Классы | Как зависят объекты домена и контракты кода? | Прикладной сервис Checkout, Order и четыре порта | +| Состояния | Какие события и охранные условия продвигают заказ? | Оплата, исполнение, отмена, возврат денег и закрытие | +| Варианты использования | Какие возможности есть у каждого участника? | Покупатель, продавец, склад и поддержка клиентов | +| Поток данных | Через какие преобразования и хранилища проходят данные? | Корзина, решения по транзакциям, события заказов, склад/логистика и подтверждения доставки | Это концептуальная демонстрация QGraphFlow, а не модель конкретного торгового репозитория. Пример `graph.json` не выдумывает пути к коду и обозначает основания связей как `inference`. Диаграммы реальных проектов требуют прослеживаемых исходников, DDL, конфигурации, тестов и согласованных требований. @@ -224,10 +290,22 @@ node --test tests/*.test.mjs skills/q-flow/scripts/*.test.mjs Нужны Node.js 22 или новее, npm, tar, zip и unzip. В сообщении о проблеме приложите минимальный граф без конфиденциальных данных, версии клиента и браузера и шаги воспроизведения. -Справочная документация (на английском): [Источники оснований](../../skills/q-flow/references/evidence-sources.md) · [Формат графов](../../skills/q-flow/references/graph-schema.md) · [Уточнение запроса](../../skills/q-flow/references/guided-intake.md) · [Разработка Viewer](../../skills/q-flow/references/viewer-development.md) · [Композиция диаграмм](../../skills/q-flow/references/visual-contract.md) +Справочная документация (на английском): [Источники оснований](../../skills/q-flow/references/evidence-sources.md) · [Формат графов](../../skills/q-flow/references/graph-schema.md) · [Уточнение запроса](../../skills/q-flow/references/guided-intake.md) · [Разработка и приёмка Viewer](../../skills/q-flow/references/viewer-development.md) · [Композиция диаграмм](../../skills/q-flow/references/visual-contract.md) ## Лицензия и принадлежность [MIT](../../LICENSE) · [Уведомления третьих сторон](../../THIRD_PARTY_NOTICES.md) -QGraphFlow — независимый проект под лицензией MIT. Сценарии в этом документе являются концептуальными и не представляют производственную архитектуру какой-либо компании; связь, спонсорство или одобрение не подразумеваются. +QGraphFlow — независимый проект под лицензией MIT. Сценарии в этом документе являются концептуальными примерами и не представляют производственную архитектуру какой-либо реальной компании. + +## Обзоры архитектуры + +Архитектура теперь включает связи компонентов, возможности платформы и инженерные слои. Опишите предмет и вопрос; навык выберет шаблон. Запрошенные коллекции могут содержать несколько архитектурных видов с независимыми правками. + +```text +$qgraphflow:q-flow Проанализируй возможности платформы и способы бизнес-интеграции текущего проекта и создай обзор возможностей платформы на китайском языке. +$qgraphflow:q-flow Проанализируй организацию текущего проекта и слои компонентов и создай китайские обзоры всего проекта и среза одного компонента. +$qgraphflow:q-flow Создай обзор возможностей платформы этого проекта на английском языке и покажи, как интегрируются прикладные модули. +``` + +См. [examples/jeepay](../../examples/jeepay) для архитектурных видов платформы, инженерной организации и связей компонентов на основе реального кода. Разблокируйте обзор, чтобы менять порядок карточек в слое или редактировать текст. Сохранение удерживает все виды; сброс восстанавливает только текущий. diff --git a/docs/readme/README.zh-CN.md b/docs/readme/README.zh-CN.md index 608ec65..4367651 100644 --- a/docs/readme/README.zh-CN.md +++ b/docs/readme/README.zh-CN.md @@ -10,7 +10,7 @@ [English](../../README.md) · [中文](../../docs/readme/README.zh-CN.md) · [Русский](../../docs/readme/README.ru.md) · [Português](../../docs/readme/README.pt.md) · [日本語](../../docs/readme/README.ja.md) · [Deutsch](../../docs/readme/README.de.md) · [Español](../../docs/readme/README.es.md) -[在线演示](https://supermax92.github.io/qgraphflow/) · [客户端安装](#安装指南) · [反馈问题](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) +[在线演示](https://supermax92.github.io/qgraphflow/) · [快速入门](#快速入门) · [客户端安装](#安装指南) · [反馈问题](https://github.com/supermax92/qgraphflow/issues) · [MIT](../../LICENSE) @@ -20,6 +20,16 @@ QGraphFlow 从源码、数据结构、配置和需求生成交互式软件图, **差异在哪:** 一个技能覆盖十一类图,每条关系都标明证据类别,有代码支撑的关系还记录对应的源码行,自动布局,能直接在页面里编辑,插件脚本和 Viewer 本身不发任何网络请求。 +![Jeepay 多视图交互演示:组件关系架构图、时序图与 ER 图](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.zh-CN.hero.gif) + +以 Jeepay 真实源码为例,在组件关系架构图、时序图与 ER 图之间切换,探索组件与调用关系。[查看原尺寸 GIF](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/jeepay.zh-CN.hero.gif) + +**复杂时序图展示** + +![复杂时序图逐步绘制:参与者、生命线、消息、激活条与嵌套组合片段](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.zh-CN.sequence-drawing.gif) + +虚构电商场景包含 9 个参与者、29 条消息和 6 个组合片段,覆盖库存重试、嵌套分支、并行处理、失败补偿与异步回调。动画逐步呈现已生成的图形,展示复杂时序图的结构与细节。[查看原尺寸 GIF](https://github.com/supermax92/qgraphflow/releases/download/showcase-v2/checkout.zh-CN.sequence-drawing.gif) + ```bash npx skills add supermax92/qgraphflow ``` @@ -36,6 +46,60 @@ npx skills add supermax92/qgraphflow The real-source [Jeepay corpus](../../examples/jeepay) contains all eleven views used by CI and the live demo. +## 快速入门 + +安装后,在客户端打开你的业务项目,选择 `q-flow` 技能。以下以 Claude Code 的 `/q-flow` 为例;Codex 中使用客户端实际提供的 `$q-flow` 或 `$qgraphflow:q-flow` 入口。还未安装?先看[安装指南](#安装指南)。 + +### 1. 空白输入:不知道从哪里开始 + +只调用技能,不附加需求: + +```text +/q-flow +``` + +技能会引导你选择要分析的部分,以及希望图回答的问题;必要信息明确后开始绘图。 + +### 2. 询问能力:先了解能画什么 + +```text +/q-flow 你能绘制哪些类型的图?每类图适合回答什么问题?我刚接手一个项目,请先介绍能力并建议一个起点。 +``` + +先了解十一类图的用途,再决定看项目结构、调用顺序、数据关系还是其他内容。 + +### 3. 模糊输入:只有一个大致目标 + +```text +/q-flow 帮我画一下这个项目,我想尽快看懂它。 +``` + +不用先指定图类型。技能会根据项目和你的目标确定合适的视图,缺少必要信息时再追问。 + +### 4. 精准输入:明确范围并要求绘制细节 + +将下面的业务名称和步骤替换成项目中实际存在的流程: + +```text +/q-flow 分析当前项目的订单创建流程,生成中文时序图。 +覆盖请求入口、价格计算、库存预占、支付授权和订单落库。 +保留源码中实际存在的同步调用、异步消息、成对返回、激活条、条件分支、重试和失败补偿,不要为了简洁省略细节。 +标明组件与调用的源码文件和行号,将结果保存到 docs/qgraphflow/order-sequence/。 +``` + +把对象、问题、详细程度和输出位置说清楚,就能直接开始;图中只保留有证据支持的事实。 + +### 5. 继续细化:展开上一张图的局部 + +在生成结果后,于同一对话中继续: + +```text +/q-flow 展开上一张时序图中的库存预占步骤,单独生成中文流程图。 +完整展示库存校验、预占成功、可重试失败、重试上限和释放库存的分支,以源码为准,不添加代码中没有的步骤。 +``` + +先看整体,再深入一个步骤;也可以继续要求补充已有图的细节或核验其中的关系。 + ## 安装指南 准备 Node.js 22 及以上版本,以及已配置好模型访问、支持插件功能的客户端。 @@ -113,7 +177,7 @@ qodercli plugins install . ### 3. 开始使用 -在客户端打开你的业务项目,新建会话并选择技能,按下方[快速使用](#快速使用)中的示例描述需求。生成后,用浏览器打开输出的 HTML。 +在客户端打开你的业务项目,新建会话并选择技能,按[快速入门](#快速入门)中的示例描述需求。生成后,用浏览器打开输出的 HTML。 需要自行构建?参见[源码构建说明](https://github.com/supermax92/qgraphflow/blob/main/docs/distribution.md#prepare-locally)。 diff --git a/examples/showcase/checkout-sequence.en.graph.json b/examples/showcase/checkout-sequence.en.graph.json new file mode 100644 index 0000000..5ad8e9f --- /dev/null +++ b/examples/showcase/checkout-sequence.en.graph.json @@ -0,0 +1,598 @@ +{ + "meta": { + "title": "Checkout and asynchronous payment sequence diagram", + "subtitle": "Independently authored fictional demo scenario", + "diagramType": "sequence", + "locale": "en", + "sourceRef": "examples/showcase/checkout-sequence.md", + "viewId": "checkout-sequence", + "scope": "Detailed: checkout, stock retries, authorization branches, parallel fulfillment and notifications, failure compensation, asynchronous callbacks and idempotency. Excludes real implementations and provider internals.", + "notes": [ + "An independently authored fictional scenario; it does not describe a real project implementation.", + "The two par operands represent parallel processing; their vertical arrangement does not imply an execution order.", + "The drawing animation progressively reveals an already generated diagram." + ] + }, + "nodes": [ + { + "id": "buyer", + "label": "Buyer", + "kind": "actor", + "subtitle": "Checkout request" + }, + { + "id": "gateway", + "label": "API Gateway", + "kind": "service", + "subtitle": "Order entry point", + "module": "access" + }, + { + "id": "orders", + "label": "Order Service", + "kind": "service", + "subtitle": "Order orchestration", + "module": "order" + }, + { + "id": "inventory", + "label": "Inventory", + "kind": "service", + "subtitle": "Stock reservation", + "module": "stock" + }, + { + "id": "payments", + "label": "Payment Service", + "kind": "service", + "subtitle": "Authorization and callbacks", + "module": "payment" + }, + { + "id": "bank", + "label": "Payment channel", + "kind": "external", + "subtitle": "External payment", + "module": "payment" + }, + { + "id": "bus", + "label": "Event Bus", + "kind": "participant", + "subtitle": "Domain events", + "module": "events" + }, + { + "id": "fulfill", + "label": "Fulfillment", + "kind": "service", + "subtitle": "Order fulfillment", + "module": "delivery" + }, + { + "id": "notify", + "label": "Notification", + "kind": "service", + "subtitle": "Customer notifications", + "module": "notification" + } + ], + "groups": [ + { + "id": "stock-retry", + "kind": "loop", + "label": "Stock reservation retries", + "loop": { + "min": 1, + "max": 3 + }, + "operands": [ + { + "id": "attempt", + "guard": "Retryable and attempt ≤ 3", + "edgeIds": [ + "m4", + "m5" + ] + } + ] + }, + { + "id": "stock-result", + "kind": "alt", + "label": "Stock reservation result", + "operands": [ + { + "id": "held", + "guard": "Stock reserved", + "edgeIds": [ + "m6", + "m7", + "m8", + "m9" + ] + }, + { + "id": "empty", + "guard": "else · Insufficient stock", + "edgeIds": [ + "m20" + ] + } + ] + }, + { + "id": "payment-result", + "kind": "alt", + "label": "Payment authorization result", + "parentId": "stock-result", + "parentOperandId": "held", + "operands": [ + { + "id": "approved", + "guard": "Authorization accepted", + "edgeIds": [ + "m10" + ] + }, + { + "id": "declined", + "guard": "else · Authorization declined", + "edgeIds": [ + "m17", + "m18", + "m19" + ] + } + ] + }, + { + "id": "parallel-consumers", + "kind": "par", + "label": "Parallel event processing", + "parentId": "payment-result", + "parentOperandId": "approved", + "operands": [ + { + "id": "delivery", + "label": "Fulfillment consumer", + "edgeIds": [ + "m11", + "m12", + "m13", + "m14" + ] + }, + { + "id": "confirmation", + "label": "Notification consumer", + "edgeIds": [ + "m15", + "m16" + ] + } + ] + }, + { + "id": "valid-webhook", + "kind": "opt", + "label": "Asynchronous payment callback", + "operands": [ + { + "id": "verified", + "guard": "Valid signature", + "edgeIds": [ + "m24" + ] + } + ] + }, + { + "id": "idempotency", + "kind": "alt", + "label": "Callback idempotency", + "parentId": "valid-webhook", + "parentOperandId": "verified", + "operands": [ + { + "id": "new", + "guard": "First processing of eventId", + "edgeIds": [ + "m25", + "m26", + "m27" + ] + }, + { + "id": "duplicate", + "guard": "else · Duplicate event", + "edgeIds": [ + "m28" + ] + } + ] + } + ], + "edges": [ + { + "id": "m1", + "source": "buyer", + "target": "gateway", + "kind": "sync", + "label": "Submit order / checkout", + "order": 1, + "evidence": "document" + }, + { + "id": "m2", + "source": "gateway", + "target": "orders", + "kind": "sync", + "label": "Create order / createOrder", + "order": 2, + "evidence": "document" + }, + { + "id": "m3", + "source": "orders", + "target": "orders", + "kind": "sync", + "label": "Validate and price", + "order": 3, + "evidence": "document" + }, + { + "id": "m4", + "source": "orders", + "target": "inventory", + "kind": "sync", + "label": "Reserve stock / reserve", + "order": 4, + "evidence": "document" + }, + { + "id": "m5", + "source": "inventory", + "target": "orders", + "kind": "return", + "label": "Reservation result / held | retry", + "order": 5, + "evidence": "document", + "replyTo": "m4" + }, + { + "id": "m6", + "source": "orders", + "target": "payments", + "kind": "sync", + "label": "Create payment / createIntent", + "order": 6, + "evidence": "document" + }, + { + "id": "m7", + "source": "payments", + "target": "bank", + "kind": "sync", + "label": "Request authorization / authorize", + "order": 7, + "evidence": "document" + }, + { + "id": "m8", + "source": "bank", + "target": "payments", + "kind": "return", + "label": "Authorization / accepted | declined", + "order": 8, + "evidence": "document", + "replyTo": "m7" + }, + { + "id": "m9", + "source": "payments", + "target": "orders", + "kind": "return", + "label": "Payment status / intent", + "order": 9, + "evidence": "document", + "replyTo": "m6" + }, + { + "id": "m10", + "source": "orders", + "target": "bus", + "kind": "async", + "label": "Publish / order.confirmed", + "order": 10, + "evidence": "document" + }, + { + "id": "m11", + "source": "bus", + "target": "fulfill", + "kind": "async", + "label": "Consume order confirmation", + "order": 11, + "evidence": "document" + }, + { + "id": "m12", + "source": "fulfill", + "target": "inventory", + "kind": "sync", + "label": "Commit stock / commit", + "order": 12, + "evidence": "document" + }, + { + "id": "m13", + "source": "inventory", + "target": "fulfill", + "kind": "return", + "label": "Stock committed", + "order": 13, + "evidence": "document", + "replyTo": "m12" + }, + { + "id": "m14", + "source": "fulfill", + "target": "bus", + "kind": "async", + "label": "Publish / fulfillment.ready", + "order": 14, + "evidence": "document" + }, + { + "id": "m15", + "source": "bus", + "target": "notify", + "kind": "async", + "label": "Consume order confirmation", + "order": 15, + "evidence": "document" + }, + { + "id": "m16", + "source": "notify", + "target": "buyer", + "kind": "async", + "label": "Send order confirmation", + "order": 16, + "evidence": "document" + }, + { + "id": "m17", + "source": "orders", + "target": "inventory", + "kind": "sync", + "label": "Release stock / release", + "order": 17, + "evidence": "document" + }, + { + "id": "m18", + "source": "inventory", + "target": "orders", + "kind": "return", + "label": "Stock released", + "order": 18, + "evidence": "document", + "replyTo": "m17" + }, + { + "id": "m19", + "source": "orders", + "target": "bus", + "kind": "async", + "label": "Publish / order.cancelled", + "order": 19, + "evidence": "document" + }, + { + "id": "m20", + "source": "orders", + "target": "bus", + "kind": "async", + "label": "Publish / order.rejected", + "order": 20, + "evidence": "document" + }, + { + "id": "m21", + "source": "orders", + "target": "gateway", + "kind": "return", + "label": "Order status / orderState", + "order": 21, + "evidence": "document", + "replyTo": "m2" + }, + { + "id": "m22", + "source": "gateway", + "target": "buyer", + "kind": "return", + "label": "Acceptance / accepted | rejected", + "order": 22, + "evidence": "document", + "replyTo": "m1" + }, + { + "id": "m23", + "source": "bank", + "target": "payments", + "kind": "async", + "label": "Payment callback / webhook", + "order": 23, + "evidence": "document" + }, + { + "id": "m24", + "source": "payments", + "target": "payments", + "kind": "sync", + "label": "Check idempotency / eventId", + "order": 24, + "evidence": "document" + }, + { + "id": "m25", + "source": "payments", + "target": "orders", + "kind": "sync", + "label": "Mark paid / markPaid", + "order": 25, + "evidence": "document" + }, + { + "id": "m26", + "source": "orders", + "target": "payments", + "kind": "return", + "label": "Order paid", + "order": 26, + "evidence": "document", + "replyTo": "m25" + }, + { + "id": "m27", + "source": "payments", + "target": "bus", + "kind": "async", + "label": "Publish / payment.settled", + "order": 27, + "evidence": "document" + }, + { + "id": "m28", + "source": "payments", + "target": "payments", + "kind": "sync", + "label": "Ignore duplicate event", + "order": 28, + "evidence": "document" + }, + { + "id": "m29", + "source": "payments", + "target": "bank", + "kind": "return", + "label": "ACK", + "order": 29, + "evidence": "document", + "replyTo": "m23" + } + ], + "executions": [ + { + "id": "x1", + "participantId": "gateway", + "start": { + "edgeId": "m1", + "at": "receive" + }, + "end": { + "edgeId": "m22", + "at": "send" + } + }, + { + "id": "x2", + "participantId": "orders", + "start": { + "edgeId": "m2", + "at": "receive" + }, + "end": { + "edgeId": "m21", + "at": "send" + } + }, + { + "id": "x3", + "participantId": "inventory", + "start": { + "edgeId": "m4", + "at": "receive" + }, + "end": { + "edgeId": "m5", + "at": "send" + } + }, + { + "id": "x4", + "participantId": "payments", + "start": { + "edgeId": "m6", + "at": "receive" + }, + "end": { + "edgeId": "m9", + "at": "send" + } + }, + { + "id": "x5", + "participantId": "bank", + "start": { + "edgeId": "m7", + "at": "receive" + }, + "end": { + "edgeId": "m8", + "at": "send" + } + }, + { + "id": "x6", + "participantId": "inventory", + "start": { + "edgeId": "m12", + "at": "receive" + }, + "end": { + "edgeId": "m13", + "at": "send" + } + }, + { + "id": "x7", + "participantId": "inventory", + "start": { + "edgeId": "m17", + "at": "receive" + }, + "end": { + "edgeId": "m18", + "at": "send" + } + }, + { + "id": "x8", + "participantId": "payments", + "start": { + "edgeId": "m23", + "at": "receive" + }, + "end": { + "edgeId": "m29", + "at": "send" + } + }, + { + "id": "x9", + "participantId": "orders", + "start": { + "edgeId": "m25", + "at": "receive" + }, + "end": { + "edgeId": "m26", + "at": "send" + } + } + ] +} diff --git a/examples/showcase/checkout-sequence.graph.json b/examples/showcase/checkout-sequence.graph.json new file mode 100644 index 0000000..20dd043 --- /dev/null +++ b/examples/showcase/checkout-sequence.graph.json @@ -0,0 +1,598 @@ +{ + "meta": { + "title": "电商下单与异步支付时序图", + "subtitle": "独立编写的虚构演示场景", + "diagramType": "sequence", + "locale": "zh-CN", + "sourceRef": "examples/showcase/checkout-sequence.md", + "viewId": "checkout-sequence", + "scope": "详细:下单、库存重试、授权分支、并行履约与通知、失败补偿、异步回调及幂等。排除真实实现与供应商内部细节。", + "notes": [ + "独立编写的虚构场景,不代表真实项目的实现。", + "par 中两个操作数表示并行处理,纵向排列不表示两者先后。", + "绘制动画对已生成图形做分步呈现。" + ] + }, + "nodes": [ + { + "id": "buyer", + "label": "买家", + "kind": "actor", + "subtitle": "提交订单" + }, + { + "id": "gateway", + "label": "API Gateway", + "kind": "service", + "subtitle": "订单入口", + "module": "access" + }, + { + "id": "orders", + "label": "Order Service", + "kind": "service", + "subtitle": "订单编排", + "module": "order" + }, + { + "id": "inventory", + "label": "Inventory", + "kind": "service", + "subtitle": "库存预占", + "module": "stock" + }, + { + "id": "payments", + "label": "Payment Service", + "kind": "service", + "subtitle": "授权与回调", + "module": "payment" + }, + { + "id": "bank", + "label": "支付渠道", + "kind": "external", + "subtitle": "外部支付", + "module": "payment" + }, + { + "id": "bus", + "label": "Event Bus", + "kind": "participant", + "subtitle": "领域事件", + "module": "events" + }, + { + "id": "fulfill", + "label": "Fulfillment", + "kind": "service", + "subtitle": "订单履约", + "module": "delivery" + }, + { + "id": "notify", + "label": "Notification", + "kind": "service", + "subtitle": "消息通知", + "module": "notification" + } + ], + "groups": [ + { + "id": "stock-retry", + "kind": "loop", + "label": "库存预占重试", + "loop": { + "min": 1, + "max": 3 + }, + "operands": [ + { + "id": "attempt", + "guard": "可重试且 attempt ≤ 3", + "edgeIds": [ + "m4", + "m5" + ] + } + ] + }, + { + "id": "stock-result", + "kind": "alt", + "label": "库存预占结果", + "operands": [ + { + "id": "held", + "guard": "库存预占成功", + "edgeIds": [ + "m6", + "m7", + "m8", + "m9" + ] + }, + { + "id": "empty", + "guard": "else · 库存不足", + "edgeIds": [ + "m20" + ] + } + ] + }, + { + "id": "payment-result", + "kind": "alt", + "label": "支付授权结果", + "parentId": "stock-result", + "parentOperandId": "held", + "operands": [ + { + "id": "approved", + "guard": "授权成功", + "edgeIds": [ + "m10" + ] + }, + { + "id": "declined", + "guard": "else · 授权失败", + "edgeIds": [ + "m17", + "m18", + "m19" + ] + } + ] + }, + { + "id": "parallel-consumers", + "kind": "par", + "label": "并行事件处理", + "parentId": "payment-result", + "parentOperandId": "approved", + "operands": [ + { + "id": "delivery", + "label": "履约消费者", + "edgeIds": [ + "m11", + "m12", + "m13", + "m14" + ] + }, + { + "id": "confirmation", + "label": "通知消费者", + "edgeIds": [ + "m15", + "m16" + ] + } + ] + }, + { + "id": "valid-webhook", + "kind": "opt", + "label": "异步支付回调", + "operands": [ + { + "id": "verified", + "guard": "签名有效", + "edgeIds": [ + "m24" + ] + } + ] + }, + { + "id": "idempotency", + "kind": "alt", + "label": "回调幂等处理", + "parentId": "valid-webhook", + "parentOperandId": "verified", + "operands": [ + { + "id": "new", + "guard": "首次处理 eventId", + "edgeIds": [ + "m25", + "m26", + "m27" + ] + }, + { + "id": "duplicate", + "guard": "else · 重复事件", + "edgeIds": [ + "m28" + ] + } + ] + } + ], + "edges": [ + { + "id": "m1", + "source": "buyer", + "target": "gateway", + "kind": "sync", + "label": "提交订单 / checkout", + "order": 1, + "evidence": "document" + }, + { + "id": "m2", + "source": "gateway", + "target": "orders", + "kind": "sync", + "label": "创建订单 / createOrder", + "order": 2, + "evidence": "document" + }, + { + "id": "m3", + "source": "orders", + "target": "orders", + "kind": "sync", + "label": "校验与计价", + "order": 3, + "evidence": "document" + }, + { + "id": "m4", + "source": "orders", + "target": "inventory", + "kind": "sync", + "label": "预占库存 / reserve", + "order": 4, + "evidence": "document" + }, + { + "id": "m5", + "source": "inventory", + "target": "orders", + "kind": "return", + "label": "预占结果 / held | retry", + "order": 5, + "evidence": "document", + "replyTo": "m4" + }, + { + "id": "m6", + "source": "orders", + "target": "payments", + "kind": "sync", + "label": "创建支付 / createIntent", + "order": 6, + "evidence": "document" + }, + { + "id": "m7", + "source": "payments", + "target": "bank", + "kind": "sync", + "label": "申请授权 / authorize", + "order": 7, + "evidence": "document" + }, + { + "id": "m8", + "source": "bank", + "target": "payments", + "kind": "return", + "label": "授权结果 / accepted | declined", + "order": 8, + "evidence": "document", + "replyTo": "m7" + }, + { + "id": "m9", + "source": "payments", + "target": "orders", + "kind": "return", + "label": "支付状态 / intent", + "order": 9, + "evidence": "document", + "replyTo": "m6" + }, + { + "id": "m10", + "source": "orders", + "target": "bus", + "kind": "async", + "label": "发布 / order.confirmed", + "order": 10, + "evidence": "document" + }, + { + "id": "m11", + "source": "bus", + "target": "fulfill", + "kind": "async", + "label": "消费订单确认事件", + "order": 11, + "evidence": "document" + }, + { + "id": "m12", + "source": "fulfill", + "target": "inventory", + "kind": "sync", + "label": "确认库存 / commit", + "order": 12, + "evidence": "document" + }, + { + "id": "m13", + "source": "inventory", + "target": "fulfill", + "kind": "return", + "label": "库存已确认", + "order": 13, + "evidence": "document", + "replyTo": "m12" + }, + { + "id": "m14", + "source": "fulfill", + "target": "bus", + "kind": "async", + "label": "发布 / fulfillment.ready", + "order": 14, + "evidence": "document" + }, + { + "id": "m15", + "source": "bus", + "target": "notify", + "kind": "async", + "label": "消费订单确认事件", + "order": 15, + "evidence": "document" + }, + { + "id": "m16", + "source": "notify", + "target": "buyer", + "kind": "async", + "label": "发送订单确认", + "order": 16, + "evidence": "document" + }, + { + "id": "m17", + "source": "orders", + "target": "inventory", + "kind": "sync", + "label": "释放库存 / release", + "order": 17, + "evidence": "document" + }, + { + "id": "m18", + "source": "inventory", + "target": "orders", + "kind": "return", + "label": "库存已释放", + "order": 18, + "evidence": "document", + "replyTo": "m17" + }, + { + "id": "m19", + "source": "orders", + "target": "bus", + "kind": "async", + "label": "发布 / order.cancelled", + "order": 19, + "evidence": "document" + }, + { + "id": "m20", + "source": "orders", + "target": "bus", + "kind": "async", + "label": "发布 / order.rejected", + "order": 20, + "evidence": "document" + }, + { + "id": "m21", + "source": "orders", + "target": "gateway", + "kind": "return", + "label": "订单状态 / orderState", + "order": 21, + "evidence": "document", + "replyTo": "m2" + }, + { + "id": "m22", + "source": "gateway", + "target": "buyer", + "kind": "return", + "label": "受理结果 / accepted | rejected", + "order": 22, + "evidence": "document", + "replyTo": "m1" + }, + { + "id": "m23", + "source": "bank", + "target": "payments", + "kind": "async", + "label": "支付回调 / webhook", + "order": 23, + "evidence": "document" + }, + { + "id": "m24", + "source": "payments", + "target": "payments", + "kind": "sync", + "label": "检查幂等 / eventId", + "order": 24, + "evidence": "document" + }, + { + "id": "m25", + "source": "payments", + "target": "orders", + "kind": "sync", + "label": "标记已支付 / markPaid", + "order": 25, + "evidence": "document" + }, + { + "id": "m26", + "source": "orders", + "target": "payments", + "kind": "return", + "label": "订单已支付", + "order": 26, + "evidence": "document", + "replyTo": "m25" + }, + { + "id": "m27", + "source": "payments", + "target": "bus", + "kind": "async", + "label": "发布 / payment.settled", + "order": 27, + "evidence": "document" + }, + { + "id": "m28", + "source": "payments", + "target": "payments", + "kind": "sync", + "label": "忽略重复事件", + "order": 28, + "evidence": "document" + }, + { + "id": "m29", + "source": "payments", + "target": "bank", + "kind": "return", + "label": "ACK", + "order": 29, + "evidence": "document", + "replyTo": "m23" + } + ], + "executions": [ + { + "id": "x1", + "participantId": "gateway", + "start": { + "edgeId": "m1", + "at": "receive" + }, + "end": { + "edgeId": "m22", + "at": "send" + } + }, + { + "id": "x2", + "participantId": "orders", + "start": { + "edgeId": "m2", + "at": "receive" + }, + "end": { + "edgeId": "m21", + "at": "send" + } + }, + { + "id": "x3", + "participantId": "inventory", + "start": { + "edgeId": "m4", + "at": "receive" + }, + "end": { + "edgeId": "m5", + "at": "send" + } + }, + { + "id": "x4", + "participantId": "payments", + "start": { + "edgeId": "m6", + "at": "receive" + }, + "end": { + "edgeId": "m9", + "at": "send" + } + }, + { + "id": "x5", + "participantId": "bank", + "start": { + "edgeId": "m7", + "at": "receive" + }, + "end": { + "edgeId": "m8", + "at": "send" + } + }, + { + "id": "x6", + "participantId": "inventory", + "start": { + "edgeId": "m12", + "at": "receive" + }, + "end": { + "edgeId": "m13", + "at": "send" + } + }, + { + "id": "x7", + "participantId": "inventory", + "start": { + "edgeId": "m17", + "at": "receive" + }, + "end": { + "edgeId": "m18", + "at": "send" + } + }, + { + "id": "x8", + "participantId": "payments", + "start": { + "edgeId": "m23", + "at": "receive" + }, + "end": { + "edgeId": "m29", + "at": "send" + } + }, + { + "id": "x9", + "participantId": "orders", + "start": { + "edgeId": "m25", + "at": "receive" + }, + "end": { + "edgeId": "m26", + "at": "send" + } + } + ] +} diff --git a/examples/showcase/checkout-sequence.md b/examples/showcase/checkout-sequence.md new file mode 100644 index 0000000..c5537a5 --- /dev/null +++ b/examples/showcase/checkout-sequence.md @@ -0,0 +1,28 @@ +# 电商下单与异步支付演示 + +这是独立编写的虚构场景,用于展示 QGraphFlow 时序图与逐笔绘制动画。不是任何真实系统的实现说明。 + +1. 买家经入口提交订单;订单服务完成校验与计价。 +2. 库存服务尝试预占库存,遇到可重试错误时最多尝试三次。 +3. 库存预占成功后,支付服务向外部支付渠道申请授权,返回授权成功或拒绝。 +4. 授权成功时发布订单确认事件,履约与通知消费者并行处理。履约服务确认库存预占并发布履约就绪事件;通知服务向买家发送确认。 +5. 授权失败时释放库存并发布订单取消事件;库存不足时发布订单拒绝事件。 +6. 订单服务与入口依次向买家返回订单状态。 +7. 支付渠道异步回调支付结果。签名有效时检查事件幂等性;新事件更新订单已支付并发布支付结算事件,重复事件忽略。最后向渠道返回 ACK。 + +覆盖:9 个参与者、29 条消息、loop / alt / par / opt 组合片段、嵌套分支、自调用、同步调用、异步事件、成对返回与激活条。绘图动画是对已生成图形的分步呈现,不代表模型内部推理过程。 + +## 重新生成与录制 + +在仓库根目录运行(录制需要已安装的 Playwright Chromium、Node.js 与 ffmpeg): + +```sh +node skills/q-flow/scripts/validate-graph.mjs examples/showcase/checkout-sequence.graph.json --input-only +node skills/q-flow/scripts/generate-viewer.mjs examples/showcase/checkout-sequence.graph.json output/playwright/checkout-sequence/viewer +node skills/q-flow/scripts/validate-graph.mjs output/playwright/checkout-sequence/viewer/graph.json +node scripts/record-sequence-drawing.mjs --view output/playwright/checkout-sequence/viewer --out output/playwright/checkout-sequence/recording +``` + +如果使用已有输出目录,显式更新时为生成命令添加 `--force`。可用 `--preview` 只生成播放器与关键截图,跳过视频编码;`PLAYWRIGHT_MODULE` 可指定本机已有的 Playwright 模块位置。 + +录制输出为可离线重播的 `drawing.html`、循环 GIF、MP4、关键截图与 `record.json`。播放器通过遮罩逐步展示生成器输出的 SVG;图形结构与最终线型不会改写。大图保留在同一视图中,使用镜头平移和缩放逐步展示。 diff --git a/examples/showcase/jeepay.en.hero.graph.json b/examples/showcase/jeepay.en.hero.graph.json new file mode 100644 index 0000000..f81bf44 --- /dev/null +++ b/examples/showcase/jeepay.en.hero.graph.json @@ -0,0 +1,1133 @@ +{ + "diagrams": [ + { + "meta": { + "title": "Jeepay payment component relationships", + "subtitle": "Jeepay source and configuration evidence", + "sourceRef": "jeequan/jeepay@e1ac9086d679826bab593f20431b7ecd292eb685", + "scope": "Unified ordering and asynchronous callback components", + "diagramType": "architecture", + "locale": "en", + "notes": [ + "SpringBeansUtil resolves channel implementations dynamically by ifCode.", + "A static call graph cannot enumerate all runtime beans.", + "Callbacks conditionally update success or failure only while the order is in STATE_ING." + ], + "architectureView": "relations", + "viewId": "jeepay-relations" + }, + "nodes": [ + { + "id": "unified", + "label": "UnifiedOrderController", + "kind": "service", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/UnifiedOrderController.java", + "lineStart": 44, + "lineEnd": 44, + "kind": "source" + }, + "tags": [ + "core" + ] + }, + { + "id": "abstract", + "label": "AbstractPayOrderController", + "kind": "business", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 64, + "lineEnd": 64, + "kind": "source" + } + }, + { + "id": "api", + "label": "ApiController", + "kind": "security", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/ApiController.java", + "lineStart": 39, + "lineEnd": 39, + "kind": "source" + } + }, + { + "id": "config", + "label": "ConfigContextQueryService", + "kind": "component", + "module": "config", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/ConfigContextQueryService.java", + "lineStart": 48, + "lineEnd": 48, + "kind": "source" + } + }, + { + "id": "passage", + "label": "MchPayPassageService", + "kind": "component", + "module": "config", + "source": { + "file": "jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/MchPayPassageService.java", + "lineStart": 45, + "lineEnd": 45, + "kind": "source" + } + }, + { + "id": "orders", + "label": "PayOrderService", + "kind": "data", + "module": "payment", + "source": { + "file": "jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderService.java", + "lineStart": 49, + "lineEnd": 49, + "kind": "source" + } + }, + { + "id": "channel", + "label": "IPaymentService", + "kind": "component", + "module": "channel", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IPaymentService.java", + "lineStart": 30, + "lineEnd": 30, + "kind": "source" + } + }, + { + "id": "process", + "label": "PayOrderProcessService", + "kind": "component", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayOrderProcessService.java", + "lineStart": 40, + "lineEnd": 40, + "kind": "source" + } + }, + { + "id": "notify", + "label": "PayMchNotifyService", + "kind": "component", + "module": "notification", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayMchNotifyService.java", + "lineStart": 45, + "lineEnd": 45, + "kind": "source" + } + }, + { + "id": "callback", + "label": "ChannelNoticeController", + "kind": "service", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java", + "lineStart": 54, + "lineEnd": 54, + "kind": "source" + } + } + ], + "edges": [ + { + "id": "r1", + "source": "unified", + "target": "abstract", + "kind": "inherits", + "label": "Inherits order orchestration", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/UnifiedOrderController.java", + "lineStart": 44, + "lineEnd": 44 + } + }, + { + "id": "r2", + "source": "abstract", + "target": "api", + "kind": "inherits", + "label": "Inherits request validation", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 64, + "lineEnd": 64 + } + }, + { + "id": "r3", + "source": "unified", + "target": "abstract", + "kind": "call", + "label": "Unified order", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/UnifiedOrderController.java", + "lineStart": 61, + "lineEnd": 61 + } + }, + { + "id": "r4", + "source": "abstract", + "target": "config", + "kind": "call", + "label": "Query merchant configuration", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 129, + "lineEnd": 129 + } + }, + { + "id": "r5", + "source": "abstract", + "target": "passage", + "kind": "call", + "label": "Select payment channel", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 167, + "lineEnd": 167 + } + }, + { + "id": "r6", + "source": "abstract", + "target": "orders", + "kind": "data", + "label": "Save payment order", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 201, + "lineEnd": 201 + } + }, + { + "id": "r7", + "source": "abstract", + "target": "channel", + "kind": "call", + "label": "Call pay", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 205, + "lineEnd": 205 + } + }, + { + "id": "r8", + "source": "abstract", + "target": "process", + "kind": "call", + "label": "Process payment status", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 383, + "lineEnd": 383 + } + }, + { + "id": "r9", + "source": "callback", + "target": "orders", + "kind": "data", + "label": "Conditionally update order", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java", + "lineStart": 237, + "lineEnd": 237 + } + }, + { + "id": "r10", + "source": "callback", + "target": "process", + "kind": "call", + "label": "Handle success", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java", + "lineStart": 254, + "lineEnd": 254 + } + }, + { + "id": "r11", + "source": "process", + "target": "notify", + "kind": "call", + "label": "Notify merchant", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayOrderProcessService.java", + "lineStart": 60, + "lineEnd": 60 + } + } + ], + "layout": { + "direction": "right" + } + }, + { + "meta": { + "title": "Unified ordering and successful callback sequence", + "subtitle": "Jeepay source and configuration evidence", + "sourceRef": "jeequan/jeepay@e1ac9086d679826bab593f20431b7ecd292eb685", + "scope": "Standard ordering and asynchronous successful callbacks", + "diagramType": "sequence", + "locale": "en", + "notes": [ + "The callback stage shows only the CONFIRM_SUCCESS branch; the failure branch updates STATE_FAIL.", + "The channel payment interface is selected through a runtime bean.", + "Channel internal calls are not exposed in this repository." + ], + "viewId": "jeepay-sequence" + }, + "nodes": [ + { + "id": "merchant", + "label": "Merchant system", + "kind": "actor" + }, + { + "id": "unified", + "label": "UnifiedOrderController", + "kind": "service", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/UnifiedOrderController.java", + "lineStart": 44, + "lineEnd": 44, + "kind": "source" + } + }, + { + "id": "abstract", + "label": "AbstractPayOrderController", + "kind": "service", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 64, + "lineEnd": 64, + "kind": "source" + } + }, + { + "id": "orders", + "label": "PayOrderService", + "kind": "service", + "module": "payment", + "source": { + "file": "jeepay-service/src/main/java/com/jeequan/jeepay/service/impl/PayOrderService.java", + "lineStart": 49, + "lineEnd": 49, + "kind": "source" + } + }, + { + "id": "channelapi", + "label": "IPaymentService", + "kind": "service", + "module": "channel", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/channel/IPaymentService.java", + "lineStart": 30, + "lineEnd": 30, + "kind": "source" + } + }, + { + "id": "channel", + "label": "Payment channel", + "kind": "external", + "module": "channel" + }, + { + "id": "callback", + "label": "ChannelNoticeController", + "kind": "service", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java", + "lineStart": 54, + "lineEnd": 54, + "kind": "source" + } + }, + { + "id": "process", + "label": "PayOrderProcessService", + "kind": "service", + "module": "payment", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayOrderProcessService.java", + "lineStart": 40, + "lineEnd": 40, + "kind": "source" + } + }, + { + "id": "notify", + "label": "PayMchNotifyService", + "kind": "service", + "module": "notification", + "source": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayMchNotifyService.java", + "lineStart": 45, + "lineEnd": 45, + "kind": "source" + } + } + ], + "edges": [ + { + "id": "m1", + "source": "merchant", + "target": "unified", + "kind": "sync", + "label": "POST /api/pay/unifiedOrder", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/UnifiedOrderController.java", + "lineStart": 52, + "lineEnd": 52 + }, + "order": 1 + }, + { + "id": "m2", + "source": "unified", + "target": "abstract", + "kind": "sync", + "label": "unifiedOrder(wayCode, bizRQ)", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/UnifiedOrderController.java", + "lineStart": 61, + "lineEnd": 61 + }, + "order": 2 + }, + { + "id": "m3", + "source": "abstract", + "target": "orders", + "kind": "sync", + "label": "save(payOrder)", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 201, + "lineEnd": 201 + }, + "order": 3 + }, + { + "id": "m4", + "source": "abstract", + "target": "channelapi", + "kind": "sync", + "label": "pay(bizRQ, payOrder, context)", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 205, + "lineEnd": 205 + }, + "order": 4 + }, + { + "id": "m5", + "source": "channelapi", + "target": "abstract", + "kind": "return", + "label": "UnifiedOrderRS / ChannelRetMsg", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 208, + "lineEnd": 208 + }, + "order": 5, + "replyTo": "m4" + }, + { + "id": "m6", + "source": "abstract", + "target": "unified", + "kind": "return", + "label": "ApiRes", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 210, + "lineEnd": 210 + }, + "order": 6, + "replyTo": "m2" + }, + { + "id": "m7", + "source": "unified", + "target": "merchant", + "kind": "return", + "label": "signed UnifiedOrderRS", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/UnifiedOrderController.java", + "lineStart": 78, + "lineEnd": 78 + }, + "order": 7, + "replyTo": "m1" + }, + { + "id": "m8", + "source": "channel", + "target": "callback", + "kind": "async", + "label": "/api/pay/notify/{ifCode}", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java", + "lineStart": 169, + "lineEnd": 169 + }, + "order": 8 + }, + { + "id": "m9", + "source": "callback", + "target": "orders", + "kind": "sync", + "label": "updateIng2Success", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java", + "lineStart": 237, + "lineEnd": 237 + }, + "order": 9 + }, + { + "id": "m10", + "source": "callback", + "target": "process", + "kind": "sync", + "label": "confirmSuccess(payOrder)", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java", + "lineStart": 254, + "lineEnd": 254 + }, + "order": 10 + }, + { + "id": "m11", + "source": "process", + "target": "notify", + "kind": "sync", + "label": "payOrderNotify(payOrder)", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayOrderProcessService.java", + "lineStart": 60, + "lineEnd": 60 + }, + "order": 11 + }, + { + "id": "m12", + "source": "callback", + "target": "channel", + "kind": "return", + "label": "responseEntity", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/ChannelNoticeController.java", + "lineStart": 259, + "lineEnd": 259 + }, + "order": 12, + "replyTo": "m8" + } + ], + "groups": [ + { + "id": "success", + "label": "Channel confirms success", + "kind": "opt", + "operands": [ + { + "id": "ok", + "guard": "CONFIRM_SUCCESS", + "edgeIds": [ + "m9", + "m10", + "m11" + ] + } + ] + } + ], + "executions": [ + { + "id": "x1", + "participantId": "unified", + "start": { + "edgeId": "m1", + "at": "receive" + }, + "end": { + "edgeId": "m7", + "at": "send" + } + }, + { + "id": "x2", + "participantId": "abstract", + "start": { + "edgeId": "m2", + "at": "receive" + }, + "end": { + "edgeId": "m6", + "at": "send" + } + }, + { + "id": "x3", + "participantId": "channelapi", + "start": { + "edgeId": "m4", + "at": "receive" + }, + "end": { + "edgeId": "m5", + "at": "send" + } + }, + { + "id": "x4", + "participantId": "callback", + "start": { + "edgeId": "m8", + "at": "receive" + }, + "end": { + "edgeId": "m12", + "at": "send" + } + } + ], + "layout": {} + }, + { + "meta": { + "title": "Jeepay core payment data ER diagram", + "subtitle": "Jeepay source and configuration evidence", + "sourceRef": "jeequan/jeepay@e1ac9086d679826bab593f20431b7ecd292eb685", + "scope": "Merchant applications, channels, payment orders and notification records", + "diagramType": "er", + "locale": "en", + "notes": [ + "init.sql declares no FOREIGN KEY constraints.", + "Edges are logical associations used by the code, not database-enforced foreign keys.", + "Each entity lists every DDL column; only native PRIMARY KEY columns are marked PK." + ], + "viewId": "jeepay-er" + }, + "nodes": [ + { + "id": "t_mch_app", + "label": "t_mch_app", + "kind": "entity", + "module": "config", + "source": { + "file": "docs/sql/init.sql", + "lineStart": 142, + "lineEnd": 142, + "kind": "schema" + }, + "fields": [ + { + "name": "app_id", + "type": "varchar(64)", + "key": "PK", + "nullable": false + }, + { + "name": "app_name", + "type": "varchar(64)", + "nullable": false + }, + { + "name": "mch_no", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "state", + "type": "TINYINT(6)", + "nullable": false + }, + { + "name": "app_secret", + "type": "VARCHAR(128)", + "nullable": false + }, + { + "name": "remark", + "type": "varchar(128)", + "nullable": true + }, + { + "name": "created_uid", + "type": "BIGINT(20)", + "nullable": true + }, + { + "name": "created_by", + "type": "VARCHAR(64)", + "nullable": true + }, + { + "name": "created_at", + "type": "TIMESTAMP(3)", + "nullable": false + }, + { + "name": "updated_at", + "type": "TIMESTAMP(3)", + "nullable": false + } + ] + }, + { + "id": "t_mch_pay_passage", + "label": "t_mch_pay_passage", + "kind": "entity", + "module": "config", + "source": { + "file": "docs/sql/init.sql", + "lineStart": 229, + "lineEnd": 229, + "kind": "schema" + }, + "fields": [ + { + "name": "id", + "type": "BIGINT(20)", + "key": "PK", + "nullable": false + }, + { + "name": "mch_no", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "app_id", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "if_code", + "type": "VARCHAR(20)", + "nullable": false + }, + { + "name": "way_code", + "type": "VARCHAR(20)", + "nullable": false + }, + { + "name": "rate", + "type": "DECIMAL(20,6)", + "nullable": false + }, + { + "name": "risk_config", + "type": "JSON", + "nullable": true + }, + { + "name": "state", + "type": "TINYINT(6)", + "nullable": false + }, + { + "name": "created_at", + "type": "TIMESTAMP(3)", + "nullable": false + }, + { + "name": "updated_at", + "type": "TIMESTAMP(3)", + "nullable": false + } + ] + }, + { + "id": "t_pay_order", + "label": "t_pay_order", + "kind": "entity", + "module": "payment", + "source": { + "file": "docs/sql/init.sql", + "lineStart": 251, + "lineEnd": 251, + "kind": "schema" + }, + "fields": [ + { + "name": "pay_order_id", + "type": "VARCHAR(30)", + "key": "PK", + "nullable": false + }, + { + "name": "mch_no", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "isv_no", + "type": "VARCHAR(64)", + "nullable": true + }, + { + "name": "app_id", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "mch_name", + "type": "VARCHAR(30)", + "nullable": false + }, + { + "name": "mch_type", + "type": "TINYINT(6)", + "nullable": false + }, + { + "name": "mch_order_no", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "if_code", + "type": "VARCHAR(20)", + "nullable": true + }, + { + "name": "way_code", + "type": "VARCHAR(20)", + "nullable": false + }, + { + "name": "amount", + "type": "BIGINT(20)", + "nullable": false + }, + { + "name": "mch_fee_rate", + "type": "decimal(20,6)", + "nullable": false + }, + { + "name": "mch_fee_amount", + "type": "BIGINT(20)", + "nullable": false + }, + { + "name": "currency", + "type": "VARCHAR(3)", + "nullable": false + }, + { + "name": "state", + "type": "TINYINT(6)", + "nullable": false + }, + { + "name": "notify_state", + "type": "TINYINT(6)", + "nullable": false + }, + { + "name": "client_ip", + "type": "VARCHAR(32)", + "nullable": true + }, + { + "name": "subject", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "body", + "type": "VARCHAR(256)", + "nullable": false + }, + { + "name": "channel_extra", + "type": "VARCHAR(512)", + "nullable": true + }, + { + "name": "channel_user", + "type": "VARCHAR(64)", + "nullable": true + }, + { + "name": "channel_order_no", + "type": "VARCHAR(64)", + "nullable": true + }, + { + "name": "refund_state", + "type": "TINYINT(6)", + "nullable": false + }, + { + "name": "refund_times", + "type": "INT", + "nullable": false + }, + { + "name": "refund_amount", + "type": "BIGINT(20)", + "nullable": false + }, + { + "name": "division_mode", + "type": "TINYINT(6)", + "nullable": true + }, + { + "name": "division_state", + "type": "TINYINT(6)", + "nullable": true + }, + { + "name": "division_last_time", + "type": "DATETIME", + "nullable": true + }, + { + "name": "err_code", + "type": "VARCHAR(128)", + "nullable": true + }, + { + "name": "err_msg", + "type": "VARCHAR(256)", + "nullable": true + }, + { + "name": "ext_param", + "type": "VARCHAR(2048)", + "nullable": true + }, + { + "name": "notify_url", + "type": "VARCHAR(128)", + "nullable": false + }, + { + "name": "return_url", + "type": "VARCHAR(128)", + "nullable": true + }, + { + "name": "expired_time", + "type": "DATETIME", + "nullable": true + }, + { + "name": "success_time", + "type": "DATETIME", + "nullable": true + }, + { + "name": "created_at", + "type": "TIMESTAMP(3)", + "nullable": false + }, + { + "name": "updated_at", + "type": "TIMESTAMP(3)", + "nullable": false + } + ] + }, + { + "id": "t_mch_notify_record", + "label": "t_mch_notify_record", + "kind": "entity", + "module": "notification", + "source": { + "file": "docs/sql/init.sql", + "lineStart": 296, + "lineEnd": 296, + "kind": "schema" + }, + "fields": [ + { + "name": "notify_id", + "type": "BIGINT(20)", + "key": "PK", + "nullable": false + }, + { + "name": "order_id", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "order_type", + "type": "TINYINT(6)", + "nullable": false + }, + { + "name": "mch_order_no", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "mch_no", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "isv_no", + "type": "VARCHAR(64)", + "nullable": true + }, + { + "name": "app_id", + "type": "VARCHAR(64)", + "nullable": false + }, + { + "name": "notify_url", + "type": "TEXT", + "nullable": false + }, + { + "name": "res_result", + "type": "TEXT", + "nullable": true + }, + { + "name": "notify_count", + "type": "INT(11)", + "nullable": false + }, + { + "name": "notify_count_limit", + "type": "INT(11)", + "nullable": false + }, + { + "name": "state", + "type": "TINYINT(6)", + "nullable": false + }, + { + "name": "last_notify_time", + "type": "DATETIME", + "nullable": true + }, + { + "name": "created_at", + "type": "TIMESTAMP(3)", + "nullable": false + }, + { + "name": "updated_at", + "type": "TIMESTAMP(3)", + "nullable": false + } + ] + } + ], + "edges": [ + { + "id": "er1", + "source": "t_mch_app", + "target": "t_mch_pay_passage", + "kind": "relationship", + "label": "Configure payment channel (logical)", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 167, + "lineEnd": 167 + }, + "sourceCardinality": "1", + "targetCardinality": "0..*" + }, + { + "id": "er2", + "source": "t_mch_app", + "target": "t_pay_order", + "kind": "relationship", + "label": "Create payment order (logical)", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/ctrl/payorder/AbstractPayOrderController.java", + "lineStart": 242, + "lineEnd": 242 + }, + "sourceCardinality": "1", + "targetCardinality": "0..*" + }, + { + "id": "er3", + "source": "t_pay_order", + "target": "t_mch_notify_record", + "kind": "relationship", + "label": "Create payment notification (logical)", + "evidence": "source", + "site": { + "file": "jeepay-payment/src/main/java/com/jeequan/jeepay/pay/service/PayMchNotifyService.java", + "lineStart": 76, + "lineEnd": 76 + }, + "sourceCardinality": "1", + "targetCardinality": "0..1" + } + ], + "layout": { + "direction": "right" + } + } + ] +} diff --git a/scripts/record-sequence-drawing.mjs b/scripts/record-sequence-drawing.mjs new file mode 100644 index 0000000..fc2b010 --- /dev/null +++ b/scripts/record-sequence-drawing.mjs @@ -0,0 +1,181 @@ +#!/usr/bin/env node +// Render a staged drawing animation from the current generator's SVG and graph. +// node scripts/record-sequence-drawing.mjs --view --out +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; +import http from 'node:http'; +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { parseArgs } from 'node:util'; +import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; +const { values } = parseArgs({ options: { + view: { type: 'string' }, out: { type: 'string' }, fps: { type: 'string', default: '15' }, + width: { type: 'string', default: '1600' }, height: { type: 'string', default: '1000' }, + preview: { type: 'boolean', default: false }, +} }); +assert.ok(values.view && values.out, 'Required: --view --out '); +const view = path.resolve(values.view), out = path.resolve(values.out); +const width = Number(values.width), height = Number(values.height), fps = Number(values.fps); +for (const n of [width, height, fps]) assert.ok(Number.isInteger(n) && n > 0); +const svgBytes = fs.readFileSync(path.join(view, 'diagram.svg'), 'utf8'); +const graphBytes = fs.readFileSync(path.join(view, 'graph.json'), 'utf8'); +const graph = JSON.parse(graphBytes); +assert.equal(graph.meta.diagramType, 'sequence'); +const locale = graph.meta.locale; +assert.ok(['en', 'zh-CN'].includes(locale), 'Supported recording languages: en, zh-CN'); +const text = locale === 'en' ? { + title: 'Sequence drawing', heading: 'Checkout · Asynchronous payment and fulfillment', + subtitle: 'Independently authored fictional demo scenario', + ending: 'Complex flows,
drawn stroke by stroke.', + participants: 'Participants', messages: 'Messages', fragments: 'Fragments', + features: 'Nested branches · Failure compensation · Async callbacks
Paired returns · Activations · Idempotency', + play: 'Play', pause: 'Pause', resume: 'Resume', replay: 'Replay', +} : { + title: '时序图绘制', heading: '电商下单 · 异步支付与履约', subtitle: '独立编写的虚构演示场景', + ending: '复杂流程,
一笔一笔成形。', participants: '参与者', messages: '消息', fragments: '组合片段', + features: '嵌套分支 · 失败补偿 · 异步回调
成对返回 · 激活条 · 幂等处理', + play: '播放', pause: '暂停', resume: '继续', replay: '重播', +}; +fs.mkdirSync(out, { recursive: true }); +const require = createRequire(import.meta.url); +const candidates = process.env.PLAYWRIGHT_MODULE ? [process.env.PLAYWRIGHT_MODULE] : [ + 'playwright', ...fs.readdirSync(path.join(os.homedir(), '.npm/_npx')).sort() + .map(name => path.join(os.homedir(), '.npm/_npx', name, 'node_modules/playwright')), +]; +let chromium; +for (const candidate of candidates) { + try { const api = require(candidate); if (fs.existsSync(api.chromium.executablePath())) { chromium = api.chromium; break; } } catch { /* use a downloaded browser */ } +} +assert.ok(chromium, 'An installed Playwright build with Chromium is required.'); +const css = `*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden}body{font-family:Inter,-apple-system,BlinkMacSystemFont,"PingFang SC",sans-serif;color:#20242c;background:#edf1f6}header{position:absolute;top:27px;left:38px;right:38px;display:flex;align-items:center;gap:15px;height:47px}#brand{width:43px;height:43px;border-radius:13px;background:#1677eb;color:white;display:grid;place-items:center;font-size:24px;font-weight:800;box-shadow:0 5px 18px #1677eb22}.brand-name{font-size:19px;font-weight:730;letter-spacing:-.5px}.brand-name span{font-size:11px;letter-spacing:2px;font-weight:600;color:#8390a5;margin-left:18px}.heading{margin-left:auto;text-align:right;font-size:17px;font-weight:650}.heading small{display:block;font-size:11px;color:#8390a5;font-weight:500;margin-top:5px}#stage{position:absolute;left:24px;right:24px;top:94px;bottom:74px;overflow:hidden;border:1px solid #e2e7ee;border-radius:20px;background:#fcfcfd;box-shadow:0 18px 55px #203c6b0b}#paper{position:absolute;left:0;top:0;transform-origin:0 0}#paper svg{display:block;overflow:visible}#lifeline-labels{position:absolute;inset:0 0 auto;height:60px;overflow:hidden;background:linear-gradient(#fcfcfd 82%,#fcfcfd00);opacity:0;z-index:2;pointer-events:none}.participant-label{position:absolute;top:12px;transform:translateX(-50%);font-size:13px;font-weight:650;text-align:center;line-height:17px;color:#506179;padding:0 5px;overflow-wrap:anywhere}footer{position:absolute;bottom:23px;left:40px;right:40px;display:flex;align-items:center;gap:18px;height:26px}#caption{font-size:14px;font-weight:600;flex:1}#counter{font-size:12px;font-variant-numeric:tabular-nums;color:#7b879a}#play{border:1px solid #d8e1ed;background:#f9fbff;border-radius:7px;min-width:52px;height:27px;color:#506179;font-size:12px;cursor:pointer}.track{position:absolute;bottom:64px;left:40px;right:40px;height:2px;background:#dfe5ef;border-radius:2px;overflow:hidden}#progress{height:100%;background:#1677eb;width:0}#ending{position:absolute;top:31%;left:58%;width:36%;opacity:0;pointer-events:none}#ending .eyebrow{font-size:12px;letter-spacing:2px;color:#1677eb;font-weight:700}#ending h1{font-size:36px;letter-spacing:-1.5px;line-height:1.35;margin:18px 0 30px}#ending .stats{display:flex;gap:38px}#ending strong{display:block;font-size:42px;line-height:1.3;font-weight:650;color:#20242c}#ending .stats span{font-size:12px;color:#8390a5}#ending .tags{margin-top:32px;display:flex;gap:9px}#ending .tags span{font-size:12px;padding:7px 12px;border:1px solid #dfe5ef;border-radius:7px;color:#506179}#ending p{font-size:13px;color:#8390a5;line-height:1.8;margin-top:22px}`; +const player = fs.readFileSync(path.join(import.meta.dirname, 'sequence-drawing-player.js'), 'utf8'); +const html = `QGraphFlow · ${text.title}
Q
QGraphFlow SEQUENCE
${text.heading}${text.subtitle}
${svgBytes}
`; +fs.writeFileSync(path.join(out, 'drawing.html'), html); +const server = http.createServer((request, response) => { + const url = new URL(request.url, 'http://localhost'); + const body = url.pathname === '/' ? html : url.pathname === '/viewer' ? fs.readFileSync(path.join(view, 'index.html')) : url.pathname === '/svg' ? svgBytes : null; + if (!body) { response.writeHead(404).end(); return; } + response.writeHead(200, { 'Content-Type': url.pathname === '/svg' ? 'image/svg+xml' : 'text/html; charset=utf-8' }); response.end(body); +}); +await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); +const base = `http://127.0.0.1:${server.address().port}`; +const record = { width, height, fps, svgSha256: createHash('sha256').update(svgBytes).digest('hex'), + graphSha256: createHash('sha256').update(graphBytes).digest('hex'), sourceRef: graph.meta.sourceRef, + commit: spawnSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).stdout.trim(), cursor: false, errors: [], remoteRequests: [], checkpoints: [] }; +let browser; +function encode(args) { + const result = spawnSync('ffmpeg', ['-y', '-loglevel', 'error', ...args], { encoding: 'utf8' }); + assert.equal(result.status, 0, result.stderr); +} +try { + browser = await chromium.launch(); + const context = await browser.newContext({ viewport: { width, height }, deviceScaleFactor: 1, colorScheme: 'light' }); + const page = await context.newPage(); + page.on('pageerror', error => record.errors.push(error.message)); + page.on('request', request => { if (/^https?:/.test(request.url()) && !request.url().startsWith(base)) record.remoteRequests.push(request.url()); }); + await page.goto(`${base}/?record`); await page.evaluate(() => document.fonts.ready); + await page.waitForFunction(() => Boolean(window.__drawing)); + const timeline = await page.evaluate(() => ({ ...window.__drawing, render: undefined })); + Object.assign(record, { duration: timeline.duration, totals: timeline.totals }); + const checkpoints = [['blank', 0], ['first-element', timeline.actions[0].start + .2], + ['participants', timeline.actions.find(a => a.kind === 'message').start - .8], + ['nested-parallel', timeline.actions.find(a => a.kind === 'message' && a.target.edge === 'm13').start + .38], + ['webhook', timeline.actions.find(a => a.kind === 'message' && a.target.edge === 'm26').start + .38], + ['complete', timeline.completeAt + 1]]; + for (const [name, time] of checkpoints) { + const state = await page.evaluate(time => window.__drawing.render(time), time); + await page.screenshot({ path: path.join(out, `${name}.png`), scale: 'css' }); + record.checkpoints.push({ name, ...state }); + } + // Verify the final presentation retains every generated relationship and notation. + const completed = await page.evaluate(time => { + window.__drawing.render(time); + const svg = document.querySelector('#paper svg'); + return { + nodes: svg.querySelectorAll('[data-diagram-node-id]').length, + messages: [...svg.querySelectorAll('[data-diagram-edge-id]')].map(element => ({ + id: element.dataset.diagramEdgeId, dash: element.querySelector('path').getAttribute('stroke-dasharray'), + marker: element.querySelector('path').getAttribute('marker-end'), opacity: element.querySelector('path').style.opacity, + })), + activations: [...svg.querySelectorAll('.sequence-execution')].map(element => ({ id: element.dataset.executionId, height: Number(element.getAttribute('height')) })), + fragments: svg.querySelectorAll('[data-diagram-group-id]').length, + clipped: document.documentElement.scrollWidth > innerWidth || document.documentElement.scrollHeight > innerHeight, + }; + }, timeline.completeAt + 1); + assert.equal(completed.nodes, graph.nodes.length); assert.equal(completed.fragments, graph.groups.length); + assert.equal(completed.messages.length, graph.edges.length); assert.ok(completed.activations.every(a => a.height > 0)); + assert.equal(completed.activations.length, graph.executions.length); assert.equal(completed.clipped, false); + for (const edge of graph.edges) { + const item = completed.messages.find(item => item.id === edge.id); + assert.equal(item.opacity, '1'); assert.ok(item.marker.startsWith('url(#arrow-')); + assert.equal(Boolean(item.dash), edge.kind === 'return'); + } + record.finalNotation = completed; + // Readability acceptance also opens the real Viewer and the unmodified static SVG. + const acceptance = await context.newPage(); + await acceptance.setViewportSize({ width: 1440, height: 900 }); + acceptance.on('pageerror', error => record.errors.push(error.message)); + await acceptance.goto(`${base}/viewer`); await acceptance.evaluate(() => document.fonts.ready); + await acceptance.screenshot({ path: path.join(out, 'viewer-1440.png') }); + await acceptance.goto(`${base}/svg`); await acceptance.screenshot({ path: path.join(out, 'export-svg.png') }); + const originalNotation = await acceptance.evaluate(() => ({ + routes: [...document.querySelectorAll('[data-diagram-edge-id]')].map(element => ({ + id: element.dataset.diagramEdgeId, d: element.querySelector('path').getAttribute('d'), + dash: element.querySelector('path').getAttribute('stroke-dasharray'), marker: element.querySelector('path').getAttribute('marker-end'), + text: element.textContent, + })), + bars: [...document.querySelectorAll('.sequence-execution')].map(element => ({ id: element.dataset.executionId, height: Number(element.getAttribute('height')) })), + })); + const presentedNotation = await page.evaluate(() => ({ + routes: [...document.querySelectorAll('#paper [data-diagram-edge-id]')].map(element => ({ + id: element.dataset.diagramEdgeId, d: element.querySelector('path').getAttribute('d'), + dash: element.querySelector('path').getAttribute('stroke-dasharray'), marker: element.querySelector('path').getAttribute('marker-end'), + text: element.textContent, + })), + bars: [...document.querySelectorAll('#paper .sequence-execution')].map(element => ({ id: element.dataset.executionId, height: Number(element.getAttribute('height')) })), + })); + assert.deepEqual(presentedNotation, originalNotation, 'The finished animation must preserve the exported geometry and notation'); + // Exercise the downloadable player's pause / resume / replay controls. + await acceptance.clock.install(); + await acceptance.goto(`${base}/?record`); await acceptance.evaluate(() => document.fonts.ready); + await acceptance.getByRole('button', { name: text.play, exact: true }).click(); + await acceptance.clock.runFor(600); + await acceptance.getByRole('button', { name: text.pause, exact: true }).click(); + const paused = await acceptance.evaluate(() => window.__drawingState.time); + await acceptance.clock.runFor(400); + assert.equal(await acceptance.evaluate(() => window.__drawingState.time), paused); + await acceptance.getByRole('button', { name: text.resume, exact: true }).click(); + await acceptance.clock.runFor(500); + assert.ok(await acceptance.evaluate(() => window.__drawingState.time) > paused); + await acceptance.clock.runFor(timeline.duration * 1000); + await acceptance.getByRole('button', { name: text.replay, exact: true }).click(); + await acceptance.clock.runFor(150); + assert.ok(await acceptance.evaluate(() => window.__drawingState.time) < 1); + record.playerControls = 'pause / resume / replay passed'; + record.viewerAcceptance = { width: 1440, height: 900, originalNotationRetained: true }; + await acceptance.close(); + if (!values.preview) { + const framesDir = fs.mkdtempSync(path.join(out, 'frames-')); + const count = Math.ceil(timeline.duration * fps); + for (let frame = 0; frame < count; frame++) { + await page.evaluate(time => window.__drawing.render(time), frame / fps); + await page.screenshot({ path: path.join(framesDir, `frame-${String(frame).padStart(4, '0')}.png`), scale: 'css' }); + if (frame % (fps * 10) === 0) console.log(`Recording ${Math.floor(frame / fps)} / ${Math.ceil(timeline.duration)} seconds`); + } + record.frames = count; record.duration = count / fps; + const input = path.join(framesDir, 'frame-%04d.png'), palette = path.join(out, 'palette.png'); + encode(['-framerate', String(fps), '-i', input, '-vf', 'palettegen=stats_mode=diff:max_colors=256', palette]); + encode(['-framerate', String(fps), '-i', input, '-i', palette, '-lavfi', 'paletteuse=dither=bayer:bayer_scale=3:diff_mode=rectangle', '-loop', '0', path.join(out, 'checkout.sequence-drawing.gif')]); + encode(['-framerate', String(fps), '-i', input, '-c:v', 'libx264', '-crf', '18', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', path.join(out, 'checkout.sequence-drawing.mp4')]); + } + assert.deepEqual(record.errors, []); assert.deepEqual(record.remoteRequests, []); + assert.equal(fs.readFileSync(path.join(view, 'diagram.svg'), 'utf8'), svgBytes); + assert.equal(fs.readFileSync(path.join(view, 'graph.json'), 'utf8'), graphBytes); + fs.writeFileSync(path.join(out, 'record.json'), JSON.stringify(record, null, 2) + '\n'); + console.log(JSON.stringify({ output: out, duration: record.duration, frames: record.frames, totals: record.totals, errors: record.errors })); +} finally { + if (browser) await browser.close(); + await new Promise(resolve => server.close(resolve)); +} diff --git a/scripts/record-showcase-hero.mjs b/scripts/record-showcase-hero.mjs new file mode 100644 index 0000000..a93dc91 --- /dev/null +++ b/scripts/record-showcase-hero.mjs @@ -0,0 +1,241 @@ +#!/usr/bin/env node +// Record the actual source-built Viewer with a cursor following real mouse input. +// The cursor is recording-only; graph content, layout and generated HTML are never altered. +// Build/generate the three-view Jeepay collection before running this development helper. +// node scripts/record-showcase-hero.mjs --page --out +// PLAYWRIGHT_MODULE optionally points to an existing Playwright installation. +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; +import http from 'node:http'; +import assert from 'node:assert/strict'; +import { createRequire } from 'node:module'; +import { spawnSync } from 'node:child_process'; +import { parseArgs } from 'node:util'; + +const require = createRequire(import.meta.url); +const { values } = parseArgs({ options: { + page: { type: 'string' }, out: { type: 'string' }, + width: { type: 'string', default: '1440' }, height: { type: 'string', default: '900' }, + fps: { type: 'string', default: '12' }, preview: { type: 'boolean', default: false }, +} }); +if (!values.page || !values.out) throw new Error('Required: --page --out '); +const pagePath = path.resolve(values.page), out = path.resolve(values.out); +const width = Number(values.width), height = Number(values.height), fps = Number(values.fps); +for (const value of [width, height, fps]) assert.ok(Number.isInteger(value) && value > 0); +const graphPath = path.join(path.dirname(pagePath), 'graph.json'); +const graphBytesBefore = fs.readFileSync(graphPath, 'utf8'); +const graphs = JSON.parse(graphBytesBefore).diagrams; +assert.deepEqual(graphs.map(graph => graph.meta.viewId), ['jeepay-relations', 'jeepay-sequence', 'jeepay-er']); +const byId = Object.fromEntries(graphs.map(graph => [graph.meta.viewId, graph])); +const locale = graphs[0].meta.locale; +assert.ok(['en', 'zh-CN'].includes(locale), 'Supported recording languages: en, zh-CN'); +assert.ok(graphs.every(graph => graph.meta.locale === locale)); +const ui = locale === 'en' + ? { hideNotes: 'Hide key points', fit: 'Fit canvas', details: 'View details', hideDetails: 'Hide details' } + : { hideNotes: '隐藏要点', fit: '适应画布', details: '查看详情', hideDetails: '隐藏右侧详情栏' }; +fs.mkdirSync(out, { recursive: true }); +const framesDir = fs.mkdtempSync(path.join(out, 'frames-')); +const candidates = process.env.PLAYWRIGHT_MODULE ? [process.env.PLAYWRIGHT_MODULE] : [ + 'playwright', ...fs.readdirSync(path.join(os.homedir(), '.npm/_npx')).sort() + .map(name => path.join(os.homedir(), '.npm/_npx', name, 'node_modules/playwright')), +]; +let chromium; +for (const candidate of candidates) { + try { const api = require(candidate); if (fs.existsSync(api.chromium.executablePath())) { chromium = api.chromium; break; } } catch { /* try the next installed build */ } +} +if (!chromium) throw new Error('An installed Playwright build with downloaded Chromium is required.'); +const html = fs.readFileSync(pagePath); +const server = http.createServer((request, response) => { + if (new URL(request.url, 'http://localhost').pathname !== '/') { response.writeHead(404).end(); return; } + response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); response.end(html); +}); +await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); +let browser, frame = 0, time = 0; +const record = { width, height, fps, page: path.relative(path.resolve(import.meta.dirname, '..'), pagePath), + commit: spawnSync('git', ['rev-parse', 'HEAD'], { encoding: 'utf8' }).stdout.trim(), + viewerSha256: (await import('node:crypto')).createHash('sha256').update(html).digest('hex'), + sourceRef: graphs[0].meta.sourceRef, views: [], errors: [], remoteRequests: [], + cursor: { style: 'arrow', clicks: [], moves: 0 } }; +const F = seconds => Math.round(seconds * fps); +function encode(args) { + const result = spawnSync('ffmpeg', ['-y', '-loglevel', 'error', ...args], { encoding: 'utf8' }); + if (result.status !== 0) throw new Error(result.stderr || 'ffmpeg failed'); +} +try { + browser = await chromium.launch(); + const context = await browser.newContext({ viewport: { width, height }, deviceScaleFactor: 1, + colorScheme: 'light', reducedMotion: 'no-preference' }); + const page = await context.newPage(); + page.on('pageerror', error => record.errors.push(error.message)); + page.on('request', request => { if (/^https?:/.test(request.url()) && !request.url().startsWith('http://127.0.0.1:')) record.remoteRequests.push(request.url()); }); + await page.clock.install(); + await page.goto(`http://127.0.0.1:${server.address().port}/?automation=1`); + await page.clock.runFor(1600); + await page.evaluate(() => document.fonts.ready); + await page.waitForFunction(() => Boolean(window.__qgraphflowAutomation)); + let recording = false; + const parkedPointer = { x: width - 110, y: Math.round(height * .66) }; + let pointer = { ...parkedPointer }; + async function installCursor() { + await page.evaluate(() => { + const element = document.createElement('div'); + element.id = '__recording-cursor'; element.setAttribute('aria-hidden', 'true'); + element.style.cssText = 'position:fixed;left:0;top:0;width:1px;height:1px;z-index:2147483647;pointer-events:none'; + element.innerHTML = ''; + document.body.append(element); + window.__recordingPointer = { x: 0, y: 0, pressed: false, clickedAt: -1000 }; + addEventListener('mousemove', event => { + Object.assign(window.__recordingPointer, { x: event.clientX, y: event.clientY }); + element.style.transform = `translate(${event.clientX}px,${event.clientY}px)`; + }); + addEventListener('mousedown', () => Object.assign(window.__recordingPointer, { pressed: true, clickedAt: window.__recordingNow ?? 0 })); + addEventListener('mouseup', () => { window.__recordingPointer.pressed = false; }); + }); + await page.mouse.move(pointer.x, pointer.y); + } + async function moveTo(x, y) { + const from = { ...pointer }; + const count = F(Math.min(.45, Math.max(.2, Math.hypot(x - from.x, y - from.y) / 3000))); + for (let i = 1; i <= count; i++) { + const t = 1 - (1 - i / count) ** 3; + pointer = { x: from.x + (x - from.x) * t, y: from.y + (y - from.y) * t }; + await page.mouse.move(pointer.x, pointer.y); await capture(); + } + record.cursor.moves++; + } + async function click(locator, { position } = {}) { + if (!recording) { await locator.click(position ? { position } : {}); return; } + await locator.waitFor({ state: 'visible' }); + const box = await locator.boundingBox(); assert.ok(box, 'Mouse target has no bounds'); + const x = box.x + (position?.x ?? box.width / 2), y = box.y + (position?.y ?? box.height / 2); + assert.ok(x >= 0 && x < width && y >= 0 && y < height, 'Mouse target is outside the viewport'); + await moveTo(x, y); + record.cursor.clicks.push({ frame, x, y, target: (await locator.getAttribute('aria-label')) || (await locator.getAttribute('title')) || (await locator.innerText()).trim().slice(0, 80) }); + await page.mouse.down(); await capture(); + await page.mouse.up(); await hold(.08); + } + async function settle() { await page.clock.runFor(800); } + async function closeNotes() { + const button = page.getByRole('button', { name: ui.hideNotes, exact: true }); + if (await button.count()) { await click(button); await settle(); } + } + async function capture() { + await page.clock.runFor(1000 / fps); time += 1000 / fps; + await page.evaluate(now => { + window.__recordingNow = now; + const pointer = window.__recordingPointer, cursor = document.querySelector('#__recording-cursor'); + if (pointer && cursor) { + const elapsed = now - pointer.clickedAt; + cursor.firstElementChild.style.opacity = elapsed < 330 ? String(.65 * (1 - elapsed / 330)) : '0'; + cursor.firstElementChild.style.transform = `scale(${.65 + Math.min(330, elapsed) / 330})`; + cursor.lastElementChild.style.transform = pointer.pressed ? 'scale(.95)' : ''; + } + window.__recordingAnimations ??= new Map(); + for (const animation of document.getAnimations()) { + if (!window.__recordingAnimations.has(animation)) window.__recordingAnimations.set(animation, now); + animation.pause(); animation.currentTime = now - window.__recordingAnimations.get(animation); + } + }, time); + await page.screenshot({ path: path.join(framesDir, `frame-${String(frame++).padStart(4, '0')}.png`), scale: 'css' }); + } + async function hold(seconds) { for (let i = 0; i < F(seconds); i++) await capture(); } + async function viewport() { return page.evaluate(() => window.__qgraphflowAutomation.getViewport()); } + async function camera(x, y, zoom, seconds) { + const from = await viewport(); + const canvas = await page.locator('.react-flow').boundingBox(); + const insets = await page.evaluate(() => ({ + top: Math.max(0, document.querySelector('header').getBoundingClientRect().bottom - document.querySelector('.react-flow').getBoundingClientRect().y), + left: document.querySelector('.canvas').dataset.navOpen === 'true' ? 316 : 0, + right: document.querySelector('.canvas').dataset.drawerOpen === 'true' ? 316 : 0, + })); + const to = { zoom, x: (insets.left + canvas.width - insets.right) / 2 - x * zoom, + y: (insets.top + canvas.height) / 2 - y * zoom }; + const count = F(seconds); + for (let i = 1; i <= count; i++) { + const t = i / count, eased = t < .5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2; + await page.evaluate(({ from, to, eased }) => window.__qgraphflowAutomation.setViewport({ + x: from.x + (to.x - from.x) * eased, y: from.y + (to.y - from.y) * eased, + zoom: from.zoom + (to.zoom - from.zoom) * eased, + }, { duration: 0 }), { from, to, eased }); + await capture(); + } + } + async function fit() { await click(page.getByRole('button', { name: ui.fit, exact: true })); await settle(); } + async function mark(name, { allNodes = false, containedNodes = [] } = {}) { + const view = await viewport(); + const bounds = await page.evaluate(() => { + const canvas = document.querySelector('.react-flow').getBoundingClientRect(); + const visibleTop = Math.max(canvas.y, document.querySelector('header').getBoundingClientRect().bottom); + const nodes = [...document.querySelectorAll('.react-flow__node[data-id]')].map(element => { + const r = element.getBoundingClientRect(); return { id: element.dataset.id, x: r.x, y: r.y, right: r.right, bottom: r.bottom, + contained: r.x >= canvas.x - 1 && r.y >= visibleTop - 1 && r.right <= canvas.right + 1 && r.bottom <= canvas.bottom + 1 }; + }); + const pointer = window.__recordingPointer; + return { nodes, cursor: pointer ? { x: pointer.x, y: pointer.y } : null, + scrollX: document.documentElement.scrollWidth > innerWidth, scrollY: document.documentElement.scrollHeight > innerHeight }; + }); + assert.equal(bounds.scrollX || bounds.scrollY, false, `${name}: unintended page scroll`); + if (allNodes) assert.ok(bounds.nodes.every(node => node.contained), `${name}: full view clips a node`); + for (const id of containedNodes) assert.ok(bounds.nodes.find(node => node.id === id)?.contained, `${name}: clips ${id}`); + await page.screenshot({ path: path.join(out, `${name}.png`), scale: 'css' }); + record.views.push({ name, frame, viewport: view, ...bounds }); + console.log(`${name}: frame ${frame}, zoom ${view.zoom.toFixed(3)}`); + } + async function switchTo(id) { + await click(page.locator('#view-menu-button')); await hold(.25); + await click(page.locator(`[data-view-id="${id}"]`)); await settle(); + await closeNotes(); await fit(); + assert.equal(await page.locator('.react-flow__node[data-id]').count(), byId[id].nodes.length + (byId[id].groups?.length ?? 0)); + } + await closeNotes(); await fit(); + await installCursor(); recording = true; + await mark('architecture-overview', { allNodes: true }); + await hold(.75); + await camera(470, 370, 1, .75); + await click(page.locator('.react-flow__node[data-id="abstract"] .diagram-node')); await settle(); + await hold(.4); + await click(page.getByRole('button', { name: ui.details, exact: true })); await settle(); + assert.match(await page.locator('.inspector .source-path').innerText(), /AbstractPayOrderController\.java/); + await camera(470, 370, .9, .4); + await hold(.5); await mark('architecture-evidence'); await hold(.75); + await click(page.getByRole('button', { name: ui.hideDetails, exact: true })); await settle(); + await fit(); await hold(.25); + await switchTo('jeepay-sequence'); + await mark('sequence-overview', { allNodes: true }); await hold(.5); + await camera(810, 390, .88, 1); await mark('sequence-order'); await hold(.75); + // Keep participant headers, the guarded fragment and all callback messages in one shot. + const callbackNodes = ['orders', 'channelapi', 'channel', 'callback', 'process', 'notify']; + const span = byId['jeepay-sequence'].nodes.filter(node => callbackNodes.includes(node.id)); + const left = Math.min(...span.map(node => node.position.x)), right = Math.max(...span.map(node => node.position.x + node.size.width)); + const top = Math.min(...span.map(node => node.position.y)), bottom = Math.max(...span.map(node => node.position.y + node.size.height)); + const callbackZoom = Math.min((width - 80) / (right - left), (height - 100) / (bottom - top)); + await camera((left + right) / 2, (top + bottom) / 2, callbackZoom, 1.15); + await mark('sequence-callback', { containedNodes: callbackNodes }); await hold(1); + await fit(); await hold(.25); + await switchTo('jeepay-er'); + await mark('er-overview', { allNodes: true }); await hold(.5); + await camera(1095, 900, .85, 1); await mark('er-fields'); await hold(1.25); + await click(page.locator('.react-flow__node[data-id="t_pay_order"] .diagram-node'), { position: { x: 180, y: 25 } }); await settle(); + assert.match(await page.locator('.node-card .card-source').innerText(), /init\.sql/); + await hold(.5); await mark('er-evidence'); await hold(.5); + await page.keyboard.press('Escape'); await settle(); await fit(); await hold(.25); + await switchTo('jeepay-relations'); await moveTo(parkedPointer.x, parkedPointer.y); await hold(.5); + assert.ok(record.cursor.clicks.length >= 10, 'Missing recorded mouse actions'); + assert.deepEqual(record.errors, [], 'Browser runtime errors'); + assert.deepEqual(record.remoteRequests, [], 'Viewer requested external resources'); + assert.equal(fs.readFileSync(graphPath, 'utf8'), graphBytesBefore, 'Recording changed the generated graph'); + if (!values.preview) { + const pattern = path.join(framesDir, 'frame-%04d.png'); + const palette = path.join(out, 'palette.png'); + encode(['-framerate', String(fps), '-i', pattern, '-vf', 'palettegen=stats_mode=diff:max_colors=256', palette]); + encode(['-framerate', String(fps), '-i', pattern, '-i', palette, '-lavfi', 'paletteuse=dither=bayer:bayer_scale=3:diff_mode=rectangle', '-loop', '0', path.join(out, `jeepay.${locale}.hero.gif`)]); + encode(['-framerate', String(fps), '-i', pattern, '-c:v', 'libx264', '-crf', '18', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', path.join(out, `jeepay.${locale}.hero.mp4`)]); + } + Object.assign(record, { frames: frame, seconds: frame / fps, framesDir }); + fs.writeFileSync(path.join(out, 'record.json'), JSON.stringify(record, null, 2) + '\n'); + console.log(JSON.stringify({ frames: frame, seconds: frame / fps, out, errors: record.errors })); +} finally { + await browser?.close(); + await new Promise(resolve => server.close(resolve)); +} diff --git a/scripts/sequence-drawing-player.js b/scripts/sequence-drawing-player.js new file mode 100644 index 0000000..05a62f5 --- /dev/null +++ b/scripts/sequence-drawing-player.js @@ -0,0 +1,296 @@ +// Presentation-only timeline over a validated, exported sequence SVG. +// All source shapes, labels, dash patterns and arrow markers remain intact. +(() => { + const graph = window.__sequenceGraph; + const text = graph.meta.locale === 'en' ? { + head: 'Build participant', name: 'Write name', life: 'Draw lifeline', + extend: 'Extend lifelines and activations', finish: 'Complete lifelines and fragments', + complete: 'From a blank canvas to a complete, detailed flow.', + forming: 'The sequence is taking shape', start: 'Start with the first element', + replay: 'Replay', resume: 'Resume', pause: 'Pause', + } : { + head: '构建参与者', name: '写入名称', life: '绘制生命线', + extend: '延伸生命线与激活条', finish: '完成生命线与组合片段', + complete: '从空白画布,到完整复杂流程。', forming: '时序正在成形', start: '从第一个元素开始', + replay: '重播', resume: '继续', pause: '暂停', + }; + const svg = document.querySelector('#paper svg'); + const paper = document.querySelector('#paper'); + const stage = document.querySelector('#stage'); + const caption = document.querySelector('#caption'); + const progress = document.querySelector('#progress'); + const labels = document.querySelector('#lifeline-labels'); + const ending = document.querySelector('#ending'); + const NS = 'http://www.w3.org/2000/svg'; + const defs = svg.querySelector('defs'); + const actions = [], cameras = []; + const nodes = new Map(), edges = new Map(), groups = new Map(); + const ease = p => p < .5 ? 4 * p ** 3 : 1 - (-2 * p + 2) ** 3 / 2; + const clamp = p => Math.min(1, Math.max(0, p)); + let nextId = 0, t = .55, currentCamera; + const ox = Number(svg.dataset.graphOffsetX), oy = Number(svg.dataset.graphOffsetY); + const bounds = () => ({ width: stage.clientWidth, height: stage.clientHeight }); + function shape(name, attributes) { + const element = document.createElementNS(NS, name); + for (const [key, value] of Object.entries(attributes)) element.setAttribute(key, value); + return element; + } + // Remove export framing only; diagram facts stay in the original SVG elements. + for (const element of [...svg.children]) { + if (element.tagName === 'rect' || element.matches('text.heading,text.meta,[data-notes]')) element.remove(); + } + function clip(element, box) { + const id = `drawing-clip-${nextId++}`; + const definition = shape('clipPath', { id, clipPathUnits: 'userSpaceOnUse' }); + const rect = shape('rect', { x: box.x - 3, y: box.y - 3, width: 0, height: box.height + 6 }); + definition.append(rect); defs.append(definition); element.setAttribute('clip-path', `url(#${id})`); + return { element, rect, box }; + } + function reveal(item, p) { + item.rect.setAttribute('width', (item.box.width + 6) * clamp(p)); + item.element.style.opacity = p > 0 ? '1' : '0'; + } + function stroke(element) { + const id = `drawing-mask-${nextId++}`; + const mask = shape('mask', { id, maskUnits: 'userSpaceOnUse', x: -100, y: -100, width: 10000, height: 10000 }); + const brush = element.cloneNode(false); + for (const key of ['class', 'style', 'marker-end', 'stroke-dasharray', 'mask', 'fill']) brush.removeAttribute(key); + brush.setAttribute('fill', 'none'); brush.setAttribute('stroke', 'white'); brush.setAttribute('stroke-width', '12'); + brush.setAttribute('stroke-linecap', 'round'); + const length = element.getTotalLength(); + brush.setAttribute('stroke-dasharray', length); brush.setAttribute('stroke-dashoffset', length); + mask.append(brush); defs.append(mask); + const marker = element.getAttribute('marker-end'); + element.setAttribute('mask', `url(#${id})`); element.style.opacity = '0'; + return { element, brush, length, marker, maskId: id }; + } + function draw(item, p) { + item.element.style.opacity = p > 0 ? '1' : '0'; + item.brush.setAttribute('stroke-dashoffset', item.length * (1 - clamp(p))); + if (p >= 1) item.element.removeAttribute('mask'); + else item.element.setAttribute('mask', `url(#${item.maskId})`); + if (item.marker) item.element.setAttribute('marker-end', p >= 1 ? item.marker : 'none'); + } + function point(item, p) { return item.element.getPointAtLength(item.length * clamp(p)); } + function boxOf(element) { + const box = element.getBBox(); + return { x: box.x, y: box.y, width: box.width, height: box.height }; + } + function cameraFor(x, y, width = 1100, height = 620, maxZoom = .92) { + const size = bounds(); + const zoom = Math.min(maxZoom, (size.width - 120) / width, (size.height - 160) / height); + return { x: size.width / 2 - x * zoom, y: size.height / 2 - y * zoom, zoom }; + } + function focus(to, duration = .25) { + cameras.push({ start: t, duration, from: currentCamera || to, to }); + currentCamera = to; t += duration; + } + function action(kind, duration, items, title, target) { + const entry = { kind, start: t, duration, items, title, target }; + actions.push(entry); t += duration; return entry; + } + for (const node of graph.nodes) { + const element = svg.querySelector(`[data-diagram-node-id="${node.id}"]`); + const head = element.querySelector('.sequence-head'); + const figures = [...head.querySelectorAll('path,rect,circle,line,polyline,ellipse')]; + const geometries = figures.map(element => ({ ...stroke(element), fill: element.getAttribute('fill') })); + const texts = [...head.querySelectorAll('text')].map(element => clip(element, boxOf(element))); + const life = stroke(element.querySelector('.lifeline')); + const start = point(life, 0), end = point(life, 1); + const activations = [...element.querySelectorAll('.sequence-execution')].map(element => ({ + element, y: Number(element.getAttribute('y')), height: Number(element.getAttribute('height')), + id: element.dataset.executionId, + })); + for (const activation of activations) { activation.element.style.opacity = '0'; activation.element.setAttribute('height', 0); } + const label = document.createElement('div'); + label.className = 'participant-label'; label.textContent = node.label; labels.append(label); + nodes.set(node.id, { node, element, head, geometries, texts, life, start, end, activations, label, + x: node.position.x + node.size.width / 2 + ox }); + } + for (const edge of graph.edges.toSorted((a, b) => a.order - b.order)) { + const element = svg.querySelector(`[data-diagram-edge-id="${edge.id}"]`); + const route = stroke(element.querySelector('path')); + const annotations = [...element.children].filter(child => child !== route.element && child.tagName !== 'title') + .map(element => clip(element, boxOf(element))); + const box = boxOf(element); + const start = point(route, 0), end = point(route, 1); + edges.set(edge.id, { edge, element, route, annotations, box, start, end }); + } + const separators = [...svg.querySelectorAll('.operand-separator')]; + for (const group of graph.groups) { + const element = svg.querySelector(`[data-diagram-group-id="${group.id}"]`); + const rect = element.querySelector('rect'); + const box = boxOf(rect); + const groupClip = clip(element, box); + // A fragment frame extends downward as its messages are constructed. + groupClip.rect.setAttribute('width', box.width + 6); groupClip.rect.setAttribute('height', 0); + const firstEdges = group.operands.flatMap(operand => operand.edgeIds || []); + function descendantEdges(id) { + return graph.groups.filter(child => child.parentId === id).flatMap(child => [ + ...child.operands.flatMap(operand => operand.edgeIds || []), ...descendantEdges(child.id), + ]); + } + const allEdges = [...firstEdges, ...descendantEdges(group.id)].map(id => edges.get(id)); + const first = Math.min(...allEdges.map(item => item.edge.order)); + const last = Math.max(...allEdges.map(item => item.edge.order)); + const guards = group.operands.map((operand, index) => { + const element = svg.querySelector(`[data-fragment-group-id="${group.id}"][data-operand-id="${operand.id}"]`); + const children = graph.groups.filter(child => child.parentId === group.id && child.parentOperandId === operand.id); + const ids = [...(operand.edgeIds || []), ...children.flatMap(child => [ + ...child.operands.flatMap(o => o.edgeIds || []), ...descendantEdges(child.id), + ])]; + const first = Math.min(...ids.map(id => edges.get(id).edge.order)); + const entry = { item: clip(element, boxOf(element)), first }; + if (index > 0) { + const guardY = entry.item.box.y; + const separator = separators.find(path => { + const b = path.getBBox(); return Math.abs(b.x - box.x) < 1 && b.y > box.y && b.y < guardY; + }); + if (separator) { entry.separator = stroke(separator); separators.splice(separators.indexOf(separator), 1); } + } + return entry; + }); + groups.set(group.id, { group, element, rect, box, groupClip, first, last, guards }); + element.style.opacity = '0'; + } + if (separators.length) throw new Error('Unassigned sequence operand separator'); + const headLeft = Math.min(...graph.nodes.map(node => node.position.x + ox)); + const headRight = Math.max(...graph.nodes.map(node => node.position.x + node.size.width + ox)); + const firstNode = nodes.values().next().value; + focus(cameraFor(firstNode.x + 300, 350, 1300, 650), 0); + for (const [id, node] of nodes) { + const focusX = Math.max(headLeft + 290, Math.min(headRight - 290, node.x)); + focus(cameraFor(focusX, 380, 1350, 650), .16); + action('head', .38, node.geometries, `${text.head} · ${node.node.label}`, { node: id }); + action('text', .23, node.texts, `${text.name} · ${node.node.label}`, { node: id }); + action('life', .23, [node.life], text.life, { node: id, y: 420 }); + } + focus(cameraFor((headLeft + headRight) / 2, 350, headRight - headLeft + 120, 560, .7), .55); t += .45; + let level = 420; + for (const [id, edge] of edges) { + const span = Math.max(1000, edge.box.width + 260); + focus(cameraFor(edge.box.x + edge.box.width / 2, edge.start.y - 110, span, 720, .92), .23); + for (const group of groups.values()) { + if (group.first === edge.edge.order) { + action('fragment', .35, [group], `${group.group.kind} · ${group.group.label}`, { group: group.group.id }); + } + for (const guard of group.guards) { + if (guard.first === edge.edge.order) action('guard', .18, [guard], `${group.group.kind} · ${group.group.label}`); + } + } + action('extend', .13, [], text.extend, { from: level, to: Math.max(level, edge.end.y + 35) }); + level = Math.max(level, edge.end.y + 35); + action('text', .22, edge.annotations, `${String(edge.edge.order).padStart(2, '0')} / ${graph.edges.length} · ${edge.edge.label}`, { edge: id }); + action('message', edge.route.length > 1800 ? .55 : .38, [edge.route], + `${String(edge.edge.order).padStart(2, '0')} / ${graph.edges.length} · ${edge.edge.label}`, { edge: id }); + t += .1; + } + action('extend', .25, [], text.finish, { from: level, to: Math.max(...[...nodes.values()].map(node => node.end.y)) + 35 }); + t += .35; + const size = bounds(); + const diagramBox = svg.querySelector('[data-diagram-node-id]').parentElement.getBBox(); + const fit = Math.min((size.height - 54) / diagramBox.height, (size.width * .52 - 50) / diagramBox.width); + focus({ x: size.width * .28 - (diagramBox.x + diagramBox.width / 2) * fit, y: 25 - diagramBox.y * fit, zoom: fit }, 1.25); + const completeAt = t; t += 3.25; + const fadeAt = t; t += .65; + const duration = t + .35; + const totals = { nodes: graph.nodes.length, messages: graph.edges.length, fragments: graph.groups.length, activations: graph.executions.length }; + function render(time) { + time = Math.max(0, Math.min(duration, time)); + let camera = cameras[0].to; + for (const entry of cameras) { + if (time < entry.start) break; + const p = ease(clamp((time - entry.start) / Math.max(.001, entry.duration))); + camera = Object.fromEntries(['x', 'y', 'zoom'].map(key => [key, entry.from[key] + (entry.to[key] - entry.from[key]) * p])); + } + paper.style.transform = `translate(${camera.x}px,${camera.y}px) scale(${camera.zoom})`; + let active, level = 0, drawnMessages = 0; + const visibleNodes = new Set(); + for (const node of nodes.values()) { + for (const item of node.geometries) { draw(item, 0); if (item.fill) item.element.setAttribute('fill', 'none'); } + node.texts.forEach(item => reveal(item, 0)); draw(node.life, 0); + node.activations.forEach(item => { item.element.style.opacity = '0'; item.element.setAttribute('height', 0); }); + } + for (const edge of edges.values()) { draw(edge.route, 0); edge.annotations.forEach(item => reveal(item, 0)); } + for (const group of groups.values()) { + group.element.style.opacity = '0'; group.groupClip.rect.setAttribute('height', 0); + for (const guard of group.guards) { reveal(guard.item, 0); if (guard.separator) draw(guard.separator, 0); } + } + for (const entry of actions) { + const p = clamp((time - entry.start) / entry.duration); + if (!p) continue; + if (p < 1) active = entry; + if (entry.kind === 'head') { + visibleNodes.add(entry.target.node); + const fraction = p * entry.items.length; + entry.items.forEach((item, index) => { + const part = clamp(fraction - index); draw(item, part); + if (item.fill && p > .92) item.element.setAttribute('fill', item.fill); + }); + } else if (entry.kind === 'text') { + entry.items.forEach(item => reveal(item, p)); + } else if (entry.kind === 'life') { + const node = nodes.get(entry.target.node); + const fraction = (entry.target.y - node.start.y) / (node.end.y - node.start.y) * p; + draw(node.life, fraction); + } else if (entry.kind === 'extend') { + level = entry.target.from + (entry.target.to - entry.target.from) * p; + } else if (entry.kind === 'message') { + draw(entry.items[0], p); + if (p === 1) drawnMessages++; + } else if (entry.kind === 'fragment') { + const group = entry.items[0]; group.element.style.opacity = '1'; + group.groupClip.rect.setAttribute('height', 72 * p); + } else if (entry.kind === 'guard') { + const guard = entry.items[0]; reveal(guard.item, p); + if (guard.separator) draw(guard.separator, p); + } + } + if (level > 0) { + for (const node of nodes.values()) { + draw(node.life, (level - node.start.y) / (node.end.y - node.start.y)); + for (const activation of node.activations) { + const height = Math.min(activation.height, Math.max(0, level - 35 - activation.y)); + activation.element.setAttribute('height', height); activation.element.style.opacity = height > 0 ? '1' : '0'; + } + } + for (const group of groups.values()) { + if (group.element.style.opacity === '1') { + group.groupClip.rect.setAttribute('height', Math.min(group.box.height + 6, Math.max(72, level - group.box.y + 15))); + } + } + } + const isComplete = time >= completeAt; + const terminal = clamp((time - fadeAt) / .65); + stage.style.opacity = String(1 - terminal); + ending.style.opacity = String(clamp((time - completeAt + .3) / .5) * (1 - terminal)); + caption.textContent = isComplete ? text.complete : active?.title || (drawnMessages ? text.forming : text.start); + progress.style.width = `${Math.min(1, time / completeAt) * 100}%`; + document.querySelector('#counter').textContent = `${String(drawnMessages).padStart(2, '0')} / ${totals.messages}`; + const sticky = !isComplete && camera.y < -120; + labels.style.opacity = sticky ? '1' : '0'; + for (const [id, node] of nodes) { + node.label.style.opacity = visibleNodes.has(id) ? '1' : '0'; + node.label.style.left = `${node.x * camera.zoom + camera.x}px`; + node.label.style.width = `${Math.max(80, node.node.size.width * camera.zoom)}px`; + } + window.__drawingState = { time, camera, drawnMessages, visibleNodes: visibleNodes.size, complete: isComplete, + active: active?.kind || null }; + return window.__drawingState; + } + window.__drawing = { render, duration, completeAt, totals, actions: actions.map(({ kind, start, duration, target }) => ({ kind, start, duration, target })) }; + let start = 0, pausedTime = 0, playing = false, raf; + const button = document.querySelector('#play'); + function tick(now) { + const time = (now - start) / 1000; + if (time >= duration) { render(duration); playing = false; button.textContent = text.replay; return; } + render(time); raf = requestAnimationFrame(tick); + } + button.addEventListener('click', () => { + if (playing) { pausedTime = window.__drawingState.time; cancelAnimationFrame(raf); playing = false; button.textContent = text.resume; } + else { if (pausedTime >= duration || button.textContent === text.replay) pausedTime = 0; + playing = true; start = performance.now() - pausedTime * 1000; button.textContent = text.pause; raf = requestAnimationFrame(tick); } + }); + render(0); + if (!new URLSearchParams(location.search).has('record')) button.click(); +})(); diff --git a/tests/jeepay-pages.test.mjs b/tests/jeepay-pages.test.mjs index 9698b64..3163c67 100644 --- a/tests/jeepay-pages.test.mjs +++ b/tests/jeepay-pages.test.mjs @@ -12,7 +12,8 @@ test('CI, Pages and release smoke tests use only Jeepay inputs', () => { assert.ok(inputs.length > 0, file); assert.ok(inputs.every(input => input.startsWith('examples/jeepay/')), file); } - assert.deepEqual(fs.readdirSync(path.join(root, 'examples')), ['jeepay']); + // Recording inputs are separate from the real-source corpus used by workflows. + assert.deepEqual(fs.readdirSync(path.join(root, 'examples')).sort(), ['jeepay', 'showcase']); assert.equal(fs.existsSync(path.join(root, 'tests/fixtures')), false); const workflow = read('.github/workflows/pages.yml'), home = read('docs/pages/index.html'); assert.match(workflow, /--repo-root output\/jeepay-source/); diff --git a/tests/open-source-hygiene.test.mjs b/tests/open-source-hygiene.test.mjs index bda6960..30f9513 100644 --- a/tests/open-source-hygiene.test.mjs +++ b/tests/open-source-hygiene.test.mjs @@ -57,8 +57,11 @@ test('IDE metadata is ignored', () => { assert.match(read('.gitignore'), /^\.idea\/$/m); }); -test('the README identifies QGraphFlow as an independent project', () => { +test('the READMEs identify project independence and the conceptual example boundary', () => { const readme = read('README.md'); assert.match(readme, /independent MIT-licensed project/); - assert.match(readme, /no affiliation, sponsorship or endorsement is implied/); + assert.match(readme, /conceptual examples and do not represent any real company's production architecture/); + const chinese = read('docs/readme/README.zh-CN.md'); + assert.match(chinese, /独立项目/); + assert.match(chinese, /概念示例,不代表任何真实公司的生产架构/); });