Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
902e2c3
Initial plan
Copilot Feb 19, 2026
f0fc000
Add Reset button and demoValue parameter to documentation examples
Copilot Feb 19, 2026
03a02bc
👀 Code review (pr-70)
bitifet Feb 20, 2026
e7dd789
Fix Reset button to target root form so demoValue default is restored…
Copilot Feb 20, 2026
49a4ad6
Preserve editor textarea when resetting via BeforeAction_reset handler
Copilot Feb 20, 2026
5c19a04
Revert "Preserve editor textarea when resetting via BeforeAction_rese…
bitifet Feb 20, 2026
d31946a
Embed demoValue in demo subform data-smark, restore reset context to …
Copilot Feb 20, 2026
8c99ff0
docs: add realistic demoValue parameters to interactive examples
Copilot Feb 20, 2026
5cc5ea6
Fix test for demoValue: filter in collector, update failing schedule …
Copilot Feb 21, 2026
b7dad3e
👀 Code review (pr-70)
bitifet Feb 21, 2026
58bde37
📝 Addressed the hidding of the min_items + default value effect
bitifet Feb 21, 2026
efcc364
refactor: replace schedule table layout with CSS grid (fixes horizont…
Copilot Feb 21, 2026
490e7c8
Restore exact data comparisons in dup test; add AGENTS knowledge dire…
Copilot Feb 21, 2026
9b07f8c
Fix AGENTS docs, add zebra rows to schedule table, add AGENTS/ refere…
Copilot Feb 21, 2026
c441006
Responsive time slots and period dates for mobile viewports
Copilot Feb 21, 2026
4be1da9
👀 Code review (pr-70)
bitifet Feb 22, 2026
bbabae4
Move schedule ➖➕ buttons below intervals so time inputs stay wider
Copilot Feb 22, 2026
f21f74a
Restore 3-col grid for wide screens; buttons drop below only at ≤44em
Copilot Feb 22, 2026
00ab039
Revert "Restore 3-col grid for wide screens; buttons drop below only …
bitifet Feb 22, 2026
240cc38
Revert "Move schedule ➖➕ buttons below intervals so time inputs stay …
bitifet Feb 22, 2026
233d420
🩹 Minor fix.
bitifet Feb 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,27 @@ npm run dev # Watch library + serve docs
node scripts/collect-docs-examples.js # Collect docs examples
```

## Getting Help
## Agent Knowledge Directory (`AGENTS/`)

The `AGENTS/` directory at the repository root contains specialised knowledge files for coding agents. These files capture patterns, gotchas, and implementation details that are not obvious from reading the source code.

| File | Contents |
|------|----------|
| `AGENTS/SmarkForm-Usage.md` | How to write SmarkForm HTML/CSS/JS — component types, list template roles, button context patterns, CSS grid layout, `exportEmpties` behaviour |
| `AGENTS/Documentation-Examples.md` | How the playground template works — `demoValue`, `DOCS_ONLY_PARAMS`, co-located test patterns, tips for harvesting realistic demo data |

**Agents should read the relevant `AGENTS/` files before making changes** to documentation examples, showcase forms, or anything involving SmarkForm component authoring.

### Keeping `AGENTS/` Up to Date

When you discover a new pattern, fix a bug caused by an undocumented constraint, or find that an existing entry in `AGENTS/` is incorrect or incomplete:

1. **Update or create the relevant file** in `AGENTS/` as part of the same commit.
2. **Add new entries** for anything you had to learn the hard way — gotchas, non-obvious constraints, error messages and their root causes.
3. **Correct inaccurate entries** whenever you verify that something documented is wrong.
4. You do not need a special PR description sentence to update `AGENTS/` files — just do it as part of your normal work.


- **Issue tracker**: [GitHub Issues](https://github.com/bitifet/SmarkForm/issues)
- **Contributing**: See `CONTRIBUTING.md`
- **Documentation**: [https://smarkform.bitifet.net](https://smarkform.bitifet.net)

Expand Down
235 changes: 235 additions & 0 deletions AGENTS/Documentation-Examples.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
# Documentation Examples — Agent Knowledge

This document describes how the Jekyll documentation example playground works and how to add/update examples. It is intended to help coding agents make correct changes to documentation.

## Architecture Overview

Documentation examples are rendered by two Liquid template files in `docs/_includes/components/`:

- **`sampletabs_tpl.md`**: The main template — renders the tabbed HTML/CSS/JS preview with all controls
- **`sampletabs_ctrl.md`**: The control logic (JS/CSS for tabs, playground buttons, hints)

Example usage in a `.md` doc file:

```liquid
{% capture myHtml %}
<form data-smark='{"name":"myForm"}'>
<input data-smark type="text" name="name">
</form>
{% endcapture %}

{% include components/sampletabs_tpl.md
formId="my_example"
htmlSource=myHtml
showEditor=true
%}
```

## Include Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `formId` | (required) | Unique ID suffix for this example. Used to generate `myForm-<formId>` element ID. |
| `htmlSource` | `'-'` | HTML source string. Pass an undefined variable to show a "Missing Example" placeholder (used to plan future examples). |
| `cssSource` | `'-'` | CSS source string |
| `jsHead` | (generated) | JS initialization code. Defaults to the simple constructor or a `demoValue` constructor. |
| `notes` | `'-'` | Markdown/HTML notes shown below the example |
| `showEditor` | `false` | Whether to show the JSON editor textarea for import/export |
| `selected` | `'preview'` | Which tab is initially active |
| `tests` | `false` | Co-located test code (JavaScript string) |
| `expectedPageErrors` | `0` | Number of JS errors expected during render |
| `demoValue` | `'-'` | JSON string for pre-populating the demo form (docs-only, filtered by collector) |

## `demoValue` Parameter — How It Works

When `demoValue` is provided:

1. **Executed JS** (hidden from user): The simple constructor is used — value is embedded directly in the `demo` subform's `data-smark`:
```html
<div data-smark='{"name":"demo","value":<demoValue>}'>
```
This sets `demo.defaultValue = demoValue`, so the Reset button restores it.

2. **Displayed JS** (shown in JS tab): The constructor shows the value for documentation clarity:
```javascript
const myForm = new SmarkForm(document.getElementById("myForm-example"), {
value: <demoValue>
});
```

3. **Reset behavior**: The Reset button targets `context:"demo"`, which calls `demo.reset()` → `demo.import(demo.defaultValue)` = the demoValue, without touching the `editor` textarea (which is a sibling, not inside `demo`).

4. **Test isolation**: The collector (`scripts/collect-docs-examples.js`) explicitly filters out `demoValue` via `DOCS_ONLY_PARAMS`. Tests always see an empty form.

### Example

```liquid
{% capture demoValue %}{"name": "Alice Johnson", "email": "alice@example.com"}{% endcapture %}

{% include components/sampletabs_tpl.md
formId="contact_form"
htmlSource=myHtml
showEditor=true
demoValue=demoValue
%}
```

### When to Skip `demoValue`

`demoValue` is safe to use alongside co-located tests — the collector filters it out so the test form always starts empty regardless of the parameter. There are only a few situations where `demoValue` should be skipped:

- **Error demonstration examples** (`expectedPageErrors=1`): Adding a `demoValue` is pointless unless the error itself could be triggered by the default value.
- **Undefined `htmlSource` (placeholder examples)**: When `htmlSource` is an undefined variable, the template renders a "Missing Example" placeholder; there's nothing to pre-populate.
- **Inline value already set**: Examples that already set initial values via an inline HTML `value` attribute or `data-smark='{"value":...}'` (e.g., `clear_reset_form`). Don't add `demoValue` on top of an existing inline value to avoid confusion.

### Tip: Harvesting Realistic Demo Data

The easiest way to produce a correct `demoValue` JSON for an existing example:
1. Open the documentation page in a browser (use `npm run servedoc`)
2. Fill in realistic data directly in the **Preview** tab
3. Click the **⬇️ Export** button
4. Copy the JSON from the editor textarea
5. Paste as the `demoValue` capture in Liquid, tweaking as needed

This ensures the JSON structure exactly matches what the form expects. One common tweak: if a list uses `exportEmpties:false`, the exported JSON will omit empty items — but if you want Reset to restore a list with one empty item, provide `[{}]` in `demoValue` for that list.

## Playground Buttons

The standard playground buttons (in order) are:
```
⬇️ Export | ⬆️ Import | ♻️ Reset | ❌ Clear
```

The Reset button targets `context:"demo"` — it resets the demo subform (not the root form). This means it restores the `demoValue` default (if set) without clearing the editor textarea.

Print styles hide all four buttons (`display: none !important`).

## The Demo/Editor Structure

The playground renders a root SmarkForm with two children:
- `demo` — the form container for the example's HTML
- `editor` — the JSON textarea (shown when `showEditor=true`)

```html
<div id="myForm-<formId>"> <!-- root form -->
<div data-smark='{"name":"demo","value":<demoValue>}'>
<!-- the example's HTML goes here -->
</div>
<textarea data-smark='{"name":"editor"}'></textarea> <!-- if showEditor=true -->
</div>
```

The Export/Import/Reset/Clear buttons all have `context:"demo"` to target the demo subform only.

## Co-located Tests

Tests are JavaScript strings captured in Liquid and passed via `tests=`:

```liquid
{% capture myTests %}
export default async ({ page, expect, id, root, readField, writeField }) => {
await expect(root).toBeVisible();
// ...
};
{% endcapture %}

{% include components/sampletabs_tpl.md
formId="my_example"
htmlSource=myHtml
tests=myTests
%}
```

### Test Helper Functions

| Function | Description |
|----------|-------------|
| `readField('/path')` | Export the field at path (returns its current value) |
| `writeField('/path', value)` | Import value into field at path |
| `page.getByTitle('...')` | Locate button by title attribute |
| `page.getByRole('button', { name: '...' })` | Locate button by text |
| `root` | Playwright locator for the form's root element |

### Important: Tests See Empty Forms

The `demoValue` parameter is filtered by the collector, so co-located tests always render the form with no pre-populated data. Tests should NOT assume data is pre-loaded.

If a test needs non-null values, use `writeField` to set them explicitly:
```javascript
await writeField('/fieldname', 'some value');
```

### `exportEmpties` in Tests

When lists have `exportEmpties:false`, null/empty items are stripped from the exported array:
```javascript
// 3 null intervals in DOM → exported as []
expect(await readField('/myList')).toEqual([]);
```

To test duplication proves data is copied (not reset), write actual values first:
```javascript
await writeField('/items/0/name', 'Test Name');
await page.getByRole('button', { name: '✨' }).click();
expect(await readField('/items/1/name')).toBe('Test Name'); // copied, not null
```

## Collector (`scripts/collect-docs-examples.js`)

The collector scans all `.md` files in `docs/` looking for `include components/sampletabs_tpl.md` calls and extracts parameters into a test manifest (`test/.cache/docs_examples.json`).

### `DOCS_ONLY_PARAMS`

Parameters listed in `DOCS_ONLY_PARAMS` are filtered out and never reach the manifest:
```javascript
const DOCS_ONLY_PARAMS = new Set(["demoValue"]);
```

To add new docs-only parameters (ones that only affect Jekyll rendering, not tests), add them to this set in `scripts/collect-docs-examples.js`.

## Adding Realistic Demo Data

To add pre-populated data to an existing example:

1. Capture the JSON value in Liquid:
```liquid
{% capture demoValue %}{"field1": "value1", "field2": "value2"}{% endcapture %}
```

2. Pass it to the include:
```liquid
{% include components/sampletabs_tpl.md
formId="..."
demoValue=demoValue
...
%}
```

3. The JSON must match the structure expected by the demo form's HTML.

4. For list fields: provide an array, e.g., `[{"name": "Alice"}, {"name": "Bob"}]`

5. For nested forms: match the nesting, e.g.:
```json
{"user": {"name": "Alice", "email": "alice@example.com"}}
```

## Updating `sampletabs_tpl.md`

Key sections in the template (approximate line numbers as of PR #70):

- ~Line 47-58: `default_jsHead` / `default_jsHead_display` conditional block — generates the two JS variants based on `demoValue`
- ~Line 64: `jsHead` / `jsHead_display` assignment (with `include.jsHead` override)
- ~Line 97: `default_buttons` capture — the four action buttons
- ~Line 224: `rendered_jsSource` uses `jsHead_display` for the JS tab display
- ~Line 312: Actual form initialization uses `jsHead` (the executed version)
- The `full_htmlSource` variable wraps the example HTML in the `demo` subform div, injecting `demoValue_inner` if set

## Updating Print Styles

In `sampletabs_ctrl.md`, the print style block hides all interactive controls. To hide a new button type, add it to the list:
```css
button[data-smark*='"action":"reset"'] {
display: none !important;
}
```
Loading