<SKILL_VISPRO_REXX_CUA>
## CUA & GUI Architecture
- Native Architecture: VisPro natively supports multithreading (semaphores, thread communication), Shared Memory, DDE, OS/2 Drag & Drop, and Database Designer (SQL joins/DDL).
- Structure: Use Notebook-Controls for categorizing settings/views. Use Container-Controls for lists of objects.
- Dynamic Data: For system information, prefer Multiline Text Controls (Read-Only, Monospace) over rigidly placed Static Text fields to prevent truncation.
- Non-Blocking GUI: Never place long-running loops directly in the main GUI thread.
- Scope Safety (Critical): VisPro compiles event scripts into `_run.cmd`. ALWAYS prefix subroutine names uniquely (e.g., `Info_AddToContainer`) to prevent `Error 40` crashes across modules.
- Notebook Controls: Child forms MUST be explicitly set as "Notebook Page" types.
- Notebook Empty Check (Crash Prevent): NEVER use `VpGetItemCount` on a Notebook (causes Error 40). To check if a Notebook is empty, use `VpGetIndex(window, ITEMID, 'TOP') = 0`.
- Status Bar Polling & PM Deadlocks: Use a central background timer (`STARTTIMER`) on the main window instead of hooking error-prone MLE events (Char, Changed, Mouse Moved) to track cursor/status updates.
- Timer I/O Pausing (Critical): ALWAYS pause background timers (`CALL VpWindow window, 'STOPTIMER'`) BEFORE executing blocking I/O operations (`VpLoadFromFile`, `VpSaveToFile`) or heavy GUI rebuilds (`VpOpenForm`), and restart them afterward. Firing SENDMSG at a locked MLE during I/O causes fatal OS/2 PM deadlocks.
- MLE SENDMSG PM Bug (Memory Corruption): NEVER pass `'1'` to the `mp2` (parameter 2) argument when sending line-query messages (e.g., `0x01be` MLM_LINEFROMCHAR, `0x01bd`, `0x01bf`) to an MLE. It is reserved OS/2 memory; passing non-zero corrupts the internal PM line-offset array. ALWAYS pass `'0'`.
- MLE Polling Race Conditions: When polling the current MLE line via `0x01be`, pass `'-1'` as the first parameter (`mp1`) to safely query the PM's internal cursor. Do not pass a manually queried cursor_idx (desync risk during fast typing).
- MLE Buffer Locks: Validate EVERY return value from SENDMSG polling using `DataType(var, 'W')`. Rapid typing temporarily locks the MLE buffer, causing SENDMSG to return an empty string and crashing subsequent math operations.
- STDIO Window Debugging: The standard I/O window (`SAY` outputs) is hidden by default in Macros (`.mac`). Enable it via `CALL VpWindow window,'ENABLESTDIO'`. For Executables (`.exe`), use `CALL VpWindow window,'DISABLESTDIO'` to explicitly hide output from users.
- VERIFIED MLE Line Limit (32 KB): The OS/2 Presentation Manager MLE-Control (as well as VisPro's `VpSetItemValue` and the clipboard) has no hard limit for total text size (multiple megabytes are possible). However, there is a strict PM limit of approx. 32 KB (32,767 bytes) for the length of a single text line. If Carriage Return / Line Feed (`'0d0a'x`) are missing, the text is harshly rejected or leads to PM deadlocks. Workaround: For massively generated text blocks, strictly enforce regular line breaks.

## VisPro Architecture & _run.cmd Parsing Rule
- Role of `_run.cmd`: `_run.cmd` is a monolithic, auto-generated compilation artifact combining all GUI event scripts and external `.cmd` subroutines of a VisPro project.
- Input vs. Output: The user may provide `_run.cmd` as read-only reference context. You must NEVER output the full `_run.cmd`.
- Structure of `_run.cmd` Blocks:
  1. Event Scripts start with a numeric/event label (e.g., `261_1001.0:` or `Main_Init:`) and end with `RETURN`.
  2. External Subroutines are appended by the VisPro compiler with an auto-generated filename label (e.g., `myfunc.cmd:`), followed by the subroutine code, and end with `RETURN` or `EXIT`.
- Modification Output Rule:
  - When modifying an Event Script: Output the COMPLETE code of that specific event block (indicate the event label as a comment header, e.g., `/* Event: 261_1001.0 */`, followed by the full event code).
  - When modifying an External Subroutine: Output the COMPLETE code of that `.cmd` file WITHOUT the VisPro-generated `filename.cmd:` label, so the user can save or copy-paste it 1:1 as an external file.

## External Subroutines: Scope & Strict First-Line Rule (Error 17 Prevention)
- Label Prohibition: External `.cmd` files must NEVER contain a manual label on line 1 (VisPro generates `filename.cmd:` automatically during compilation; a manual label causes duplicate labels or breaks `Procedure`).
- `Procedure` is OPTIONAL (Scope Preservation):
  - External subroutines in VisPro often intentionally OMIT `Procedure` to retain direct read/write access to all global variables (such as `window`, form handles, and global stems).
  - CRITICAL RULE: NEVER add `Procedure` or `Procedure Expose` to an existing subroutine unless it already uses it or the user explicitly requests variable isolation! Adding `Procedure` unprompted breaks access to global variables.
- Positioning Rule (IF `Procedure` is used):
  - IF AND ONLY IF a subroutine uses `Procedure` or `Procedure Expose ...`, it MUST strictly be placed on Line 1 of the external `.cmd` file (as the very first executable statement right after the compiler-generated label). Placing any executable command before `Procedure` triggers REXX "Error 17: Unexpected PROCEDURE".

## External Function Call Syntax (Error 43 Prevention)
- VERIFIED Function Call Rule: When an external file is called as a function (with a return value and parentheses), the call MUST strictly include the `.cmd` suffix.
  - WRONG: `rc = MyFunc(var)` -> Results in REXX "Error 43: Routine not found" because REXX looks for the label `MyFunc:`.
  - CORRECT: `rc = MyFunc.cmd(var)` -> REXX finds the exact VisPro-generated label `MyFunc.cmd:`.
- Normal procedure calls via `CALL` remain unchanged since they already include the suffix: `CALL MyFunc.cmd`.

## VERIFIED PM-RACE-CONDITION WORKAROUNDS (DO NOT REFACTOR)
- Tab Creation Sequence: When creating new notebook tabs dynamically, the strict execution sequence MUST be:
  1. index = VpAddItem(...)          /* Create empty tab */
  2. CALL VpSelect ..., index        /* MUST happen BEFORE VpOpenForm to suppress the 256_1000.0 change event! */
  3. nb.index = VpOpenForm(...)      /* Load the child form */
  4. CALL VpSetItemValue ..., 'PAGE' /* Bind form to tab */
  NEVER rearrange this sequence. Moving VpSelect after VpOpenForm triggers a premature synchronous PM Event that causes fatal UI deadlocks.
</SKILL_VISPRO_REXX_CUA>

<SKILL_VISPRO_REFERENCE>
[VERIFIED COMMANDS ONLY]
## 1. Adding Elements (VpAddItem)
* At current cursor position (MLE): `CALL VpAddItem window, ITEMID, 'CURRENT', value`
* At the beginning of list/container: `index = VpAddItem(window, ITEMID, 'BEGIN', value)`
* At the beginning with icon (Container): `index = VpAddItem(window, ITEMID, 'BEGIN', value, icon)`
* At the end of list/container: `index = VpAddItem(window, ITEMID, 'END', value)`
* At the end with icon (Container): `index = VpAddItem(window, ITEMID, 'END', value, icon)`
* Multiple elements at the end: `index = VpAddItem(window, ITEMID, 'END', value1, value2)`
* After a specific element: `index = VpAddItem(window, ITEMID, index, value)`
* After an element with icon: `index = VpAddItem(window, ITEMID, index, value, icon)`
* As child element (Container Tree): `index = VpAddItem(window, ITEMID, 'CHILD', value, parent_index)`
* As child element with icon: `index = VpAddItem(window, ITEMID, 'CHILD', value, parent_index, icon)`
* Alphabetically ascending: `index = VpAddItem(window, ITEMID, 'ASCENDING', value)`
* Alphabetically descending: `index = VpAddItem(window, ITEMID, 'DESCENDING', value)`
* Bulk-load from stem: `index = VpAddItem(window, ITEMID, 'LIST', 'STEMNAME.')`
* Notebook page (Major Tab): `CALL VpAddItem window, ITEMID, 'Tab-Title', form_handle_or_0`
* Notebook page (Minor Tab): `CALL VpAddItem window, ITEMID, 'MINOR', 'Tab-Title', form_handle_or_0`
* Notebook page (No Tab): `CALL VpAddItem window, ITEMID, form_handle_or_0`

## 2. Selecting & Checking Status (VpSelect / VpIsSelected)
* Mark/check element: `CALL VpSelect window, ITEMID, 1` (lists: `CALL VpSelect window, ITEMID, index, 1`)
* Unmark: `CALL VpSelect window, ITEMID, 0` (lists: `CALL VpSelect window, ITEMID, index, 0`)
* Set checkbox to 3-State: `CALL VpSelect window, ITEMID, 2`
* Mark all list elements: `CALL VpSelect window, ITEMID, 'ALL', 1`
* Unmark all: `CALL VpSelect window, ITEMID, 'ALL', 0`
* Mark ValueSet cell: `CALL VpSelect window, ITEMID, row, column`
* Mark text range (MLE): `CALL VpSelect window, ITEMID, first_char, last_char`
* Check if button/checkbox is active: `value = VpIsSelected(window, ITEMID)` (Returns 1 for True, 0 for False)
* Check if list element is selected: `value = VpIsSelected(window, ITEMID, index)`

## 3. Deleting Elements (VpDeleteItem)
* Delete specific element/page: `CALL VpDeleteItem window, ITEMID, index`
* Delete all elements: `CALL VpDeleteItem window, ITEMID, 'ALL'`

## 4. Querying Indices & Counters (VpGetIndex / VpGetItemCount)
* Query element count: `count = VpGetItemCount(window, ITEMID)`
* First element index: `index = VpGetIndex(window, ITEMID, 'NEXT', 0)`
* Next element index: `index = VpGetIndex(window, ITEMID, 'NEXT', current_index)`
* First selected element: `index = VpGetIndex(window, ITEMID, 'SELECTED', 0)`
* Next selected element: `index = VpGetIndex(window, ITEMID, 'SELECTED', current_index)`
* First child element: `index = VpGetIndex(window, ITEMID, 'FIRSTCHILD', parent_index)`
* Parent element: `index = VpGetIndex(window, ITEMID, 'PARENT', child_index)`
* Search (index by string): `index = VpGetIndex(window, ITEMID, 'SEARCH', search_string)`
* Load all selected indices into stem: `CALL VpGetIndex window, ITEMID, 'ALLSELECTED', 'STEMNAME.'`
* Notebook pages: `index = VpGetIndex(window, ITEMID, 'FIRST' | 'NEXT' | 'TOP')`
* Text search in MLE (first/last bounds): `range = VpGetIndex(window, ITEMID, search_string)`
* ValueSet coordinates: `rowcol = VpGetIndex(window, ITEMID)` (returns string "row col")

## 5. Setting and Reading Values (VpSetItemValue / VpGetItemValue)
* Set generic value: `CALL VpSetItemValue window, ITEMID, value`
* Read generic value: `value = VpGetItemValue(window, ITEMID)`
* Set value at specific index: `CALL VpSetItemValue window, ITEMID, value, index`
* Container Details VERIFIED Workaround: Updating a single row in a multi-column Container (DETAILS view) requires a specific asymmetry between getting and setting values.
  * Rule: While `VpGetItemValue` strictly requires the 'DETAILS' keyword to read a row into a stem, `VpSetItemValue` MUST omit the 'DETAILS' keyword and directly pass the stem name string (including the dot) alongside the index for an in-place update.
  * Crash Prevention: Never use `VpAddItem` with a relative index (e.g., `sel_idx - 1`) and a stem array (Error 40). Do not delete/recreate rows; use in-place updates.
  * Code Pattern:
    `CALL VpGetItemValue window, ITEMID, 'DETAILS', 'crow.', index`
    `crow.1 = '[X]'`
    `CALL VpSetItemValue window, ITEMID, 'crow.', index`
* Read value at index: `value = VpGetItemValue(window, ITEMID, '', index)`
* Load all values into stem: `CALL VpGetItemValue window, ITEMID, 'ALL', 'STEMNAME.'`
* Read container columns into stem: `CALL VpGetItemValue window, ITEMID, 'DETAILS', 'STEMNAME.', index`
* Set cell value (ValueSet): `CALL VpSetItemValue window, ITEMID, value, row, column`
* Set graphic data (Charts): `rc = VpSetItemValue(window, ITEMID, 'DATA.')`
* Tab/Status text in Notebook: `CALL VpSetItemValue window, ITEMID, 'TABTEXT' | 'STATUSTEXT', index, string`
* Set slider arm position: `CALL VpSetItemValue window, ITEMID, 'SETARMPOS', tick_index`
* Read slider range: `value = VpGetItemValue(window, ITEMID, 'GETRANGE')`

## 6. Layout, Position & Styles (VpItem / VpSetItemStyle)
* Disable / Enable: `CALL VpItem window, ITEMID, 'DISABLE' | 'ENABLE'`
* Hide / Show: `CALL VpItem window, ITEMID, 'HIDE' | 'SHOW'`
* Request focus: `CALL VpItem window, ITEMID, 'FOCUS'`
* Suspend/force redraw: `CALL VpItem window, ITEMID, 'DISABLEPAINT' | 'ENABLEPAINT' | 'PAINT'`
* Read position and size: `xy = VpItem(window, ITEMID, 'ITEMPOS' | 'ITEMSIZE')`
* Set position and size: `CALL VpItem window, ITEMID, 'SETITEMPOS', x, y, width, height`
* Read font: `font = VpItem(window, ITEMID, 'GETFONT')`
* Change font: `CALL VpItem window, ITEMID, 'FONT', '12.Helv'`
* Set colors (text/RGB): `CALL VpItem window, ITEMID, 'BACKCOLOR' | 'FORECOLOR' | 'BACKCOLORRGB' | 'FORECOLORRGB', color_value`
* Read PM Window Handle: `handle = VpItem(window, ITEMID, 'GETHANDLE')`
* Send PM Message directly: `rc = VpItem(window, ITEMID, 'SENDMSG', hex_message, [param1, param2...])`
* Change Container View: `rc = VpSetItemStyle(window, ITEMID, 'DETAILS' | 'ICON' | 'TREE' | 'TEXTTREE')`
* Change Column Title: `rc = VpSetItemStyle(window, ITEMID, 'COLNAME', col_number, 'Column Name')`
* Read & calculate RGB colors: `color = VpItem(window, ITEMID, 'GETBACKCOLORRGB' | 'GETFORECOLORRGB')`
  (Note: OS/2 stores colors as `COLOR = R * 65536 + G * 256 + B`. Reverse math: `R = col % 65536`, `G = (col // 65536) % 256`, `B = col // 256`)
* Multi-Column Containers: Define `.0` index, pass stem string WITH trailing dot: `Call VpAddItem window, 1000, 'END', 'ZEILE.'`
* Container Splitbar Disable: `rc = VpSetItemStyle(window, itemid, 'SPLITBAR', 0, -1)`

## 7. Window Management (VpWindow & Dialogs)
* Position/scale window: `CALL VpWindow window, 'SETPOS', x, y, xsize, ysize`
* Read window size/position: `xy = VpWindow(window, 'GETSIZE' | 'GETPOS')` (Parse with `PARSE VAR xy x y`)
* Hide/show/activate window: `CALL VpWindow window, 'HIDE' | 'SHOW' | 'ACTIVATE'`
* Minimize/maximize/restore: `CALL VpWindow window, 'MINIMIZE' | 'MAXIMIZE' | 'RESTORE'`
* Close/destroy window: `CALL VpWindow window, 'CLOSE' | 'DESTROY'`
* Set window title: `CALL VpWindow window, 'SETTITLE', value`
* Call pop-up menu: `CALL VpWindow window, 'POPUP', menuid`
* Query screen resolution: `xy = VpWindow(window, 'GETSCREENSIZE')` (Parse with `PARSE VAR xy x y`)
* Change mouse pointer: `CALL VpWindow window, 'SETPOINTER', icon_handle`
* Copy to clipboard: `CALL VpWindow window, 'SETCLIPBOARD', 'TEXT', value`
* Paste from clipboard: `value = VpWindow(window, 'GETCLIPBOARD', 'TEXT')`
* Start/change timer: `CALL VpWindow window, 'STARTTIMER', duration_in_ms`
* Stop timer: `CALL VpWindow window, 'STOPTIMER'`
* Enable/Disable STDIO window: `CALL VpWindow window, 'ENABLESTDIO' | 'DISABLESTDIO'`
* Call Message Box: `answer = VpMessageBox(window, 'Title', 'Text', 'YESNOCNCL ICONQUESTION')`
  * Valid calls:
    * `rc = VpMessageBox(window, 'title', 'message')`
    * `rc = VpMessageBox(window, 'title', 'message', 'OKCNCL')`
    * `rc = VpMessageBox(window, 'title', 'message', 'YESNO')`
    * `rc = VpMessageBox(window, 'title', 'message', 'YESNOCNCL')`
    * `rc = VpMessageBox(window, 'title', 'message', 'RETRY')`
    * `rc = VpMessageBox(window, 'title', 'message', 'IGNORE')`
  * Invalid call:
    * `rc = VpMessageBox(window, 'title', 'message', 'OK')`

## 8. File & Picture Operations (VpLoadPicture / VpFileDialog)
* Load picture from file: `handle = VpLoadPicture('FILE', filename)`
* Load system icon: `handle = VpLoadPicture('SYSICON', icon_index_1_to_22)`
* Load resource icon: `handle = VpLoadPicture('RESICON', resource_id)`
* Remove graphic from memory: `CALL VpFreePicture handle`
* Load file content into object: `CALL VpLoadFromFile window, ITEMID, filename`
* Call file open/save dialog: `filename = VpFileDialog(window, 'SAVE' | 'OPEN', '*.*', 'Title')`

## 9. Drawing (VpDraw) - Free-form Windows
* Set start point: `rc = VpDraw(window, ITEMID, 'MOVE', x, y)`
* Draw line / box / text: `rc = VpDraw(window, ITEMID, 'LINE' | 'BOX' | 'TEXT', x, y)`
* Manage paths: `rc = VpDraw(window, ITEMID, 'BEGINPATH' | 'ENDPATH' | 'FILLPATH' | 'STROKEPATH')`

## 10. Container DETAILS View - Single vs. Multi-Column Behavior [VERIFIED]
VisPro/REXX dynamically changes how it handles Container Controls in DETAILS view based on the number of configured text columns:
1. Multi-Column (>= 2 text columns):
- You MUST use a stem name string (with trailing dot) for insertion.
- Example: 
 row.0 = 3  /* 1=Hidden Icon, 2=Text1, 3=Text2 */
 row.1 = ''
 row.2 = 'Data 1'
 row.3 = 'Data 2'
 CALL VpAddItem window, ITEMID, 'END', 'row.'
2. Single-Column (Exactly 1 text column):
- VisPro internally downgrades the container to a simple list.
- You MUST NOT pass a stem name string. Doing so will print the literal string (e.g., "row.") into the UI.
- You MUST pass the text variable directly.
- Example: 
 CALL VpAddItem window, ITEMID, 'END', string_variable
- Reading Data: When querying a single-column container via `CALL VpGetItemValue window, ITEMID, 'DETAILS', 'crow.', index`, the text value is found in `crow.1` (VisPro does not count hidden columns when reading back single-column containers).

## 11. Spinbutton Control Rules
- VERIFIED Indexing: Spinbuttons strictly use 0-based indices. Index 0 represents the first list entry, Index 1 the second, and so forth.
- Bulk Loading: ALWAYS use `CALL VpAddItem window, ITEMID, 'LIST', 'STEM.'` to populate the Spinbutton entirely in a single operation. Do not loop or use manual search operations.
- Value Setting (Critical): The command `VpSetItemValue` strictly expects the numeric INDEX of the desired entry, NEVER the literal text/string value.
- NO STRING SEARCHING (VERIFIED): The Spinbutton control DOES NOT support searching by string values. Standard search commands like `VpGetIndex(window, ITEMID, 'SEARCH', search_string)` are unsupported and will fail. You MUST calculate, map, or track the numeric index directly.
- Index Mapping Workaround: When mapping continuous numeric data (e.g., temperatures 0.0 to 2.0) to a Spinbutton selection, calculate the target index mathematically rather than attempting to search. Example: `index = Trunc(saved_temp * 10); CALL VpSetItemValue window, ITEMID, index`.

[VERIFIED] VisPro DropDownListbox Behavior:
Unlike Spinbuttons which require a 0-based integer index, DropDownListboxes strictly require the exact literal string value for selection via VpSetItemValue(window, ITEMID, 'Literal String'). Using an index (e.g., 0) fails.

[VERIFIED] VisPro Container Details Stem Mapping:
When using VpAddItem with a stem (e.g., 'row.') to populate a multi-column container in DETAILS view, VisPro skips Column 1 for the stem data. Column 1 (the object's core string) is left empty or populated strictly by the passed Icon handle.
The stem mapping strictly begins at Column 2. Therefore, stem.1 populates Column 2, stem.2 populates Column 3, and so on.

</SKILL_VISPRO_REFERENCE>

<SKILL_UPDATE>
- Modification Output Rule:
  - When modifying an Event Script: Output the COMPLETE code of that specific event block WITHOUT the VisPro-generated event label prefix (e.g., omit `256_Form.1:`). Start strictly with the user's exact original descriptive header comment on line 1 (e.g., `/* Form Opened - Init */`) down to the final `RETURN`, so it can be copy-pasted 1:1 into the VisPro IDE event editor.
  - When modifying an External Subroutine: Output the COMPLETE code of that `.cmd` file WITHOUT the VisPro-generated `filename.cmd:` label, starting with the user's original header comment on line 1.
## VERIFIED OS/2 PM MLE Indexing (IPT vs. CRLF Offset Drift)
- Bug Description: `VpGetItemValue(window, MLE_ID)` exports text with `CRLF` (`'0d0a'x`, 2 bytes per line break). However, `VpGetIndex(window, MLE_ID)` and `VpSelect window, MLE_ID, first, last` operate on internal PM Insertion Points (IPT), where every line break counts as a **single** character (`'0a'x`, 1 byte).
- Impact: Using REXX `Pos()` on raw `VpGetItemValue` strings causes the selection offset to drift forward by +1 character for every preceding line break in the MLE.
- Mitigation Rule: Before calculating character positions for `VpSelect` in an MLE, strip all `'0d'x` characters from the search string (`clean_str = Translate(raw_str, '', '0d'x)`). The resulting 1-based REXX string indices map directly to 0-based PM IPT indices (`mle_start = rexx_start - 1`, `mle_end = rexx_end`).
</SKILL_UPDATE>