New skills

This commit is contained in:
ivarsbariss
2026-02-27 13:20:38 +01:00
parent f207f55565
commit 68ad98ede4
4 changed files with 246 additions and 52 deletions

View File

@@ -13,22 +13,27 @@ Each query lives under the page in a folder named after the query:
- **Path**: `pages/<PageName>/queries/<query_name>/`
- **Files**:
- **`<query_name>.txt`** Raw SQL. This is the **source of truth** for the query body. Keep it readable (real newlines, no escaping).
- **`metadata.json`** Appsmith action config. The `body` inside `unpublishedAction.actionConfiguration` must match the SQL in the `.txt` file, stored as a single-line string with `\n` for newlines and `\"` for double quotes.
- **`<query_name>.txt`** -- Raw SQL. This is the **source of truth** for the query body. Keep it readable (real newlines, no escaping).
- **`metadata.json`** -- Appsmith action config. The `body` inside `unpublishedAction.actionConfiguration` must match the SQL in the `.txt` file, stored as a single-line string with `\n` for newlines and `\"` for double quotes.
**Naming**: Use lowercase with underscores (e.g. `units_ordered_by_series`, `units_shipped_by_power_input`). The query name is the identifier used in bindings (e.g. `{{query_name.data}}`).
**Naming**: Use lowercase with underscores (e.g. `units_ordered_by_series`, `pending_pos_slx_pending`). The query name is the identifier used in bindings (e.g. `{{query_name.data}}`).
## 2. Metadata.json required fields
- **`id`**: `"<PageName>_<query_name>"` (e.g. `"Sales - Units Ordered_units_ordered_by_series"`).
- **`gitSyncId`** (CRITICAL): Must be the **first field** in the root object. Format: `"<24-char-hex>_<uuid-v4>"`. Without this, Appsmith will auto-remove the query on the next pull/sync. Generate with:
```bash
python3 -c "import uuid; print(uuid.uuid4().hex[:24] + '_' + str(uuid.uuid4()))"
```
Use the same 24-char hex prefix as the page's `gitSyncId` but a unique UUID suffix.
- **`id`**: `"<PageName>_<query_name>"` (e.g. `"Pending POs - SLx Pending_pending_pos_slx_pending"`).
- **`name`**: `"<query_name>"` (must match the folder and the name used in bindings).
- **`unpublishedAction.actionConfiguration.body`**: The exact SQL from `<query_name>.txt`, escaped for JSON (newlines `\n`, `"``\"`).
- **`unpublishedAction.actionConfiguration.body`**: The exact SQL from `<query_name>.txt`, escaped for JSON (newlines -> `\n`, `"` -> `\"`).
- **`unpublishedAction.datasource`**: Use the same as other queries on the page (e.g. `xTuple_Sandbox`).
- **`unpublishedAction.pageId`**: `"<PageName>"`.
- **`unpublishedAction.dynamicBindingPathList`**: `[{"key":"body"}]` when the body contains `{{...}}` widget references.
- Keep **`pluginId`**: `"postgres-plugin"`, **`pluginType`**: `"DB"`, and existing flags like **`encodeParamsToggle`**, **`paginationType`**, **`pluginSpecifiedTemplates`**, **`timeoutInMillisecond`** consistent with other DB queries in the app.
When adding a new query, copy an existing querys `metadata.json` from the same page and change `id`, `name`, and `body` (and ensure `body` stays in sync with the new `.txt` file).
When adding a new query, copy an existing query's `metadata.json` from the same page and change `gitSyncId`, `id`, `name`, and `body` (and ensure `body` stays in sync with the new `.txt` file).
## 3. Wiring the query to a Table widget
@@ -36,7 +41,7 @@ When adding a new query, copy an existing querys `metadata.json` from the sam
- **`tableData`**: `"{{<query_name>.data}}"`
- Table columns read from the query result by **key**. The key is the **SELECT alias** from the query (e.g. `"Product"`, `"Qty Ordered"`). In `primaryColumns`, the column **id** can be a safe identifier (e.g. `Qty_Ordered`) while **originalId** / **alias** / **label** match the display name; **computedValue** must use the same key as in the query result, e.g. `currentRow["Qty Ordered"]`.
So: **query SELECT aliases = keys in the tables row object**. Keep column keys and any `currentRow["..."]` references in the table in sync with those aliases.
So: **query SELECT aliases = keys in the table's row object**. Keep column keys and any `currentRow["..."]` references in the table in sync with those aliases.
## 4. Parameter search syntax (widgets in SQL)
@@ -45,15 +50,15 @@ So: **query SELECT aliases = keys in the tables row object**. Keep column key
- **Syntax**: `{{WidgetName.property}}`
- **Examples**:
- DatePicker date: `{{SeriesDateFrom.selectedDate}}`, `{{PowerDateTo.selectedDate}}`
- Other widgets: use the widgets value property (e.g. `selectedOptionValue`, `text`) as per Appsmith docs.
- Other widgets: use the widget's value property (e.g. `selectedOptionValue`, `text`) as per Appsmith docs.
Values are injected as strings. For PostgreSQL dates you typically cast in SQL, e.g. `'{{DatePicker1.selectedDate}}'::date`.
### 4.2 Optional date range (no filter when either date is empty)
Use this pattern so that:
- If **either** date widget is empty the date condition is **not** applied (all dates allowed).
- If **both** are set filter by `date_column BETWEEN from ::date AND to ::date`.
- If **either** date widget is empty -> the date condition is **not** applied (all dates allowed).
- If **both** are set -> filter by `date_column BETWEEN from ::date AND to ::date`.
**SQL pattern** (replace widget names and column as needed):
@@ -72,30 +77,31 @@ AND (
### 4.3 Required parameters (always filter)
If the filter must always be applied (no show all when empty):
If the filter must always be applied (no "show all" when empty):
- Use the binding directly and ensure the widget always has a value, or use a default in the widget.
- Example: `AND cohead_orderdate BETWEEN '{{DateFrom.selectedDate}}'::date AND '{{DateTo.selectedDate}}'::date` then empty dates may produce invalid SQL or empty results, so prefer the optional pattern above unless the UI guarantees non-empty values.
- Example: `AND cohead_orderdate BETWEEN '{{DateFrom.selectedDate}}'::date AND '{{DateTo.selectedDate}}'::date` -- then empty dates may produce invalid SQL or empty results, so prefer the optional pattern above unless the UI guarantees non-empty values.
## 5. Checklist when adding a new query
1. Create `pages/<PageName>/queries/<query_name>/`.
2. Add `<query_name>.txt` with the full SQL (source of truth).
3. Add `metadata.json` with correct `id`, `name`, `body` (body = SQL from .txt, JSON-escaped), and same datasource/pageId as other page queries.
3. Add `metadata.json` with correct `gitSyncId`, `id`, `name`, `body` (body = SQL from .txt, JSON-escaped), and same datasource/pageId as other page queries.
4. If the SQL uses `{{...}}`, set `dynamicBindingPathList` to `[{"key":"body"}]`.
5. In the Table widget that should show the data, set `tableData` to `{{<query_name>.data}}`.
6. Ensure table column keys / `currentRow["..."]` match the querys SELECT aliases.
6. Ensure table column keys / `currentRow["..."]` match the query's SELECT aliases.
## 6. Renaming or replacing a query
- To **rename** (e.g. `units_shipped_by_series``units_ordered_by_series`):
- Create the new folder and files under the new name.
- Update every reference to the old query (e.g. `tableData`: `{{old_name.data}}``{{new_name.data}}`).
- To **rename** (e.g. `units_shipped_by_series` -> `units_ordered_by_series`):
- Create the new folder and files under the new name (with a new `gitSyncId`).
- Update every reference to the old query (e.g. `tableData`: `{{old_name.data}}` -> `{{new_name.data}}`).
- Remove the old query folder (both `.txt` and `metadata.json`).
- When **replacing** the SQL but keeping the same name, update the `.txt` first, then update the `body` in `metadata.json` to match (same content, JSON-escaped).
## Reference
- **gitSyncId**: `<24-char-hex>_<uuid-v4>`. Without this, Appsmith auto-removes the query on sync.
- **Optional date range**: NULLIF + IS NULL + OR + BETWEEN as above.
- **Table data binding**: `{{<query_name>.data}}`.
- **Column keys**: Must match query SELECT aliases (e.g. `"Product"`, `"Qty Ordered"`).

View File

@@ -1,6 +1,6 @@
---
name: create-new-page
description: Creates a new Sales page by cloning Sales - Capacity Planning, adds a nav button for it on every page, and sets the new page name for the button label and the page Heading. Use when the user asks to add a new page, clone a page, create a page from template, or add a new Sales navigation page.
description: Creates a new page by cloning Sales - Capacity Planning, adds a nav button for it on every page, and sets the new page name for the button label and the page Heading. Use when the user asks to add a new page, clone a page, create a page from template, or add a new navigation page.
---
# Create New Page (Clone + Navigation)
@@ -9,14 +9,15 @@ Creates a new page from the **Sales - Capacity Planning** template, registers it
## Prerequisites
- **New page name**: User must provide the exact display name (e.g. `Sales - Units Planning`). Use it consistently for:
- **New page name**: User must provide the exact display name (e.g. `Sales - Units Planning`, `Pending POs - SLx Pending`). Use it consistently for:
- Page folder name: `pages/<New Page Name>/`
- Page JSON filename: `<New Page Name>.json`
- `unpublishedPage.name` and `unpublishedPage.slug` (slug = lowercase, spaces hyphens, e.g. `sales-units-planning`)
- `unpublishedPage.name` and `unpublishedPage.slug` (slug = lowercase, spaces to hyphens, e.g. `pending-pos-slx-pending`)
- Heading widget `text` on the new page
- Nav button `text` on all pages
- `navigateTo('<New Page Name>', {}, 'SAME_WINDOW')` in the new buttons `onClick`
- `navigateTo('<New Page Name>', {}, 'SAME_WINDOW')` in the new button's `onClick`
- Follow the **navigation-button-add** skill for button styling and placement.
- If the page belongs to a **new section** (e.g. "Pending POs" section distinct from "Sales"), follow the **navigation-section-add** skill to add the section label on all pages.
## Instructions
@@ -26,42 +27,76 @@ Creates a new page from the **Sales - Capacity Planning** template, registers it
- **Target**: `pages/<New Page Name>/`
- Copy the entire directory tree (widgets, queries, and the page JSON).
- In the new folder, rename the page file to `<New Page Name>.json` and update inside it:
- `unpublishedPage.name` `<New Page Name>`
- `unpublishedPage.slug` slug form (e.g. `sales-units-planning`)
- Replace all widget IDs in the new pages JSON files with new unique IDs (e.g. new UUIDs or unique strings) so the new page does not conflict with the template. Update any `parentId` references to the new IDs.
- `unpublishedPage.name` -> `<New Page Name>`
- `unpublishedPage.slug` -> slug form (e.g. `pending-pos-slx-pending`)
- Replace all widget IDs in the new page's JSON files with new unique IDs (e.g. new UUIDs or unique strings) so the new page does not conflict with the template. Update any `parentId` references to the new IDs.
- In every widget JSON under the new page, replace any reference to the old page name or slug with the new page name or slug (e.g. in queries or onClick that might point to the template page).
### 2. Set the new pages Heading
### 2. Add gitSyncId (CRITICAL)
Appsmith's git sync uses `gitSyncId` to track entities. **Without this field, Appsmith will auto-remove the page and query on the next pull/sync.**
- **Page JSON** (`<New Page Name>.json`): Add `"gitSyncId"` as the **first field** in the root object.
- **Query metadata** (`queries/<query_name>/metadata.json`): Add `"gitSyncId"` as the **first field** in the root object.
**Format**: `"<24-char-hex>_<uuid-v4>"`
Generate unique IDs:
```bash
python3 -c "import uuid; print(uuid.uuid4().hex[:24] + '_' + str(uuid.uuid4()))"
```
Use the **same 24-char hex prefix** for both the page and its queries (they share a creation context), but a **unique UUID suffix** for each.
**Example** (page JSON):
```json
{
"gitSyncId": "3ac30122d2934ca9bf65e36a_54bc4b88-d468-4faa-bab0-80921aa88795",
"unpublishedPage": { ... }
}
```
**Example** (query metadata):
```json
{
"gitSyncId": "3ac30122d2934ca9bf65e36a_da6910cc-4263-496f-ba35-ffc773eddaae",
"id": "PageName_query_name",
...
}
```
### 3. Set the new page's Heading
- In `pages/<New Page Name>/widgets/Heading.json`, set:
- `"text": "<New Page Name>"`
- `"text": "<New Page Name>"`
so the heading always shows the page name (no dynamic binding; `appsmith.page` is undefined in this app).
### 3. Register the new page in the app
### 4. Register the new page in the app
- In `application.json`, add to the `pages` array one entry:
- `{"id": "<New Page Name>", "isDefault": false}`
- Do not change `isDefault: true` on the existing default page unless the user asks to make the new page default.
### 4. Add the new nav button on every page
### 5. Add the new nav button on every page
Apply the **navigation-button-add** skill:
- **Pages to update**: Every existing Sales page (Sales - Capacity Planning, Sales - Units Shipped, Sales - Units Ordered) **and** the new page.
- **On each page**, in `widgets/Container1/`, add a new button (e.g. copy `Button1Copy2` and name the new file `Button1Copy3` or next available name):
- `text`: `<New Page Name>` (or a short label that matches the page, e.g. “Units Planning” if the full name is “Sales - Units Planning” — prefer using the **new page name** for consistency unless the user asks for a shorter label).
- **Pages to update**: **All** existing pages **and** the new page.
- **On each page**, in `widgets/Container1/`, add a new button:
- `text`: short label for the page (e.g. "SLx Pending" for "Pending POs - SLx Pending").
- `onClick`: `{{navigateTo('<New Page Name>', {}, 'SAME_WINDOW');}}`
- `buttonColor`:
- `buttonColor`:
- On the **new page only**: `{{appsmith.theme.colors.backgroundColor}}` and add `{"key":"buttonColor"}` to `dynamicBindingPathList`.
- On all **other** pages: `#ffffff`, and do not add `buttonColor` to `dynamicBindingPathList`.
- Place the new button below the existing nav buttons: set `topRow` / `bottomRow` so it stacks vertically (e.g. copy from the last button and add 4 rows for the new one).
- Assign a new `widgetId` and `widgetName` for the new button on each page.
- Update `Container1.json` (or the parent that lists children) so the new button is included in the layout if the DSL is widget-list based.
- Place the new button below existing nav items within its section. Each button occupies 4 rows (e.g. topRow 22, bottomRow 26).
- Assign a new unique `widgetId` and `widgetName` for the new button on each page.
- Set `parentId` to the **Canvas widget ID** inside that page's `Container1.json` (the `widgetId` of the `CANVAS_WIDGET` child). This varies per page -- read `Container1.json` to find it.
### 5. Verify
### 6. Verify
- Run `git status` / `git diff` and confirm only intended files were added or changed.
- Ensure no duplicate `widgetId` values across pages and that the new pages slug and name are used consistently.
- Ensure no duplicate `widgetId` values across pages and that the new page's slug and name are used consistently.
- Confirm `gitSyncId` is present in both the page JSON and every query metadata.
## Summary
@@ -69,8 +104,9 @@ Apply the **navigation-button-add** skill:
|------|--------|
| Template | Sales - Capacity Planning |
| New page folder | `pages/<New Page Name>/` |
| gitSyncId | `<24-char-hex>_<uuid-v4>` in page JSON + query metadata |
| Heading text | `<New Page Name>` |
| Nav button text | `<New Page Name>` (or user-specified short label) |
| Nav button text | Short label (e.g. "SLx Pending") |
| Nav button onClick | `navigateTo('<New Page Name>', {}, 'SAME_WINDOW')` |
| Highlight (new page) | `buttonColor`: `{{appsmith.theme.colors.backgroundColor}}` |
| Inactive (other pages) | `buttonColor`: `#ffffff` |
@@ -78,4 +114,6 @@ Apply the **navigation-button-add** skill:
## Reference
- Nav button behavior and layout: follow **navigation-button-add** skill.
- Slug format: lowercase, spaces to hyphens (e.g. `Sales - Units Planning``sales-units-planning`).
- New nav sections: follow **navigation-section-add** skill.
- Slug format: lowercase, spaces to hyphens (e.g. `Pending POs - SLx Pending` -> `pending-pos-slx-pending`).
- `gitSyncId` format: `<24-char-hex>_<uuid-v4>`. Without this, Appsmith auto-removes the entity on sync.

View File

@@ -1,28 +1,54 @@
---
name: navigation-button-add
description: Implements a new Sales navigation button with the existing highlight/default colors. Use when the user asks to add buttons to the nav block, mentions “Units Ordered”, “Units Shipped”, or “Sales” navigation, or instructs to keep the highlight behavior consistent with the latest change.
description: Implements a new navigation button with the existing highlight/default colors. Use when the user asks to add buttons to the nav block, mentions page navigation, or instructs to keep the highlight behavior consistent with the latest change.
---
# Navigation Button Addition
## Instructions
1. **Understand the current navigation canvas** by checking `pages/*/widgets/Container1/Button*`. Each Sales page reuses the same container; the existing buttons are `Button1` (Capacity Planning), `Button1Copy` (Units Shipped), and `Button1Copy2` (Units Ordered).
1. **Understand the current navigation canvas** by checking `pages/*/widgets/Container1/`. Each page reuses the same container structure. The navigation is organized into **sections**, each with a Text label and one or more buttons:
- **Sales** section: `Text1` (label), `Button1` (Capacity Planning), `Button1Copy` (Units Shipped), `Button1Copy2` (Units Ordered)
- **Pending POs** section: `Text2` (label), `Button2` (SLx Pending)
- Future sections follow the same pattern (`Text3`/`Button3`, etc.)
2. **Follow the established color scheme**:
- The highlighted button uses `appsmith.theme.colors.backgroundColor`.
- The highlighted button (current page) uses `appsmith.theme.colors.backgroundColor`.
- The inactive buttons use `#ffffff`.
3. **When adding a new navigation button**:
- Copy one of the existing `Button*` definitions, adjusting `text`, `onClick`, `widgetId`, `widgetName`, `bottomRow`, and any other placement metadata so it fits below the existing items.
- Set `buttonColor` to `#ffffff` (inactive) unless the new button replaces the active page; then set its own definition (`on the relevant page`) to `appsmith.theme.colors.backgroundColor`.
- Ensure `dynamicBindingPathList` stays empty when `buttonColor` is static white; populate it only if the button needs dynamic logic.
- Point `onClick` to `navigateTo` the new page slug and keep `placement`/`responsiveBehavior` matching other nav buttons.
4. **Run git status/diff** to verify only the intended files changed before reporting back.
- Copy one of the existing `Button*` definitions, adjusting `text`, `onClick`, `widgetId`, `widgetName`, `topRow`, `bottomRow`, and any other placement metadata so it fits below the existing items in its section.
- Set `buttonColor` to `#ffffff` (inactive) unless the button is on its own page; then set its definition to `appsmith.theme.colors.backgroundColor` with `dynamicBindingPathList: [{"key": "buttonColor"}]`.
- Ensure `dynamicBindingPathList` stays empty when `buttonColor` is static white.
- Point `onClick` to `navigateTo('<Page Name>', {}, 'SAME_WINDOW')` and keep `placement`/`responsiveBehavior` matching other nav buttons.
- Set `parentId` to the **Canvas widget ID** inside that page's `Container1.json` (the `widgetId` of the `CANVAS_WIDGET` child). This varies per page -- always read `Container1.json` to find the correct value.
4. **Add the button on ALL pages** (not just pages in the same section). Every page gets the full navigation.
5. **If adding a button to a new section**, follow the **navigation-section-add** skill first to create the section label.
6. **Run git status/diff** to verify only the intended files changed before reporting back.
## Row Layout
Each widget occupies 4 rows. Sections are separated by a 2-row gap:
| Widget | topRow | bottomRow |
|--------|--------|-----------|
| Text1 "Sales" | 0 | 4 |
| Button1 "Capacity Planning" | 4 | 8 |
| Button1Copy "Units Shipped" | 8 | 12 |
| Button1Copy2 "Units Ordered" | 12 | 16 |
| *(2-row gap)* | 16 | 18 |
| Text2 "Pending POs" | 18 | 22 |
| Button2 "SLx Pending" | 22 | 26 |
When adding a new button within a section, increment from the last button's `bottomRow` by 4. When adding a new section after the last one, add 2 rows gap, then 4 for the label, then 4 for each button.
## Examples
- *Adding Units Ordered button*:
1. Copy `Button1Copy` (Units Shipped) in each pages `Container1`.
2. Set `text` to Units Ordered, `onClick` to `navigateTo('Sales - Units Ordered', {}, 'SAME_WINDOW');`.
3. Assign `buttonColor` to `#ffffff` for inactive cases and to `appsmith.theme.colors.backgroundColor` inside the `Sales - Units Ordered` page definition for highlight.
4. Adjust `bottomRow` incrementally so buttons stack vertically.
- *Adding "Units Ordered" button to Sales section*:
1. Copy `Button1Copy` (Units Shipped) in each page's `Container1`.
2. Set `text` to "Units Ordered", `onClick` to `navigateTo('Sales - Units Ordered', {}, 'SAME_WINDOW');`.
3. Assign `buttonColor` to `#ffffff` for inactive cases and to `appsmith.theme.colors.backgroundColor` inside the `Sales - Units Ordered` page definition for highlight.
4. Set `topRow: 12`, `bottomRow: 16` (next slot after Units Shipped).
- *Adding a second button to Pending POs section*:
1. Create `Button2Copy.json` in each page's `Container1`.
2. Set `topRow: 26`, `bottomRow: 30` (next slot after SLx Pending at 22-26).
3. Follow the same highlight/inactive pattern.

View File

@@ -0,0 +1,124 @@
---
name: navigation-section-add
description: Adds a new navigation section (label + first button) to the sidebar on all pages. Use when the user asks to create a new section like "Pending POs", add a section header to navigation, or group pages under a new category separate from "Sales".
---
# Navigation Section Addition
Adds a new **section** to the left-side navigation container on **all pages**. A section consists of a bold Text label (e.g. "Pending POs") followed by one or more page buttons. This skill covers adding the section label and first button; additional buttons follow the **navigation-button-add** skill.
## Current Sections
| Section | Label widget | Buttons | Row range |
|---------|-------------|---------|-----------|
| Sales | `Text1` | `Button1`, `Button1Copy`, `Button1Copy2` | 0-16 |
| Pending POs | `Text2` | `Button2` | 18-26 |
## Instructions
### 1. Determine row placement
Sections are separated by a **2-row gap**. Find the last widget's `bottomRow` in the current navigation, add 2 for the gap, then place:
- **Section label** (Text widget): 4 rows (e.g. topRow 18, bottomRow 22)
- **First button**: 4 rows immediately after (e.g. topRow 22, bottomRow 26)
### 2. Choose widget names
Follow the naming sequence based on existing sections:
- First section: `Text1`, `Button1` / `Button1Copy` / `Button1Copy2`
- Second section: `Text2`, `Button2` / `Button2Copy` / ...
- Third section: `Text3`, `Button3` / `Button3Copy` / ...
### 3. Create the Text label widget on ALL pages
Add a `Text<N>.json` file in `pages/<each page>/widgets/Container1/` with:
```json
{
"animateLoading": true,
"borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}",
"bottomRow": <calculated>,
"dynamicBindingPathList": [
{"key": "truncateButtonColor"},
{"key": "fontFamily"},
{"key": "borderRadius"}
],
"dynamicHeight": "AUTO_HEIGHT",
"dynamicTriggerPathList": [],
"fontFamily": "{{appsmith.theme.fontFamily.appFont}}",
"fontSize": "1rem",
"fontStyle": "BOLD",
"isLoading": false,
"isVisible": true,
"key": "<unique-key>",
"leftColumn": 0,
"maxDynamicHeight": 9000,
"minDynamicHeight": 4,
"minWidth": 450,
"mobileBottomRow": <calculated>,
"mobileLeftColumn": 10,
"mobileRightColumn": 26,
"mobileTopRow": <calculated>,
"needsErrorInfo": false,
"originalBottomRow": <calculated>,
"originalTopRow": <calculated>,
"overflow": "NONE",
"parentColumnSpace": 3.841796875,
"parentId": "<Canvas widgetId from Container1.json>",
"parentRowSpace": 10,
"renderMode": "CANVAS",
"responsiveBehavior": "fill",
"rightColumn": 64,
"shouldTruncate": false,
"text": "<Section Name>",
"textAlign": "LEFT",
"textColor": "#231F20",
"topRow": <calculated>,
"truncateButtonColor": "{{appsmith.theme.colors.primaryColor}}",
"type": "TEXT_WIDGET",
"version": 1,
"widgetId": "<unique per page>",
"widgetName": "Text<N>"
}
```
### 4. Create the first button on ALL pages
Follow the **navigation-button-add** skill to add `Button<N>.json`. Key points:
- On the page the button navigates to: `buttonColor` = `{{appsmith.theme.colors.backgroundColor}}` (highlighted), with `dynamicBindingPathList: [{"key": "buttonColor"}]`.
- On all other pages: `buttonColor` = `#ffffff` (inactive), with empty `dynamicBindingPathList`.
- `topRow` = label's `bottomRow`, `bottomRow` = topRow + 4.
### 5. parentId -- CRITICAL
Each page's Container1 has a **different Canvas widget ID**. The `parentId` for all nav widgets must match the `widgetId` of the `CANVAS_WIDGET` child inside that page's `Container1.json`.
**Always read** `pages/<PageName>/widgets/Container1/Container1.json` to find the Canvas `widgetId` before creating widgets. Do NOT copy parentId from another page.
Example Canvas widget IDs (verify these are still current):
- Sales - Capacity Planning: `x3pc17vp6q`
- Sales - Units Shipped: `wj6fxg5wpg`
- Sales - Units Ordered: `wj6fxg5wpg`
### 6. Widget IDs -- must be unique
Every widget across all pages must have a **unique `widgetId`**. Use a short, unique string per widget per page. A practical pattern: `<page-prefix><widget-abbrev>` (e.g. `cp1txtpndpo` for Capacity Planning's "Pending POs" text label).
### 7. Apply to ALL pages
The section label and button must appear on **every page in the app** -- not just pages within the new section. This ensures the full navigation is visible regardless of which page the user is on.
## Example: Adding "Pending POs" section
Files created on each page's `widgets/Container1/`:
- `Text2.json` -- "Pending POs" label, topRow 18, bottomRow 22
- `Button2.json` -- "SLx Pending" button, topRow 22, bottomRow 26
On `Pending POs - SLx Pending` page: Button2 is highlighted.
On all Sales pages: Button2 is white/inactive.
## Reference
- Button styling and highlight pattern: **navigation-button-add** skill.
- Creating the page itself: **create-new-page** skill.
- Row layout: 4 rows per widget, 2-row gap between sections.