@baravak/risloo-profile-cli 4.70.0 → 4.77.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/create-profile/SKILL.md +1 -1
- package/.claude/skills/create-profile/SKILL.md +157 -34
- package/AGENTS.md +203 -29
- package/package.json +1 -1
- package/src/publish/json/profiles/NEO93.json +1 -1
- package/src/publish/json/profiles/NEO9Q.json +1 -1
- package/src/publish/json/profiles/NEO9V.json +1 -1
- package/src/samples/MCMI9A.js +530 -0
- package/src/samples/NEO93.js +6 -4
- package/src/samples/NEO9A.js +3 -1
- package/src/samples/NEO9Q.js +6 -4
- package/src/samples/NEO9V.js +8 -5
- package/views/profiles/MCMI9A_facets_partial.hbs +96 -0
- package/views/profiles/MCMI9A_main_partial.hbs +262 -0
- package/views/profiles/MCMI9A_noteworthy_partial.hbs +66 -0
- package/views/profiles/MCMI9A_vertical_label_partial.hbs +8 -0
- package/views/profiles/NEO_long_facets.hbs +1 -1
- package/views/profiles/NEO_main.hbs +14 -12
- package/views/profiles/NEO_short_facets.hbs +1 -1
- package/views/profiles/samples/BSSI93.hbs +2 -2
- package/views/profiles/samples/MCMI9A_1.hbs +3 -0
- package/views/profiles/samples/MCMI9A_2.hbs +3 -0
- package/views/profiles/samples/MCMI9A_3.hbs +3 -0
- package/views/profiles/samples/MCMI9A_4.hbs +3 -0
- package/views/profiles/samples/RSI93.hbs +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: create-profile
|
|
3
|
-
description: Build a new psychological profile (نیمرخ) or a new page/chart of one — or edit an existing profile's chart — from a Figma design into a JS controller
|
|
3
|
+
description: Build a new psychological profile (نیمرخ) or a new page/chart of one — or edit an existing profile's chart — from a Figma design into a sample JS controller and Handlebars SVG template. Use when the user wants to create, extend, or edit a profile; provides Figma SVG/HTML exports for a sample; or asks to wire a `score` dataset into a chart. Read AGENTS.md alongside this skill for project conventions, helper references, and reusable SVG guidance.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# create-profile (pointer)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: create-profile
|
|
3
|
-
description: Build a new psychological profile (نیمرخ) or a new page/chart of one — or edit an existing profile's chart — from a Figma design into a JS controller
|
|
3
|
+
description: Build a new psychological profile (نیمرخ) or a new page/chart of one — or edit an existing profile's chart — from a Figma design into a sample JS controller and Handlebars SVG template. Use when the user wants to create, extend, or edit a profile; provides Figma SVG, PNG, HTML, or handoff exports for a sample; or asks to wire a `score` dataset into a chart. Read AGENTS.md alongside this skill for project conventions, helper references, and reusable SVG guidance.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Create / edit a profile (نیمرخ)
|
|
@@ -11,70 +11,193 @@ This project renders psychological-test results into SVG/PNG profiles. A profile
|
|
|
11
11
|
|
|
12
12
|
You draw only the Figma **Chart** layer (the raw chart). The engine injects the header, sidebar and footer via the `{{#> layout}}` wrapper. **Never put header/sidebar/footer in the HBS.** The CLI emits a `raw` variant (chart only) and a full with-sidebar variant. (AGENTS.md › _What the HBS Draws_.)
|
|
13
13
|
|
|
14
|
-
## 1. Gather inputs
|
|
14
|
+
## 1. Gather inputs and assign authority
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Ask for the relevant inputs:
|
|
17
|
+
|
|
18
|
+
1. **Scoring source** — the engine that computes the `score` object. Its **language varies (PHP, Python, or Node.js)** and it usually lives in a sibling repo or service. Don't assume the language/path — ask. It tells you how each `score` key is computed and what flags mean.
|
|
17
19
|
2. **Scoring documentation** — the validity rules, thresholds, and level/norm descriptions (a file or pasted text).
|
|
18
|
-
3. **Profile SVG** — `x/<NAME>.svg`.
|
|
19
|
-
4. **Profile HTML
|
|
20
|
+
3. **Profile SVG** — `x/<NAME>.svg`.
|
|
21
|
+
4. **Profile HTML**, when available and authorized — `x/<NAME>.html`.
|
|
20
22
|
5. **Profile JSON** — `src/publish/json/profiles/<NAME>.json` (input dataset; its `score` object drives everything).
|
|
21
|
-
6. **Reference PNG** — the designer's rendered export of the **Chart** layer (same variant as the CLI `raw` output, *not* the full sidebar page).
|
|
23
|
+
6. **Reference PNG** — the designer's rendered export of the **Chart** layer (same variant as the CLI `raw` output, *not* the full sidebar page).
|
|
24
|
+
|
|
25
|
+
Then **ask for the designer's Figma comments** — thresholds, coefficients, conditional behavior, spacing rules, and other implementation notes. The Figma MCP / SVG / HTML do **not** contain comments, and these specs usually live only there. Don't start coding until you have them.
|
|
26
|
+
|
|
27
|
+
Do not select one design artifact and ignore the others. Establish an authority table for the current task:
|
|
28
|
+
|
|
29
|
+
- **PNG** — final visual acceptance target, including visible orientation, wrapping, cropping, alignment, and composition.
|
|
30
|
+
- **SVG** — exact vector geometry, coordinates, colors, gradients, filters, clipping, strokes, radii, and icon paths.
|
|
31
|
+
- **HTML** — optional text-content and semantic-order aid only when the user authorizes its use.
|
|
32
|
+
- **Designer notes** — conditional behavior, thresholds, spacing rules, exceptions, and presentation states.
|
|
33
|
+
- **Scoring source** — score keys, valid raw values, report states, thresholds, sorting, and correction logic.
|
|
34
|
+
|
|
35
|
+
If two authorized sources disagree, stop and surface the conflict. Do not silently choose one. Respect task-specific source restrictions; if the user excludes HTML or declares PNG authoritative for appearance, record and follow that decision.
|
|
36
|
+
|
|
37
|
+
## 2. Select reusable patterns
|
|
38
|
+
|
|
39
|
+
Search the repository for existing implementations that match the required chart structure, such as bar orientation, axis mapping, repeated rows, conditional panels, pagination, or RTL label layout.
|
|
40
|
+
|
|
41
|
+
Choose reusable patterns by structural similarity, not by a permanently designated reference profile. More than one existing implementation may be relevant.
|
|
22
42
|
|
|
23
|
-
|
|
43
|
+
Before reusing code:
|
|
24
44
|
|
|
25
|
-
|
|
45
|
+
1. Identify which behavior is genuinely shared.
|
|
46
|
+
2. List the design-specific differences.
|
|
47
|
+
3. Decide whether reuse belongs in a partial, controller utility, configuration object, or remains profile-local.
|
|
48
|
+
4. Keep profile-specific formulas, text, thresholds, dimensions, and palettes outside shared components.
|
|
26
49
|
|
|
27
|
-
|
|
50
|
+
Read every selected controller and template end to end before copying or extracting a pattern.
|
|
28
51
|
|
|
29
|
-
|
|
30
|
-
- Horizontal bars + validity boxes → `src/samples/FRHPT93.js` + `views/profiles/samples/FRHPT93_2.hbs`.
|
|
31
|
-
- A simple 5-row bar chart → `src/samples/BSCT93.js`.
|
|
32
|
-
- The most recent full example built with this skill → `src/samples/NEO93.js` + `views/profiles/samples/NEO93_1.hbs`.
|
|
52
|
+
## 3. Inventory the design and variations
|
|
33
53
|
|
|
34
|
-
|
|
54
|
+
Before implementation, create an inventory of:
|
|
35
55
|
|
|
36
|
-
|
|
56
|
+
- pages;
|
|
57
|
+
- presentation variants;
|
|
58
|
+
- data or scoring variants;
|
|
59
|
+
- repeated component types;
|
|
60
|
+
- optional components;
|
|
61
|
+
- variable-length content;
|
|
62
|
+
- numeric rendering thresholds; and
|
|
63
|
+
- every existing consumer of a partial that will be changed.
|
|
37
64
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
65
|
+
For each variation, record whether it changes data, geometry, text, palette, visibility, or only title metadata.
|
|
66
|
+
|
|
67
|
+
Use this inventory to define the controller context and the validation matrix. Do not encode assumptions from another profile.
|
|
68
|
+
|
|
69
|
+
Before writing controller or template code, freeze a per-page rendering contract:
|
|
70
|
+
|
|
71
|
+
| Field | Required value |
|
|
72
|
+
|---|---|
|
|
73
|
+
| Final Chart size | Exact width × height of the delivered raw chart |
|
|
74
|
+
| Logical design size | Upright working-space width × height |
|
|
75
|
+
| Orientation | Normal, 90° clockwise, 90° counter-clockwise, or 180° |
|
|
76
|
+
| Production transform | The single exact transform applied to the logical design |
|
|
77
|
+
| Layout-owned padding | Padding applied by the rendering layout |
|
|
78
|
+
| Chart-owned offset | An offset that visibly exists inside the Chart layer |
|
|
79
|
+
| Overflow policy | Content must fit, clip, or intentionally overflow |
|
|
80
|
+
| Axis mapping | Linear or piecewise score-to-position mapping |
|
|
81
|
+
|
|
82
|
+
Do not begin detailed drawing while any value in this contract is inferred or provisional. Treat every page independently; never assume that pages in one profile have the same dimensions, padding, orientation, or coordinate system.
|
|
83
|
+
|
|
84
|
+
## 4. Derive geometry from the SVG
|
|
85
|
+
|
|
86
|
+
- The Figma **Chart** layer = the SVG `viewBox` (e.g. `0 0 800 674`) and defines the HBS coordinate space.
|
|
87
|
+
- Measure padding independently for each page. `dimensions = Chart + 2·padding`, but padding is layout metadata; do not apply it again inside the HBS when the layout already owns it.
|
|
88
|
+
- Never introduce a conventional `translate(20,20)`, arbitrary centering transform, or scale. Add an inner transform only when it visibly belongs to the Chart layer and is measured from an authorized source.
|
|
89
|
+
- If most elements share one rotation, author the page in an upright logical coordinate system and rotate one outer `<g>` into the final Chart coordinate system. Keep ordinary text, bars, cards, icons, multiline labels, and alignment components unrotated inside that logical space. Use local rotation only for genuine exceptions.
|
|
90
|
+
- For a final `W × H` chart rendered by rotating an upright `H × W` design clockwise, use the exact equivalent of `<g transform="translate(W 0) rotate(90)">`. Never use scaling to make a rotated design fit.
|
|
41
91
|
- **Transcribe verbatim**, in the SVG's element order, the static parts: gridlines (`line`), tracks/boxes (`rect`), `linearGradient`, `filter`, icon `path`s. Draw order = z-order — keep it (e.g. a shadowed baseline drawn *after* the bars sits in front).
|
|
42
92
|
- **Reconstruct** every text run as a real `<text>` at the SVG's x/y — Figma exports text as outlined `<path>`, never `<text>` (AGENTS.md › Gotchas).
|
|
43
|
-
- Check the **axis**: if gridline positions look
|
|
44
|
-
- **Alignment is the whole job.** This is pixel-precise infographic work — measure exact x/y
|
|
93
|
+
- Check the **axis**: if gridline positions look irregular, derive either the linear map or every piecewise segment from labeled score/position pairs. A score exactly on a breakpoint must land exactly on its gridline.
|
|
94
|
+
- **Alignment is the whole job.** This is pixel-precise infographic work — measure exact x/y from the SVG; never estimate. Determine whether each spacing specification refers to component bounds, a text layout box, glyph bounds, a baseline, or visible pixels. Get every label's horizontal alignment (`text-anchor`, RTL-aware) and vertical alignment (row mid-line, shared baseline) right. If the chart sits in an offset frame, transcribe its `translate` exactly. See AGENTS.md › _Alignment and text measurement_.
|
|
95
|
+
|
|
96
|
+
Before drawing repeated components, build a geometry and style ledger containing:
|
|
45
97
|
|
|
46
|
-
|
|
98
|
+
- bar origin, direction, width, height, and rounded corners;
|
|
99
|
+
- gridline and breakpoint coordinates;
|
|
100
|
+
- invisible text-box dimensions;
|
|
101
|
+
- code-box, dash, and title gaps;
|
|
102
|
+
- text alignment and writing direction;
|
|
103
|
+
- multiline block center;
|
|
104
|
+
- inside/outside score threshold and gap;
|
|
105
|
+
- exact palette tokens for every component state;
|
|
106
|
+
- font class for Persian text, English codes, and numeric values; and
|
|
107
|
+
- icon source path and dimensions.
|
|
108
|
+
|
|
109
|
+
Identify text alignment families before assigning individual coordinates. Record whether each text or number belongs to a shared row, column, baseline, or guide; which edge or center aligns to that guide; its layout-box dimensions; `text-anchor`; writing direction; and behavior after page rotation. Derive family members from the shared guide instead of nudging them independently. Verify the shortest, longest, and an intermediate member.
|
|
110
|
+
|
|
111
|
+
## 5. Wire the data
|
|
47
112
|
|
|
48
113
|
- In the controller, define `labels` so each entry's `eng` matches a `score` key; `Object.values(this.labels)` feeds `Dataset._extractData`, producing `dataset.score = [{ label, mark }]`.
|
|
49
114
|
- In `_calcContext()`, read **every** mark with `?? 0` (AGENTS.md › `?? 0` rule) — `_extractData` turns a `0` into `undefined`. Compute per-item geometry (bar width / percentage, text x + anchor) and any validity indicators.
|
|
50
115
|
- Confirm ambiguous encodings (validity flags `1`=valid vs invalid, reversed answer scales, norm-based levels) against the **scoring source** (PHP/Python/Node.js) — never guess.
|
|
51
116
|
|
|
52
|
-
##
|
|
117
|
+
## 6. Write the controller + template
|
|
53
118
|
|
|
54
119
|
- **Controller**: `labels`; `profileSpec` (`sample`, `profile.dimensions`/`padding`, `labels: Object.values(this.labels)`); `constructor` calling `this._init(...)`; `_calcContext()` returning one context object per page.
|
|
55
|
-
- **Template**: `{{#> layout}}` → `<defs>` (gradients/clips/filters) →
|
|
120
|
+
- **Template**: `{{#> layout}}` → `<defs>` (gradients/clips/filters) → optional measured global-orientation or Chart-owned transform → static structure + `{{#each items}}` rows + conditional boxes → `{{/layout}}`.
|
|
56
121
|
- Use the helpers from AGENTS.md › _Helper Reference_ (`bar`, `math`, `boolean`, `ternary`, `object`, `toRad`). Apply the gotcha checklist below.
|
|
57
122
|
|
|
58
|
-
|
|
123
|
+
When extracting a partial:
|
|
124
|
+
|
|
125
|
+
- define a small explicit input contract;
|
|
126
|
+
- pass semantic colors and geometry anchors;
|
|
127
|
+
- derive dependent coordinates from those anchors;
|
|
128
|
+
- keep scoring and profile-specific decisions in the controller;
|
|
129
|
+
- namespace or parameterize `<defs>` IDs; and
|
|
130
|
+
- render every existing consumer after changing the partial.
|
|
131
|
+
|
|
132
|
+
Do not create a partial solely because markup looks similar. Extract it only when the components share the same behavioral and geometric contract.
|
|
133
|
+
|
|
134
|
+
Implement through three visual gates:
|
|
135
|
+
|
|
136
|
+
1. **Frame gate** — render only the page frame, major groups, axes, baselines, and representative tracks. Confirm dimensions, origin, orientation, and the global transform.
|
|
137
|
+
2. **Component gate** — complete one representative bar, one label cluster, one card, one multiline label, and one conditional state. Compare them against the PNG and exact SVG coordinates.
|
|
138
|
+
3. **Repetition gate** — expand verified components through loops and partials.
|
|
139
|
+
|
|
140
|
+
Do not build the whole page before validating the frame and one representative instance of each component contract.
|
|
141
|
+
|
|
142
|
+
## 7. Multi-page and variants
|
|
59
143
|
|
|
60
144
|
- Set `static pages = N`; build `<NAME>_1.hbs … <NAME>_N.hbs`. Create blank `{{#> layout}} … {{/layout}}` placeholders for pages not built yet — **a missing template file errors the whole run**.
|
|
61
|
-
- Make `profile.dimensions` / `profile.padding` arrays (indexed `page-1` by `layout.hbs`)
|
|
145
|
+
- Make `profile.dimensions` / `profile.padding` arrays (indexed `page-1` by `layout.hbs`).
|
|
146
|
+
- Treat pages and presentation variants as independent output dimensions. Enumerate the supported combinations before implementation.
|
|
147
|
+
- Give each page context the required `titleAppend`. Build it from semantic page and variant metadata rather than template filenames.
|
|
148
|
+
- Keep scoring and geometry identical across presentation variants unless the specification explicitly requires a difference.
|
|
62
149
|
|
|
63
|
-
##
|
|
150
|
+
## 8. Verify
|
|
64
151
|
|
|
65
152
|
- Render: `./bin/risloo.js E <NAME> -d ./src/publish/json/profiles/<NAME>.json -a ./temp`
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
153
|
+
- Build scratch JSONs for the verification matrix, render them with `-d <scratch>.json`, and clean them up afterward.
|
|
154
|
+
|
|
155
|
+
Derive the verification matrix from the profile's own variability axes:
|
|
156
|
+
|
|
157
|
+
1. Render every page.
|
|
158
|
+
2. Render every supported presentation variant.
|
|
159
|
+
3. Exercise each independent conditional component in visible and hidden states.
|
|
160
|
+
4. Exercise supported content cardinalities where layout depends on item count.
|
|
161
|
+
5. For every rendering threshold, test the nearest valid values below, at, and above it.
|
|
162
|
+
6. For every piecewise axis, test every breakpoint and the nearest valid value on both sides.
|
|
163
|
+
7. Cover minimum, maximum, missing, and clamped values where applicable.
|
|
164
|
+
8. Render both raw and full output when injected layout or title metadata is involved.
|
|
165
|
+
9. After changing a shared component, render all its consumers.
|
|
166
|
+
|
|
167
|
+
Build a coverage matrix, not an automatic Cartesian product. One fixture may cover several independent states. Use a full Cartesian matrix only when the states interact visually or geometrically; otherwise use focused or pairwise cases that still exercise every behavior. Name each fixture by the states it exercises, and use scoring-valid raw values from the scoring source. If a fixture is intentionally presentation-only and scoring-inconsistent, label it explicitly.
|
|
168
|
+
|
|
169
|
+
Verification has three separate gates:
|
|
170
|
+
|
|
171
|
+
1. **Semantic validation**
|
|
172
|
+
- Confirm expected texts and title suffixes exist.
|
|
173
|
+
- Confirm no `undefined`, `NaN`, invalid dimensions, or broken references exist.
|
|
174
|
+
- Confirm referenced gradients, clips, masks, and filters resolve.
|
|
175
|
+
2. **Structural SVG validation**
|
|
176
|
+
- Confirm coordinates, transforms, clipping, filter bounds, radii, strokes, and paint order match the authoritative SVG.
|
|
177
|
+
3. **Visual validation**
|
|
178
|
+
- Compare raw output with the raw design reference using identical data.
|
|
179
|
+
- Inspect every page and variant.
|
|
180
|
+
- Use the PNG to locate discrepancies, then use SVG measurements to determine the correction.
|
|
181
|
+
|
|
182
|
+
Passing one gate does not replace the others. Do not claim a visual issue is fixed from code inspection or a successful render command alone. Close by iterating render → open the actual PNG → compare → measure → fix until the raw output matches the reference, then inspect the full output for injected layout and title metadata. If local coordinate patches accumulate, stop and re-establish the coordinate model instead of continuing to nudge individual elements.
|
|
69
183
|
|
|
70
184
|
## Gotcha checklist (detail in AGENTS.md › Gotchas)
|
|
71
185
|
|
|
72
|
-
- [ ]
|
|
186
|
+
- [ ] Globally rotated pages authored upright and transformed by one exact outer group; no per-element rotation unless genuinely exceptional.
|
|
187
|
+
- [ ] No generic padding translation or scale inside the HBS.
|
|
188
|
+
- [ ] RTL/LTR `direction` and `text-anchor` derived together from the intended visual edge and growth direction.
|
|
73
189
|
- [ ] `?? 0` on every mark read in `_calcContext`.
|
|
74
190
|
- [ ] Text reconstructed as `<text>`, not transcribed glyph paths.
|
|
75
|
-
- [ ]
|
|
191
|
+
- [ ] English font class used only for semantic Latin identifiers; score and report numbers retain the intended numeric font.
|
|
192
|
+
- [ ] Mixed-direction tokens such as percentages visually rechecked after changing spacing or direction.
|
|
76
193
|
- [ ] Elements emitted in Figma-SVG order (draw order = z-order).
|
|
77
|
-
- [ ] Bars drawn on the same scale as the
|
|
78
|
-
- [ ]
|
|
194
|
+
- [ ] Bars drawn on the same linear or piecewise scale as the axis labels, with exact breakpoint tests.
|
|
195
|
+
- [ ] Text families derived from shared rows, columns, baselines, or guides and tested with different content lengths.
|
|
196
|
+
- [ ] Shared-partial geometry derived from explicit anchors; semantic variant inputs passed from the controller.
|
|
197
|
+
- [ ] Conditional components follow the design's reserve/collapse/reflow behavior for every independent condition.
|
|
198
|
+
- [ ] `<defs>` geometry, filter bounds and primitives, clips, masks, borders, radii, shadows, and paint order verified exactly.
|
|
199
|
+
- [ ] Numeric rendering boundaries tested below, at, and above the boundary with valid domain values.
|
|
200
|
+
- [ ] Every page, presentation variant, and affected shared-partial consumer rendered.
|
|
201
|
+
- [ ] `titleAppend` verified in full output whenever page or variant metadata requires it.
|
|
79
202
|
- [ ] No header/sidebar/footer in the HBS — only the Chart layer.
|
|
80
|
-
- [ ] Closed with
|
|
203
|
+
- [ ] Closed with semantic, structural SVG, and visual checks against the reference using identical data.
|
package/AGENTS.md
CHANGED
|
@@ -32,7 +32,7 @@ risloo-extractor-app/
|
|
|
32
32
|
│ │ └── utilities/ # BaseOps, Benchmarker, Response, Errors, Status codes
|
|
33
33
|
│ ├── Profile.js # Base Profile class (math utilities)
|
|
34
34
|
│ ├── Gift.js # Gift card class
|
|
35
|
-
│ ├── samples/ # JS controller files — one per profile
|
|
35
|
+
│ ├── samples/ # JS controller files — one per profile
|
|
36
36
|
│ ├── handlebars/
|
|
37
37
|
│ │ ├── init.js # Handlebars initialization
|
|
38
38
|
│ │ ├── helpers.js # Entry point for all helpers
|
|
@@ -42,7 +42,7 @@ risloo-extractor-app/
|
|
|
42
42
|
│ ├── helpers/ # Math helpers (angleABS, polarXY, gauge, polygonXY)
|
|
43
43
|
│ ├── qrcode/ # QR code generation & rendering
|
|
44
44
|
│ └── publish/
|
|
45
|
-
│ ├── json/profiles/ # Template JSON per sample
|
|
45
|
+
│ ├── json/profiles/ # Template JSON per sample
|
|
46
46
|
│ ├── json/gift/ # Gift template data
|
|
47
47
|
│ ├── test.js # Auto-test all samples
|
|
48
48
|
│ └── bot.js # Post-publish automation
|
|
@@ -74,15 +74,25 @@ npm test # → node ./src/publish/test.js
|
|
|
74
74
|
|
|
75
75
|
## Profile Development Workflow (Figma → Code)
|
|
76
76
|
|
|
77
|
-
### Design
|
|
77
|
+
### Design Sources and Authority
|
|
78
78
|
|
|
79
79
|
Each profile is designed in Figma. Per task, one or both of the following may be provided:
|
|
80
80
|
|
|
81
81
|
- **Figma design file** — connect via Figma MCP to read layer dimensions and structure. The relevant layer is named **Chart**.
|
|
82
|
-
- **Figma handoff file** — contains additional implementation notes and specs.
|
|
82
|
+
- **Figma handoff file** — contains additional implementation notes and specs. It may state that this profile is similar to an existing one. In that case, inspect the referenced profile's JS + HBS files, identify the genuinely shared behavior, and apply only the described differences.
|
|
83
83
|
|
|
84
84
|
> **Important:** The Figma MCP reads text layers only — it does NOT read Figma comments. Designers often leave critical specs (thresholds, coefficients, pixel values) as Figma comments. Always ask the user to share designer comments before finalizing the plan.
|
|
85
85
|
|
|
86
|
+
Assign authority by source role instead of selecting one artifact and ignoring the others:
|
|
87
|
+
|
|
88
|
+
- **PNG** — final visual acceptance target, including visible orientation, wrapping, cropping, alignment, and composition.
|
|
89
|
+
- **SVG** — exact vector geometry, coordinates, colors, gradients, filters, clipping, strokes, radii, and icon paths.
|
|
90
|
+
- **HTML** — optional text-content and semantic-order aid only when the user authorizes its use.
|
|
91
|
+
- **Designer notes** — conditional behavior, thresholds, spacing rules, exceptions, and presentation states.
|
|
92
|
+
- **Scoring source** — score keys, valid raw values, report states, thresholds, sorting, and correction logic.
|
|
93
|
+
|
|
94
|
+
Respect task-specific source restrictions. If two authorized sources disagree, stop and surface the conflict instead of silently choosing one.
|
|
95
|
+
|
|
86
96
|
### What the HBS Draws
|
|
87
97
|
|
|
88
98
|
The HBS file draws **only the Chart layer** — i.e. the **raw** chart. The engine produces the final page by automatically injecting the header (test name, dates, client info), the sidebar (logo, room info, prerequisites) and the footer/closing info via the `{{#> layout}}` wrapper. Never include any of these in the HBS. (The CLI emits both a `raw` variant — chart only, e.g. `<NAME>.raw.svg/png` — and the full with-sidebar variant `<NAME>.svg/png`.)
|
|
@@ -97,18 +107,47 @@ Since this is SVG, coordinate origin matters for correctness across **all** inpu
|
|
|
97
107
|
- Vertical total bar fills bottom → up via the transform trick: `translate(0, barHeight - barHeight * p)`
|
|
98
108
|
- All positions must remain stable for edge-case inputs: 0% score, 100% score, missing data
|
|
99
109
|
|
|
100
|
-
###
|
|
110
|
+
### Global Page Orientation
|
|
111
|
+
|
|
112
|
+
Global rotation is a coordinate-system decision. When most elements on a page share one rotation, author the page in an upright logical coordinate system and rotate one outer group into the final Chart space.
|
|
113
|
+
|
|
114
|
+
For a final `W × H` chart rendered by rotating an upright `H × W` design clockwise:
|
|
115
|
+
|
|
116
|
+
```svg
|
|
117
|
+
<g transform="translate(W 0) rotate(90)">
|
|
118
|
+
<!-- author regular geometry here -->
|
|
119
|
+
</g>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The coordinate mapping is:
|
|
101
123
|
|
|
102
|
-
|
|
124
|
+
```text
|
|
125
|
+
finalX = W - logicalY
|
|
126
|
+
finalY = logicalX
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
and its inverse is:
|
|
103
130
|
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{{else}}
|
|
108
|
-
<text x="{{math (math BAR_WIDTH '*' factor.p) '-' 4}}" ...>{{factor.percentage}} ٪</text>
|
|
109
|
-
{{/if}}
|
|
131
|
+
```text
|
|
132
|
+
logicalX = finalY
|
|
133
|
+
logicalY = W - finalX
|
|
110
134
|
```
|
|
111
135
|
|
|
136
|
+
Use this conversion systematically when measurements come from an already rotated SVG. Do not mentally swap `x` and `y` for individual nodes. Keep ordinary text, bars, cards, icons, multiline labels, and alignment components unrotated inside the logical space; apply local rotation only to genuine exceptions. Never use scaling to make a rotated design fit.
|
|
137
|
+
|
|
138
|
+
### Bar Geometry and Inside/Outside Text
|
|
139
|
+
|
|
140
|
+
Use `{{bar ...}}` when only the terminal corners are rounded. Do not replace a partially rounded bar with `<rect rx="...">`, which rounds all four corners.
|
|
141
|
+
|
|
142
|
+
Treat the bar end as the center of a two-sided safe zone:
|
|
143
|
+
|
|
144
|
+
- **Inside label** — anchor at `barEnd - gap` and let the text grow opposite the fill direction.
|
|
145
|
+
- **Outside label** — anchor at `barEnd + gap` and let the text grow in the fill direction.
|
|
146
|
+
|
|
147
|
+
Choose `text-anchor` from the actual writing direction and coordinate system. Do not preserve an anchor after changing `direction` or moving a label between rotated and unrotated spaces.
|
|
148
|
+
|
|
149
|
+
Inside/outside thresholds are presentation rules and may differ between chart groups. Never reuse one global threshold unless the design explicitly defines one. Verify zero-width, narrow, threshold, maximum, and clamped bars.
|
|
150
|
+
|
|
112
151
|
### File Structure per Profile
|
|
113
152
|
|
|
114
153
|
Each profile (نیمرخ) consists of three files:
|
|
@@ -128,12 +167,9 @@ Multi-page mechanics:
|
|
|
128
167
|
|
|
129
168
|
### Dimensions & Padding Convention
|
|
130
169
|
|
|
131
|
-
The
|
|
170
|
+
The HBS owns exactly the raw Chart coordinate space. Its origin is the Chart layer's `(0, 0)`, and all authored content must remain within the declared Chart width and height unless the design explicitly clips or overflows it.
|
|
132
171
|
|
|
133
|
-
|
|
134
|
-
- `profile.padding` = 20 units per side
|
|
135
|
-
- The `x` and `y` values (drawing area) = Main dimensions minus one padding unit each:
|
|
136
|
-
- e.g. Main = 104×255 → `{ x: 84, y: 235 }`
|
|
172
|
+
There is no universal profile padding. Measure padding independently for every page.
|
|
137
173
|
|
|
138
174
|
The `dimensions` property in the JS controller adds padding back on both sides of the **Chart** layer dimensions:
|
|
139
175
|
|
|
@@ -146,6 +182,20 @@ get dimensions() {
|
|
|
146
182
|
},
|
|
147
183
|
```
|
|
148
184
|
|
|
185
|
+
Padding is layout metadata. Do not apply the same padding again inside the HBS when the layout already owns it.
|
|
186
|
+
|
|
187
|
+
Never introduce a conventional `translate(20,20)`, arbitrary centering transform, or scale. Add an inner translation only when that offset visibly belongs to the Chart layer and is measured from an authorized source.
|
|
188
|
+
|
|
189
|
+
Fix geometry at the layer that owns the error:
|
|
190
|
+
|
|
191
|
+
1. Chart dimensions
|
|
192
|
+
2. Global orientation transform
|
|
193
|
+
3. Major group anchor
|
|
194
|
+
4. Repeated component geometry
|
|
195
|
+
5. Individual text placement
|
|
196
|
+
|
|
197
|
+
Do not alter a lower layer to compensate for an error in a higher layer.
|
|
198
|
+
|
|
149
199
|
### Data / Labels Convention
|
|
150
200
|
|
|
151
201
|
All data needed for rendering goes in the `labels` property of the JS controller.
|
|
@@ -162,7 +212,7 @@ get labels() {
|
|
|
162
212
|
}
|
|
163
213
|
```
|
|
164
214
|
|
|
165
|
-
**Where the `score` values come from.** The JSON `score` object fed to a profile is produced by a separate **scoring engine** whose language **varies per test — it may be PHP, Python, or Node.js** — and which usually lives in a sibling repo
|
|
215
|
+
**Where the `score` values come from.** The JSON `score` object fed to a profile is produced by a separate **scoring engine** whose language **varies per test — it may be PHP, Python, or Node.js** — and which usually lives in a sibling repo or service. **Always ask the user for the actual scoring-source file** rather than assuming the language/path. When a score key's meaning is ambiguous — especially validity flags (is `1` valid or invalid?) — read that source to confirm the encoding instead of guessing; the scoring source also defines the level thresholds (norms) and per-item (reverse) scoring. Each `score` key is matched to a label via `label.eng`.
|
|
166
216
|
|
|
167
217
|
**The `?? 0` rule.** `Dataset._extractData` (in `src/Profile.js`) maps `mark: score[label.eng] || …`, so a score of `0` (or any falsy value) falls through to `undefined`. Always read marks with `?? 0` in `_calcContext` to avoid `NaN` widths/positions.
|
|
168
218
|
|
|
@@ -187,18 +237,142 @@ Common helpers used in profile templates (defined under `src/handlebars/helpers/
|
|
|
187
237
|
|
|
188
238
|
Hard-won knowledge that applies to **every** profile:
|
|
189
239
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
240
|
+
### Alignment and text measurement
|
|
241
|
+
|
|
242
|
+
- **Alignment is the whole job — measure, never estimate.** These are pixel-precise **infographics** built from SVG, so every element must line up with the design both horizontally and vertically. Pull exact coordinates from the SVG instead of eyeballing them. Decide per label whether it is left-, center-, or right-aligned, and center row text on the row mid-line with a shared baseline. If the chart lives inside an offset frame, transcribe that `translate` exactly.
|
|
243
|
+
- Identify what a design spacing measurement refers to before implementing it: component bounds, text layout box, glyph bounds, baseline, or visible painted pixels. These are not interchangeable.
|
|
244
|
+
- Measure the two relevant edges from the authoritative SVG. Do not compensate for a spacing error by moving unrelated elements.
|
|
245
|
+
- When several labels share a relationship with a chart origin, derive all their positions from that origin so alternate dimensions remain aligned automatically.
|
|
246
|
+
|
|
247
|
+
Before placing individual `<text>` nodes, identify alignment families: axis values, raw scores, codes, dashes, Persian titles, card values, section headings, and bar labels often share a row, column, baseline, or guide. For each family record:
|
|
248
|
+
|
|
249
|
+
| Field | Meaning |
|
|
250
|
+
|---|---|
|
|
251
|
+
| `axis` | Whether the guide lies on `x` or `y` |
|
|
252
|
+
| `coordinate` | Exact guide coordinate |
|
|
253
|
+
| `alignedPart` | Start edge, end edge, center, or baseline |
|
|
254
|
+
| `layoutBox` | Invisible text-box dimensions |
|
|
255
|
+
| `anchor` | `text-anchor` |
|
|
256
|
+
| `direction` | Actual writing direction |
|
|
257
|
+
| `baseline` | Shared baseline or vertical center |
|
|
258
|
+
|
|
259
|
+
Members of one family do not necessarily use `text-anchor="middle"`. Determine what the design aligns:
|
|
260
|
+
|
|
261
|
+
- If axis numbers end on a gridline, align the text end rather than its center.
|
|
262
|
+
- If codes occupy equal invisible boxes, align the boxes first and align text within each box.
|
|
263
|
+
- If titles begin after a dash, derive their start edge from the shared dash guide.
|
|
264
|
+
- For values of different lengths, align the specified edge or layout box rather than visible glyph centers.
|
|
265
|
+
- For texts in one row, share a baseline or vertical center instead of merely assigning similar `y` values.
|
|
266
|
+
|
|
267
|
+
Identify these families in the upright logical space for globally rotated pages. A logical column may appear as a final horizontal row after rotation. Verify each family with its shortest, longest, and an intermediate member.
|
|
268
|
+
|
|
269
|
+
When a design specifies an invisible code box, treat it as real geometry. Record its origin and dimensions, text alignment, chart-to-box gap, box-to-dash gap, dash-to-title gap, and shared row baseline. Measure gaps from component bounds, not visible glyph edges. Center a multiline label as one block, not as independently positioned lines. Extract a partial when this full contract repeats.
|
|
270
|
+
|
|
271
|
+
### RTL alignment
|
|
272
|
+
|
|
273
|
+
- The root SVG sets `direction="rtl"` in `views/profiles/layout.hbs`, which affects horizontal text flow and the advance direction of rotated labels.
|
|
274
|
+
- Under RTL, `text-anchor="start"` puts the right edge at `x` and grows text leftward; `text-anchor="end"` puts the left edge at `x` and grows text rightward. Derive `direction` and `text-anchor` together from the intended aligned edge and growth direction; do not reuse an anchor after changing direction.
|
|
275
|
+
- A `<text transform="rotate(-90,0,0) translate(tx,ty)">` advances downward on screen under the inherited RTL direction. Its baseline x is `<group-x> + ty`, and its top edge is `<group-y> − tx`. Measure the intended top and baseline from the SVG rather than assuming the text advances upward.
|
|
276
|
+
|
|
277
|
+
### Font and bidirectional numeric tokens
|
|
278
|
+
|
|
279
|
+
- Apply the English font class only to semantic Latin identifiers such as codes and abbreviations, including digits embedded in those identifiers.
|
|
280
|
+
- Do not apply the English font class to raw scores, BR values, percentages, axis values, or report numbers unless the design explicitly requests it. Those values should normally remain in the profile's Persian numeric font.
|
|
281
|
+
- Treat mixed-direction tokens such as numbers with percent signs as one visual component. Set `direction`, `unicode-bidi`, anchors, and spacing explicitly; do not rely on source-string order under inherited RTL.
|
|
282
|
+
- After adding whitespace to a mixed-direction token, re-render it. Bidi reordering can change both visual order and anchor behavior.
|
|
283
|
+
|
|
284
|
+
### Source fidelity and rendering rules
|
|
285
|
+
|
|
194
286
|
- **`?? 0` on every mark.** See _Data / Labels Convention_ — `_extractData` turns a `0` mark into `undefined`; always guard with `?? 0`.
|
|
195
|
-
- **Figma SVG = outlined paths, not text.**
|
|
196
|
-
- **
|
|
287
|
+
- **Figma SVG = outlined paths, not text.** Do not transcribe glyph paths. Reconstruct text with real `<text>` elements and obtain content from an authorized semantic source such as designer notes, JSON/scoring labels, or HTML when its use is allowed.
|
|
288
|
+
- **Authority is field-specific.** Use the source-role contract above instead of treating one artifact as authoritative for every concern. Inspect every authorized source and surface conflicts.
|
|
197
289
|
- **Persian digits come from the font.** Emit Western digits in templates (`{{item.mark}}`, `از 192`, `50 ٪`); the `DanaFaNum` font shapes them to Persian. Never hand-convert.
|
|
198
|
-
- **Draw order = z-order.** Later elements paint on top.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
290
|
+
- **Draw order = z-order.** Later elements paint on top. Preserve the Figma SVG's element order.
|
|
291
|
+
|
|
292
|
+
### Piecewise score axes
|
|
293
|
+
|
|
294
|
+
Never assume that one coefficient covers a score axis's full domain. Represent the axis as ordered breakpoints or segments. For a segment from score `a` at position `p0` to score `b` at position `p1`:
|
|
295
|
+
|
|
296
|
+
```js
|
|
297
|
+
position = p0 + ((score - a) * (p1 - p0)) / (b - a);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
Select the segment containing the score and clamp only at the domain boundaries. A score exactly equal to a breakpoint must land exactly on that breakpoint's gridline. Verify every breakpoint and the nearest valid value on both sides.
|
|
301
|
+
|
|
302
|
+
### Exact palettes and source icons
|
|
303
|
+
|
|
304
|
+
- Create semantic palette tokens from exact SVG color values. Every related part of a stateful component—bar, track, label, card body, footer, divider, border, and icon—must derive from the selected theme unless an exception is documented.
|
|
305
|
+
- Do not substitute a visually similar hex value.
|
|
306
|
+
- Use the exact SVG path for design-specific arrows, alerts, and symbols. Do not replace them with Unicode characters, generic icon libraries, or hand-drawn approximations.
|
|
307
|
+
|
|
308
|
+
### Shared templates and partials
|
|
309
|
+
|
|
310
|
+
- Treat every partial as a parameterized visual component. Do not bake a specific profile's coordinates, palette, labels, thresholds, or variant rules into a shared partial.
|
|
311
|
+
- Express geometry relative to meaningful anchors such as `barX`, `barWidth`, row center, label edge, or component bounds. Avoid unrelated absolute coordinates when the component is shared.
|
|
312
|
+
- Pass visual differences through semantic properties such as `primary`, `accent`, `track`, `border`, `shadow`, and `labelOutside`; do not infer colors from profile names, row indexes, or variant labels.
|
|
313
|
+
- Before changing a shared partial, find every controller and template that consumes it. After the change, render all affected consumers, including at least one unchanged consumer as a regression check.
|
|
314
|
+
- A shared partial must have an explicit input contract: required values, defaults, coordinate system, conditional sections, and ownership of `<defs>` IDs.
|
|
315
|
+
|
|
316
|
+
### Conditional components
|
|
317
|
+
|
|
318
|
+
- For every conditional component, determine from the design whether hiding it preserves its reserved space, collapses its space, or causes another component to occupy its position.
|
|
319
|
+
- Do not assume independent conditions are mutually exclusive.
|
|
320
|
+
- For components containing a variable number of items, compute item positions and separators from the active items instead of maintaining fixed markup for anticipated combinations.
|
|
321
|
+
- Build validation cases from the component's actual independent conditions and supported item cardinalities. Create a coverage matrix rather than an automatic Cartesian product: one fixture may cover several independent states, and a full product is needed only when states interact visually or geometrically. Name each fixture by the states it exercises.
|
|
322
|
+
|
|
323
|
+
### SVG effects, clipping, borders, and shadows
|
|
324
|
+
|
|
325
|
+
- Treat `<defs>` as measurable design geometry, not approximate decoration. Reproduce and verify `filterUnits`; filter bounds (`x`, `y`, `width`, `height`); `dx` and `dy`; blur radius; flood color and opacity; primitive order and `in`/`result` wiring; clip/mask geometry; transform coordinate space; and final paint order.
|
|
326
|
+
- Verify both the direction and extent of every shadow against the reference PNG.
|
|
327
|
+
- When a rounded component contains fills or effects, use clipping where required and draw its final border after the clipped content. Confirm that radius, border thickness, corners, and shadow are visually independent.
|
|
328
|
+
- Use unique IDs for gradients, clips, masks, and filters when a partial can appear more than once in one document.
|
|
329
|
+
|
|
330
|
+
### Boundary-driven rendering tests
|
|
331
|
+
|
|
332
|
+
- Extract every rendering boundary from the design notes, scoring source, and controller logic.
|
|
333
|
+
- For each numeric boundary, render values immediately below, at, and immediately above it, using the nearest valid values for that domain.
|
|
334
|
+
- For every piecewise axis, test each breakpoint and the nearest valid value on both sides.
|
|
335
|
+
- Also cover the domain minimum, maximum, zero when valid, missing input, and out-of-range input if clamping is expected.
|
|
336
|
+
- Test the visual behavior, not only the computed value: bar width, clipping, label placement, anchor, contrast, and overflow.
|
|
337
|
+
- Test fixtures must be derived from the current profile's rules and use scoring-valid raw values. Fixed example values must not become global conventions. Label intentionally presentation-only, scoring-inconsistent fixtures explicitly.
|
|
338
|
+
|
|
339
|
+
### Visual verification before claiming a fix
|
|
340
|
+
|
|
341
|
+
Do not report a visual issue as fixed based only on code inspection or a successful render command.
|
|
342
|
+
|
|
343
|
+
For every affected page:
|
|
344
|
+
|
|
345
|
+
1. Render the raw PNG.
|
|
346
|
+
2. Open and inspect the actual PNG.
|
|
347
|
+
3. Compare it with the reference at the same orientation.
|
|
348
|
+
4. Use the PNG to locate the mismatch.
|
|
349
|
+
5. Return to the SVG or geometry ledger for the exact correction.
|
|
350
|
+
6. Re-render and inspect again.
|
|
351
|
+
|
|
352
|
+
Validate major geometry before typography:
|
|
353
|
+
|
|
354
|
+
1. Chart width and height
|
|
355
|
+
2. Origin and global transform
|
|
356
|
+
3. Major group extents
|
|
357
|
+
4. Bar and grid dimensions
|
|
358
|
+
5. Repeated alignment contracts
|
|
359
|
+
6. Text baselines and glyph-level adjustments
|
|
360
|
+
|
|
361
|
+
If several local coordinate patches accumulate, stop. Re-establish the coordinate model from the sources instead of continuing to nudge individual elements.
|
|
362
|
+
|
|
363
|
+
### Pages, variants, and title metadata
|
|
364
|
+
|
|
365
|
+
- Treat pages and presentation variants as independent output dimensions. Enumerate the supported combinations before implementation.
|
|
366
|
+
- Each page context must provide the title suffix required to distinguish that output. Build `titleAppend` from semantic page and variant metadata rather than from template filenames.
|
|
367
|
+
- Validate `titleAppend` in the full SVG/PNG, because it belongs to the injected page header and is not visible in the raw chart.
|
|
368
|
+
- A variant must not change scoring or geometry unless the specification explicitly says so.
|
|
369
|
+
|
|
370
|
+
### Concurrent-work hygiene
|
|
371
|
+
|
|
372
|
+
- Assume files may change while the task is in progress. Re-read shared files immediately before editing them.
|
|
373
|
+
- Inspect the diff before and after each shared-file modification.
|
|
374
|
+
- Stage explicit intended paths unless the user has expressly requested publishing all workspace changes.
|
|
375
|
+
- Do not overwrite or normalize concurrent changes merely to make the current diff cleaner.
|
|
202
376
|
|
|
203
377
|
---
|
|
204
378
|
|
|
@@ -224,7 +398,7 @@ Hard-won knowledge that applies to **every** profile:
|
|
|
224
398
|
- `raw` — total/aggregate element
|
|
225
399
|
- `ticks` — graduation marks on profiles
|
|
226
400
|
- `s` suffix — denotes arrays (not `Arr`)
|
|
227
|
-
- Sample names: uppercase acronym + year (
|
|
401
|
+
- Sample names: uppercase acronym + version/year suffix (for example, `<ACRONYM><SUFFIX>`)
|
|
228
402
|
- Dataset score structure: `dataset.score = [{ label: { eng, ... }, mark }]`
|
|
229
403
|
|
|
230
404
|
---
|
package/package.json
CHANGED