Files
ARCADIA-Studio/crates/oa-desktop/src/guide.rs
T

981 lines
65 KiB
Rust

//! Offline engineering help. Topics describe the shared model and real UI controls.
//!
//! This module owns only reading/navigation state. Application navigation and demo
//! loading use normal App messages, so help never bypasses the model's draft guards.
use crate::{
app::{Message, Page, Pending},
style,
};
use iced::{
Element, Fill, Font,
widget::{button, column, container, keyed_column, row, scrollable, text, text_input},
};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Topic {
#[default]
FirstProject,
Perspectives,
Participants,
Nesting,
Capabilities,
Exchanges,
Chains,
Scenarios,
States,
Interfaces,
Transitions,
Artifacts,
Source,
Recovery,
Files,
Server,
}
#[derive(Debug, Clone, Default)]
pub struct State {
pub query: String,
pub topic: Topic,
}
#[derive(Debug, Clone)]
pub enum Event {
Search(String),
Topic(Topic),
}
impl State {
pub fn update(&mut self, event: Event) {
match event {
Event::Search(query) => {
self.query = query;
let matches = search(&self.query);
if !matches.contains(&self.topic)
&& let Some(first) = matches.first()
{
self.topic = *first;
}
}
Event::Topic(topic) => {
self.topic = topic;
// Related/contextual links always reveal the requested topic.
self.query.clear();
}
}
}
}
impl Topic {
const ALL: [Self; 16] = [
Self::FirstProject,
Self::Perspectives,
Self::Participants,
Self::Nesting,
Self::Capabilities,
Self::Exchanges,
Self::Chains,
Self::Scenarios,
Self::States,
Self::Interfaces,
Self::Transitions,
Self::Artifacts,
Self::Source,
Self::Recovery,
Self::Files,
Self::Server,
];
pub fn title(self) -> &'static str {
match self {
Self::FirstProject => "Build your first model",
Self::Perspectives => "Operational & System Analysis",
Self::Participants => "Participants, roles & work",
Self::Nesting => "Ownership, allocation & views",
Self::Capabilities => "Missions & capabilities",
Self::Exchanges => "Exchanges & dataflow",
Self::Chains => "Processes & functional chains",
Self::Scenarios => "Scenarios & alternatives",
Self::States => "States, modes & transitions",
Self::Interfaces => "Data types, ports & interfaces",
Self::Transitions => "Move between OA & SA",
Self::Artifacts => "Connect engineering evidence",
Self::Source => "Textual editing & Git",
Self::Recovery => "Checks & error recovery",
Self::Files => "Files & keyboard shortcuts",
Self::Server => "REST API & automation",
}
}
fn category(self) -> &'static str {
match self {
Self::FirstProject | Self::Perspectives => "START HERE",
Self::Participants | Self::Nesting | Self::Capabilities | Self::Exchanges => {
"MODEL THE SYSTEM"
}
Self::Chains | Self::Scenarios | Self::States | Self::Interfaces => "DESCRIBE BEHAVIOR",
Self::Transitions | Self::Artifacts | Self::Source => "CONNECT YOUR WORK",
Self::Recovery | Self::Files | Self::Server => "REFERENCE",
}
}
}
struct Article {
summary: &'static str,
keywords: &'static str,
sections: &'static [Section],
related: &'static [Topic],
destination: Option<(Page, &'static str)>,
}
struct Section {
title: &'static str,
body: &'static str,
steps: &'static [&'static str],
code: Option<&'static str>,
}
const fn explain(title: &'static str, body: &'static str) -> Section {
Section {
title,
body,
steps: &[],
code: None,
}
}
const fn walkthrough(title: &'static str, steps: &'static [&'static str]) -> Section {
Section {
title,
body: "",
steps,
code: None,
}
}
const fn example(title: &'static str, body: &'static str, code: &'static str) -> Section {
Section {
title,
body,
steps: &[],
code: Some(code),
}
}
fn article(topic: Topic) -> Article {
match topic {
Topic::FirstProject => {
const {
Article {
summary: "Create a small operational model, then derive the system contribution. Use concrete names for work and keep your first diagram focused.",
keywords: "getting started tutorial beginner quick start new example rescue coordinator allocation",
sections: &[
walkthrough(
"Create an operational context",
&[
"Choose New in the top bar. Use Project properties to name the model and describe its purpose. Apply changes commits the property draft.",
"On Architecture, choose Create architecture view. Architecture · allocated work shows work inside its performer.",
"Choose Entity under Create element, then + At root. Name it Rescue coordinator and Apply changes. Use Actor for an external participant such as a Vessel in distress.",
"Select Rescue coordinator, choose Activity, then + Allocated. Name the new activity Assess incident. It appears inside its performer; an Allocation relationship records that responsibility.",
"Add a second activity for another participant. Select an activity, choose + Relationship, select Interaction and choose the compatible target. Name the exchange after what is conveyed.",
"Visit Model checks to review unfinished work. Save project writes the accepted model and applied drafts to your chosen file.",
],
),
explain(
"Continue into system design",
"Use Create / update counterpart on an operational element to review an OA → SA mapping. In System Analysis, identify the System under study, its external Actors and the Functions for which each is responsible. The transition tutorial explains scope, reuse and review decisions.",
),
explain(
"Explore a worked model",
"Open the coastal rescue example to inspect allocation, decomposition, repeated scenario messages, operating modes, typed interfaces and artifact references. Loading the example uses the normal unsaved-work prompt. It replaces the current project only after that workflow completes.",
),
],
related: &[
Topic::Perspectives,
Topic::Participants,
Topic::Nesting,
Topic::Transitions,
],
destination: Some((Page::Diagram, "Open Architecture")),
}
}
}
Topic::Perspectives => {
const {
Article {
summary: "Operational Analysis asks what people and organizations need to accomplish. System Analysis asks what the system must do within that operation.",
keywords: "oa sa arcadia capella system architecture operational need boundary abstraction actor system of interest",
sections: &[
explain(
"Operational Analysis: understand the need",
"Describe entities and actors, their activities, roles, interactions and operational capabilities without assuming a particular technical solution. An activity such as Assess incident states work in the operation; it does not imply which software module implements it.",
),
explain(
"System Analysis: define the contribution",
"Describe one System under study, external System actors, Functions, functional exchanges, missions and system capabilities. A participant's operational activity may be supported by several system functions or may remain outside the system boundary. Review that engineering decision explicitly.",
),
explain(
"Switching perspective does not transition the model",
"The 01 Operational and 02 System controls change the visible perspective and available creation kinds. They do not move or delete records. Create / update counterpart makes a reviewed, traceable correspondence while preserving both perspectives.",
),
explain(
"Keep the boundary explicit",
"Separate work performed by the System from work performed by external actors. Allocate leaf functions to the responsible participant. Logical and Physical Architecture belong to later design layers; a system-level function is not a software class or a physical component.",
),
],
related: &[Topic::FirstProject, Topic::Capabilities, Topic::Transitions],
destination: Some((Page::Catalog, "Open Model explorer")),
}
}
}
Topic::Participants => {
const {
Article {
summary: "Represent who participates separately from the work they perform. Decomposition and responsibility answer different engineering questions.",
keywords: "entity actor participant role realization assignment leaf activity function performer decomposition responsibility",
sections: &[
walkthrough(
"Create and allocate work",
&[
"Choose the Operational or System perspective. Create participants using Entity/Actor in OA or System/System actor in SA.",
"Create Activity in OA or Function in SA. Select existing work and use + In selected to decompose it into smaller work units.",
"To create work for a participant, select the participant, choose Activity or Function, then + Allocated. To allocate existing work, use + Relationship with Allocation from the participant to the leaf work.",
"Choose Architecture · allocated work to review responsibilities. Choose Functional dataflow to review the activity/function hierarchy and its exchanges independently.",
],
),
explain(
"Operational roles",
"A Role describes a responsibility independently of a particular entity. Role realization links an Entity or Actor to a Role. Role assignment links a Role to an Activity. These relations express role responsibilities; an Allocation identifies the concrete performer of leaf work.",
),
explain(
"Name work as an action",
"Prefer Assess incident or Deliver electrical power for work, and Rescue coordinator or Power controller for participants. A meaningful description records assumptions, input/output expectations and the decision behind an allocation.",
),
explain(
"Creation choices are constrained",
"+ In selected is enabled only for a legal semantic parent. + Allocated appears when the selected participant can perform the chosen work. Pickers show compatible endpoints. If a control is unavailable, check the active perspective, selected owner and element kind.",
),
],
related: &[Topic::Nesting, Topic::Exchanges, Topic::Capabilities],
destination: Some((Page::Catalog, "Open Model explorer")),
}
}
}
Topic::Nesting => {
const {
Article {
summary: "A diagram is a view of the model. Its boxes, positions and nesting reveal selected relationships; moving a box does not redefine engineering ownership.",
keywords: "containment semantic parent hierarchy nested nesting allocation deployment diagram placement arrange fit fold hide zoom resize routing connection bend waypoint detour label reset",
sections: &[
explain(
"Three things to distinguish",
"Semantic parent records ownership: an activity contains subactivities, or a scenario owns occurrences. Allocation records who performs leaf work. A placement records where one element appears in a diagram. One element can have placements in several views without becoming several semantic elements.",
),
walkthrough(
"Browse a large model",
&[
"Open Model explorer. Choose a group, then a container to see its direct children. Breadcrumbs return to its owner or group; Allocated work is a separate list of responsibilities.",
"Search names, kinds or @aliases in the left outline. Matching elements retain their ancestor context; the main browser lists up to 100 results per page. Select a result to open its details and clear the search.",
"Use Collapse all to fold the outline and Locate to reveal the selected element. Search keeps matching ancestry open; clear the search before folding individual groups or containers.",
],
),
walkthrough(
"Choose and edit the relationship you mean",
&[
"Use the diagram selector or + View to work with a focused diagram. View properties edits its name, perspective and projection.",
"Choose Containment for semantic children, Architecture · allocated work for responsibilities, Functional dataflow for work and exchanges, or Missions & capabilities for outcomes. All relationships is a broader exploration view; OA ↔ SA traceability displays counterparts.",
"Select an element and edit Semantic parent to change ownership. Use an Allocation relationship to change responsibility. Apply changes validates the semantic change.",
"Drag nodes to arrange the current view, scroll to zoom and use Fit to frame its contents. Arrange changes diagram geometry. Export SVG produces a shareable view.",
"Select a diagram node to access Placement, Fold / unfold and Hide. Hide removes its view placement; Delete from model removes model records and requires review of the affected references.",
],
),
explain(
"Keep diagrams readable",
"Use separate views for responsibility, decomposition and cross-layer coverage. Place existing elements with Show in this diagram. A placement's Diagram field can relocate it while preserving its identity and artifact references. Do not duplicate a semantic element merely to show it in another view.",
),
explain(
"Read allocation summaries and ports",
"A dashed parent activity/function with an italic name and SUMMARY heading groups leaves assigned to the same performer. It has no direct allocation of its own; its inspector explains the leaf performer. Small ports belong to the boundary of their visible owner and are selectable model elements. Folding a container hides its inner work and ports; summary exchange lines do not invent new ports on the container.",
),
walkthrough(
"Route connections around the model",
&[
"Select an architecture connection and drag a segment to add a bend. Right-click a line for Add bend here; right-click a circle for Remove bend. With the canvas focused, Insert adds a bend and Delete removes the selected bend. Drag the label independently. Escape cancels a gesture; Undo reverses one completed drag. Shift+F10 opens the selected diagram item's context menu; use arrow keys and Enter to choose an action.",
"Automatic orthogonal routing avoids cards and container headers where space permits. Amber connections need attention: move an obstructing element or a bend. In Select / move, drag a port to any border of its owner. Manual positions are per diagram; right-click Reset automatic position restores connection-aware placement. Ports remain attached to their actual owners. Manual routes are saved separately for each diagram and appear in source, API and SVG export.",
"Right-click a connection and choose Reset routing to clear its pins and label position in this view. The Properties Actions menu provides the same commands. Arrange preserves pins. Moving one endpoint keeps pins fixed; moving a container carrying both endpoints translates their pins. When folding promotes an endpoint, expand it again to resume and edit its saved route.",
],
),
explain(
"What nesting does not imply",
"Containment in an allocation projection expresses a performer relationship. It is not a physical deployment statement. Physical deployment is a different architectural concern; do not infer hardware installation from an OA/SA box layout.",
),
],
related: &[Topic::Participants, Topic::Perspectives, Topic::Artifacts],
destination: Some((Page::Diagram, "Open Architecture")),
}
}
}
Topic::Capabilities => {
const {
Article {
summary: "Capabilities describe an ability to achieve an outcome. Missions explain the purpose served by those abilities. Both Operational and System Analysis have missions, capabilities and participants; activities and functions describe supporting work.",
keywords: "capability mission outcome operational capability system capability involvement contribution trace coverage use case",
sections: &[
walkthrough(
"Connect outcomes to the work",
&[
"Create an OA Capability such as Protect life at sea. Describe the operational outcome and the conditions in which it matters.",
"Select a relevant participant, role or activity. Use + Relationship → Capability involvement and choose the capability as the target.",
"Create an Operational mission in OA, or a Mission in SA. Select it and choose Exploit capability to connect it to a capability in the same perspective. Involve participant associates an entity or actor with the mission.",
"Connect System participants/functions to the capability using Capability involvement. Connect a Process or Functional chain to a capability using Chain involvement.",
"Review OA/SA counterpart links in Traceability and read Model checks for incomplete coverage. A named capability alone does not demonstrate that the necessary behavior exists.",
],
),
walkthrough(
"Include, extend or specialize a capability",
&[
"Select a capability in the diagram or explorer, then open Actions at the top of its properties. Include capability identifies behavior that is always part of it; choose the included capability as the target.",
"Extend capability connects this extending capability to the extended capability. Enter the condition under which the extension adds behavior. An empty condition remains editable and appears in Model checks.",
"Specialize capability connects a specific capability to a more general one. Inclusion and specialization cannot form cycles.",
"The relationship preview names both ends and generates a readable name. Apply adds one undoable change; Cancel preserves the model. Include and extend labels remain visible in the diagram.",
],
),
explain(
"Use scenarios as concrete evidence",
"A scenario illustrates one path through the behavior: normal response, degraded operation or an exception. Keep its intended capability and assumptions clear in its engineering description and available model relationships. Scenarios complement the capability definition; they do not replace the outcome.",
),
explain(
"Avoid implementation names at this stage",
"Protect life at sea is a capability. Coordinate rescue is work. Send a JSON request is usually a design choice. Start with the engineering obligation and add technical detail when the perspective calls for it.",
),
],
related: &[
Topic::Perspectives,
Topic::Chains,
Topic::Scenarios,
Topic::Transitions,
],
destination: Some((Page::Catalog, "Open Model explorer")),
}
}
}
Topic::Exchanges => {
const {
Article {
summary: "An exchange says what passes between work or across a participant boundary. Its payload, endpoints and allocation make that statement precise.",
keywords: "interaction functional exchange boundary exchange flow information material energy event source target port exchange allocation",
sections: &[
walkthrough(
"Create a typed exchange",
&[
"Create the source and target activities/functions in one perspective. Give each an engineering name and allocate leaf work to its responsible participant.",
"Choose Interaction or Functional exchange in the diagram palette. Click the source, then a highlighted compatible target, or drag between them. Functional exchanges connect output ports to input ports; function cards create or reuse real ports in the same transaction. Middle-button drag pans while keeping the source; arrow keys also pan when the drawing canvas has focus. Click empty space to restart, or Escape to cancel.",
"Create an Exchange item under Data & interfaces. Choose its kind (Information, Material, Energy or Event) and describe its payload.",
"Select the exchange in Model explorer or the diagram. Edit its properties to reference the appropriate exchange items and available ports; Apply changes validates the complete record.",
"Choose Boundary exchange and connect compatible component ports. Flow goes from Out to In; Standard is unoriented. Select / move returns to arranging. Exchange allocation associates a boundary exchange with the functional exchange it carries.",
],
),
explain(
"Flow and sequence are different",
"A dataflow graph shows possible dependencies between activities/functions. It does not by itself impose an execution order. Use a Process, Functional chain or Scenario when you need to describe an ordered path, repeated exchanges or alternatives.",
),
explain(
"Use engineering payloads",
"Choose Information for data such as a distress report, Energy for delivered power, Material for a transferred substance and Event for a notification. Data types and fields can make structured payloads precise. URI artifact links can point to detailed interface-control documents.",
),
],
related: &[
Topic::Interfaces,
Topic::Chains,
Topic::Scenarios,
Topic::Artifacts,
],
destination: Some((Page::Catalog, "Open Model explorer")),
}
}
}
Topic::Chains => {
const {
Article {
summary: "An operational Process or system Functional chain selects an ordered path through existing exchanges. It explains how work contributes to an outcome.",
keywords: "process functional chain ordered occurrence sequence path involvement dataflow exchange repeated steps",
sections: &[
walkthrough(
"Describe a path through exchanges",
&[
"First create the activities/functions and their exchanges. Check the source and target work for every exchange.",
"Open Behavior & scenarios. Create Process in OA or Functional chain in SA using Create element and + At root.",
"Select that behavior in the page's selector. Use + Work or + Exchange to involve an existing source. Each contribution keeps its own identity while referring to the shared engineering definition.",
"Select a contribution, then add another after it or use Connect to wire existing contributions. Branch / parallel creates a split, two paths and their merge in one undoable edit. Enter explicit alternative conditions; use Fit and zoom to inspect the topology.",
"Use Chain involvement from the Process/Functional chain to the capability it supports. Read Model checks for unfinished or disconnected paths.",
],
),
explain(
"Choose the right behavioral description",
"A chain concentrates on a route through existing exchanges. A scenario can also carry actions, state observations, lifelines and nested fragments. The architecture diagram remains a view of the possible relationships rather than an execution trace.",
),
explain(
"Review the path as an engineer",
"Check that exchanges express meaningful handoffs and that the links and branch guards support the selected outcome. Use Reuse to involve an existing process or chain as a nested reference. Imported ordered exchange paths retain their meaning; the first topology edit explains and preserves their implied links in the same transaction.",
),
],
related: &[Topic::Exchanges, Topic::Capabilities, Topic::Scenarios],
destination: Some((Page::Behavior, "Open Behavior & scenarios")),
}
}
}
Topic::Scenarios => {
const {
Article {
summary: "Use a scenario to tell a concrete behavioral story, including repeated messages, actions, state observations and guarded alternatives.",
keywords: "scenario sequence lifeline sender receiver fragment branch alternative loop guard parallel strict optional occurrence exchange action state",
sections: &[
walkthrough(
"Create a scenario",
&[
"Open Behavior & scenarios, choose Scenario under Create element and add it at a legal root/owner. Select the scenario in the behavior selector.",
"Use + Lifeline and choose the participant or work it represents. Choices follow the scenario's viewpoint: operational activities, operational participants, system functions or system boundary participants.",
"Choose + Message and its style. Call + correlated reply creates both occurrences in one edit. Select the exchange and compatible sending/receiving lifelines. Reply to existing call offers valid unanswered calls.",
"Use Action, Observe state or Fragment for other behavior. A local timer has an expression and a lifeline, with no global exchange. Create and delete messages describe participant lifetime; Model checks also flag risks inside repeated loops.",
"Use the ordered list's up/down controls to change sequence order. Click a message or list entry to inspect that occurrence rather than the reusable exchange definition.",
],
),
walkthrough(
"Describe alternatives",
&[
"Choose Fragment and select ALT, PAR, LOOP or OPT. Enter the branch conditions; the tool creates the fragment and its operands together.",
"Use the Destination selector in subsequent message or action forms to choose the intended operand. New occurrences append to that operand's ordered behavior.",
"Use Reuse scenario to refer to existing behavior. Bind every referenced lifeline to a compatible local lifeline explicitly. Open source follows the original scenario without copying its contents.",
"Inspect the scenario projection and Model checks. State exactly what each guard means, including units and assumptions where relevant.",
],
),
explain(
"Conditions are engineering statements",
"Guards, actions and effects document behavior; a drawn scenario does not execute code or prove timing. Add evidence references when a simulation, test or analysis establishes a stronger claim.",
),
],
related: &[
Topic::Chains,
Topic::States,
Topic::Exchanges,
Topic::Artifacts,
],
destination: Some((Page::Behavior, "Open Behavior & scenarios")),
}
}
}
Topic::States => {
const {
Article {
summary: "Describe the conditions in which the operation or system behaves differently. States and modes belong to regions within a state machine.",
keywords: "state machine mode region initial final transition trigger guard effect entry exit do availability degraded nominal",
sections: &[
walkthrough(
"Build a state machine",
&[
"In Behavior & scenarios, create a State machine and select it. Use + Region to give its state structure an owner.",
"Use + State for conditions such as Nominal and Degraded, and + Initial for the starting pseudostate. Select a region and use Create element for other supported state kinds.",
"Select a source state and choose + Relationship → State transition. Pick a compatible target in the same state machine.",
"Edit the transition's Trigger, Guard and Effect in Properties. These distinguish what initiates the transition, what must be true, and what follows.",
"Review the rendered state view and numbered transition list. Model checks calls out unfinished structures so they can be corrected during authoring.",
],
),
explain(
"Availability and state actions",
"Availability links a participant or behavior to a State/Mode in which it is available. State action links a State/Mode to an Activity/Function and records its entry, do or exit phase. Use these typed relationships when work depends on operating conditions.",
),
explain(
"Keep conditions testable",
"Prefer a guard such as battery_level < reserve_threshold over a vague phrase such as low battery, and define the quantities elsewhere in the model or linked evidence. A state diagram documents the intended behavior; it is not a running state-machine simulator.",
),
],
related: &[
Topic::Scenarios,
Topic::Participants,
Topic::Recovery,
Topic::Artifacts,
],
destination: Some((Page::Behavior, "Open Behavior & scenarios")),
}
}
}
Topic::Interfaces => {
const {
Article {
summary: "Define exchanged payloads independently of the ports and participants that carry them. Reuse the same data definitions in related exchanges.",
keywords: "data interface port direction input output inout contract provision requirement exchange item enum enumeration record primitive field value type multiplicity minimum maximum",
sections: &[
walkthrough(
"Define a reusable payload",
&[
"Open Data & interfaces and create an Exchange item. Select Information, Material, Energy or Event to describe the nature of the exchange.",
"Create a Data type where the payload needs a named definition. Choose its Data type definition and edit the corresponding values in Properties.",
"For structured definitions, create Data fields under a legal Data type or Exchange item owner. Set Value type and the minimum/maximum count as required by the contract.",
"Select the exchange and reference the payload items. Apply changes and check the result in Model checks.",
],
),
walkthrough(
"Connect participant boundaries",
&[
"In System Analysis, select a System or System Actor and choose Add port in Model explorer or Properties. Name it, choose Standard or Flow, then Create port. Standard ports describe interfaces; Flow ports have their own input/output orientation. Functions use separate input/output port actions.",
"Use Interface provision and Interface requirement relationships to state who provides or needs the contract.",
"Select a visible component port and choose Draw boundary exchange, then click or drag to a highlighted compatible port. The diagram palette offers the same tool. If the endpoints are outside the diagram, Configure boundary exchange opens the form with owner names and aliases. Create the matching port first if needed. Exchange orientation is separate from Flow port orientation. Exchange allocation associates carried functional exchanges with the boundary connection.",
"Link the detailed interface-control document, CAD connector or schematic through an Artifact reference when an external artifact supplies additional evidence.",
],
),
explain(
"Preserve meaning across perspectives",
"Shared kinds such as Data type and Interface still belong to an engineering perspective. Their Engineering perspective property is explicit. Use counterpart traces for separate OA and SA definitions that correspond; do not assume that similar names create a trace.",
),
],
related: &[Topic::Exchanges, Topic::Artifacts, Topic::Transitions],
destination: Some((Page::Data, "Open Data & interfaces")),
}
}
}
Topic::Transitions => {
const {
Article {
summary: "Create or reuse counterpart records across OA and SA through a reviewable preview. Both perspectives keep their identities and remain linked.",
keywords: "transition counterpart bidirectional mapping realization drift conflict merge keep target take source baseline scope dependency reverse update trace reuse",
sections: &[
walkthrough(
"Review a counterpart transition",
&[
"Select an operational element and choose Create / update counterpart. Selecting a system element starts the reverse System → Operational workflow.",
"Choose Include: Selected only, Required references or Whole subtree. Required dependencies must have compatible counterparts before the plan can apply.",
"For each source, choose Create new counterpart or reuse an existing target. Review the target kind and any parent, performer or System-boundary decisions offered by the preview.",
"Read the proposed changes and conflicts. For a descriptive conflict, choose the source value, keep the target value, or enter a merged value. Changing the target/kind requires reviewing that target's decisions again.",
"Apply only after every blocking decision is resolved. The accepted plan is one undoable model change; Cancel preserves the current model.",
],
),
explain(
"Review drift deliberately",
"Traceability marks descriptive links Reviewed or Review needed by comparing both endpoints with their last review baseline. Independent edits may be intentional. Keeping a target value records that reviewed decision; it does not delete the operational need. A manual trace records correspondence without propagating properties.",
),
explain(
"Know what reconciliation changes",
"The current counterpart workflow reviews names and descriptions of existing targets. It can create structural dependencies for new counterparts. Reparenting, reallocating, removing or restructuring an existing counterpart remains an explicit engineering edit; do not assume Refresh preview automatically synchronizes every structural change.",
),
explain(
"Avoid duplicate counterparts",
"Check an existing trace before requesting a new counterpart. Several engineering records can correspond, but each mapping should have a reason. Reverse transitions also use the trace to reuse the intended record rather than silently replacing the original.",
),
],
related: &[
Topic::Perspectives,
Topic::Participants,
Topic::Capabilities,
Topic::Recovery,
],
destination: Some((Page::Traces, "Open Traceability")),
}
}
}
Topic::Artifacts => {
const {
Article {
summary: "Connect model decisions to external engineering evidence using stable references to code, CAD, schematics, documents and other artifacts.",
keywords: "artifact traceability evidence code cad schematic electrical docx requirements revision fragment uri git commit owner placement document",
sections: &[
walkthrough(
"Link an artifact",
&[
"Select the model record the evidence concerns. This may be an element, relationship, diagram, placement or other supported persistent record.",
"Choose + Artifact reference in Properties, or + Artifact in Traceability. Name the reference so another engineer understands its purpose.",
"Enter the Artifact URI, optional external Revision and Fragment. Use a commit or document revision when a reproducible reference matters; the fragment identifies a particular part of the artifact.",
"Add a description explaining whether the artifact specifies, implements or verifies the engineering decision. Apply changes, then inspect the owner and reference in Traceability.",
],
),
explain(
"A reference is not an attachment",
"The model preserves the author-provided URI, revision and fragment. Loading or saving a model does not fetch, execute or verify an external artifact. A path on one workstation may not exist on another; choose team-resolvable locations and identifiers.",
),
explain(
"Choose the right owner",
"Link semantic evidence to the element or relationship it supports. Link view-specific evidence to a diagram or placement. A diagram node's placement identity remains distinct from its represented element, so the reference can express the intended scope.",
),
explain(
"Automation uses the same records",
"A CAD or version-control plugin can query and edit artifact links through the REST API. Its update should preserve stable model IDs and use the server's conditional write contract to avoid overwriting another engineer's work.",
),
],
related: &[Topic::Source, Topic::Server, Topic::Nesting],
destination: Some((Page::Traces, "Open Traceability")),
}
}
}
Topic::Source => {
const {
Article {
summary: "The text editor and graphical forms edit the same model. Source projects preserve file organization and comments for human review, automation and version control.",
keywords: "text textual source language syntax highlighting lint parser git aliases uuid identity rename diff merge restructuredtext spec canonical parity",
sections: &[
walkthrough(
"Edit and review source",
&[
"Open Textual model and expand folders to choose a source file, or choose Actions > Open source in Properties to locate the selected record. Switching files preserves each draft. Collapsed folders show modified-file and diagnostic counts; Locate reveals the active file. Right-click a file for its actions.",
"Use Find / go to, Ctrl+F or Ctrl+G to navigate. Literal search is case sensitive. Enter/F3 selects the next match, Shift+F3 selects the previous match, and results wrap. Go to line accepts the displayed one-based line number.",
"Edit the draft. Syntax highlighting distinguishes source structure; live lint reports errors with line and column information.",
"Choose Apply all to parse and validate all reachable included files. A successful application is one model change; graphical views and Properties then reflect it. Finish or revert source drafts before editing graphical properties.",
"If parsing or validation fails, the accepted model remains unchanged and the source draft is preserved. Correct the reported location or use Revert draft to restore the accepted source.",
"Save all writes the entry and its included UTF-8 files. Review the Git diff before committing, including relationships, traces and diagram changes that belong to the same engineering edit.",
],
),
explain(
"Names, aliases and IDs serve different purposes",
"The displayed name is engineering prose. A stable @alias is the readable source reference and need not change when the name changes. UUID identity distinguishes the persistent record. Preserve IDs when revising the same engineering object; changing identity is a deliberate operation that affects references.",
),
example(
"Readable references",
"For example, renaming an activity does not require renaming @assess_incident. A related record can still refer to that alias.",
"source @receive_distress;\ntarget @assess_incident;",
),
explain(
"Use one contract",
"The normative specification is docs/language.rst in the distribution. It defines source grammar and supported graphical semantics together. Text preserves semantic records, diagram placements, ordered occurrences, traces and artifact references. Graphical edits preserve unaffected source formatting and comments; accepted model Undo/Redo also restores the exact source files. Comments and source-file organization are not part of JSON export.",
),
explain(
"Resolve source-control conflicts before applying",
"Ordinary Git conflict markers are not Arcadia syntax. Resolve the text merge, preserve intended UUIDs/references, then apply and inspect Model checks. The editor does not decide which engineer's model change is correct.",
),
],
related: &[
Topic::Recovery,
Topic::Files,
Topic::Artifacts,
Topic::Server,
],
destination: Some((Page::Text, "Open Textual model")),
}
}
}
Topic::Recovery => {
const {
Article {
summary: "Structural errors protect the accepted model. Warnings keep incomplete engineering work visible without preventing you from developing it incrementally.",
keywords: "validation checks error warning invalid alias missing reference undo redo revert recover draft unsaved structural external conflict lint",
sections: &[
walkthrough(
"Resolve a model issue",
&[
"Open Model checks and read the issue's message and severity. Use its Open action, where available, to inspect the affected record.",
"Correct its properties or missing relationships. Check the active perspective and compatible parent/endpoints before changing record kinds.",
"Apply changes and revisit Model checks. A warning about missing allocation or coverage may describe unfinished work; a rejected structural error must be corrected before that candidate can become the model.",
],
),
explain(
"A property draft is not yet the model",
"Apply changes validates the candidate. When it is rejected, correct the retained draft or use Revert. The canvas continues to show accepted values. Navigation that needs to apply an invalid draft may remain on the current record so your work is not lost. You can read this guide without applying or discarding that draft.",
),
explain(
"Aliases and references",
"Stable source aliases must follow the language's identifier rules and avoid reserved words. Use a simple descriptive identifier such as assess_incident. Spaces belong in the displayed Name, not the alias. Reference pickers offer compatible model records; creating the required target first is often the missing step.",
),
explain(
"Undo and recover deliberately",
"Ctrl+Z and redo restore accepted model changes. While the source editor is active, they operate on the source draft instead. Revert restores the accepted source or properties. If a file changed externally, save refuses a blind overwrite: preserve your work with Save as, then compare the files.",
),
explain(
"Recover an interrupted source save",
"If a staged multi-file save was interrupted, Textual model offers Recover interrupted save. Recovery checks the journal and file contents before restoring originals; it does not overwrite unrelated external changes. This is recovery of a save transaction, not periodic autosave of unsaved edits.",
),
explain(
"Save work before leaving",
"New, Open, loading the example and Close use the unsaved-work prompt. Save and continue, discard or cancel is your decision. There is no autosave recovery journal in the current desktop; save meaningful milestones and use version control or backups.",
),
],
related: &[Topic::Source, Topic::Files, Topic::Transitions],
destination: Some((Page::Validation, "Open Model checks")),
}
}
}
Topic::Files => {
const {
Article {
summary: "Keep editable model files and exported pictures distinct. Save the model to retain its semantics, identities and traceability.",
keywords: "file open save save as keyboard shortcuts ctrl control shift export svg json arcadia backup external changes migration",
sections: &[
explain(
"Editable formats",
".arcadia is editable text suitable for Git and can include other source files. .json is the typed model exchange format. Both preserve the persistent model, diagrams and traceability; only a source project preserves its comments and file organization. Export SVG creates a diagram image for reports; it is not an editable model backup.",
),
example(
"Keyboard reference",
"Shortcuts act on the current project. In Textual model, undo and redo affect the source draft.",
"Ctrl+N New project\nCtrl+O Open model\nCtrl+S Save project\nCtrl+Shift+S Save as\nCtrl+Z Undo\nCtrl+Shift+Z Redo\nCtrl+Y Redo\nEscape Cancel the active dialog",
),
walkthrough(
"Save and share a model",
&[
"Use Save all to apply valid drafts and save the source project to the current path. For a new project, select a destination and format in the file dialog.",
"Use Save as for a new path or explicit format conversion. Check the filename extension when selecting text or JSON.",
"After saving, check All changes saved. Share or commit the entry and every included source file, together with the necessary external evidence locations.",
"If another tool changed or deleted the open file, preserve your candidate using a new Save as destination. Compare and reconcile the versions before replacing either one.",
],
),
explain(
"Open and migration",
"Open reads and validates a complete candidate before replacing the current project. Supported legacy files are migrated explicitly; save the resulting current format under a new name if the legacy original must remain available. Unsupported or invalid input does not replace the accepted project.",
),
],
related: &[Topic::Source, Topic::Recovery, Topic::Artifacts],
destination: None,
}
}
}
Topic::Server => {
const {
Article {
summary: "The Rust REST service gives scripts and engineering-tool plugins access to the same model, commands, validation and source representation.",
keywords: "server rest api automation ai plugin curl http etag if-match revision resource_id deploy hosted cad scripting openapi token",
sections: &[
example(
"Start a local service",
"Run the separate arcadia-server binary from a terminal. Its default address is loopback 127.0.0.1:8787. docs/server.md describes storage, authentication and Linux deployment.",
"arcadia-server --data-dir ./arcadia-models\n\nGET http://127.0.0.1:8787/health\nGET http://127.0.0.1:8787/api/v1/openapi.json",
),
walkthrough(
"Build a safe editing client",
&[
"Create or import a project using /api/v1/projects or /api/v1/projects/import. Keep the returned service resource_id; it is distinct from the editable model UUID.",
"Read the project's envelope, source or model. Preserve its exact ETag as the precondition for your edit.",
"Submit typed commands to /transactions, or a complete candidate to /source or /model, using If-Match. Use transaction dry-run or validation before committing substantial generated changes.",
"A stale write returns 412. Keep the user's draft, reload the current head and reconcile deliberately. After an uncertain write outcome, read the server state before retrying.",
"Query /artifact-links and /traces to connect domain-tool artifacts to model records. The server stores these references; the integration is responsible for resolving its external artifacts.",
],
),
explain(
"Desktop files and hosted models",
"The desktop currently edits local model files. To exchange with the service, export its /source or /model response and open that file; upload a reviewed candidate through the API with its original revision precondition. The desktop does not maintain a live hosted editing session.",
),
explain(
"Deployment and persistence",
"Non-loopback binding requires a configured bearer token. Use the documented local-disk storage and deployment configuration. The service keeps the current revision and serializes accepted updates; it does not provide retained revision history or server-side undo. Stop the service cleanly before copying its committed store for backup.",
),
explain(
"Use the precise contract",
"docs/api.rst and docs/openapi.json define routes, command shapes, limits and errors. The running server also serves OpenAPI. No network request or service mutation is triggered by reading this guide.",
),
],
related: &[
Topic::Artifacts,
Topic::Source,
Topic::Transitions,
Topic::Recovery,
],
destination: None,
}
}
}
}
}
/// Help search is independent of model search. All query words must occur in
/// the article, title or retrieval keywords; order and case are insignificant.
fn search(query: &str) -> Vec<Topic> {
let query = query.to_lowercase();
let words = query.split_whitespace().collect::<Vec<_>>();
Topic::ALL
.into_iter()
.filter(|topic| {
let article = article(*topic);
let mut haystack =
format!("{} {} {}", topic.title(), article.summary, article.keywords);
for section in article.sections {
haystack.push_str(section.title);
haystack.push(' ');
haystack.push_str(section.body);
for step in section.steps {
haystack.push(' ');
haystack.push_str(step);
}
if let Some(code) = section.code {
haystack.push(' ');
haystack.push_str(code);
}
}
let haystack = haystack.to_lowercase();
words.iter().all(|word| haystack.contains(word))
})
.collect()
}
fn topic_button(topic: Topic, selected: bool) -> iced::widget::Button<'static, Message> {
button(text(topic.title()).size(13))
.padding([10, 11])
.width(Fill)
.style(if selected {
style::selected
} else {
style::nav
})
.on_press(Message::Guide(Event::Topic(topic)))
}
fn article_view(topic: Topic) -> Element<'static, Message> {
let article = article(topic);
let mut content = column![
text(topic.category()).size(11).color(style::MUTED),
text(topic.title()).size(25),
text(article.summary).size(15).color(style::MUTED),
]
.spacing(14)
.width(Fill);
for section in article.sections {
let mut body = column![text(section.title).size(18)]
.spacing(12)
.width(Fill);
if !section.body.is_empty() {
body = body.push(text(section.body).size(14));
}
for (index, step) in section.steps.iter().enumerate() {
body = body.push(
row![
text(format!("{:02}", index + 1))
.size(13)
.color(style::TEAL)
.width(28),
text(*step).size(14).width(Fill),
]
.spacing(9),
);
}
if let Some(code) = section.code {
body = body.push(
container(text(code).font(Font::MONOSPACE).size(12).width(Fill))
.padding(14)
.width(Fill)
.style(style::subtle),
);
}
content = content.push(container(body).padding(20).width(Fill).style(style::card));
}
if let Some((page, label)) = article.destination {
content = content.push(
button(text(label).size(13))
.padding([11, 15])
.on_press(Message::Page(page)),
);
}
if topic == Topic::FirstProject {
content = content.push(
button(text("Open coastal rescue example").size(13))
.padding([11, 15])
.style(style::nav)
.on_press(Message::Request(Pending::Demo)),
);
}
content = content.push(text("CONTINUE EXPLORING").size(11).color(style::MUTED));
for related in article.related {
content = content.push(topic_button(*related, false));
}
scrollable(content.padding(iced::Padding {
right: 12.,
bottom: 24.,
..Default::default()
}))
.width(Fill)
.height(Fill)
.into()
}
pub fn view(state: &State) -> Element<'_, Message> {
let matches = search(&state.query);
let mut navigation = column![].spacing(5).width(Fill);
let mut category = "";
for topic in &matches {
if category != topic.category() {
category = topic.category();
navigation = navigation.push(
container(text(category).size(10).color(style::MUTED)).padding(iced::Padding {
top: 12.,
bottom: 4.,
left: 8.,
right: 4.,
}),
);
}
navigation = navigation.push(topic_button(*topic, state.topic == *topic));
}
let topic_list = container(scrollable(navigation.padding(iced::Padding {
right: 8.,
..Default::default()
})))
.width(200)
.height(Fill);
let article: Element<'_, Message> = if matches.is_empty() {
container(column![
text("No help topics found").size(23),
text("Try a modeling concept such as allocation, scenario, data type, Git or API. Help search does not search your model.").size(14).color(style::MUTED),
button(text("Clear search").size(13)).padding([10, 14]).on_press(Message::Guide(Event::Search(String::new()))),
].spacing(16).max_width(520)).padding(24).width(Fill).into()
} else {
// A different article gets a fresh scroll state and starts at its title.
keyed_column([(state.topic, article_view(state.topic))])
.width(Fill)
.height(Fill)
.into()
};
column![
column![
text("Engineering guide").size(25),
text("Practical workflows, modeling concepts and reference · available offline")
.size(12)
.color(style::MUTED),
]
.spacing(5),
row![
text_input("Search help: allocation, scenario, Git…", &state.query)
.on_input(|query| Message::Guide(Event::Search(query)))
.padding(11)
.size(14)
.width(Fill),
button(text("Clear").size(12))
.padding(11)
.style(style::nav)
.on_press_maybe(
(!state.query.is_empty()).then(|| Message::Guide(Event::Search(String::new())))
),
]
.spacing(8),
text(format!("{} topics", matches.len()))
.size(11)
.color(style::MUTED),
row![topic_list, article].spacing(20).height(Fill),
]
.spacing(14)
.padding(22)
.width(Fill)
.height(Fill)
.into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn search_finds_concepts_in_walkthroughs_and_api_reference() {
assert!(search(" ALLOCATED rescue ").contains(&Topic::FirstProject));
assert!(search("sender lifeline").contains(&Topic::Scenarios));
assert!(search("If-Match 412").contains(&Topic::Server));
assert!(search("Ctrl+Shift+S").contains(&Topic::Files));
assert!(search("no-such-modeling-topic-987").is_empty());
assert_eq!(search(" \n\t "), Topic::ALL);
}
#[test]
fn filtering_and_related_navigation_keep_selection_visible() {
let mut state = State::default();
state.update(Event::Search("If-Match 412".into()));
assert_eq!(state.topic, Topic::Server);
state.update(Event::Topic(Topic::Nesting));
assert!(state.query.is_empty());
assert_eq!(state.topic, Topic::Nesting);
state.update(Event::Search("nonexistent-topic-987".into()));
assert_eq!(state.topic, Topic::Nesting);
state.update(Event::Search(String::new()));
assert!(search(&state.query).contains(&state.topic));
}
}