Load images and select a task.
@@ -77,7 +77,7 @@
Image Measurement family
Image Library
API Reference
-Functions And API Open a function for exact MATLAB syntax, arguments, outputs, behavior, and source.
+Functions And API Open a function for exact MATLAB syntax, arguments, outputs, behavior, and source.
Generated from tracked Markdown and MATLAB source contracts.
diff --git a/site/apps/labkit-core/figure-studio.html b/site/apps/labkit-core/figure-studio.html
index 7b310bdfc..ac22c91d7 100644
--- a/site/apps/labkit-core/figure-studio.html
+++ b/site/apps/labkit-core/figure-studio.html
@@ -34,9 +34,9 @@
For a source using FIG default , Figure Studio adopts its font, semantic line widths, annotations, legend, grid, and axes appearance. Selecting LabKit figure applies the calibrated single-panel publication hierarchy while retaining the source legend placement. Switching a text preset never changes the selected plot-frame width, aspect, or export scale.
Style And Canvas Controls
Group Controls Text Style preset, all/title/axis-label/tick/annotation font size, X tick-label angle, grid alpha, grid visibility Lines + Boundaries data, uncertainty, main-graphic boundary, reference-line, and axes line widths Legend source/on/off display, location, font size, columns, and border Plot frame reference, 6:5, 4:3, 16:9, 1:1, 3:2, or source aspect; a fixed plot width or source size; export supersampling; top/right frame visibility
-Changing All font updates title, axis-label, tick, annotation, and legend sizes together. Individual controls then provide category-level refinement. LabKit figure uses a calibrated 900-by-725 px reference plot frame : 45 pt title, axis-label, tick, annotation, and legend text; 6.5 pt data strokes; 2 pt uncertainty strokes; and 1.5 pt boundary, reference, and axes strokes. The default uses a complete top/right frame with no grid and no legend box. Legend samples use the reference's long line tokens. These editable baselines come from normalized pixel measurements across all nine panels of the maintained 3-by-3 visual reference, not from a single imported FIG. The configured width and aspect always describe the inside of the axes frame. Figure Studio calculates the enclosing figure's outer margins from the current title, labels, ticks, legend, and visible annotations, so changing a long label cannot silently shrink the data region. Empty ruler text is ignored, including the zero-area placeholders MATLAB exposes on logarithmic axes. Choose Source size or one of 640, 720, 900, 960, 1200, 1237, 1364, 1600, or 2400 px; an aspect choice sets the paired plot-frame height. The workbench preview is a real interactive axes, not a raster image. When its allotted screen area changes, Studio reflows only the display text and strokes; project settings and export proportions remain unchanged. This calibration is validated on both 72-PPI Linux renderers and 96-PPI desktop MATLAB so display-density differences do not restyle exports. Export x is raster supersampling only. Top/right frame only shows or hides those two axes edges; it never changes the plot-frame size.
+Changing All font updates title, axis-label, tick, annotation, and legend sizes together. Individual controls then provide category-level refinement. LabKit figure uses a calibrated 900-by-725 px reference plot frame : 45 pt title, axis-label, tick, annotation, and legend text; 6.5 pt data strokes; 2 pt uncertainty strokes; and 1.5 pt boundary, reference, and axes strokes. The default uses a complete top/right frame with no grid and no legend box. Legend samples use the reference's long line tokens. These editable baselines come from normalized pixel measurements across all nine panels of the maintained 3-by-3 visual reference, not from a single imported FIG. The configured width and aspect always describe the inside of the axes frame. Figure Studio calculates the enclosing figure's outer margins from the current title, labels, ticks, legend, and visible annotations, so changing a long label cannot silently shrink the data region. Empty ruler text is ignored, including the zero-area placeholders MATLAB exposes on logarithmic axes. If a Windows desktop refuses the requested hidden figure size, Studio recomputes the plot frame from the accepted canvas after reserving measured label and tick insets plus a typography-derived minimum outer margin. Older Windows print releases reserve one additional text line because their pre-print screen extent can omit that line from the hardcopy bounds. Choose Source size or one of 640, 720, 900, 960, 1200, 1237, 1364, 1600, or 2400 px; an aspect choice sets the paired plot-frame height. The workbench preview is a real interactive axes, not a raster image. When its allotted screen area changes, Studio reflows only the display text and strokes; project settings and export proportions remain unchanged. This calibration is validated on both 72-PPI Linux renderers and 96-PPI desktop MATLAB so display-density differences do not restyle exports. Export x is raster supersampling only. Top/right frame only shows or hides those two axes edges; it never changes the plot-frame size.
Font controls use 0.5 pt steps and line controls use 0.1 pt steps. The preset styles every existing category but does not create or move a legend by default, because either action can cover data. Use the Legend panel for those explicit layout changes. X tick labels retains the source angle, makes labels horizontal, or rotates them 45 degrees.
-PNG and JPG resolution is 300 * Export x, with a minimum of 72 dpi. SVG uses vector content. FIG default records the source plot-frame ratio as its reference, so reopening a source does not rescale its original typography. If limits are stale after copying or editing, use Recalculate X/Y limits to fit the visible graphics, update the interactive viewport, and refresh the four limit inputs. The X/Y minimum and maximum controls accept values within the finite visible-data extent expanded by exactly 50% on both sides; they do not change the source calculation.
+PNG and JPG resolution is 300 * Export x, with a minimum of 72 dpi. SVG uses vector content. On MATLAB releases before R2025a, Figure Studio uses MATLAB's native print exporter to retain the complete styled figure because exportgraphics does not yet support figure padding or SVG. FIG default records the source plot-frame ratio as its reference, so reopening a source does not rescale its original typography. If limits are stale after copying or editing, use Recalculate X/Y limits to fit the visible graphics, update the interactive viewport, and refresh the four limit inputs. The X/Y minimum and maximum controls accept values within the finite visible-data extent expanded by exactly 50% on both sides; they do not change the source calculation.
Quick Exports
The Figures tab provides:
+
Generated from tracked Markdown and MATLAB source contracts.
diff --git a/site/assets/search-index.js b/site/assets/search-index.js
index b625cd957..d4857bad8 100644
--- a/site/assets/search-index.js
+++ b/site/assets/search-index.js
@@ -1 +1 @@
-window.LABKIT_SEARCH_INDEX = [{"title":"LabKit Documentation","url":"index.html","kind":"overview","section":"general","keywords":"LabKit Documentation","text":"LabKit Documentation LabKit documentation is organized by the question you are trying to answer. Start with a task guide, then open an API reference only when you need exact MATLAB call syntax or returned data shapes. Choose A Starting Point I want to Start here Install, update, or open LabKit [Getting started](getting-started/README.md) Understand every launcher action or call it from MATLAB [LabKit Launcher](apps/labkit-core/launcher/README.md) Choose an app and understand its inputs and outputs [App guide](apps/README.md) Call a reusable `labkit.*` function [Public API reference](reference/README.md) Understand ownership and package boundaries [Architecture](development/build-apps/architecture.md) Create or modify an app [App development](development/build-apps/app-development.md) Run tests or diagnose performance [Testing](development/maintain-and-release/testing.md) Call packaging, profiling, codecheck, or documentation tools [Maintainer tools](development/tools/README.md) Understand documentation sources and generated HTML [Documentation system](development/maintain-and-release/documentation.md) Maintain a private app workspace [Private apps](development/maintain-and-release/private-apps.md) Prepare a release [Release process](development/maintain-and-release/release.md) Documentation Layers getting-started/ installation, launcher, updates, and first-run concepts apps/ one directory per family and one subdirectory per app including the LabKit Launcher under LabKit Core framework/ UI runtime concepts, behavior, and app-authoring contracts libraries/ one directory per reusable public MATLAB facade reference/ generated-function reference landing page development/ reader-task folders for app building, maintenance, and tools history/ chronological change records and the project history index Every Markdown file is published automatically. Its path owns navigation and its first level-one heading owns the page title. Public App manuals are matched to `labkit_launcher(\"list\")`; App-owned function pages are discovered from complete public MATLAB help contracts. Reference Conventions Public function pages follow the MATLAB reference pattern: summary and syntax description and behavior input arguments and options output arguments and data shapes algorithms or scientific semantics where relevant errors and limitations executable examples version history, See Also, and related topics Each app family has a landing page. Each concrete app owns a directory whose `README.md` is its Get Started and detailed behavior page; complex apps can add focused workflow, file-format, or algorithm topics beside it. App pages explain launch, task flow, interaction rules, inputs, outputs, persistence, scientific meaning, non-GUI APIs, errors, limitations, examples, related topics, and component history as applicable. They do not document internal callbacks. Framework and library landing pages follow the Qt module pattern: overview and ownership first, followed by grouped concepts, supported public members, detailed behavior, examples, and related modules. Generated function pages are owned by MATLAB source help blocks. Handwritten HTML is never a source. Project History And Support [Project history](history/README.md) lists all change records and connects them to the affected apps, framework, and libraries. [Support](../.github/SUPPORT.md) explains how to report workflow problems."},{"title":"EIS","url":"apps/electrochemistry/eis.html","kind":"app","section":"apps","keywords":"apps electrochemistry eis EIS","text":"EIS EIS overlays impedance data from one or more Gamry `ZCURVE` tables, supports Nyquist and Bode-style axis combinations, and exports the values currently selected for plotting. Requirements And Launch labkit_EIS_app Inputs Add one or more `.DTA` files containing a readable EIS `ZCURVE`. Files that do not contain the required curve are reported and omitted from the plot. The successfully decoded source list and its order are preserved in project state through portable references. Basic Workflow Add the EIS DTA files. Choose X and Y quantities. Choose **mΩ**, **Ω**, **kΩ**, or **MΩ** for impedance axes. New projects default to **kΩ**. Enable logarithmic X or Y scaling only for strictly positive plotted data. Use **Fit X/Y limits** to re-estimate independent limits from the current data, or **Use equal X/Y scale** when equal data units are wanted. Adjust marker, line, grid, and legend presentation. Export the current plot data CSV. Axis Quantities The available quantities are frequency, log10 frequency, time, point number, real impedance, imaginary impedance, negative imaginary impedance, impedance magnitude, phase, DC current, and DC voltage. Default axes are `Zreal` and `-Zimag`, displayed in kΩ for a new project. Use `Zreal` versus `-Zimag` for the conventional Nyquist orientation. Use frequency versus magnitude or phase for Bode-style views. The log-axis checkbox changes MATLAB axes scaling; choosing `log10(Freq)` changes the data coordinate itself. Do not apply both transformations unless that is explicitly intended. Plot Parameters Parameter Default Impedance unit kΩ Line width 1.4 Marker size 6 Show markers on Log X / Log Y off / off Legend / Grid on / on The app never infers an equal aspect ratio from the selected quantities: a Nyquist plot starts with independently fitted limits. Use **Use equal X/Y scale** only when equal data units are useful for the current comparison. Use **Fit X/Y limits** to return to independent limits after equal scaling or a manual zoom. Equal scaling expands a fitted limit when necessary so X and Y data units have the same on-screen length; it is a one-time reset and does not constrain later wheel zooming. Axis and styling changes preserve the current source set and the current viewport; the two view buttons explicitly replace that viewport. Output **Export current plot CSV** writes the selected X/Y values for each valid file on a shared row index. Each file retains its own X and Y pair, so unequal curve lengths do not imply interpolation. Impedance columns use the selected display unit and include an ASCII unit suffix such as `kohm` in the column name. A result manifest records the selected axes, impedance unit, plot parameters, source references, and output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"spectrum.DTA\", \"eis\"); assert(status.ok, status.message); units = eis.impedanceDisplay.catalog(); x = eis.analysisRun.valuesForAxis(item, \"Zreal\", units.choices(3)); y = eis.analysisRun.valuesForAxis(item, \"-Zimag\", units.choices(3)); plot(x, y, \"o-\"); axis equal `valuesForAxis` is app-owned and not currently part of the published app API catalog. The DTA loader and `getZCurve` are supported reusable APIs. Errors And Limitations Log axes omit or reject nonpositive coordinates according to MATLAB axes behavior; inspect the data rather than treating missing points as zero. Overlaying files does not normalize electrode area or fixture geometry. Changing the impedance display unit rescales impedance axes and exported impedance columns; it does not alter the DTA values stored in base ohms. Axis labels describe parsed DTA columns; they do not validate the experiment configuration recorded by the instrument. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md)"},{"title":"2.1: RHS apps and shared runtime stability","url":"history/records/2026/06/LK-20260621-v2-1-rhs-and-runtime-stability.html","kind":"history","section":"history","keywords":"LK-20260621-v2-1-rhs-and-runtime-stability 2026-06-21 7 feat compatible historical project evolution","text":"2.1: RHS apps and shared runtime stability id: LK-20260621-v2-1-rhs-and-runtime-stability date: 2026-06-21 sequence: 7 type: feat compatibility: compatible scope: historical project evolution Context After v2.0, the UI runtime supported several app families but still repeated important behavior around busy actions, path events, preview zoom, and layout ownership. A new neurophysiology family would amplify those inconsistencies if each app solved them independently. Decision and rationale Add RHS reading and review as a first-class app family while centralizing only the runtime mechanics shared across domains. Filtering choices and response analysis remained in the RHS workflows; transactions, path events, zoom, and layout ownership belonged to the UI layer. Changes Added RHS Preview, Nerve Response Analysis, and Response Review Stats. Folded the separate screening step into preview filtering so file review used one workflow rather than two overlapping stages. Added duplicate Batch Crop tasks for repeated crop layouts. Unified preview scroll zoom and centralized action busy transactions. Centralized path-event contracts and app layout ownership. Added a ZIP-based updater and stabilized image and app interactions before release. Reduced focused test discovery and guardrail cost as the repository grew. User and data impact Neurophysiology users gained file preview, response analysis, and review statistics apps. Existing users saw more consistent zoom, busy feedback, path updates, and responsive layout behavior. The updater provided a repository- owned way to install a packaged release. Compatibility and migration The RHS family was additive. UI implementations that maintained their own copies of busy, path, or layout mechanics were migrated to the shared runtime contracts. Validation The stage added RHS-focused tests, interaction regressions, compatibility input checks, path-event checks, and faster focused test routing. Tag `2.1` points to `76ddf7d0`. Evidence RHS app family `7ddc036f` and preview filtering `ca8a37dd`. Shared interaction mechanics `eb52eb17`, `f3e42b8e`, `1fe89ba2`. Layout ownership `3ce4c8f7`, `09baf7fe`. Updater `e4b02f3a`. 2.1 release stabilization `76ddf7d0`. Known limitations and follow-up The updater still depended on how the launcher was installed, and several image workflows reset zoom or recomputed previews during edits. The following 2.2 and 2.3 releases concentrated on self-contained launch and direct image manipulation."},{"title":"Adjacent navigation for history records","url":"history/records/2026/07/LK-20260716-history-adjacent-navigation.html","kind":"history","section":"history","keywords":"LK-20260716-history-adjacent-navigation 2026-07-16 64 docs compatible documentation `tools/docs/` `site/history/records/`","text":"Adjacent navigation for history records id: LK-20260716-history-adjacent-navigation date: 2026-07-16 sequence: 64 type: docs compatibility: compatible component: `documentation` scope: `tools/docs/` scope: `site/history/records/` Context The complete timeline linked every recorded change, but a reader who opened one detail page had to return to the index before continuing chronologically. Dates and filenames could not safely identify adjacent records because several changes may share one date. Decision and rationale Use validated history `sequence` metadata to add adjacent-record links at the end of every rendered history detail page. **Previous change** means the record with `sequence - 1`; **Next change** means `sequence + 1`. This preserves one unambiguous project-history order without binding authored content to Git. Changes Added responsive previous/next navigation immediately before the generated page footer. Showed only existing directions at the oldest and newest ends. Added regression coverage for link targets, ordering, and page placement. User and data impact Readers can traverse project history linearly without returning to the index. No MATLAB runtime, app state, scientific data, or exports change. Compatibility and migration Existing history URLs and record metadata remain valid. Regenerating `site/` adds navigation to every record page and updates the search index normally. Validation Renderer regression tests verify middle and endpoint records against source sequence metadata. The documentation consistency check independently rebuilds and byte-compares the tracked site. Evidence `renderLabKitHistorySequenceNavigation` owns adjacent link generation. `DocumentationRendererRegressionTest` verifies the generated navigation. Known limitations and follow-up Navigation is intentionally chronological across all project components. It does not filter to the current app or library; component-specific history remains available from each component manual."},{"title":"App SDK explicit contract replaces the retired UI runtime","url":"history/records/2026/07/LK-20260719-ui-explicit-contract-migration.html","kind":"history","section":"history","keywords":"LK-20260719-ui-explicit-contract-migration 2026-07-19 138 refactor breaking labkit.app labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app App Framework DIC Electrochem Gait Image Measurement LabKit Core Neurophysiology Statistics Wearable Project persistence Result provenance","text":"App SDK explicit contract replaces the retired UI runtime id: LK-20260719-ui-explicit-contract-migration date: 2026-07-19 sequence: 138 type: refactor compatibility: breaking component: `labkit.app` | `new -> 1.0.0` component: `labkit_DICPostprocess_app` | `1.4.7 -> 1.5.0` component: `labkit_DICPreprocess_app` | `1.5.8 -> 1.6.0` component: `labkit_ChronoOverlay_app` | `1.4.7 -> 1.5.0` component: `labkit_CIC_app` | `1.4.7 -> 1.5.0` component: `labkit_CSC_app` | `1.4.8 -> 1.5.0` component: `labkit_EIS_app` | `1.4.7 -> 1.5.0` component: `labkit_VTResistance_app` | `1.4.7 -> 1.5.0` component: `labkit_GaitAnalysis_app` | `2.0.8 -> 2.1.0` component: `labkit_BatchImageCrop_app` | `1.7.7 -> 1.8.0` component: `labkit_CurvatureMeasurement_app` | `1.4.6 -> 1.5.0` component: `labkit_FLIRThermal_app` | `1.4.8 -> 1.5.0` component: `labkit_FocusStack_app` | `1.5.6 -> 1.6.0` component: `labkit_ImageEnhance_app` | `1.6.7 -> 1.7.0` component: `labkit_ImageMatch_app` | `1.6.8 -> 1.7.0` component: `labkit_VideoMarker_app` | `1.5.7 -> 1.6.0` component: `labkit_FigureStudio_app` | `0.2.9 -> 0.3.0` component: `labkit_NerveResponseAnalysis_app` | `1.4.8 -> 1.5.0` component: `labkit_ResponseReviewStats_app` | `1.4.7 -> 1.5.0` component: `labkit_RHSPreview_app` | `1.4.6 -> 1.5.0` component: `labkit_TTestWizard_app` | `1.0.1 -> 1.1.0` component: `labkit_ECGPrint_app` | `1.4.6 -> 1.5.0` scope: App Framework scope: DIC scope: Electrochem scope: Gait scope: Image Measurement scope: LabKit Core scope: Neurophysiology scope: Statistics scope: Wearable scope: Project persistence scope: Result provenance Context The retired UI runtime removed substantial per-App lifecycle code, but Apps still registered callback tables, repeated bound values in presenters, authored standard file add/remove/clear behavior, and depended on nested event/service structs. A replacement SDK kernel had already established immutable semantic values, pre-GUI validation, transactional state/presentation commits, project documents, result manifests, resources, and portable sources. The migration then had to restore the complete behavior and visual contract of every tracked App before the retired production facade could be deleted. Decision and rationale Create `labkit.app` as the stable SDK rather than misnaming the expanded contract `labkit.ui` or adapting it back to Runtime V2 transport structs. Keep the public root small, partition authoring by capability, and concentrate complexity in a paved path: direct-callback `layout.*` nodes, strict bindings, runtime-completed `view.Snapshot` values, standard file lifecycle, fixed `CreateSession(project,context)`, and private native adapters. Migrate all 21 tracked Apps through capability waves, treating their previous controls, tabs, layout proportions, interactions, project behavior, results, debug samples, and workflow wording as product contracts rather than reducing the task to launch compatibility. Changes Added the private native MATLAB adapter with semantic component ownership, typed RuntimeKernel callbacks, native dialog results, complete-presentation reconciliation, and rollback to the previous native view after a failed renderer commit. Added `Definition.launch`, fixed renderer `(axes,model)` dispatch, semantic labels, runtime-owned file add/remove/clear and selection, and transient session rebuild after source collection changes. Added strict table view options, typed complete-data edits, and distinct `event.TableCellEdit`, `event.TableCellSelection`, and `event.ListSelection` values; the private adapter absorbs native MATLAB table-value differences. Fixed session construction to `CreateSession(project,context)` so Apps resolve opaque portable sources without reading their representation. Migrated Chrono Overlay to one directly bound export callback, four state bindings, one directly bound two-axis renderer, and a two-operation view snapshot. Partitioned the public SDK into `layout`, `view`, `event`, `project`, `result`, and `dialog`; layout nodes, option parsing, stores, adapters, and runtime execution remain hidden under `internal`. Reduced Chrono's noncomment layout/action/presenter code from 277 lines to 86 while preserving its DTA alignment, plot options, project schema, CSV columns, and result provenance. Migrated T-Test Wizard as the typed editable-table, feature-fragment, and multi-page workspace proof: table selections and edits have explicit payload classes, `+workbench` exposes product assembly, workflow packages own their layout/presentation/actions, and the private adapter owns concrete layout. Migrated VT Resistance to direct file and analysis-setting bindings, a complete summary/table/two-axis snapshot, and an App-owned result-package export. Plot renderers and scientific choices now live with their owning analysis capabilities instead of a technical UI package. Migrated Gait Analysis to capability-owned source adoption, option invalidation, deterministic analysis, step selection/navigation, three-axis rendering, CSV-set export, and result packaging. Migrated DIC Preprocess to two role-bound image sources, paired-anchor registration, managed crop and mask editors, two-axis rendering, edit replay, and result-package exports owned by its analysis, mask, and result capabilities. Migrated Batch Image Crop to framework-owned source selection plus App-owned duplicate crop tasks, direct crop-center and scale-reference interactions, capability snapshots, and result-package export. Added `labkit.app.project.sourceRecord` so pure payload migrations can convert legacy paths into portable sources without constructing the framework-owned reference representation. Corrected folder chooser dispatch to its one-path backend contract and applied table data before table selection during native reconciliation, so a selection may legally target rows introduced by the same snapshot. Preserved cell-valued interaction payloads in the transactional event queue and kept multi-target interaction bridge specifications scalar, enabling paired-anchor gestures across two semantic axes. Removed handler objects, callback tables, renderer registries, and their forwarding from the App authoring contract. Layout controls and plot areas reference concrete functions directly. Migrated the remaining electrochemistry, DIC, image-measurement, neurophysiology, core, wearable, and high-state workflows, including editable tables, workspace pages, file roles, multi-axis plots, managed point/rectangle/interval/scale interactions, long-lived video resources, project recovery, result packages, and synthetic diagnostic samples. Restored shared product presentation: versioned titles and dirty markers, startup progress and failure surfaces, guarded close behavior, utility menus, adjustable pane dividers, scroll and grow policies, numeric panners, adaptive action grids, usage text, plot navigation, pop-out/export tools, and viewport-preserving overlays. Internalized contract compilation, runtime construction, native platform plans, target inventories, and callback-context creation. `Definition` is the sole author-created root; `CallbackContext` is a sealed runtime-injected port; optional concepts remain grouped under purpose-specific packages. Deleted the complete retired `labkit.ui` production facade and the migration-only analyzer, prototypes, compatibility tools, and debt-only tests after source scans and focused App tests proved that no production consumer remained. User and data impact Chrono Overlay retains its input formats, pulse-gap alignment, plot meanings, parameter defaults, CSV table, and version-2 project payload. T-Test Wizard retains its source formats, group/test calculations, plot meaning, two CSV exports, and version-2 project payload. VT Resistance retains its pulse detection, resistance calculations, plot semantics, CSV schema, and version-1 project payload while recomputing the decoded batch under shared settings. Gait Analysis retains its Video Marker payload contract, project migrations, step segmentation, gait metrics, CSV set, and version-3 project payload. File identities and portable paths remain runtime-owned. DIC Preprocess retains its rigid registration, common crop, mask editing, image/mask exports, and version-1 project payload. Batch Image Crop retains duplicate tasks per source, fixed-pixel and physical crops, rotation/padding, scale calibration, scale-bar placement, image/CSV exports, and its version-2 payload. Existing payload migrations remain App-owned. The other 15 Apps retain their documented source formats, scientific calculations, units, thresholds, project payload versions, export schemas, and result meanings. Their product versions advance once from the `main` baseline to identify the new App SDK source contract and restored complete UI behavior. No project payload version was increased merely because the UI framework changed. Compatibility and migration `labkit.app` 1 is a source-breaking replacement contract for App definitions, presenters, callbacks, events, and interactions. Every tracked App migrated before the retired `labkit.ui` boundary was deleted; there is no runtime adapter or dual authoring surface. Public App entrypoint commands remain stable. Project documents retain their format and App payload versions independently of the facade transition. Validation Focused headless tests cover strict values, transactional runtime behavior, project save/restore, authoring defaults, and Chrono calculations/exports. Hidden GUI tests cover native semantic construction, typed control and table callbacks, bound side effects, standard file lifecycle, transient session rebuild, two-axis rendering, viewport preservation, renderer rollback, Chrono export, and project restore. VT Resistance focused tests cover resistance calculations, CSV compatibility, native layout, shared batch recomputation, two-axis rendering, result packaging, and project restore. Gait focused tests cover project migration, pose decoding, scientific calculations, CSV compatibility, typed table navigation, three-axis rendering, folder selection, result packaging, and project restore. DIC Preprocess focused tests cover project state, image loading, edit replay, registration/crop/mask helpers, export manifests, native two-axis layout, paired-anchor alignment, and managed crop interaction. Framework regression tests cover cell-valued event payloads and native multi-axis interaction bridging. Batch Image Crop focused tests cover crop geometry, padding, physical scaling, project migration, duplicate tasks, output planning/writes, native semantic layout, current-center editing, and standard result manifests. Focused framework and App tests additionally cover all 21 semantic layouts, typed events, managed interactions, project migration/recovery, result writing, synthetic sample packs, diagnostics, resource cleanup, window titles, startup success/failure, close behavior, and native adapter reconciliation. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Batch Image Crop](../../../../apps/image-measurement/batch-crop/README.md) [App catalog](../../../../apps/README.md) [LabKit App Framework](../../../../framework/README.md) [Build a Complete App](../../../../development/build-apps/complete-app.md) Known limitations and follow-up Automated GUI evidence does not replace developer-led validation of native dialogs, editable-table feel, pointer interaction, long-lived resource use, representative exports, visual quality, or scientific workflow suitability. That interactive validation remains a release input for the exact integrated commit. labkit.app.project.sourceRecord Create a portable source value during project migration."},{"title":"App SDK supports equal-data-unit plot fitting","url":"history/records/2026/07/LK-20260721-app-sdk-equal-data-unit-fit.html","kind":"history","section":"history","keywords":"LK-20260721-app-sdk-equal-data-unit-fit 2026-07-21 148 fix compatible labkit.app App Framework EIS Overlay","text":"App SDK supports equal-data-unit plot fitting id: LK-20260721-app-sdk-equal-data-unit-fit date: 2026-07-21 sequence: 148 type: fix compatibility: compatible component: `labkit.app` | `1.2.1 -> 1.2.2` scope: App Framework scope: EIS Overlay Context Apps needed an equal X/Y data-unit view for plots such as Nyquist diagrams, but setting an axes aspect mode directly also changed the allocated drawing area and persisted into later wheel and zoom interactions. Decision and rationale Keep viewport geometry in the framework fitter. An App can request equal data units while fitting the current plotted data, after which ordinary framework viewport preservation continues without an aspect lock. Changes Added the public `EqualDataUnits` option to `labkit.app.plot.fitAxesToGraphics`. Fit equal data units by expanding the smaller data span for the current axes pixel geometry, including logarithmic dimensions in log space. Removed the retired EIS axis-label rule; EIS now exposes explicit fit and equal-scale view actions. User and data impact The new option is additive. Existing fit calls keep independent X/Y limits. EIS projects and exports are unchanged; equal scaling is a transient view action and does not alter later zoom or wheel behavior. Compatibility and migration No project or result migration is required. Existing callers retain their independent fit behavior unless they opt into `EqualDataUnits=true`. Validation EIS GUI coverage exercises independent fitting, explicit equal scaling, and later viewport preservation. The App SDK plot helper tests cover finite data, logarithmic axes, and the public option contract. Evidence The fitting helper applies limits from finite plotted data and expands only the smaller working-space span for the axes pixel geometry. The EIS workflow test then verifies that a later framework redraw preserves the chosen viewport. Known limitations and follow-up Developer-led visual validation remains responsible for assessing layout balance on target displays and MATLAB releases. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data."},{"title":"App actions require explanatory hover help","url":"history/records/2026/07/LK-20260720-app-action-tooltips.html","kind":"history","section":"history","keywords":"LK-20260720-app-action-tooltips 2026-07-20 140 feat compatible labkit.app labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app App Framework All tracked Apps","text":"App actions require explanatory hover help id: LK-20260720-app-action-tooltips date: 2026-07-20 sequence: 140 type: feat compatibility: compatible component: `labkit.app` | `1.0.0 -> 1.1.0` component: `labkit_DICPostprocess_app` | `1.5.0 -> 1.5.1` component: `labkit_DICPreprocess_app` | `1.6.0 -> 1.6.1` component: `labkit_ChronoOverlay_app` | `1.5.0 -> 1.5.1` component: `labkit_CIC_app` | `1.5.0 -> 1.5.1` component: `labkit_CSC_app` | `1.5.0 -> 1.5.1` component: `labkit_EIS_app` | `1.5.0 -> 1.5.1` component: `labkit_VTResistance_app` | `1.5.0 -> 1.5.1` component: `labkit_GaitAnalysis_app` | `2.1.0 -> 2.1.1` component: `labkit_BatchImageCrop_app` | `1.8.0 -> 1.8.1` component: `labkit_CurvatureMeasurement_app` | `1.5.0 -> 1.5.1` component: `labkit_FLIRThermal_app` | `1.5.0 -> 1.5.1` component: `labkit_FocusStack_app` | `1.6.0 -> 1.6.1` component: `labkit_ImageEnhance_app` | `1.7.0 -> 1.7.1` component: `labkit_ImageMatch_app` | `1.7.0 -> 1.7.1` component: `labkit_VideoMarker_app` | `1.6.0 -> 1.6.1` component: `labkit_FigureStudio_app` | `0.3.0 -> 0.3.1` component: `labkit_NerveResponseAnalysis_app` | `1.5.0 -> 1.5.1` component: `labkit_ResponseReviewStats_app` | `1.5.0 -> 1.5.1` component: `labkit_RHSPreview_app` | `1.5.0 -> 1.5.1` component: `labkit_TTestWizard_app` | `1.1.0 -> 1.1.1` component: `labkit_ECGPrint_app` | `1.5.0 -> 1.5.1` scope: App Framework scope: All tracked Apps Context The native text-fit adapter copied button text through a column-shaped char conversion. MATLAB therefore received one newline between every character and rendered hover help as a narrow vertical strip. The generated tooltip also only repeated the visible label, so it did not explain the scientific or workflow consequence of an action. Decision and rationale Make explanatory hover help part of the semantic layout contract. Every `layout.button` produces a nonempty Tooltip, and tracked Apps must replace the label-based framework fallback with App-owned explanatory text. File-list actions expose dedicated tooltip fields and retain framework-owned defaults for generic folder, remove, and clear mechanics. Scientific meaning stays in the owning App rather than in the native adapter. All tracked Apps now describe what their actions consume, calculate, mutate, or export. The contract guardrail rejects App tooltips that only repeat the visible action label and also requires an explanatory input-selection tooltip. Changes Preserved char row vectors as one text line during native text fitting, eliminating the injected character-by-character newlines. Added `Tooltip` support with a nonempty label fallback to `layout.button` and dedicated file-list tooltips for choose, folder, recursive folder, remove, and clear actions. Added scientific and workflow-specific text for all 138 tracked App business buttons and all 26 App input selectors. Centralized the shared tooltip contract in the App Framework and App catalog instead of repeating it in every App manual. User and data impact Existing tracked Apps and third-party layouts retain their actions and calculations. Hovering now always shows readable text; tracked Apps additionally carry domain-specific explanations. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation App SDK unit coverage for required and compiled tooltip values. Cross-App definition guardrail for non-label business and input tooltips. Native adapter GUI coverage for exact tooltip text without injected newlines. DIC Postprocess GUI coverage for scientific action and Ncorr input help. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive checks should confirm tooltip timing, width, and line wrapping on supported MATLAB releases and desktop platforms."},{"title":"App alerts through UI facade","url":"history/records/2026/06/LK-20260630-app-alerts-through-ui-facade.html","kind":"history","section":"history","keywords":"LK-20260630-app-alerts-through-ui-facade 2026-06-30 22 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_ECGPrint_app App alerts through UI facade","text":"App alerts through UI facade id: LK-20260630-app-alerts-through-ui-facade date: 2026-06-30 sequence: 22 type: feat compatibility: compatible component: `labkit.ui` | `3.2.7 -> 3.2.8` component: `labkit_DICPostprocess_app` | `1.2.2 -> 1.2.3` component: `labkit_DICPreprocess_app` | `1.2.1 -> 1.2.2` component: `labkit_ChronoOverlay_app` | `1.2.0 -> 1.2.1` component: `labkit_CIC_app` | `1.2.0 -> 1.2.1` component: `labkit_CSC_app` | `1.2.0 -> 1.2.1` component: `labkit_EIS_app` | `1.2.0 -> 1.2.1` component: `labkit_VTResistance_app` | `1.2.0 -> 1.2.1` component: `labkit_BatchImageCrop_app` | `1.3.6 -> 1.3.7` component: `labkit_CurvatureMeasurement_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.2.4 -> 1.2.5` component: `labkit_ImageEnhance_app` | `1.3.3 -> 1.3.4` component: `labkit_ImageMatch_app` | `1.3.4 -> 1.3.5` component: `labkit_ECGPrint_app` | `1.2.1 -> 1.2.2` scope: App alerts through UI facade Context Apps opened alerts directly, which made hidden GUI tests unreliable and led to small differences in modal behavior across workflows. Decision and rationale Route user-visible alerts through one UI service that can display a modal message normally and record it safely during hidden tests. Keep each app responsible for the message and the decision that triggers it. Changes `labkit.ui` `3.2.7 -> 3.2.8` DIC, electrochem, image-measurement, and ECG apps patch bumped where alert routing changed. Routed app alerts through hidden-test-safe `labkit.ui.app.showAlert`. User and data impact Errors continued to appear as app alerts, but test and debug runs could capture them without blocking on an invisible dialog. No saved or exported data changed. Compatibility and migration App error conditions and messages remained app-owned. Only the display route changed, so user data and app project formats required no conversion. Validation Commit `8d7c83b1` migrated alert call sites and updated hidden workflow tests across the listed apps. Evidence Main commit `8d7c83b1`. Known limitations and follow-up The historical `labkit.ui.app.showAlert` entry point was later replaced by the injected `services.dialogs.alert` service in Runtime V2."},{"title":"App diagnostics and hardened UI workflows","url":"history/records/2026/06/LK-20260628-app-diagnostics-and-hardened-ui-workflows.html","kind":"history","section":"history","keywords":"LK-20260628-app-diagnostics-and-hardened-ui-workflows 2026-06-28 15 feat compatible labkit_launcher labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app App diagnostics and hardened UI workflows","text":"App diagnostics and hardened UI workflows id: LK-20260628-app-diagnostics-and-hardened-ui-workflows date: 2026-06-28 sequence: 15 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.1 -> 1.1.2` component: `labkit.ui` | `3.1.0 -> 3.1.2` component: `labkit.ui` | `3.1.2 -> 3.1.3` component: `labkit_BatchImageCrop_app` | `1.3.0 -> 1.3.1` component: `labkit_BatchImageCrop_app` | `1.3.1 -> 1.3.2` component: `labkit_FocusStack_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.2.1 -> 1.2.2` component: `labkit_ImageMatch_app` | `1.2.0 -> 1.2.1` component: `labkit_NerveResponseAnalysis_app` | `1.2.0 -> 1.2.1` component: `labkit_ResponseReviewStats_app` | `1.2.0 -> 1.2.1` component: `labkit_RHSPreview_app` | `1.2.0 -> 1.2.1` scope: App diagnostics and hardened UI workflows Context When an app stalled or caught an exception, screenshots and the visible Log tab often omitted the active callback and stack information needed to reproduce the problem. Decision and rationale Capture active operations, uncaught crashes, caught errors, and suspected stalls in structured local reports while hardening the UI paths that generate those events. Keep reports outside project data so diagnostics cannot alter results. Changes `labkit.ui` `3.1.0 -> 3.1.3` Batch Crop, Focus Stack, Image Enhance/Match, neurophysiology apps, and the launcher patch bumped where runtime behavior changed. Hardened LabKit UI workflows. Added crash reports, active-operation reports, caught-error reports, and stall diagnostics. User and data impact Debug runs produced evidence that could identify the failing operation and app state without copying laboratory inputs into the repository. Normal projects and exports kept their previous formats. Compatibility and migration The diagnostic files were additive and existing app inputs and saved results remained readable. Debug runs began producing more detailed local artifacts. Validation Commits `e966457b` and `f5bc6f98` updated launcher, framework, and affected app workflow tests for diagnostic creation and hardened callbacks. Evidence Main commits `e966457b` and `f5bc6f98`. Known limitations and follow-up Diagnostics report the state and stack available at capture time; they do not replace a reproducible input or a focused regression test."},{"title":"App file-selection and electrochem control fixes","url":"history/records/2026/07/LK-20260703-app-file-selection-and-electrochem-control-fixes.html","kind":"history","section":"history","keywords":"LK-20260703-app-file-selection-and-electrochem-control-fixes 2026-07-03 33 fix compatible labkit_CIC_app labkit_CIC_app labkit_CSC_app labkit_CSC_app labkit_VTResistance_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"App file-selection and electrochem control fixes id: LK-20260703-app-file-selection-and-electrochem-control-fixes date: 2026-07-03 sequence: 33 type: fix compatibility: compatible component: `labkit_CIC_app` | `1.3.1 -> 1.3.2` component: `labkit_CIC_app` | `1.3.2 -> 1.3.3` component: `labkit_CSC_app` | `1.3.1 -> 1.3.2` component: `labkit_CSC_app` | `1.3.2 -> 1.3.3` component: `labkit_VTResistance_app` | `1.3.1 -> 1.3.2` component: `labkit_VTResistance_app` | `1.3.2 -> 1.3.3` component: `labkit_BatchImageCrop_app` | `1.6.2 -> 1.6.3` component: `labkit_FLIRThermal_app` | `1.2.1 -> 1.2.2` component: `labkit_FocusStack_app` | `1.4.1 -> 1.4.2` component: `labkit_ImageEnhance_app` | `1.5.1 -> 1.5.2` component: `labkit_ImageMatch_app` | `1.5.1 -> 1.5.2` scope: historical project evolution Context Adding files to an existing multi-file session could replace or select the wrong entry in several apps. CIC, CSC, and VT Resistance also exposed manual plot controls that no longer matched their intended result-review workflow. Decision and rationale Preserve the complete file list when new selections are appended and select a newly added item predictably. Remove obsolete electrochem controls instead of keeping buttons whose effect was ambiguous or redundant. Changes CIC, CSC, VT Resistance, Batch Crop, FLIR Thermal, Focus Stack, Image Enhance, and Image Match patch bumped for appended file selections. CIC, CSC, and VT Resistance patch bumped again for manual plot-control removal. Preserved appended file selections. Removed electrochem manual plot controls that no longer matched the workflow. User and data impact Users could add another batch without losing files already loaded, across the listed electrochem and image apps. The electrochem workbenches became simpler; calculations and exported result schemas were unchanged. Compatibility and migration Existing loaded-file and result formats remained valid. Appended selections now preserved the active item, and the removed manual plot controls did not alter the underlying calculation options. Validation The file-selection commit added GUI regression coverage to every affected app. The control-removal commit updated the three electrochem GUI workflows and shared test helpers. Exact historical commands were not recorded. Evidence Main commits `6348185e` and `674d5d4b`. Known limitations and follow-up The change covered app-level append handling. Later framework work unified the native file-selection behavior across platforms."},{"title":"App manuals become reader-oriented and focused tests gain one wrapper","url":"history/records/2026/07/LK-20260720-reader-oriented-app-manuals-and-test-wrapper.html","kind":"history","section":"history","keywords":"LK-20260720-reader-oriented-app-manuals-and-test-wrapper 2026-07-20 142 docs compatible App documentation Documentation validation Agent testing workflow Agent skill maintenance","text":"App manuals become reader-oriented and focused tests gain one wrapper id: LK-20260720-reader-oriented-app-manuals-and-test-wrapper date: 2026-07-20 sequence: 142 type: docs compatibility: compatible scope: App documentation scope: Documentation validation scope: Agent testing workflow scope: Agent skill maintenance Context Concrete App manuals repeated shared hover-help and runtime architecture contracts. The repetition displaced workflow and scientific content, became stale when the framework changed, and made every App page appear more implementation-oriented than user-oriented. Focused test execution also required agents to repeatedly assemble low-level runner arguments; omitting GUI inclusion could produce a zero-match selection that looked like a test problem. Decision and rationale Keep shared behavior in the App catalog and framework manual. Concrete App pages describe only their inputs, workflow, App-specific interactions, scientific semantics, outputs, recovery, programmatic surface, and limitations. Protect that boundary with a documentation guardrail. Provide one platform-independent MATLAB wrapper with explicit operations for listing a file's canonical test names, running an exact file, running a canonical class or method within its owning file, and running a suite. Keep broad final gates on the existing `buildtool` interface. Treat recurring reasoning, command construction, discovery, and retry cost as skill-maintenance signals. Improvements must describe durable concepts and semantic operations rather than one product version, App, transient failure, or current repository snapshot. Changes Removed repeated hover-help and Framework Compatibility boilerplate from all tracked App manuals. Rewrote project-state passages around user-visible persistence, recovery, source relinking, and recalculation rather than internal factories, callbacks, and schema implementation. Clarified the App-manual authoring contract and added a guardrail against reintroducing shared framework boilerplate. Added `runLabKitTestTarget.m` to the test-planner skill with automatic repository discovery, GUI inference for exact files and GUI suites, hidden figures, focused artifacts, and fail-on-zero-match behavior. Made exact canonical selectors resolve their unique owning test file, while retaining an explicit file constraint for ambiguous ownership. Made local parallel build tasks retain structured shard state while relaying concise human-readable heartbeats, clear stale transient status at worker startup, and print only failing worker logs instead of remaining silent and then dumping every shard log. Moved child-process orchestration out of `buildfile.m` into a MATLAB/Java runner shared by local macOS, Linux, and Windows, removing generated POSIX scripts and adding an independent ETA to each shard summary. Hosted CI remains single-process pending explicit concurrent-license evidence. Partitioned broad local workers by unique test-class owner file so each worker discovers only its assigned files instead of repeating full-tree discovery. Batched App path registration while keeping every setup call sensitive to newly added App entrypoints, added quiet discovery probes, and removed one unreferenced GUI assertion helper. Updated the skill to list canonical names before exact method reruns and to distinguish selection failures from passing evidence. Added a root agent rule to improve skills and scripts when reusable automation can remove recurring inference or retry cost. User and data impact App behavior, calculations, saved data, and exports are unchanged. Existing manual content about scientific meaning and recovery remains available with less implementation noise. The wrapper is an agent convenience over the official test runner and does not add another CI or public build interface. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Documentation guardrails scan every concrete App manual for shared boilerplate. Documentation rendering and byte-for-byte site comparison cover all edited pages and this record. The wrapper lists and runs canonical GUI methods through the official runner, demonstrating automatic hidden-GUI selection. Test-runner profiling measured fresh full discovery before and after the path change at 5.83 and 4.82 seconds; path setup fell from 1.27 to 0.32 seconds. The retained rescan avoids a stale-cache failure when an App is added during a MATLAB session. Build contracts prove file partitions are disjoint, cover every selected test, accept deeply nested owning files, and preserve explicit failures for missing or ambiguous owners. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Continue reviewing App manuals when user-visible behavior changes; structural guardrails prevent known boilerplate but cannot judge scientific clarity."},{"title":"App tools and presentation controls become more deliberate","url":"history/records/2026/07/LK-20260720-app-tools-menu-and-screenshot-clipboard.html","kind":"history","section":"history","keywords":"LK-20260720-app-tools-menu-and-screenshot-clipboard 2026-07-20 141 feat compatible labkit.app App Framework All tracked Apps","text":"App tools and presentation controls become more deliberate id: LK-20260720-app-tools-menu-and-screenshot-clipboard date: 2026-07-20 sequence: 141 type: feat compatibility: compatible component: `labkit.app` | `1.1.0 -> 1.2.0` scope: App Framework scope: All tracked Apps Context Plot, Screenshot, Save State, and Load State appeared as separate root-level native menu items. Plot was a container while the other items executed immediately, so controls at the same visual level had inconsistent behavior and consumed unnecessary horizontal space. Screenshot also required a file dialog even when the user only needed an image for another application. Short status summaries consumed the same height as detail panels, and Apps could preserve plot zoom but had no declarative one-shot reset revision. Decision and rationale Install one framework-owned **Tools** root with purpose-based **Plots**, **Screenshot**, and conditional **Project State** submenus. Keep scientific workflow controls in the App layout while grouping domain-neutral native utilities predictably. Use MATLAB's existing `copygraphics` clipboard path so the capability remains platform-neutral and introduces no external runtime. Changes Moved plot utilities beneath **Tools > Plots**. Added **Tools > Screenshot > Copy to Clipboard** for the complete App surface and retained file export as **Save to File...**. Moved project persistence beneath **Tools > Project State** when the App owns a project document. Preserved existing executable action tags for compatibility with GUI automation and added stable tags for the new menu containers and clipboard action. Added a line-count hint for compact status summaries and a general plot-view revision that accepts renderer-fitted limits once while preserving user zoom during ordinary redraws. Updated the getting-started and framework manuals. User and data impact App calculations, project files, plots, and exports are unchanged. Existing action tags continue to identify save screenshot, save state, load state, and plot commands, but users now reach them through the Tools hierarchy. Clipboard copy uses the operating system clipboard and may be unavailable in a noninteractive MATLAB session. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Hidden native-adapter GUI coverage verifies the menu hierarchy, labels, conditional Project State menu, stable action tags, and clipboard callback wiring. Focused framework tests validate the App SDK version and native runtime. Documentation rendering and consistency checks cover the updated manuals and history record. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive checks should confirm clipboard image fidelity and native menu appearance on supported MATLAB desktop platforms."},{"title":"App validation no longer relies on sibling App paths","url":"history/records/2026/07/LK-20260717-isolated-app-validation.html","kind":"history","section":"history","keywords":"LK-20260717-isolated-app-validation 2026-07-17 128 test compatible Public App path isolation Changed-file validation routing GUI CI evaluation","text":"App validation no longer relies on sibling App paths id: LK-20260717-isolated-app-validation date: 2026-07-17 sequence: 128 type: test compatibility: compatible scope: Public App path isolation scope: Changed-file validation routing scope: GUI CI evaluation Context The ordinary public test setup adds every App entry folder so family and cross-App contract suites can run efficiently. That broad path can conceal an accidental dependency on a sibling App package. Static source scans rejected direct sibling calls, and individual Gait/private launch tests used isolated paths, but there was no uniform executable contract for every public App or automatic changed-file route to that contract. Decision and rationale Keep the convenient broad path for ordinary suites, but add one fast independent proof. For each public App, restore MATLAB's default path, add only the repository root and the owning App root, load its complete definition, check declared facade compatibility, and write its synthetic debug sample. Run this contract whenever public App source changes. Static call scanning and dynamic isolation cover different risks and remain separate. The former identifies the forbidden source reference; the latter proves metadata, framework requirements, and debug generation work in the same path shape as a single-App package. Changes Added one isolated-path contract covering all 20 public Apps. Routed every App source change to that contract in both conservative and fast changed-file plans. Added a planner regression so future routing changes cannot silently drop isolation coverage. Documented why independent private workspaces must run their own compatibility checks without inheriting public App roots. Evaluated, but did not add, a hidden-GUI job to every pull request. User and data impact There is no App or saved-data behavior change. A new sibling package dependency, stale facade requirement, incomplete definition, or broken debug sample now fails in a focused non-GUI contract instead of passing because all Apps happened to be on the developer's MATLAB path. Compatibility and migration No migration is required. Existing test commands remain unchanged; the changed-file planner adds the focused isolation selection automatically. Validation The all-App isolation contract completed in about 14 seconds locally. Existing changed-file evidence measured representative hidden image workflows at about 18 seconds each and a Gait hidden launch at about 30 seconds, before MATLAB startup and license acquisition. Since those synthetic GUI checks still do not prove native dialogs, pointer feel, or visual quality, ordinary CI keeps the fast non-GUI contract while App-specific GUI checks remain changed-file, scheduled, manual, and release validation. The private workspace's independent suite passes after removing public App roots from its runner path. Evidence [Testing and validation](../../../../development/maintain-and-release/testing.md) [Architecture](../../../../development/build-apps/architecture.md) [App development](../../../../development/build-apps/app-development.md) Known limitations and follow-up MATLAB process-global function caches cannot replace package-boundary source review, so the static sibling-call guard remains required. Hidden GUI tests remain bounded semantic checks rather than substitutes for manual interaction and visual assessment."},{"title":"Base-MATLAB image compatibility","url":"history/records/2026/07/LK-20260713-base-matlab-image-compatibility.html","kind":"history","section":"history","keywords":"LK-20260713-base-matlab-image-compatibility 2026-07-13 45 feat compatible labkit.image labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app Base-MATLAB image compatibility","text":"Base-MATLAB image compatibility id: LK-20260713-base-matlab-image-compatibility date: 2026-07-13 sequence: 45 type: feat compatibility: compatible component: `labkit.image` | `1.1.0 -> 1.2.0` component: `labkit_DICPostprocess_app` | `1.3.4 -> 1.3.5` component: `labkit_DICPreprocess_app` | `1.3.5 -> 1.3.6` component: `labkit_FocusStack_app` | `1.4.7 -> 1.4.8` component: `labkit_ImageEnhance_app` | `1.5.6 -> 1.5.7` component: `labkit_ImageMatch_app` | `1.5.6 -> 1.5.7` scope: Base-MATLAB image compatibility Context Several image workflows appeared toolbox-optional but still called Image Processing Toolbox functions for conversion, grayscale luminance, resizing, smoothing, alignment, or warping. Development machines with the toolbox hid those calls, so a nominally supported Base MATLAB installation could fail only after the user entered a particular workflow. Decision and rationale Own the essential image operations used by LabKit and make their Base MATLAB paths the tested behavior. Optional toolbox acceleration could remain only when the call was explicit, a repository-owned fallback existed, and parity tests protected the app-consumed result. Changes `labkit.image` `1.1.0 -> 1.2.0` `labkit_DICPreprocess_app` `1.3.5 -> 1.3.6` `labkit_DICPostprocess_app` `1.3.4 -> 1.3.5` `labkit_FocusStack_app` `1.4.7 -> 1.4.8` `labkit_ImageEnhance_app` `1.5.6 -> 1.5.7` `labkit_ImageMatch_app` `1.5.6 -> 1.5.7` Added `labkit.image.toDouble` and `labkit.image.toLuma`, and replaced hard Image Processing Toolbox calls in shared image facade code and image-app workflow paths with base-MATLAB implementations. DIC preprocessing now uses a toolbox-free phase-correlation translation path for automatic alignment and a base-MATLAB rigid warp for control-point alignment. DIC postprocessing, Focus Stack, Image Enhance, and Image Match now use app-local or facade-owned image normalization, resizing, smoothing, and luma helpers instead of requiring toolbox functions. Added a project hygiene guardrail that rejects unguarded toolbox image helper calls under `apps/` and `+labkit/`, while still allowing explicit optional toolbox paths with fallbacks. User and data impact DIC alignment and overlays, Focus Stack, Image Enhance, and Image Match could run without Image Processing Toolbox. Users with the toolbox retained compatible workflows, while CI now exercised the installation that previously failed late and silently. Compatibility and migration Existing app workflows and exported schemas are preserved. Optional toolbox acceleration paths remain allowed only when a base-MATLAB fallback is present. Validation The commit expanded DIC, Focus Stack, Image Enhance, Image Match, and image- facade unit suites and added `ToolboxDependencyGuardrailTest` to detect new unguarded calls. GUI layout tests covered the affected DIC workflows. The exact historical command was not recorded. Evidence Mainline commit `bcd5f51f`. Known limitations and follow-up Base MATLAB compatibility does not mean every optional accelerated algorithm is numerically identical by construction. Where a toolbox branch affects scientific values, representative parity and idempotency tests remain required until the repository-owned implementation fully replaces it."},{"title":"Batch Crop assigns state to workflow capabilities","url":"history/records/2026/07/LK-20260716-batch-crop-structure.html","kind":"history","section":"history","keywords":"LK-20260716-batch-crop-structure 2026-07-16 101 refactor compatible labkit_BatchImageCrop_app Image Measurement App structure","text":"Batch Crop assigns state to workflow capabilities id: LK-20260716-batch-crop-structure date: 2026-07-16 sequence: 101 type: refactor compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.2 -> 1.7.3` scope: Image Measurement scope: App structure Context Batch Crop split static metadata across separate files, spread project creation, validation, migration, and session construction through a generic lifecycle package, and placed source decoding, crop-task operations, geometry, scale calibration, export planning, and transient caches together under `+appState`. The App also retained an unused scale-unit adapter and a startup callback whose only job duplicated the shared source-adjacent output-folder default. Decision and rationale Use the compact Runtime V2 definition and one project contract, then assign remaining helpers to the workflow capability that owns their meaning. Crop geometry, scale calibration, and export planning are real App complexity and remain App-owned; generic lifecycle and state buckets are structural debt and are removed. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderers, and optional capabilities in `definition.m`. Concentrated durable creation, validation, and the version-1 upgrade in the single `projectSpec.m` entry; root `createSession.m` rebuilds transient state and lazily loads the selected source. Replaced embedded image data and direct path fields with canonical source records and `sourceId` task references. Assigned file reconstruction to `+sourceFiles`, task operations to `+cropTasks`, coordinate and cache behavior to `+cropGeometry`, calibration semantics to `+scaleCalibration`, and export state to `+resultFiles`. Removed the generic lifecycle/state packages, separate metadata files, redundant startup callback, and unused scale-unit wrapper. User and data impact Multiple crop tasks per source, draggable and resizable ROIs, zoom-preserving preview edits, rotation, edge-continuous padding, pixel and physical crop modes, per-source calibration, scale bars, manifests, and exports keep their existing behavior. Empty launch no longer computes an output folder; importing sources still selects the source-adjacent default. Developers can now find each calculation and state transition under its owning workflow concept without learning a generic App state package. Compatibility and migration Version-1 saved projects upgrade automatically to schema version 2. The migration discards reconstructible embedded pixels, creates portable source records for legacy paths, and preserves crop-task settings. Runtime source reconciliation handles moved or missing required files. Validation Focused unit tests cover project creation, validation and migration, source reading, crop geometry, scale planning, export planning, and manifest output. The hidden GUI workflow loads an image, manipulates the crop workflow, and exports a synthetic crop through the real Runtime callbacks. Evidence [Batch Image Crop](../../../../apps/image-measurement/batch-crop/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Image Library](../../../../libraries/image/README.md) Known limitations and follow-up Automated GUI tests do not replace manual judgment of ROI pointer feel, resampling quality, scale-reference placement, or export suitability for a particular quantitative workflow. Other Apps that still use generic lifecycle or state packages remain scheduled for the same ownership review."},{"title":"Batch Crop file workflow feedback","url":"history/records/2026/06/LK-20260628-batch-crop-file-workflow-feedback.html","kind":"history","section":"history","keywords":"LK-20260628-batch-crop-file-workflow-feedback 2026-06-28 14 feat compatible labkit.ui labkit_BatchImageCrop_app Batch Crop file workflow feedback","text":"Batch Crop file workflow feedback id: LK-20260628-batch-crop-file-workflow-feedback date: 2026-06-28 sequence: 14 type: feat compatibility: compatible component: `labkit.ui` | `3.0.1 -> 3.1.0` component: `labkit_BatchImageCrop_app` | `1.2.0 -> 1.3.0` scope: Batch Crop file workflow feedback Context In a multi-file Batch Crop session, the window and preview did not consistently show which list item supplied the current image and crop settings. Decision and rationale Make the selected file part of the shared window-title context and update Batch Crop feedback whenever selection changes. Keep the title derived from the file panel so the app does not maintain a second selection label. Changes `labkit.ui` `3.0.1 -> 3.1.0` Batch Crop `1.2.0 -> 1.3.0` Added selected-file title context. Improved Batch Crop file workflow feedback. User and data impact The active filename became visible while reviewing or editing a crop task, reducing the chance of applying settings to the wrong image. Crop geometry and exported data were unchanged. Compatibility and migration Batch Crop tasks and exports kept their existing format. The change affected file-selection context and feedback in the window only. Validation Commit `61e8edd3` updated shared title-context and Batch Crop GUI workflow coverage. Evidence Main commit `61e8edd3`. Known limitations and follow-up Later file-panel work extended the same title context and append behavior to the rest of the app fleet."},{"title":"Binding-only controls and default resource cleanup","url":"history/records/2026/07/LK-20260717-binding-and-resource-defaults.html","kind":"history","section":"history","keywords":"LK-20260717-binding-and-resource-defaults 2026-07-17 122 refactor compatible labkit.ui labkit_VideoMarker_app Runtime V2 action registration Runtime V2 resource ownership","text":"Binding-only controls and default resource cleanup id: LK-20260717-binding-and-resource-defaults date: 2026-07-17 sequence: 122 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.5 -> 7.4.6` component: `labkit_VideoMarker_app` | `1.5.4 -> 1.5.5` scope: Runtime V2 action registration scope: Runtime V2 resource ownership Context Runtime V2 already supported controls whose complete behavior is writing one bound state path, but Video Marker still registered an empty action for its skeleton-preset selector. The private resource registry also had a default cleanup policy, while the injected App service required every call to supply a fourth cleanup function. Video Marker therefore kept a second empty function for its plain decoded-video resource. Decision and rationale Use the existing binding-only contract for state-only controls and expose the resource registry's existing default cleanup through the injected service. Apps should declare an action only when a user event has workflow behavior beyond committing the bound value. Plain structs and values require no App-owned cleanup placeholder. Changes Removed Video Marker's empty skeleton-preset change action and left the dropdown as a direct session binding. Made the injected `services.resources.set(scope,id,value)` form select the Runtime's default cleanup, while retaining the optional fourth custom cleanup function. Removed Video Marker's empty video-resource cleanup function. Added Runtime GUI coverage for binding-only state commits and resources registered without a custom cleanup. User and data impact There is no workflow, scientific, project-data, or saved-file change. Selecting a skeleton preset still updates the session immediately, and **Use preset** still performs the only durable skeleton edit. Video cache replacement and session cleanup retain the same observable behavior. Compatibility and migration The change is source-compatible. Existing four-argument resource registration continues to use the supplied cleanup function. No project migration is required. Validation The focused Runtime V2 GUI test exercises both service forms and proves that a binding without `Event` commits state without invoking an App action. Video Marker structure and targeted behavior tests cover the simplified definition. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents bound controls and injected resource ownership. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) describes the preset and decoded-video resource behavior. Known limitations and follow-up Other bound events still require App actions when they normalize values, invalidate results, update previews, or log workflow changes. They should be reviewed by behavior rather than removed merely because their handlers are short."},{"title":"CIC consolidates its project contract without losing lazy loading","url":"history/records/2026/07/LK-20260716-cic-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-cic-project-spec 2026-07-16 87 refactor compatible labkit_CIC_app Electrochemistry Project lifecycle","text":"CIC consolidates its project contract without losing lazy loading id: LK-20260716-cic-project-spec date: 2026-07-16 sequence: 87 type: refactor compatibility: compatible component: `labkit_CIC_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context CIC split static metadata across three files and its first-version project across a generic lifecycle package. Its session factory also encodes an important performance policy: restore only the first source for preview rather than parsing an entire large batch. Decision and rationale Consolidate product metadata in `definition.m` and durable schema behavior in `projectSpec.m`. Keep `createSession.m` separate and explicit because lazy source decoding is real transient reconstruction and a user-visible performance boundary, not lifecycle boilerplate. Changes Moved command metadata, version, update date, and requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved lazy preview restoration to one package-root session factory. Removed separate metadata files and the generic lifecycle package. Kept pulse detection, CIC formulas, units, limits, plots, and exports unchanged. User and data impact Launch, source selection, large-batch behavior, computation, save/load, and CSV exports behave unchanged. The App structure is smaller while the reason for its lazy session factory is now documented next to the code. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing CIC projects require no migration. Validation Unit tests cover the project contract, CIC calculation, units, failed rows, CSV schema, and presentation. The hidden GUI workflow covers real launch, source selection, calculation, save/load, and export through Runtime V2. Evidence [CIC](../../../../apps/electrochemistry/cic/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The session currently reads `reference.originalPath` directly. That internal portable-reference representation will be replaced by a simple framework source-path service across all Apps rather than patched only in CIC."},{"title":"CIC uses Runtime-owned source and workflow mechanics","url":"history/records/2026/07/LK-20260716-cic-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-cic-runtime-source-reconciliation 2026-07-16 112 refactor compatible labkit_CIC_app Electrochemistry App structure Runtime adoption","text":"CIC uses Runtime-owned source and workflow mechanics id: LK-20260716-cic-runtime-source-reconciliation date: 2026-07-16 sequence: 112 type: refactor compatibility: compatible component: `labkit_CIC_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context CIC intentionally registers paths before decoding them so large batches remain lazy, but it still generated, appended, and deleted portable source records in App code. It also wrote directly to `session.workflow.logLines`, requiring its session factory to reproduce Runtime-owned empty workflow and view buckets. Those details increased the amount of framework structure an App maintainer had to understand without expressing CIC analysis behavior. Decision and rationale CIC continues to own the ordered lazy path set, current-index policy, deferred decoding, scientific options, and result workflow. It now passes that ordered path set to `services.project.reconcileSources` after add, removal, and clear operations. Runtime preserves matching identities and allocates new unique ones. Analysis messages use `services.workflow.log`, the same service as other workflow messages. `createSession` returns only CIC-specific selection and decoded-cache state; Runtime creates missing transient buckets before actions run. Changes Removed App-local source-ID generation and source append helpers. Reconciled registered paths through the Runtime project service. Routed analysis messages through the Runtime workflow service. Removed empty workflow and view boilerplate from `createSession`. Added GUI checks for stable and unique identities across batch add, save, reopen, removal, and re-addition. Advanced the CIC App version to 1.4.4. User and data impact Lazy decoding, file order, selected preview, analysis values, plots, exports, logs, and project files retain their behavior. Source identities remain stable for retained files and collision-free for newly registered files. Scientific calculations and output schemas are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing source records and their identities are preserved when reopened. Validation The CIC hidden GUI workflow covers three-source lazy registration, selection, whole-batch recomputation, export, save, clear, reopen, remove, re-add, source identity, viewport refresh, and final clear. Focused calculation/view, App structure, version, documentation, and history tests cover the surrounding contracts. Evidence [Charge-Injection Capacity](../../../../apps/electrochemistry/cic/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up CSC and VT Resistance still own similar source registration helpers. Their result indexing and lazy-load behavior must be checked independently before adopting the Runtime service."},{"title":"CSC consolidates its product and project contracts","url":"history/records/2026/07/LK-20260716-csc-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-csc-project-spec 2026-07-16 88 refactor compatible labkit_CSC_app Electrochemistry Project lifecycle","text":"CSC consolidates its product and project contracts id: LK-20260716-csc-project-spec date: 2026-07-16 sequence: 88 type: refactor compatibility: compatible component: `labkit_CSC_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context CSC split product metadata across three files and its durable schema across a generic lifecycle package even though it has no historical payload migration. The additional entry points did not own distinct electrochemical behavior. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because reloading CV/CT curves and restoring file/cycle selection is genuine transient work. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved decoded curve and selection restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Kept integration formulas, sign splitting, scan-rate handling, area normalization, plots, and export schemas unchanged. User and data impact Launch, source and cycle selection, save/load, calculations, plots, and both export paths behave unchanged. The durable contract is now readable in one place without hiding scientific behavior in the framework. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing CSC projects require no migration. Validation Unit tests cover CT/CV calculations, edge-cycle policy, normalization, result tables, export schemas, project/session contracts, and presentation. The hidden GUI workflow covers real launch, file/cycle selection, calculation, save/load, plot overlays, and exports. Evidence [CSC](../../../../apps/electrochemistry/csc/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Decoded source restoration still reaches into portable-reference internals. That cross-App framework boundary remains scheduled for one shared source-path service rather than an App-specific wrapper."},{"title":"CSC delegates decoded-source identity to Runtime V2","url":"history/records/2026/07/LK-20260716-csc-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-csc-runtime-source-reconciliation 2026-07-16 113 refactor compatible labkit_CSC_app Electrochemistry App structure Runtime adoption","text":"CSC delegates decoded-source identity to Runtime V2 id: LK-20260716-csc-runtime-source-reconciliation date: 2026-07-16 sequence: 113 type: refactor compatibility: compatible component: `labkit_CSC_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context CSC eagerly decodes accepted CV/CT files and keeps decoded item order aligned with durable source order. Its action file nevertheless generated source IDs, appended records, and deleted records by index. The session factory also declared empty workflow and view buckets already guaranteed by Runtime V2. Those mechanics were framework persistence responsibilities rather than CSC curve-selection or scientific behavior. Decision and rationale Continue using successfully decoded item order as the App-owned source order, then reconcile durable records through `services.project.reconcileSources` after additions, removals, and clearing. Runtime preserves records for retained paths and allocates collision-free IDs for new paths. CSC continues to own decoding, active file and curve selection, plot defaults, calculation, exports, and failure messages. Return only the CSC-specific selection fields and decoded item cache from `createSession`; Runtime supplies missing transient buckets. Changes Removed App-local source-ID generation and source append helpers. Reconciled source records from the decoded item list. Replaced the growing failure array with a single first-failure record while retaining per-file workflow logs. Removed empty workflow and view session boilerplate. Added GUI checks for canonical buckets and stable, unique identities across add, save, reopen, removal, and re-addition. Advanced the CSC App version to 1.4.4. User and data impact File order, selected curve, plot resets, CSC calculations, reload, exports, logs, and saved projects retain their behavior. Retained source identities no longer depend on App-local append/delete logic. Scientific values and result schemas are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing portable records retain their IDs when the corresponding paths remain loaded. Validation The CSC hidden GUI workflow covers two-file decoding, file and cycle selection, plot changes, calculation display, export, save, clear, reopen, remove, re-add, source identity, and final plot clearing. Focused CSC calculation/view and repository contract tests cover the remaining behavior. Evidence [Charge-Storage Capacity](../../../../apps/electrochemistry/csc/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up VT Resistance remains the last electrochem batch App with the same local source-ID and append pattern and requires a separate behavior audit."},{"title":"CSC export and viewport policy","url":"history/records/2026/07/LK-20260703-csc-export-and-viewport-policy.html","kind":"history","section":"history","keywords":"LK-20260703-csc-export-and-viewport-policy 2026-07-03 35 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app CSC export and viewport policy","text":"CSC export and viewport policy id: LK-20260703-csc-export-and-viewport-policy date: 2026-07-03 sequence: 35 type: feat compatibility: compatible component: `labkit.ui` | `4.0.0 -> 4.1.0` component: `labkit_DICPostprocess_app` | `1.3.2 -> 1.3.3` component: `labkit_DICPreprocess_app` | `1.3.2 -> 1.3.3` component: `labkit_ChronoOverlay_app` | `1.3.2 -> 1.3.3` component: `labkit_CIC_app` | `1.3.4 -> 1.3.5` component: `labkit_CSC_app` | `1.3.4 -> 1.3.6` component: `labkit_EIS_app` | `1.3.2 -> 1.3.3` component: `labkit_VTResistance_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.6.4 -> 1.6.5` component: `labkit_CurvatureMeasurement_app` | `1.3.2 -> 1.3.3` component: `labkit_FLIRThermal_app` | `1.2.3 -> 1.2.4` component: `labkit_FocusStack_app` | `1.4.3 -> 1.4.4` component: `labkit_ImageEnhance_app` | `1.5.3 -> 1.5.4` component: `labkit_ImageMatch_app` | `1.5.3 -> 1.5.4` component: `labkit_NerveResponseAnalysis_app` | `1.3.2 -> 1.3.3` component: `labkit_ResponseReviewStats_app` | `1.3.2 -> 1.3.3` component: `labkit_RHSPreview_app` | `1.3.2 -> 1.3.3` component: `labkit_ECGPrint_app` | `1.3.3 -> 1.3.4` scope: CSC export and viewport policy Context CSC could export the selected cycle, but comparing activation and stability across a recording required all cycles in one dataset. In parallel, app layouts made their own assumptions about minimum window size and scrollable content, so the same control panel could behave differently in a small viewport. Decision and rationale Add an explicit all-cycle CSC export without changing the existing selected- cycle result. Define viewport behavior in the UI contract so each app declares its content needs while the framework decides when scrolling or minimum sizing is necessary. Changes `labkit.ui` `4.0.0 -> 4.1.0` All supported apps received aligned patch bumps. Added CSC all-cycle export. Added viewport policy support and aligned app contracts with the UI 4.x line. User and data impact CSC users could export every cycle for downstream comparison while retaining the existing focused export. Across the workbench, small windows followed one scrolling and sizing policy instead of clipping controls according to the app that happened to create them. Compatibility and migration The selected-cycle CSC export remained available and the all-cycle table was a new option. Existing app definitions inherited the default viewport behavior until they declared more specific needs. Validation Commit `a69829c6` added a dedicated CSC export suite, extended the CSC GUI layout test, and added workbench viewport coverage. Evidence Main commit `a69829c6`. Known limitations and follow-up All-cycle CSV output was a separate export choice rather than a new session format. Later CSC work refined column naming and voltage/current organization."},{"title":"Callback-safe native layout synchronization","url":"history/records/2026/07/LK-20260721-callback-safe-layout-synchronization.html","kind":"history","section":"history","keywords":"LK-20260721-callback-safe-layout-synchronization 2026-07-21 152 fix compatible labkit.app App Framework Hidden GUI validation","text":"Callback-safe native layout synchronization id: LK-20260721-callback-safe-layout-synchronization date: 2026-07-21 sequence: 152 type: fix compatibility: compatible component: `labkit.app` | `1.2.3 -> 1.2.4` scope: App Framework scope: Hidden GUI validation Context The App SDK synchronizes native layout before calculating pixel-dependent plot geometry and while publishing busy, startup, close-prompt, and popout views. A full MATLAB `drawnow` also executes pending callbacks, which can introduce unrelated UI or timer work during those internal rendering operations. Decision and rationale Keep the required native graphics synchronization, but defer callbacks while the framework is calculating or publishing its own view. Long-running interactive launcher and profiler waits continue to process callbacks so users can close their windows. Changes Use callback-deferred graphics updates for equal-scale fitting and fixed-aspect preview geometry. Use callback-deferred updates while the native App runtime publishes busy, startup, failure, close-prompt, and popout-style state. Verify equal-scale EIS fitting, preview-canvas layout, and native runtime lifecycle behavior with hidden GUI tests. User and data impact Apps retain the same layouts and fitted limits while framework rendering no longer processes unrelated pending callbacks mid-operation. Projects, source records, results, and exports are unchanged. Compatibility and migration This compatible patch does not alter the public API or require project/result migration. Validation Focused hidden-GUI EIS equal-axis, Figure Studio preview, and native adapter lifecycle checks pass locally. The complete platform matrix remains the final validation for native GUI scheduling behavior. Evidence MATLAB documents that full `drawnow` both updates figures and processes pending callbacks, while the `nocallbacks` forms defer those callbacks. The framework uses callback-deferred synchronization only where it must read settled native geometry or publish internal view state. Known limitations and follow-up Automated hidden GUI checks do not substitute for manual visual and interaction assessment on supported MATLAB desktop platforms."},{"title":"Canonical role-based source collections","url":"history/records/2026/07/LK-20260716-canonical-role-based-sources.html","kind":"history","section":"history","keywords":"LK-20260716-canonical-role-based-sources 2026-07-16 73 refactor compatible labkit_RHSPreview_app labkit_NerveResponseAnalysis_app project payload schema source roles and relinking","text":"Canonical role-based source collections id: LK-20260716-canonical-role-based-sources date: 2026-07-16 sequence: 73 type: refactor compatibility: compatible component: `labkit_RHSPreview_app` | `1.4.0 -> 1.4.1` component: `labkit_NerveResponseAnalysis_app` | `1.4.0 -> 1.4.1` scope: project payload schema scope: source roles and relinking Context RHS Preview and Nerve Response Analysis stored external files in separate app-specific fields such as `rhsSource`, `filterSources`, and `protocolSource`. The records already carried explicit roles, but Runtime V2 could only discover the canonical `project.inputs.sources` collection during portable save/load and missing-file relinking. Decision and rationale Store every external dependency in one canonical collection and select records by their app-owned role. The framework remains domain-neutral, while each app continues to distinguish preview recordings, filter recordings, filter JSON, and optional protocols without duplicating persistence mechanics. Changes Advanced both project payload schemas from version 1 to version 2. Added ordered migrations that combine the former role-specific fields without changing source record contents or order. Added app-local role selection and replacement operations used consistently by lifecycle creation, actions, presenters, exports, and validation. Kept at most one primary recording/protocol per relevant app while allowing RHS Preview to retain an ordered collection of filter recordings. User and data impact Project reopen, result provenance, and future missing-file recovery can now see all selected dependencies. Preview, filtering, event detection, CAP metrics, and output formats are unchanged. Compatibility and migration Existing version 1 payloads remain readable and preserve every source record. The old fields are removed only from the migrated in-memory project; saving writes the version 2 canonical collection. Validation Focused unit tests verify role-preserving migrations and project versions. Focused GUI workflows verify RHS indexing, multiple filter-file management, project save/reopen, filter analysis, export, and transient cache rebuilding. Evidence [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) documents its three source roles. [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) documents filter/protocol restoration and relinking. [App Framework](../../../../framework/README.md) defines the canonical project source collection. Known limitations and follow-up This change makes every source discoverable but does not yet rebase relative paths at the final project save destination. That framework serialization change is tracked as the next persistence batch."},{"title":"Canonical single-source app projects","url":"history/records/2026/07/LK-20260716-canonical-single-source-projects.html","kind":"history","section":"history","keywords":"LK-20260716-canonical-single-source-projects 2026-07-16 72 refactor compatible labkit_GaitAnalysis_app labkit_CurvatureMeasurement_app labkit_ECGPrint_app labkit_ResponseReviewStats_app project payload schema source relinking","text":"Canonical single-source app projects id: LK-20260716-canonical-single-source-projects date: 2026-07-16 sequence: 72 type: refactor compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.0 -> 2.0.1` component: `labkit_CurvatureMeasurement_app` | `1.4.0 -> 1.4.1` component: `labkit_ECGPrint_app` | `1.4.0 -> 1.4.1` component: `labkit_ResponseReviewStats_app` | `1.4.0 -> 1.4.1` scope: project payload schema scope: source relinking Context The Runtime V2 project contract discovers external files through the `project.inputs.sources` collection. Four single-source apps instead stored their record under a singular `project.inputs.source` field. Their own actions could open that path, but framework save/load relinking could not discover it. Decision and rationale Use the same canonical source collection for every single-source app. The plural field is a collection even when it currently contains zero or one record, so persistence and relinking do not need app-specific field knowledge. Changes Moved Gait Analysis, Curvature Measurement, ECG Print, and Response Review Stats source ownership to `project.inputs.sources`. Advanced each payload schema by one version and added the corresponding ordered migration. Updated action, presenter, session, validation, export, and manual references to the canonical collection. Corrected the Gait family overview to describe its current strict Video Marker project input instead of retired generic MAT/table inputs. User and data impact New saves expose their source records to the common portable-project resolver. Scientific calculations, UI choices, annotations, parameters, and result values are unchanged. Compatibility and migration Existing payloads remain readable. Loading copies the former singular field to the canonical collection and removes the retired field. A later save writes the new payload version; source record contents are not transformed. Validation Focused unit tests verify each field migration, current project version, and existing calculations. Focused hidden-GUI tests verify that each app still launches and follows its source workflow with the renamed durable field. Evidence [App Framework](../../../../framework/README.md) defines the canonical source collection used by project persistence. [Gait Analysis](../../../../apps/gait/gait-analysis/README.md), [Curvature Measurement](../../../../apps/image-measurement/curvature/README.md), [ECG Print](../../../../apps/wearable/ecg-print/README.md), and [Response Review Stats](../../../../apps/neurophysiology/response-review-stats/README.md) describe their upgraded project behavior. Known limitations and follow-up This record covers single-source apps. Multi-role neurophysiology projects are audited separately because preserving source roles requires a richer migration than renaming one field."},{"title":"Chrono Overlay adopts one version-aware project migration entry","url":"history/records/2026/07/LK-20260716-chrono-overlay-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-chrono-overlay-project-spec 2026-07-16 86 refactor compatible labkit_ChronoOverlay_app Electrochemistry Project lifecycle","text":"Chrono Overlay adopts one version-aware project migration entry id: LK-20260716-chrono-overlay-project-spec date: 2026-07-16 sequence: 86 type: refactor compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context Chrono Overlay split product metadata across three files and represented its only payload upgrade as a separately named `migrateProjectV1ToV2.m` file in a generic lifecycle package. Adding another schema version would have added yet another file and exposed migration sequencing to the App definition. Decision and rationale Make `definition.m` the product declaration and `projectSpec.m` the sole durable-schema entry. Its `Migrate(project, fromVersion)` callback owns App semantics for one version step; Runtime V2 owns the loop, version increment, validation after every step, and unsupported-envelope handling. Changes Consolidated project creation, validation, and migration in one project spec. Replaced the per-version migration file with one version-aware local function. Moved transient DTA reconstruction to a package-root session factory. Removed separate requirements, version, and generic lifecycle files. Kept DTA parsing, pulse-gap alignment, interpolation, plots, and exports unchanged. User and data impact Launch, plotting, save/load, v1 project upgrade, and CSV export behavior remain unchanged. Maintainers now add future schema cases in one ordered migration entry instead of editing both definition wiring and file inventories. Compatibility and migration Version-1 payloads still remove the obsolete decoded `inputs.items` field so portable source records remain the only durable input. Version-2 payloads are unchanged and need no migration. Validation Unit tests call the new migration entry with a synthetic v1 payload and verify validation, session reconstruction, pulse alignment, interpolation, and presenter output. The hidden GUI workflow covers real launch, source loading, plotting, and export through the consolidated definition. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Only v1-to-v2 is currently required. Future cases remain App-owned in the same function; they must not bypass the framework's stepwise validation loop."},{"title":"Chrono Overlay removes App-owned source identity bookkeeping","url":"history/records/2026/07/LK-20260716-chrono-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-chrono-runtime-source-reconciliation 2026-07-16 111 fix compatible labkit_ChronoOverlay_app Electrochemistry App structure Project identity","text":"Chrono Overlay removes App-owned source identity bookkeeping id: LK-20260716-chrono-runtime-source-reconciliation date: 2026-07-16 sequence: 111 type: fix compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Project identity Context Chrono Overlay generated a new source ID from the current record count. After removing an earlier source, adding another file could therefore reuse an ID still owned by a retained record. Runtime validation correctly rejects duplicate IDs, but the App should not have been maintaining this persistence invariant itself. The App also duplicated source append/removal helpers and empty transient workflow/view buckets already supplied by Runtime V2. Decision and rationale After every successful add, removal, or clear operation, derive the ordered path list from the decoded items and delegate durable-record reconciliation to `services.project.reconcileSources`. Chrono Overlay continues to own decoding, pulse-gap alignment, item order, selection, logs, and failure wording. Runtime owns stable identity reuse and collision-free allocation. Only App-specific selection and decoded cache data are returned by `createSession`; Runtime canonicalizes the remaining transient buckets. Changes Removed App-local source record append, removal, and count-based ID logic. Reconciled source records from the accepted decoded item order. Removed empty workflow and view boilerplate from the session factory. Replaced the dynamically growing failure list with one first-failure record while retaining per-file log messages. Added a GUI regression sequence that removes the first of two sources, adds it again, checks unique IDs, saves, clears, and reopens the project. Advanced the Chrono Overlay App version to 1.4.4. User and data impact Normal file loading, ordering, selection, plots, exports, and saved project payloads retain their behavior. The remove-then-add sequence no longer risks a duplicate source ID and failed callback. Persistence IDs remain internal and do not change curve values or scientific output. Compatibility and migration The current payload remains version 2 and no new migration is needed. Existing records retain their IDs when their paths remain in the project. Validation The Chrono Overlay hidden GUI workflow covers two-file loading, selection, remove and re-add, unique identity, export, save, clear, reopen, option redraw, and final plot clearing. Focused unit, structure, version, documentation, and history tests protect the calculation and product contracts. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up CIC, CSC, and VT Resistance still have equivalent App-local source identity helpers and require their own ordering and behavior audits before conversion."},{"title":"Clean-room cross-platform CI","url":"history/records/2026/07/LK-20260717-clean-room-cross-platform-ci.html","kind":"history","section":"history","keywords":"LK-20260717-clean-room-cross-platform-ci 2026-07-17 131 ci compatible project validation and release automation","text":"Clean-room cross-platform CI id: LK-20260717-clean-room-cross-platform-ci date: 2026-07-17 sequence: 131 type: ci compatibility: compatible scope: project validation and release automation Context The previous MATLAB workflow combined ordinary CI, scheduled reports, release validation, and raw tag creation. Its optional Base MATLAB job also mixed static source checks with installed-product dependency analysis, even though that analysis can only report products present on the runner. The design made it difficult to tell which evidence was required and did not exercise every candidate outside the developer's operating system. Decision and rationale Make a Toolbox-free MATLAB installation the baseline for all required CI behavior. Let one CI workflow run complete headless and hidden-GUI tasks on Linux, macOS, and Windows. Keep coverage as an explicit local report. Release creation remains a developer-initiated process after manual App validation, requires a successful main-push CI run for the exact commit, and stops at a draft for final human review. This directly tests the promised runtime instead of trying to infer the same fact from a runner with optional products installed. Changes Replaced the monolithic MATLAB workflow with one ordinary/reusable CI workflow and one manual release workflow. Made cross-platform Base MATLAB headless and hidden-GUI validation mandatory for pull requests and main pushes, and required exact same-commit CI evidence before release. Removed the installed-product dependency-analysis task and all CI Toolbox installation. Kept static known-Toolbox call detection and representative shadowed-helper workflows inside the ordinary headless suite. Kept coverage out of CI because it has no failure threshold and repeats the non-GUI tests; maintainers can still request the local report explicitly. Replaced raw Git-ref creation with a validated annotated tag, tag-blob launcher staging, remote asset verification, and a draft GitHub Release. User and data impact No App runtime, calculation, project, or saved-data behavior changes. CI may now expose operating-system, path, case-sensitivity, layout, or missing- dependency failures that a configured development machine did not reveal. Compatibility and migration Maintainers should use `Continuous Integration` for required checks, `buildtool coverage` for an on-demand local report, and the manual `Release` workflow only after interactive App validation. The retired `buildtool baseMatlab` task has no compatibility alias because clean Base MATLAB execution is now the environment of every required build task rather than a separate partial check. Validation The CI policy contract checks trigger separation, read-only ordinary permissions, three-platform matrices, absence of `products:` installation, public build-task use, timeouts, diagnostics, exact-sha CI evidence, manual release confirmation, tag-blob asset generation, and draft-only publication. Build-task and Toolbox guardrails verify the simplified catalog and retained static/fallback checks. Evidence `.github/workflows/ci.yml` and `release.yml` define the executable pipeline. `tests/cases/contract/project/ci/CiValidationPolicyGuardrailTest.m` protects the new boundaries. Known limitations and follow-up Hidden GUI tests cannot drive native dialogs or judge visual and pointer quality. Developer-led manual App testing therefore remains mandatory before starting a release. CI also detects only Toolbox calls reached by tests or covered by the maintained static-call set; temporary product use still needs its declared fallback, idempotency, and parity evidence."},{"title":"Close guards and caught-exception diagnostics","url":"history/records/2026/06/LK-20260630-close-guards-and-caught-exception-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260630-close-guards-and-caught-exception-diagnostics 2026-06-30 21 feat compatible labkit.ui labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Close guards and caught-exception diagnostics","text":"Close guards and caught-exception diagnostics id: LK-20260630-close-guards-and-caught-exception-diagnostics date: 2026-06-30 sequence: 21 type: feat compatibility: compatible component: `labkit.ui` | `3.2.6 -> 3.2.7` component: `labkit_DICPostprocess_app` | `1.2.1 -> 1.2.2` component: `labkit_BatchImageCrop_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.3.5 -> 1.3.6` component: `labkit_CurvatureMeasurement_app` | `1.2.1 -> 1.2.2` component: `labkit_FocusStack_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.2.3 -> 1.2.4` component: `labkit_ImageEnhance_app` | `1.3.2 -> 1.3.3` component: `labkit_ImageMatch_app` | `1.3.2 -> 1.3.3` component: `labkit_ImageMatch_app` | `1.3.3 -> 1.3.4` component: `labkit_NerveResponseAnalysis_app` | `1.2.3 -> 1.2.4` component: `labkit_ResponseReviewStats_app` | `1.2.2 -> 1.2.3` component: `labkit_RHSPreview_app` | `1.2.1 -> 1.2.2` component: `labkit_ECGPrint_app` | `1.2.0 -> 1.2.1` scope: Close guards and caught-exception diagnostics Context Many app callbacks caught an exception, showed its message, and returned. That protected the UI but discarded the stack and callback context needed to diagnose the failure. Image apps also tracked unsaved or incomplete work in slightly different ways when deciding whether a window could close. Decision and rationale Report caught exceptions to the existing debug trace before presenting the user-facing error, and promote the file-index and close-guard mechanics that were shared by several image apps. Preserve app ownership of what counts as dirty or incomplete work. Changes `labkit.ui` `3.2.6 -> 3.2.7` DIC, Batch Crop, Curvature, Focus Stack, Image Match, neurophysiology apps, and ECG Print patch bumped for diagnostics or close-guard work. Reported caught app-runner exceptions through framework debug diagnostics. Promoted file-entry index helpers. Connected dirty/incomplete workflow state to close guards. User and data impact An app could still recover from a failed load, calculation, or export, while its debug report retained the exception evidence. Closing an image workflow with unfinished state produced a consistent warning instead of silently discarding work. Scientific results and saved schemas were unchanged. Compatibility and migration Existing app state and result files remained valid. Closing an incomplete workflow gained an additional confirmation, and debug reports gained exception details. Validation The exception-reporting commit expanded app/library compatibility checks across the affected runners. The close-guard commit added public-surface and compatibility coverage for promoted file indices and app close behavior. Exact historical commands were not recorded. Evidence Main commits `c0028a81` and `a81853ef`. Known limitations and follow-up Runtime V2 later replaced direct debug-log and close-guard calls with lifecycle and diagnostic services, but retained the requirement that caught exceptions remain observable."},{"title":"Consistent electrochemistry batch analysis","url":"history/records/2026/07/LK-20260713-electrochem-batch-consistency.html","kind":"history","section":"history","keywords":"LK-20260713-electrochem-batch-consistency 2026-07-13 46 fix compatible labkit_CIC_app labkit_VTResistance_app Consistent electrochemistry batch analysis","text":"Consistent electrochemistry batch analysis id: LK-20260713-electrochem-batch-consistency date: 2026-07-13 sequence: 46 type: fix compatibility: compatible component: `labkit_CIC_app` | `1.3.7 -> 1.3.8` component: `labkit_VTResistance_app` | `1.3.7 -> 1.3.8` scope: Consistent electrochemistry batch analysis Context CIC and VT Resistance could retain per-file derived values from different control settings, and CIC could sample outside the recorded time range while displaying a delay without units. Decision and rationale Treat analysis controls as one batch contract and recompute every file before display/export so rows cannot silently mix stale and current parameters. Changes Recompute all loaded files when shared controls change and once more before CSV export. Label CIC delay in microseconds, reject out-of-range sampling, and export the area and delay used for each result. Added family regression coverage and audited sibling electrochem apps. User and data impact Batch rows now represent one consistent area, delay, pulse-detection, resistance-window, and voltage-mode configuration. Invalid delay choices fail instead of extrapolating a misleading value. Compatibility and migration CIC CSV adds trailing `Area_cm2` and `Delay_us` columns. Existing columns keep their names and order; VT Resistance exports remain schema-compatible. Validation Electrochem unit and GUI tests cover batch recomputation, display units, out-of-range handling, and export-time refresh. Evidence The guarded calculations and export builders are app-owned. Commit `67ea2286` introduced the consistent batch-analysis and export behavior. Known limitations and follow-up The fix enforces internal batch consistency but does not choose scientifically appropriate area, delay, or resistance windows for the user."},{"title":"Core, neurophysiology, and ECG project validation ownership","url":"history/records/2026/07/LK-20260716-core-neuro-ecg-project-validation.html","kind":"history","section":"history","keywords":"LK-20260716-core-neuro-ecg-project-validation 2026-07-16 121 refactor compatible labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app remaining public App project schemas App maintenance cost","text":"Core, neurophysiology, and ECG project validation ownership id: LK-20260716-core-neuro-ecg-project-validation date: 2026-07-16 sequence: 121 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.6 -> 0.2.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.4 -> 1.4.5` component: `labkit_ResponseReviewStats_app` | `1.4.4 -> 1.4.5` component: `labkit_RHSPreview_app` | `1.4.4 -> 1.4.5` component: `labkit_ECGPrint_app` | `1.4.4 -> 1.4.5` scope: remaining public App project schemas scope: App maintenance cost Context The remaining public project validators still surrounded their own style, recording-role, metric-window, protocol, ECG, and result rules with repeated canonical bucket and source-record format checks. Decision and rationale Complete the public-App validation ownership split. Runtime owns canonical bucket structs and source-record internals. Each App continues to require its source collection and to enforce its own fields, role/cardinality rules, numeric limits, tables, annotations, and results. Changes Removed 66 net lines of repeated framework structure checks from Figure Studio, the three neurophysiology Apps, and ECG Print. Preserved Nerve Response Analysis's fixed source ID-role pairs and RHS Preview's recording/protocol/filter roles and cardinalities. Added focused project-spec contracts for default acceptance, missing-source rejection, and the retained neurophysiology role rules. User and data impact Valid projects, migrations, signal calculations, previews, and exports are unchanged. Framework-shape failures now have one Runtime owner; domain-schema failures remain attributable to the App that defines them. Compatibility and migration No payload format changed and no migration is required. All supported saved projects continue through their existing centralized `projectSpec.m` migration entry. Validation Focused project-spec tests cover all five defaults and missing-source cases, plus invalid Nerve and RHS source roles. Representative unit and hidden-GUI workflows cover project reconstruction, preview, analysis, and export behavior. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Neurophysiology Apps](../../../../apps/neurophysiology/README.md) [ECG Print](../../../../apps/wearable/ecg-print/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Project validator boilerplate is now removed across the public App fleet. Further App simplification must target independently evidenced action, presentation, or cache patterns rather than weakening domain validation."},{"title":"Cross-platform validation exits cleanly after settled plot fitting","url":"history/records/2026/07/LK-20260721-cross-platform-validation-exit.html","kind":"history","section":"history","keywords":"LK-20260721-cross-platform-validation-exit 2026-07-21 151 fix compatible labkit.app App Framework Continuous Integration","text":"Cross-platform validation exits cleanly after settled plot fitting id: LK-20260721-cross-platform-validation-exit date: 2026-07-21 sequence: 151 type: fix compatibility: compatible component: `labkit.app` | `1.2.2 -> 1.2.3` scope: App Framework scope: Continuous Integration Context Windows headless validation completed every test and report but retained a progress heartbeat resource until the GitHub Actions step timed out. Hosted macOS GUI validation also measured equal-scale plot limits before native layout had settled, producing a platform-specific false failure. Decision and rationale Make lifecycle ownership explicit at the test-runner boundary and measure equal-data-unit limits only after MATLAB has applied pending native layout. This keeps CI diagnostics available while ensuring successful validation exits promptly, without changing the App SDK surface or EIS workflow meaning. Changes Explicitly release each progress plugin and its heartbeat timer when a test runner returns, including nested focused runs. Verify focused runs leave no tagged heartbeat timer behind. Settle native layout before equal-data-unit fitting and assert EIS scaling from the post-action axes allocation. User and data impact Users receive the same transient equal-scale EIS view, now calculated against the displayed axes allocation. Projects, source records, results, and exports are unchanged. Compatibility and migration The patch release is compatible with all 1.x App SDK requirements. No project or result migration is required. Validation Focused nested-runner coverage verifies heartbeat cleanup after return. Focused EIS GUI coverage verifies independent fitting and equal-scale limits. GitHub Actions will re-run the complete platform matrices after delivery. Evidence The timed-out Windows job reported all 468 headless tests and `** Finished headless` more than twenty minutes before the action timeout. The macOS GUI artifact consistently identified the pre-layout equal-scale pixel assertion. Known limitations and follow-up The Windows hosted runner remains the final verification environment for process teardown. Manual visual assessment of plot layout remains outside hidden-GUI automation."},{"title":"Curvature consolidates migration and analysis state","url":"history/records/2026/07/LK-20260716-curvature-project-structure.html","kind":"history","section":"history","keywords":"LK-20260716-curvature-project-structure 2026-07-16 97 refactor compatible labkit_CurvatureMeasurement_app Image Measurement App structure","text":"Curvature consolidates migration and analysis state id: LK-20260716-curvature-project-structure date: 2026-07-16 sequence: 97 type: refactor compatibility: compatible component: `labkit_CurvatureMeasurement_app` | `1.4.2 -> 1.4.3` scope: Image Measurement scope: App structure Context Curvature split static metadata across files, exposed four generic lifecycle functions including a filename-encoded migration, and stored five computation tasks/results under `+appState`. Session restoration also read the nested portable source path directly. Decision and rationale Use one project specification with a version-aware migration entry. Runtime V2 calls `Migrate(project,fromVersion)` for each missing version; Curvature keeps only the actual version-1 transformation. Put fit and length task/result shapes beside the analysis functions that consume them. Changes Consolidated product metadata and requirements in `definition.m`. Consolidated project creation, validation, and the v1 source migration in `projectSpec.m`. Moved decoded-image reconstruction to root `createSession.m`. Moved all five generic state helpers to `+analysisRun`. Removed separate metadata and lifecycle files. Replaced direct portable-reference access with the Runtime path accessor. Kept managed anchor editing, scale calibration, curve fitting, length measurement, overlay rendering, and exports unchanged. User and data impact Opening current or version-1 projects, tracing/removing anchors, calibrating scale, fitting curvature, measuring length, and exporting results behave unchanged. Developers now find every durable-version rule in one file and every numerical result/task next to the numerical implementation. Compatibility and migration The current payload stays at version 2. Version-1 `inputs.source` is still moved to `inputs.sources` exactly once. No new payload migration is introduced. Validation Unit tests cover current project/session construction, v1 migration, fit and length numerics, physical/pixel scales, densification, invalid points, result tables, and deterministic task fingerprints. The hidden GUI suite covers image loading, anchor editing guidance, managed interactions, viewport behavior, scale calibration, results, and save/load. Evidence [Curvature Measurement](../../../../apps/image-measurement/curvature/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other versioned Apps still use filename-per-step migrations and will move to one project entry during their own behavior audits."},{"title":"DIC Postprocess adopts one product and project declaration","url":"history/records/2026/07/LK-20260716-dic-postprocess-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-dic-postprocess-project-spec 2026-07-16 83 refactor compatible labkit_DICPostprocess_app DIC Project lifecycle","text":"DIC Postprocess adopts one product and project declaration id: LK-20260716-dic-postprocess-project-spec date: 2026-07-16 sequence: 83 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.1 -> 1.4.2` scope: DIC scope: Project lifecycle Context DIC Postprocess split static product metadata across `definition.m`, `requirements.m`, and `version.m`, while its version-1 durable schema occupied three generic `+appLifecycle` files. None of those separations represented a separate scientific capability or independently evolving contract. Decision and rationale Make `definition.m` the product declaration and `projectSpec.m` the only durable-schema entry. Keep `createSession.m` separate because it restores file-backed strain, image, mask, and prepared overlay caches that must not be serialized into the project. Changes Moved command metadata, version, update date, and facade requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved transient reconstruction to one explicitly named package-root entry. Removed the two metadata files and generic lifecycle package. Replaced the stale GUI-free manual example with an executable call matching the real input structure, complete parameter contract, and three outputs. Kept all calculation, parameter, action, presentation, and export behavior unchanged. User and data impact Ncorr loading, overlay preparation, statistics, save/load, and exports behave unchanged. Maintainers can now find the complete product contract in one file and the complete durable schema in one adjacent file. Compatibility and migration The command, project ID, payload version, fields, defaults, validation rules, and source records are unchanged. Existing DIC Postprocess projects require no migration. Validation Unit tests exercise Ncorr loading, cached session reconstruction, numerical preparation, summary/export behavior, and presentation. The hidden GUI flow launches the real App and generates overlays and a summary through Runtime V2. Evidence [DIC Postprocess](../../../../apps/dic/dic-postprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The action file remains a meaningful workflow boundary because it coordinates input selection, calculation, export, diagnostics, and dialogs. Its naming and possible smaller capability boundaries will be compared across the DIC family instead of changed in isolation."},{"title":"DIC Preprocess consolidates product and project declarations","url":"history/records/2026/07/LK-20260716-dic-preprocess-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-dic-preprocess-project-spec 2026-07-16 84 refactor compatible labkit_DICPreprocess_app DIC Project lifecycle","text":"DIC Preprocess consolidates product and project declarations id: LK-20260716-dic-preprocess-project-spec date: 2026-07-16 sequence: 84 type: refactor compatibility: compatible component: `labkit_DICPreprocess_app` | `1.5.1 -> 1.5.2` scope: DIC scope: Project lifecycle Context DIC Preprocess split static product metadata across three files and split one version-1 durable schema across a generic lifecycle package. That structure made a maintainer traverse six declarations before reaching any registration, crop, or mask behavior. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the only durable-schema entry. Keep `createSession.m` explicit because decoding source images and replaying alignment/crop steps reconstructs transient cache state rather than defining persistence. Changes Moved command metadata, version, update date, and facade requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved transient image and edit replay to one package-root session factory. Removed the metadata files and generic lifecycle package. Kept registration, crop, mask, history, managed interactions, export, and scientific parameter behavior unchanged. User and data impact Manual and automatic alignment, zoom-preserving point/ROI editing, crop and mask history, project save/load, and exports behave unchanged. The durable and transient state boundaries are now visible from two adjacent files. Compatibility and migration The command, project ID, payload version, fields, defaults, validation, source records, edit steps, and mask history are unchanged. Existing projects require no migration. Validation Unit tests cover project/session reconstruction, state history, registration, crop/mask calculations, source IO, exports, and presenter models. The hidden GUI workflow covers real launch, manual point matching, ROI crop, mask editing, viewport preservation, and generated outputs. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The `+appState` package mixes edit-history and mask-geometry capabilities. It is retained for this compatibility-preserving checkpoint and will be split or renamed by responsibility after its callers and tests are audited."},{"title":"DIC Preprocess replaces its generic state bucket","url":"history/records/2026/07/LK-20260716-dic-preprocess-state-capabilities.html","kind":"history","section":"history","keywords":"LK-20260716-dic-preprocess-state-capabilities 2026-07-16 85 refactor compatible labkit_DICPreprocess_app DIC App ownership","text":"DIC Preprocess replaces its generic state bucket id: LK-20260716-dic-preprocess-state-capabilities date: 2026-07-16 sequence: 85 type: refactor compatibility: compatible component: `labkit_DICPreprocess_app` | `1.5.2 -> 1.5.3` scope: DIC scope: App ownership Context The generic `+appState` package mixed three unrelated concepts: durable align/crop undo history, mask editing and undo, and a predicate over transient source-image cache state. The name exposed storage mechanics without helping a maintainer find the behavior they needed. Decision and rationale Group these operations by the capability that owns their invariants. `editHistory` owns align/crop snapshots and reset behavior, `maskEditing` owns mask canvas composition and undo snapshots, and `sourceFiles` owns the loaded image-pair predicate. None is promoted to LabKit because the project fields, undo limits, image semantics, and operation order are specific to DIC Preprocess. Changes Moved four edit-history operations into `+editHistory`. Moved five mask composition/history operations into `+maskEditing`. Moved the image-pair readiness predicate into `+sourceFiles`. Updated actions, presenters, preview requests, and direct state tests to use the capability paths. Removed the generic `+appState` package without adding an adapter. User and data impact All controls, calculations, undo limits, project fields, masks, alignment/crop history, and viewport behavior are unchanged. Code navigation now starts from the user capability rather than an undifferentiated state bucket. Compatibility and migration This is an app-internal ownership change. Project payloads and public GUI-free scientific APIs are unchanged, so saved projects require no migration. Validation State tests cover history trimming, reset/restore behavior, mask composition, and image-pair readiness through the new capability paths. The full DIC Preprocess unit and hidden GUI workflows verify the unchanged callers. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up `definitionActions.m` remains a large but cohesive workflow coordinator. Its callbacks will be compared with other interaction-heavy image Apps before any further extraction; file length alone is not a reason to create another technical package."},{"title":"DIC and Gait project validation ownership","url":"history/records/2026/07/LK-20260716-dic-gait-project-validation-ownership.html","kind":"history","section":"history","keywords":"LK-20260716-dic-gait-project-validation-ownership 2026-07-16 120 refactor compatible labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_GaitAnalysis_app DIC and Gait project schemas App maintenance cost","text":"DIC and Gait project validation ownership id: LK-20260716-dic-gait-project-validation-ownership date: 2026-07-16 sequence: 120 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.4 -> 1.4.5` component: `labkit_DICPreprocess_app` | `1.5.5 -> 1.5.6` component: `labkit_GaitAnalysis_app` | `2.0.5 -> 2.0.6` scope: DIC and Gait project schemas scope: App maintenance cost Context DIC Postprocess, DIC Preprocess, and Gait repeated Runtime's canonical project and source-record shape checks around their real domain constraints. The duplication made it harder to see which saved fields these Apps actually own. Decision and rationale Retain each App's required source collection and domain schema while relying on Runtime for canonical bucket structs and source-record internals. DIC continues to own alignment, crop, mask, summary, and parameter fields; Gait continues to own pose-analysis options, numeric limits, and result structure. Changes Removed 26 net lines of repeated framework structure checks from the three project specifications. Preserved every App-required source field and domain-specific validator. Added GUI-free DIC and Gait contracts that accept default projects and reject projects missing their required source collection. User and data impact Valid DIC and Gait projects, migrations, calculations, and exports are unchanged. Malformed framework structures still fail in Runtime and malformed domain fields still fail in the owning App. Compatibility and migration No payload format changed and no migration is required. Supported older projects continue through the same App-owned migration callbacks. Validation Focused project-spec tests cover default acceptance and missing-source rejection. Hidden GUI workflows cover representative DIC load/edit/analysis and Gait project load/step-analysis behavior. Evidence [DIC Apps](../../../../apps/dic/README.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up This boundary cleanup does not change the scientific gait analysis or image registration algorithms. Their domain complexity remains App-owned."},{"title":"DIC uses framework-owned optional source slots","url":"history/records/2026/07/LK-20260716-dic-source-slots.html","kind":"history","section":"history","keywords":"LK-20260716-dic-source-slots 2026-07-16 93 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app DIC Runtime source boundary","text":"DIC uses framework-owned optional source slots id: LK-20260716-dic-source-slots date: 2026-07-16 sequence: 93 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.0 -> 7.4.1` component: `labkit_DICPostprocess_app` | `1.4.2 -> 1.4.3` component: `labkit_DICPreprocess_app` | `1.5.3 -> 1.5.4` scope: DIC scope: Runtime source boundary Context DIC Preprocess and Postprocess use stable semantic source slots such as `referenceImage`, `movingImage`, `maskImage`, and `dicMat`. Those slots are legitimately absent before the user selects a file. Each App therefore carried an identical `pathForId.m` wrapper that returned empty text for an unset slot and otherwise inspected the nested portable reference. The first source accessor required every requested ID to exist. Applying that rule would have preserved the duplicate wrapper instead of lowering App cost. Decision and rationale Make ID-based source lookup preserve the requested shape and return an empty string for a semantic slot that has not been added. Continue to reject malformed records and references. This matches ordinary presenter and session behavior without hiding corrupt project data. Changes Refined `sourcePaths(sources,ids)` for optional semantic slots. Removed both DIC `pathForId.m` wrappers and their duplicate schema knowledge. Migrated DIC loading, summaries, presenters, save defaults, and export paths to the Runtime accessor. Preserved alignment, crop, mask, anchor, rendering, calculation, and export behavior. User and data impact An empty DIC project still presents empty file controls and enables them as the workflow requires. Selected and relinked files load through the same paths. Developers no longer implement a source-ID lookup helper for each App. Compatibility and migration The accessor change relaxes one error case and is compatible within UI 7. No saved project shape changed and no DIC payload migration is required. Validation Runtime unit tests cover mixed present and absent IDs. DIC unit and hidden-GUI tests cover empty startup, file selection, point matching, ROI and mask edits, viewport-preserving interaction, project state, and result export. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [DIC Postprocess](../../../../apps/dic/dic-postprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other families still contain direct portable-reference reads. They will move to the same accessor before the repository-wide no-leak contract is enabled."},{"title":"DTA facade and app ownership boundaries","url":"history/records/2026/05/LK-20260529-dta-facade-and-app-boundaries.html","kind":"history","section":"history","keywords":"LK-20260529-dta-facade-and-app-boundaries 2026-05-29 2 refactor compatible historical project evolution","text":"DTA facade and app ownership boundaries id: LK-20260529-dta-facade-and-app-boundaries date: 2026-05-29 sequence: 2 type: refactor compatibility: compatible scope: historical project evolution Context The first extraction pass separated calculations from the imported GUIs, but the electrochem apps still depended on a broad internal package and shared session helpers. A new app could reuse a parser only by learning implementation details that had nothing to do with reading a DTA file. Decision and rationale Make DTA loading the reusable boundary and keep workflow decisions in the app that owns them. The public layer would load one file, discover files, or load a folder and return documented report structures. Plot labels, callback order, session state, result summaries, and export choices would remain app concerns. This distinction became an early form of LabKit's present architecture: a small library presents stable data operations, while an app owns the laboratory workflow assembled around those operations. Changes Added GUI-independent DTA loading and adopted it in the EIS and Chrono Overlay apps. Added discovery and folder-loading operations, including validation for empty selections, missing folders, and expected experiment kinds. Documented and tested the report schemas returned by batch operations. Moved EIS, Chrono Overlay, CSC, VT Resistance, and CIC out of the original `gamrywb` namespace and into app-owned entry points. Returned calculation details, labels, callback flow, and exports to their owning apps when they were not genuinely reusable. Introduced boundary tests to prevent UI, data, IO, analysis, and utility packages from growing through accidental cross-dependencies. User and data impact Electrochem users kept the same principal workflows, but file-loading failures became more explicit and batch loading gained a defined result report. App authors could use the DTA layer without starting a GUI or depending on the layout of an existing app. No laboratory file format or scientific result schema was intentionally changed by the ownership cleanup. Compatibility and migration The supported app commands replaced direct use of the old `gamrywb` package. Code that reached into that package's internal helpers needed to move either to the DTA facade or into the app that owned the behavior. Validation The period added DTA schema tests, empty- and missing-folder tests, architectural boundary checks, and behavior-focused calculation tests. The repository did not yet have the later unified MATLAB test command, so no single historical validation invocation is available. Evidence GUI-free loading facade `88b19851` and initial app adoption `789ef507`, `0ccc3ad6`. App ownership migration `1ee8e82d` through `1b6042bf`. Discovery and folder loading `c06946d9`, `82f4146d`. Report documentation and contract tests `669eea38`, `e04292c0`. Boundary and failure-handling work from `ad2b6c74` through `7506fa32`. Known limitations and follow-up The public surface was still organized around the first electrochem use cases. The next stage renamed the workbench, narrowed the package map further, and tested whether the same app-first approach could support image and biosignal workflows."},{"title":"Debug sample packs","url":"history/records/2026/07/LK-20260701-debug-sample-packs.html","kind":"history","section":"history","keywords":"LK-20260701-debug-sample-packs 2026-07-01 28 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Debug sample packs","text":"Debug sample packs id: LK-20260701-debug-sample-packs date: 2026-07-01 sequence: 28 type: feat compatibility: compatible component: `labkit.ui` | `3.3.1 -> 3.4.0` component: `labkit_DICPostprocess_app` | `1.2.4 -> 1.3.0` component: `labkit_DICPreprocess_app` | `1.2.2 -> 1.3.0` component: `labkit_ChronoOverlay_app` | `1.2.1 -> 1.3.0` component: `labkit_CIC_app` | `1.2.1 -> 1.3.0` component: `labkit_CSC_app` | `1.2.1 -> 1.3.0` component: `labkit_EIS_app` | `1.2.1 -> 1.3.0` component: `labkit_VTResistance_app` | `1.2.1 -> 1.3.0` component: `labkit_BatchImageCrop_app` | `1.5.1 -> 1.6.0` component: `labkit_CurvatureMeasurement_app` | `1.2.4 -> 1.3.0` component: `labkit_FLIRThermal_app` | `1.1.2 -> 1.2.0` component: `labkit_FocusStack_app` | `1.3.0 -> 1.4.0` component: `labkit_ImageEnhance_app` | `1.4.1 -> 1.5.0` component: `labkit_ImageMatch_app` | `1.4.1 -> 1.5.0` component: `labkit_NerveResponseAnalysis_app` | `1.2.4 -> 1.3.0` component: `labkit_ResponseReviewStats_app` | `1.2.3 -> 1.3.0` component: `labkit_RHSPreview_app` | `1.2.4 -> 1.3.0` component: `labkit_ECGPrint_app` | `1.2.2 -> 1.3.0` scope: Debug sample packs Context Debug reports could identify a failed callback, but reproducing the failure still depended on the original laboratory file and the user's exact sequence of actions. Those inputs were often unavailable to the person investigating the report and were unsuitable as permanent test fixtures. Decision and rationale Let every app create a small, synthetic sample pack that exercises its normal loading path without containing laboratory data. Store the generated inputs and expected output location beside the debug artifacts so a report can be replayed from one self-contained folder. Changes `labkit.ui` `3.3.1 -> 3.4.0` All supported apps moved into the `1.3.x`, `1.4.x`, `1.5.x`, or `1.6.x` debug-sample-pack lines. Added app-owned debug sample packs. Added debug artifact sample and output folders. User and data impact Debug mode gained a repeatable demonstration dataset for each supported app. These packs were synthetic and intended for diagnosis, not as substitutes for real experiment files or as reference scientific results. Compatibility and migration The sample packs were additive debug assets. Existing app inputs and outputs were unchanged, and no user file was copied into a pack. Validation Commit `279befbc` added coverage tests for every app family, focused tests for DIC, electrochem, image, RHS, and wearable sample writers, and layout checks that exercised the generated packs. Evidence Main commit `279befbc`. Known limitations and follow-up Sample packs were deliberately app-owned because each workflow knows which inputs make a useful reproduction. They do not contain or archive a user's original files."},{"title":"Debug workflows, launcher tools, and changelog governance","url":"history/records/2026/07/LK-20260707-debug-workflows-launcher-tools-and-changelog-governance.html","kind":"history","section":"history","keywords":"LK-20260707-debug-workflows-launcher-tools-and-changelog-governance 2026-07-07 39 feat compatible labkit_launcher labkit_launcher labkit_launcher labkit.ui labkit.ui labkit_DICPreprocess_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_FigureStudio_app Debug workflows, launcher tools, and changelog governance","text":"Debug workflows, launcher tools, and changelog governance id: LK-20260707-debug-workflows-launcher-tools-and-changelog-governance date: 2026-07-07 sequence: 39 type: feat compatibility: compatible component: `labkit_launcher` | `1.2.4 -> 1.2.5` component: `labkit_launcher` | `1.2.5 -> 1.2.6` component: `labkit_launcher` | `1.2.6 -> 1.2.7` component: `labkit.ui` | `5.0.0 -> 5.0.1` component: `labkit.ui` | `5.0.1 -> 5.0.2` component: `labkit_DICPreprocess_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.6.6 -> 1.6.7` component: `labkit_FocusStack_app` | `1.4.5 -> 1.4.6` component: `labkit_FigureStudio_app` | `0.1.4 -> 0.1.5` scope: Debug workflows, launcher tools, and changelog governance Context The first complete debug sample packs exposed several false failures. A modal file chooser could be mistaken for a stalled callback, DIC could read an older ROI snapshot than the editor displayed, duplicate crops needed confirmation before export, and Focus Stack's folder workflow was hard to reach from its sample instructions. The launcher was also becoming a practical maintenance and distribution tool. Code Analyzer reports needed a durable home outside launcher source, individual apps needed an offline package format, and private app workspaces needed to remain discoverable without entering the public repository. Finally, the growing changelog needed to explain the direction of the project, not merely repeat tags and commit subjects. Decision and rationale Use v3.1.0 as a stabilization release for reproducible debug workflows. Treat native modal dialogs as active UI, read live editor state when it is newer than the stored snapshot, and make required gestures visible in the app. Move code analysis and packaging into dedicated tools, let the launcher invoke them, and keep private app discovery separate from public release contents. Changes Release tag `v3.1.0` `labkit_launcher` `1.2.4 -> 1.2.7` `labkit.ui` `5.0.1 -> 5.0.2` `labkit_FigureStudio_app` `0.1.4 -> 0.1.5` `labkit_DICPreprocess_app` `1.3.4 -> 1.3.5` `labkit_BatchImageCrop_app` `1.6.6 -> 1.6.7` `labkit_FocusStack_app` `1.4.5 -> 1.4.6` DIC Preprocess ROI mask export now reads the live ROI editor anchors when building a mask, so preview/save do not misreport a drawn ROI as empty when editor state is newer than the app state snapshot. DIC Preprocess keeps the double-click ROI anchor workflow and makes the double-click requirement explicit in the visible details text. Batch Image Crop duplicate tasks now redraw with finite preview overlay coordinates while still requiring users to confirm the duplicated crop center before export. Figure Studio quick PNG/JPG/SVG export actions use runtime-compatible handler signatures. Focus Stack exposes a direct `Choose folder` action for loading all supported images from a focus-stack folder. Debug trace diagnostics no longer write stalled-callback crash reports while a file chooser modal is active. Moved the launcher Code Analyzer scan into `tools/codecheck`, which writes timestamped JSON/HTML report pairs under `artifacts/code-check/` without overwriting earlier runs. Added launcher actions and a deployment tool that package one selected LabKit app into a standalone zip, either as source `.m` files or encoded `.p` files. Added launcher discovery for local private app workspaces under `private_apps/apps/` and roots named by `LABKIT_PRIVATE_APP_ROOTS`. Clarified the public changelog model as a project evolution map organized by reader-facing evolution entries, with release tags and commits kept as anchors and evidence rather than the primary structure. User and data impact The supplied debug packs could be followed without producing a false crash report or an apparently disabled workflow. DIC mask export used the anchors currently visible in the editor, duplicate crops remained blocked until their centers were confirmed, and Focus Stack offered a direct folder action. Maintainers gained timestamped HTML and JSON Code Analyzer reports. A selected app could be packaged for production or offline use without tests, unrelated apps, or repository metadata. Private apps could live beside the checkout, appear in the ordinary launcher, and retain their own repository and history. Compatibility and migration DIC ROI editing still uses double-click to add anchors; no interaction-mode migration is required. Existing file-panel image selection remains available in Focus Stack. Code Analyzer report consumers should read the timestamped `artifacts/code-check/matlab_code_issues_*.json` files. Full LabKit checkout installs are unchanged. Single-app packages can start through either the packaged launcher or the direct run file; P-code packages require MATLAB to run the generated `.p` files. Public apps, public releases, and public CI remain scoped to `apps/`. Validation PR #34 updated debug sample, ROI, crop, folder-loading, launcher-tool, deployment, and private-workspace tests before its squash merge. Tag `v3.1.0` points to release commit `9db01952`; the exact local command sequence was not recorded in this page. Evidence PR #34 squash merge `9db01952` and release tag `v3.1.0`. Known limitations and follow-up The first deployment tool packaged one app at a time. Multi-app bundles were added later. The changelog model introduced here was subsequently replaced by component-linked history pages generated with the documentation site."},{"title":"Declarative app runtime","url":"history/records/2026/07/LK-20260703-declarative-app-runtime.html","kind":"history","section":"history","keywords":"LK-20260703-declarative-app-runtime 2026-07-03 32 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Declarative app runtime","text":"Declarative app runtime id: LK-20260703-declarative-app-runtime date: 2026-07-03 sequence: 32 type: refactor compatibility: compatible component: `labkit.ui` | `3.4.4 -> 3.4.5` component: `labkit_DICPostprocess_app` | `1.3.0 -> 1.3.1` component: `labkit_DICPreprocess_app` | `1.3.0 -> 1.3.1` component: `labkit_ChronoOverlay_app` | `1.3.0 -> 1.3.1` component: `labkit_CIC_app` | `1.3.0 -> 1.3.1` component: `labkit_CSC_app` | `1.3.0 -> 1.3.1` component: `labkit_EIS_app` | `1.3.0 -> 1.3.1` component: `labkit_VTResistance_app` | `1.3.0 -> 1.3.1` component: `labkit_BatchImageCrop_app` | `1.6.1 -> 1.6.2` component: `labkit_CurvatureMeasurement_app` | `1.3.0 -> 1.3.1` component: `labkit_FLIRThermal_app` | `1.2.0 -> 1.2.1` component: `labkit_FocusStack_app` | `1.4.0 -> 1.4.1` component: `labkit_ImageEnhance_app` | `1.5.0 -> 1.5.1` component: `labkit_ImageMatch_app` | `1.5.0 -> 1.5.1` component: `labkit_NerveResponseAnalysis_app` | `1.3.0 -> 1.3.1` component: `labkit_ResponseReviewStats_app` | `1.3.0 -> 1.3.1` component: `labkit_RHSPreview_app` | `1.3.0 -> 1.3.1` component: `labkit_ECGPrint_app` | `1.3.1 -> 1.3.2` scope: Declarative app runtime Context Apps had adopted common shells and controls, but each runner still assembled callbacks, startup steps, refresh behavior, and debug hooks in its own order. Understanding an app meant reading a long construction procedure before reaching the scientific workflow. Decision and rationale Describe an app through a definition containing its layout, actions, state, startup, and rendering contracts, then let the runtime perform the common wiring. Keep action bodies, calculations, and result structures in the app so the definition exposes workflow structure without becoming a generic analysis language. Changes `labkit.ui` `3.4.4 -> 3.4.5` All supported apps received patch bumps. Migrated apps to declarative workflow runtime. User and data impact The visible workflows were intended to remain the same. For maintainers, each app gained a recognizable definition and lifecycle, while repeated setup moved out of its runner. Debug sample packs and focused app tests continued to call the same app-owned operations. Compatibility and migration Users kept the same app entry points and data files. App implementations moved to definitions and runtime actions; unsupported code that reached into old runner construction details needed to adopt the declarative APIs. Validation The migration changed 545 files and updated the affected app, layout, export, calculation, sample-pack, and package-boundary suites in the same commit. The commit message records final profiler and debug evidence; the exact local command was not preserved in this history. Evidence Main commit `568b3e9b`. Known limitations and follow-up The first declarative vocabulary still used the mixed `app`, `spec`, `view`, `tool`, and `diag` package names. The UI 4 and UI 5 passes refined how groups, plots, interactions, and lifecycle responsibilities were named."},{"title":"Default LabKit close protection","url":"history/records/2026/07/LK-20260709-default-labkit-close-protection.html","kind":"history","section":"history","keywords":"LK-20260709-default-labkit-close-protection 2026-07-09 43 fix compatible labkit.ui labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app Default LabKit close protection","text":"Default LabKit close protection id: LK-20260709-default-labkit-close-protection date: 2026-07-09 sequence: 43 type: fix compatibility: compatible component: `labkit.ui` | `5.0.2 -> 5.0.3` component: `labkit_FocusStack_app` | `1.4.6 -> 1.4.7` component: `labkit_ImageEnhance_app` | `1.5.5 -> 1.5.6` component: `labkit_ImageMatch_app` | `1.5.5 -> 1.5.6` scope: Default LabKit close protection Context Only apps that installed a dirty-state guard warned before closing. A newly created public or private app could therefore discard work silently until its author implemented and tested a separate guard. Repeated close shortcuts could also race with a modal confirmation. Decision and rationale Make close confirmation a default property of every framework-owned app window and remove the optional app-facing guard API. Keep one in-window prompt active at a time; a repeated close shortcut confirms that prompt instead of opening another one. Changes `labkit.ui` `5.0.2 -> 5.0.3` `labkit_FocusStack_app` `1.4.6 -> 1.4.7` `labkit_ImageEnhance_app` `1.5.5 -> 1.5.6` `labkit_ImageMatch_app` `1.5.5 -> 1.5.6` LabKit runtime figures now show an in-window confirmation prompt before any framework-owned app window closes, even when the app has not marked itself dirty. Removed the app-facing `labkit.ui.runtime.setCloseGuard` API and migrated existing app close-guard dirty checks to the framework default behavior. Repeating or holding the app close shortcut while the in-window prompt is active confirms the close. User and data impact Every public or private LabKit app asked before its window closed, even if the app had no custom dirty-state logic. Users gained a consistent protection against accidental closure at the cost of one confirmation step. Compatibility and migration Closing LabKit apps now requires one confirmation step by default. App code that calls `labkit.ui.runtime.setCloseGuard` must remove that call; close confirmation is framework-owned. Validation Commit `0c9f472b` expanded the UI busy-state GUI suite for initial close, cancel, confirmation, and repeated-shortcut behavior, then updated public- surface and app-compatibility checks. Evidence Mainline commit `0c9f472b`. Known limitations and follow-up This policy protected the window, not the semantic completeness of each saved project. Runtime V2 later combined default close handling with app-owned dirty state and lifecycle services."},{"title":"Destination-rebased source references","url":"history/records/2026/07/LK-20260716-destination-rebased-source-references.html","kind":"history","section":"history","keywords":"LK-20260716-destination-rebased-source-references 2026-07-16 74 fix compatible labkit.ui project serialization named save, explicit autosave, and recovery","text":"Destination-rebased source references id: LK-20260716-destination-rebased-source-references date: 2026-07-16 sequence: 74 type: fix compatibility: compatible component: `labkit.ui` | `6.0.4 -> 6.0.5` scope: project serialization scope: named save, explicit autosave, and recovery Context Runtime source records were created before an App knew its future project-file location, so they began with an empty `relativePath`. Project-envelope creation copied those records unchanged. As a result, the resolver supported relative references, but ordinary Runtime V2 saves did not actually generate them. Decision and rationale Rebase source references at the serialization boundary using the actual MAT destination. A named project, app-owned autosave, and framework recovery file can live in different folders, so each write must calculate its own relative path rather than reusing a path cached in live state. Changes Passed the real destination path into every Runtime V2 envelope writer. Copied the durable project and refreshed reference schema, relative path, original path, and filename immediately before serialization. Preserved additive app/reference fields while replacing standard path fields. Kept resolved absolute paths in live state so current-session readers do not need to interpret portable references. User and data impact A project and its source directory can now move together to another root or machine and reopen through the saved relative relationship even when the old absolute path no longer exists. Existing projects without a relative path remain readable through their original-path and relink fallbacks. Compatibility and migration The envelope and source-reference schema versions are unchanged. No payload migration is needed: the next named save, explicit autosave, or recovery write adds the destination-correct relative path. Validation The Runtime V2 project GUI test saves a source/project directory tree, verifies the generated relative path, moves the tree, and reloads from the new root. Video Marker verifies that its source-adjacent explicit autosave is rebased from the autosave destination. Existing atomic-save, relink, recovery, and additive field tests remain in the same focused suites. Evidence [Runtime and Data Model](../../../../framework/guides/runtime.md) explains when relative paths are created and how they are resolved. [UI Framework](../../../../framework/README.md) describes Runtime V2 project ownership and persistence. Known limitations and follow-up The portable-reference creation and resolution algorithms remain public in UI 6 for compatibility. They are implementation mechanics rather than App-facing workflow APIs and are reviewed with the remaining Runtime public surface for a single future major-boundary cleanup."},{"title":"Documentation navigation follows topic hierarchy","url":"history/records/2026/07/LK-20260717-hierarchical-documentation-navigation.html","kind":"history","section":"history","keywords":"LK-20260717-hierarchical-documentation-navigation 2026-07-17 132 docs compatible MATLAB documentation site navigation","text":"Documentation navigation follows topic hierarchy id: LK-20260717-hierarchical-documentation-navigation date: 2026-07-17 sequence: 132 type: docs compatibility: compatible scope: MATLAB documentation site navigation Context The documentation site exposed its major areas in the top bar, but each area's left sidebar was a flat list. Related app families, development tasks, and API packages were not visually distinguished, so readers could not readily see the path from an area to a topic and then to a specific page. Decision and rationale Render contextual hierarchy from the existing page and catalog metadata. App families, libraries, development tasks, and MATLAB packages become labeled sidebar branches. This preserves one structured source of navigation truth while making downstream topics visible from their area landing pages. Changes Grouped app navigation by family and indented app manuals below family pages. Grouped function, framework, and development navigation by topic. Grouped API siblings under their owning MATLAB package. Defined the second `nav` entry in `docs/site.json` as the branch label and ordered branches by their earliest page. Added responsive sidebar styling and renderer regression coverage for the hierarchy. User and data impact Readers can move from a top-level area to a topic branch and then to a specific manual without searching a flat list. Documentation content, URLs, MATLAB behavior, and scientific data are unchanged. Compatibility and migration Existing page URLs and top-level navigation remain compatible. Documentation authors should assign new multi-page topics a stable second-level `nav` label in `docs/site.json`. Validation The documentation renderer regression verifies Development topic groups, App family branches, and API package labels. `docsCheck` rebuilds and compares the complete generated site. Evidence The generated area landing pages contain labeled branch headings and parent/child link classes derived from tracked page and app catalog metadata. Known limitations and follow-up Single-page areas do not display an artificial empty branch. History records remain in the generated timeline because reproducing the full record set in the sidebar would make navigation less usable."},{"title":"Documentation rendering avoids repeated model capture","url":"history/records/2026/07/LK-20260717-documentation-render-performance.html","kind":"history","section":"history","keywords":"LK-20260717-documentation-render-performance 2026-07-17 130 perf compatible MATLAB documentation renderer","text":"Documentation rendering avoids repeated model capture id: LK-20260717-documentation-render-performance date: 2026-07-17 sequence: 130 type: perf compatibility: compatible scope: MATLAB documentation renderer Context Two project documentation tests each rebuild the complete tracked site. Profiling showed that most render time was not file generation or tree comparison: every inline Markdown fragment created an anonymous function that captured the complete documentation model before scanning links. Decision and rationale Pass the shared model and current page directly into token protection and dispatch code, image, and link rendering by token type. This preserves the renderer contract and generated bytes while avoiding thousands of large closure allocations. Changes Replaced per-fragment renderer closures with direct token dispatch. Retained the same code, image, link, emphasis, and nested inline-code behavior. Added a focused regression guard against restoring the measured closure pattern. User and data impact Generated HTML is byte-for-byte unchanged. Documentation rebuilds, consistency checks, and the launcher documentation action complete substantially sooner. Compatibility and migration `renderLabKitDocs`, `checkLabKitDocs`, `buildtool docs`, and `buildtool docsCheck` retain their existing syntax, outputs, and failure behavior. No migration is required. Validation On the same MATLAB R2025a host and complete `checkLabKitDocs` scenario, profiler total time decreased from 46.52 seconds to 9.66 seconds, approximately 79 percent. The after-profile run also completed the byte comparison against the tracked site. Evidence The focused renderer regression protects inline link behavior and the allocation fix. `docsCheck` independently rebuilds all narrative and API pages and compares every generated file. Known limitations and follow-up Absolute times vary by machine and filesystem. API help rendering is now the largest measured renderer-owned cost and should be changed only after a separate profile identifies a safe optimization."},{"title":"Documentation search ranks page intent and scopes history explicitly","url":"history/records/2026/07/LK-20260720-ranked-and-scoped-documentation-search.html","kind":"history","section":"history","keywords":"LK-20260720-ranked-and-scoped-documentation-search 2026-07-20 146 docs compatible Documentation system tools/docs/ site search","text":"Documentation search ranks page intent and scopes history explicitly id: LK-20260720-ranked-and-scoped-documentation-search date: 2026-07-20 sequence: 146 type: docs compatibility: compatible scope: Documentation system scope: tools/docs/ scope: site search Context The generated documentation search treated every term in a page title, body, and rendered component-history links as equivalent. A shared history record could therefore make an unrelated App page appear before the App named by a search. Readers also had no way to restrict a query to history records. Decision and rationale Keep component-history links visible on their related pages, but index history records as their own searchable documents. Give title and explicit keywords more weight than body text, then provide a visible section filter for readers who want Apps, APIs, history, or another documentation area. Changes Added section and keyword fields to generated search entries. Excluded rendered component-history link text from narrative page search bodies while retaining complete history-record search entries. Added deterministic field-weighted ranking, result excerpts, and section filtering to the offline-safe client search. Added regression coverage for search-entry separation, filters, and ranking contracts. User and data impact Searching a named App now favors that App and its family over unrelated pages that share a historical record. Readers can search only History when tracing a decision. No App behavior, calculations, projects, exports, or laboratory data changes. Compatibility and migration The generated site remains usable from file:// without network fetches. Existing documentation URLs, history records, and page-level change-history links remain unchanged. Validation Focused documentation search contract tests verify the generated index and client controls. Documentation rendering and consistency checks regenerate the tracked site and validate the history record. Evidence renderLabKitDocs now emits separated search fields. The client script ranks title, keyword, and body matches independently and filters by the generated section field. Known limitations and follow-up Search is intentionally dependency-free and uses exact lexical matching. Typo-tolerant suggestions can be considered later only if they preserve the current deterministic title-first ordering."},{"title":"Documentation structure becomes the navigation contract","url":"history/records/2026/07/LK-20260719-path-derived-documentation.html","kind":"history","section":"history","keywords":"LK-20260719-path-derived-documentation 2026-07-19 136 refactor compatible labkit_launcher Documentation renderer Documentation maintenance","text":"Documentation structure becomes the navigation contract id: LK-20260719-path-derived-documentation date: 2026-07-19 sequence: 136 type: refactor compatibility: compatible component: `labkit_launcher` | `1.5.1 -> 1.5.2` scope: Documentation renderer scope: Documentation maintenance Context The local HTML site duplicated documentation ownership across Markdown files, `site.json`, two catalog JSON files, and generated output. Moving or adding a page therefore required several coordinated edits. Markdown links also embedded many relative paths without a repository-owned way to repair links after a move. Decision and rationale Treat the `docs/` directory structure as the narrative navigation contract. Discover public Apps from `labkit_launcher(\"list\")`, associate each App with its unique path-conventional manual, and discover public API pages from complete MATLAB help contracts. Keep ordinary relative Markdown links so source pages remain useful in GitHub and add a checker that can repair a broken link only when its destination is unambiguous. The generated `site/` remains the offline and local HTML product. It is still produced exclusively by the repository renderer. Changes Removed `docs/site.json` and the App and API catalog JSON files. Reorganized development and framework guides into path-owned navigation groups and moved the API landing page to `docs/reference/README.md`. Made the renderer discover Markdown pages, public Apps, and complete public MATLAB help without duplicated registries. Added `maintainLabKitDocLinks` for checking and uniquely repairing relative Markdown links after file moves. Changed launcher documentation lookup to derive the selected App manual and generated HTML path from App discovery and documentation structure. Removed obsolete compatibility redirect pages instead of preserving legacy documentation routes. User and data impact Local HTML documentation and launcher documentation actions remain available. Contributors add or move a page by placing Markdown in the intended directory and running the link maintainer and renderer; no navigation catalog needs a matching edit. Scientific behavior and project data are unchanged. Compatibility and migration Existing current documentation links are rewritten to their new relative paths. Old generated documentation routes are intentionally retired and are not maintained as redirect pages. Rebuild `site/` after updating a checkout. Validation Focused documentation contract and renderer regression tests cover discovery, navigation, link repair, public API help, and launcher documentation lookup. The documentation checker validates the generated site after a clean local render. Evidence [Documentation Build Tools](../../../../development/tools/documentation.md) [Maintaining LabKit Documentation](../../../../development/maintain-and-release/documentation.md) [LabKit Launcher](../../../../apps/labkit-core/launcher/README.md) [API Reference](../../../../reference/README.md) Known limitations and follow-up Automatic link repair deliberately refuses ambiguous destinations. Authors must resolve those cases explicitly so the checker never guesses between pages with the same filename or title."},{"title":"Durable Video Marker source metadata","url":"history/records/2026/07/LK-20260716-video-marker-durable-metadata.html","kind":"history","section":"history","keywords":"LK-20260716-video-marker-durable-metadata 2026-07-16 70 feat compatible labkit_VideoMarker_app project payload schema downstream gait inputs","text":"Durable Video Marker source metadata id: LK-20260716-video-marker-durable-metadata date: 2026-07-16 sequence: 70 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `1.4.1 -> 1.5.0` scope: project payload schema scope: downstream gait inputs Context Video Marker previously kept frame rate, frame count, duration, and image size only in transient decoded-video state. Its MAT project preserved coordinates but a GUI-independent downstream analysis could not recover time semantics without reopening the original video or inventing a frame rate. Decision and rationale Make immutable decoded-video facts part of the durable Video Marker project. The portable source record continues to own the file path; the metadata record contains only finite numeric facts required to interpret saved annotations. Changes Advanced the Video Marker project payload from version 1 to version 2. Added durable frame count, frame rate, duration, height, and width. Populate metadata when a video or marker CSV is opened and seed rebuilt sessions from saved metadata before optional source decoding. Refresh durable metadata from the reopened source before an explicit **Save autosave**, so a loaded version 1 autosave upgrades in place. Validate metadata values and annotation/frame-count consistency. Added an ordered v1-to-v2 migration that recovers frame count from saved annotation coordinates and leaves unknowable source facts at zero. User and data impact New named projects and source-adjacent autosaves contain enough timing and geometry information for Gait Analysis to treat the MAT document as its authoritative input. Paths are not duplicated into the metadata record. Compatibility and migration Version 1 LabKit project envelopes remain readable. Opening one marks it for an upgrade; saving writes payload version 2. A frame rate absent from version 1 cannot be inferred from coordinates alone and remains zero until the source is opened by the current Video Marker app. Validation Video Marker unit tests verify metadata selection, absence of transient paths, frame-count recovery, payload validation, and mismatched annotation rejection. The existing GUI suite covers video opening, marker CSV import, project saves, and recovery loading. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the persisted fields and downstream use. Gait Analysis input tests exercise the current payload contract separately. Known limitations and follow-up Projects whose old payload never recorded a frame rate must reopen their source video before producing a fully self-describing current project. Opening that source through the saved reference and pressing **Save autosave** performs the upgrade without another location prompt."},{"title":"ECG Print uses one project contract","url":"history/records/2026/07/LK-20260716-ecg-print-structure.html","kind":"history","section":"history","keywords":"LK-20260716-ecg-print-structure 2026-07-16 106 refactor compatible labkit_ECGPrint_app Wearable App structure","text":"ECG Print uses one project contract id: LK-20260716-ecg-print-structure date: 2026-07-16 sequence: 106 type: refactor compatibility: compatible component: `labkit_ECGPrint_app` | `1.4.2 -> 1.4.3` scope: Wearable scope: App structure Context ECG Print kept product metadata in separate requirement and version functions, and split one durable schema across project creation, validation, migration, and session reconstruction files under a generic lifecycle package. Session and presentation code also read the Runtime-owned portable-reference structure directly. Decision and rationale Use the compact Runtime V2 App structure. The definition owns product metadata and optional capabilities, one project contract owns durable schema evolution, and one root session factory rebuilds decoded signal state. ECG-specific parsing, analysis, presentation, and result packages already express real workflow capabilities and remain App-owned. Changes Consolidated command identity, display metadata, version, requirements, layout, actions, presenter, renderer, and debug capability in `definition`. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved transient recording and analysis reconstruction to root `createSession` and reused that factory when clearing a failed decode. Replaced nested portable-reference reads with semantic `sourcePaths` lookup. Removed the generic lifecycle package and separate requirement/version files. Updated the App package structure guardrail so actions, presentation, project persistence, and transient sessions are optional capabilities; Apps that adopt root `projectSpec` cannot regress to split lifecycle metadata. User and data impact Recording import, parsing controls, channel selection, filtering, peak detection, segmentation, template construction, SNR measurements, plots, exports, manifests, project reopening, and user wording are unchanged. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still move their singular source record into the canonical source collection before validation. Runtime V2 now invokes the single migration entry and owns iteration to the current payload version. Current version-2 project files require no data transformation. Validation Focused GUI-free tests cover the migration callback, definition contract, import parsing, analysis products, presentation models, and result tables. The hidden GUI workflow covers launch, recording import, analysis, plots, both exports, project save, and session reconstruction after load. Project guardrails cover the compact optional-capability structure, embedded metadata, version/history ownership, and generated documentation consistency. Evidence [ECG Print](../../../../apps/wearable/ecg-print/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up Automated tests do not judge ECG morphology, peak quality, or the visual suitability of exported waveforms for a particular experiment. Remaining Runtime V2 App migrations and the stale agent guidance require separate review."},{"title":"EIS consolidates its product and project contracts","url":"history/records/2026/07/LK-20260716-eis-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-eis-project-spec 2026-07-16 89 refactor compatible labkit_EIS_app Electrochemistry Project lifecycle","text":"EIS consolidates its product and project contracts id: LK-20260716-eis-project-spec date: 2026-07-16 sequence: 89 type: refactor compatibility: compatible component: `labkit_EIS_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context EIS split static product metadata across three files and one first-version project schema across a generic lifecycle package. Those files did not own separate impedance or plotting contracts. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because restoring decoded ZCURVE items and source selection is genuine transient work. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved decoded curve and selection restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Delayed log-scale assignment until filtered positive data establishes valid automatic limits, and corrected the GUI regression fixture to use a legal positive manual range while testing stale-zoom replacement. Kept impedance values, Nyquist/Bode axis semantics, log scaling, zoom behavior, and export schemas unchanged. User and data impact Launch, multi-file selection, plotting, zoom, save/load, and export behavior remain unchanged. Project and transient state ownership are now adjacent and explicit. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing EIS projects require no migration. Validation Unit tests cover ZCURVE parsing, axis values, Nyquist mode, log scaling, project/session contracts, presentation, and export columns. The hidden GUI workflow covers real launch, file loading, plot interaction, save/load, and export. Evidence [EIS](../../../../apps/electrochemistry/eis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Source restoration still consumes portable-reference internals. The planned shared source-path service will remove that leak across all Apps together."},{"title":"EIS delegates source identity reconciliation to Runtime V2","url":"history/records/2026/07/LK-20260716-eis-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-eis-runtime-source-reconciliation 2026-07-16 110 refactor compatible labkit_EIS_app Electrochemistry App structure Runtime adoption","text":"EIS delegates source identity reconciliation to Runtime V2 id: LK-20260716-eis-runtime-source-reconciliation date: 2026-07-16 sequence: 110 type: refactor compatibility: compatible component: `labkit_EIS_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context EIS already stored only portable source records in its durable project, but its action file still generated source IDs, appended records, and removed records with App-local helpers. That duplicated Runtime V2's source reconciliation service and made the App responsible for an identity invariant owned by project persistence. The EIS session factory also repeated empty workflow and view buckets even though Runtime V2 canonicalizes missing transient buckets. Decision and rationale Use `services.project.reconcileSources` as the single source-record owner after successful file additions, removals, and clearing. The EIS workflow continues to decide which decoded files are accepted and their order; Runtime preserves matching source identities and allocates collision-free identities for new files. Return only EIS-specific selection and decoded-cache state from `createSession`. Runtime supplies the empty workflow and view buckets before the state becomes visible to actions or presenters. Changes Removed EIS-local source-ID generation, append, and removal helpers. Reconciled durable source records from the successfully decoded EIS items. Removed empty workflow and view boilerplate from the session factory. Added GUI assertions for canonical Runtime buckets, unique source IDs, and identity preservation across additions and project reopen. Advanced the EIS App version to 1.4.4. User and data impact File selection, decoding, plotting, export, save, and reopen behavior are unchanged. Existing saved source IDs remain stable when their paths match. Newly added records receive Runtime-managed IDs. IDs are persistence identities and are not displayed as scientific results. Compatibility and migration The project payload remains version 1 and needs no migration. Existing project files continue to load through their stored portable references. Validation The EIS hidden GUI workflow covers two-file loading from separate folders, selection, plotting, export, project save, clear, and reopen. Focused unit and contract tests cover EIS calculations, App structure, version ownership, documentation, and history ordering. Evidence [EIS](../../../../apps/electrochemistry/eis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up Other multi-file Apps still contain equivalent local source-list helpers. They should adopt the same Runtime service only after their role and ordering semantics are verified by their own focused tests."},{"title":"EIS uses explicit plot-view actions","url":"history/records/2026/07/LK-20260721-eis-explicit-plot-views.html","kind":"history","section":"history","keywords":"LK-20260721-eis-explicit-plot-views 2026-07-21 147 fix compatible labkit_EIS_app EIS Overlay","text":"EIS uses explicit plot-view actions id: LK-20260721-eis-explicit-plot-views date: 2026-07-21 sequence: 147 type: fix compatibility: compatible component: `labkit_EIS_app` | `1.5.1 -> 1.5.2` scope: EIS Overlay Context EIS inferred an equal X/Y aspect ratio from specific Nyquist axis selections. That implicit choice could shrink the usable plotting area and make a redraw look unlike the user's current view. Decision and rationale Keep plot semantics in EIS, but use the App framework's viewport revision and data-fitting capability for view behavior. Equal data units are now an explicit one-time view reset rather than a rule tied to axis labels or a constraint on later zooming. Changes Removed the EIS axis-label rule that automatically selected `axis equal`. Added **Fit X/Y limits** for independent data-fitted limits. Added **Use equal X/Y scale** for an explicit equal-data-unit view without changing the plot size allocated by the App layout. Kept ordinary zoom and wheel viewports under the framework-managed viewport preservation path. User and data impact Nyquist plots now begin with independent limits and a stable plot area. Equal scaling preserves the App layout's plot size. The new view buttons change only transient presentation; EIS sources, projects, calculations, and exports are unchanged. Compatibility and migration Existing saved projects remain valid. The selected axes and Log X/Y options keep their existing meaning; the equal-aspect choice is intentionally not saved with the project. Validation The EIS GUI workflow test covers both buttons, independent fitting, explicit equal aspect, and preservation of an ordinary manually chosen viewport. Evidence The renderer now uses `labkit.app.plot.fitAxesToGraphics` for both independent fits and equal-data-unit limits, and receives a new view revision only from an explicit view action. Known limitations and follow-up Automated GUI coverage cannot assess visual balance on every monitor and MATLAB release. Verify the Nyquist layout interactively before a release. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data."},{"title":"Electrochem Apps stop reading portable references","url":"history/records/2026/07/LK-20260716-electrochem-source-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-electrochem-source-boundary 2026-07-16 92 refactor compatible labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app Electrochemistry Runtime source boundary","text":"Electrochem Apps stop reading portable references id: LK-20260716-electrochem-source-boundary date: 2026-07-16 sequence: 92 type: refactor compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.2 -> 1.4.3` component: `labkit_CIC_app` | `1.4.2 -> 1.4.3` component: `labkit_CSC_app` | `1.4.2 -> 1.4.3` component: `labkit_EIS_app` | `1.4.2 -> 1.4.3` component: `labkit_VTResistance_app` | `1.4.2 -> 1.4.3` scope: Electrochemistry scope: Runtime source boundary Context The Electrochem Apps already delegated portable source creation, save-time rebasing, and load-time relinking to Runtime V2. Their session factories, source loaders, actions, and presenters nevertheless read the runtime's nested path field directly. CIC and VT Resistance also carried duplicate local path extraction functions. Decision and rationale Use the public Runtime source-path accessor at every App boundary. This keeps lazy-loading and batch-selection policies App-owned while removing knowledge of the portable-reference storage schema. Changes Replaced direct nested-reference reads in all five Electrochem Apps. Removed duplicate CIC and VT Resistance path extraction functions. Preserved source order and lazy first-item decoding. Kept DTA parsing, formulas, thresholds, plots, exports, and error wording unchanged. User and data impact File selection, project reopen/relink, calculation, preview, and export behavior are unchanged. App code now expresses only its workflow use of paths; Runtime V2 owns how those paths are stored and resolved. Compatibility and migration No project schema or saved source record changed. Existing projects require no migration and remain compatible with the same UI 7 contract range. Validation The Electrochem unit and hidden-GUI suites cover project reconstruction, DTA loading, selection, plotting, computation, export, and saved-state workflows. Package and version guardrails verify the shared API and component metadata. Evidence [Electrochemistry Apps](../../../../apps/electrochemistry/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other App families still contain direct reads and will migrate in their own behavior-tested commits before a repository-wide no-leak guard is enabled."},{"title":"Electrochemistry source-field validation boundary","url":"history/records/2026/07/LK-20260716-electrochem-source-field-validation.html","kind":"history","section":"history","keywords":"LK-20260716-electrochem-source-field-validation 2026-07-16 118 fix compatible labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app electrochemistry project schemas malformed project rejection","text":"Electrochemistry source-field validation boundary id: LK-20260716-electrochem-source-field-validation date: 2026-07-16 sequence: 118 type: fix compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.5 -> 1.4.6` component: `labkit_CIC_app` | `1.4.5 -> 1.4.6` component: `labkit_CSC_app` | `1.4.5 -> 1.4.6` component: `labkit_EIS_app` | `1.4.5 -> 1.4.6` component: `labkit_VTResistance_app` | `1.4.5 -> 1.4.6` scope: electrochemistry project schemas scope: malformed project rejection Context Runtime owns the format of a portable source record but intentionally allows a project whose `inputs` bucket has no `sources` field. Static and embedded-data Apps may not use external sources. The first electrochemistry validator reduction correctly removed record-format duplication but also stopped declaring that these five file-backed Apps require a source collection. Decision and rationale Keep field presence with the App schema and record shape with Runtime. This is the narrow ownership split: the App decides whether sources are required; Runtime decides what every supplied source record means. Changes Restored the `inputs.sources` presence requirement in all five electrochemistry project validators. Added a GUI-free unit contract that accepts each default project and rejects the same project after its App-required source collection is removed. Kept canonical bucket and source-record field validation out of the Apps. User and data impact Valid projects are unchanged. A malformed electrochemistry project missing its entire source collection is rejected by the App validator before session reconstruction instead of failing later while decoded data are rebuilt. Compatibility and migration No saved format changed and no migration is required. Every project created or saved by these Apps already contains `inputs.sources`. Validation The focused project-spec test covers all five accepted defaults and all five missing-source rejection cases. The Runtime project and electrochemistry GUI tests continue to cover canonical record validation and working file flows. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) separates framework-owned record shape from App-owned fields and roles. The [Electrochemistry App manuals](../../../../apps/electrochemistry/README.md) identify the required App source collection. Known limitations and follow-up The same distinction must be applied explicitly while reducing validators in other App families; a generic source format does not imply that every App has the same required source fields or roles."},{"title":"Explicit application autosave","url":"history/records/2026/07/LK-20260716-explicit-autosave.html","kind":"history","section":"history","keywords":"LK-20260716-explicit-autosave 2026-07-16 68 feat compatible labkit.ui labkit_VideoMarker_app Runtime V2 project recovery Video Marker session workflow","text":"Explicit application autosave id: LK-20260716-explicit-autosave date: 2026-07-16 sequence: 68 type: feat compatibility: compatible component: `labkit.ui` | `6.0.2 -> 6.0.3` component: `labkit_VideoMarker_app` | `1.3.0 -> 1.4.0` scope: Runtime V2 project recovery scope: Video Marker session workflow Context Runtime V2 already wrote debounced recovery generations after durable edits, but an app could only expose the named-project save operation. Video Marker users who wanted to force the current recovery point had no direct control and using **Save State** introduced a destination prompt for an unnamed project. Decision and rationale Expose the existing framework-owned recovery writer as an injected action service. The service accepts the current action state, writes the same bounded recovery generations as the timer, and deliberately leaves the named project path and dirty status unchanged. Video Marker exposes that operation as **Save autosave** in its Session panel. Changes Factored the atomic recovery-generation writer out of the timer scheduler. Added `services.project.saveAutosave(state)` for an immediate, pathless recovery write. Added the Video Marker **Save autosave** button and visible workflow log acknowledgement. Kept ordinary **Save State** as the separate operation for choosing or updating a named project file. User and data impact Clicking **Save autosave** does not open a native file dialog. It updates the current app/document recovery file and retains one previous generation. It does not make the recovery file the active named project and does not suppress later unsaved-close protection. Compatibility and migration Existing projects, recovery files, and automatic autosave behavior remain compatible. Apps that do not expose the new service behave exactly as before. Validation The focused Video Marker GUI workflow verifies that the button immediately creates a recovery file while the runtime document path remains empty. The existing Runtime V2 project test continues to cover debouncing, bounded generations, and recovery loading. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) documents the injected recovery operation and its distinction from named saves. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the Session button and no-dialog behavior. Known limitations and follow-up An autosave is a recovery aid, not a user-named archival project. Users still use **Save State** when they need a deliberate project filename or location."},{"title":"Explicit layout action contract","url":"history/records/2026/07/LK-20260717-explicit-layout-action-contract.html","kind":"history","section":"history","keywords":"LK-20260717-explicit-layout-action-contract 2026-07-17 123 refactor compatible labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Runtime V2 layout callback resolution public App maintenance cost","text":"Explicit layout action contract id: LK-20260717-explicit-layout-action-contract date: 2026-07-17 sequence: 123 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.5 -> 1.4.6` component: `labkit_DICPreprocess_app` | `1.5.6 -> 1.5.7` component: `labkit_ChronoOverlay_app` | `1.4.6 -> 1.4.7` component: `labkit_CIC_app` | `1.4.6 -> 1.4.7` component: `labkit_CSC_app` | `1.4.6 -> 1.4.7` component: `labkit_EIS_app` | `1.4.6 -> 1.4.7` component: `labkit_VTResistance_app` | `1.4.6 -> 1.4.7` component: `labkit_GaitAnalysis_app` | `2.0.6 -> 2.0.7` component: `labkit_BatchImageCrop_app` | `1.7.5 -> 1.7.6` component: `labkit_CurvatureMeasurement_app` | `1.4.5 -> 1.4.6` component: `labkit_FLIRThermal_app` | `1.4.5 -> 1.4.6` component: `labkit_FocusStack_app` | `1.5.4 -> 1.5.5` component: `labkit_ImageEnhance_app` | `1.6.5 -> 1.6.6` component: `labkit_ImageMatch_app` | `1.6.5 -> 1.6.6` component: `labkit_VideoMarker_app` | `1.5.5 -> 1.5.6` component: `labkit_FigureStudio_app` | `0.2.7 -> 0.2.8` component: `labkit_NerveResponseAnalysis_app` | `1.4.5 -> 1.4.6` component: `labkit_ResponseReviewStats_app` | `1.4.5 -> 1.4.6` component: `labkit_RHSPreview_app` | `1.4.5 -> 1.4.6` component: `labkit_ECGPrint_app` | `1.4.5 -> 1.4.6` scope: Runtime V2 layout callback resolution scope: public App maintenance cost Context Every public App layout carried the same local `callbackValue` helper. The helper returned an empty value when a callback field was absent, so a misspelled or unregistered action could survive startup as a control that silently did nothing. Twenty copies also obscured that Runtime already creates the complete callback table from each App definition. Decision and rationale Layouts reference Runtime-generated callbacks directly. MATLAB field access therefore validates the action ID while the data-only layout is built. Runtime remains the single callback-adapter owner; Apps own only their semantic action registries and layout references. Changes Removed the duplicated callback fallback from all 20 public App layouts. Replaced static lookups with direct `callbacks.actionId` references and kept dynamic field access only in three small controls whose action names are supplied by the surrounding layout. Added a fleet-wide contract that creates every App definition and layout with the Runtime callback inventory. Updated direct neurophysiology layout tests to supply explicit callbacks derived from their action registries. User and data impact Correctly registered controls behave as before. A broken App definition now fails during layout construction instead of presenting an inert control. No scientific calculation, project payload, saved file, or interaction semantics changed. Compatibility and migration No project migration is required. This is an App-development contract tightening: custom layouts must register every referenced semantic action. Validation The focused fleet contract builds every public App layout and catches missing or misspelled callback fields. The App package guardrail rejects reintroduced silent callback fallbacks, and focused workflow-layout tests cover direct construction outside a running figure. Evidence [Runtime layout and action rules](../../../../framework/guides/runtime.md#layout-and-action-rules) [Apps](../../../../apps/README.md) [App development](../../../../development/build-apps/app-development.md) Known limitations and follow-up This change deliberately does not merge short semantic actions or dynamic control helpers merely to reduce line count. Those remain candidates only when a repeated behavior has one stable, domain-neutral owner."},{"title":"FLIR Thermal separates durable annotations from decoded sources","url":"history/records/2026/07/LK-20260716-flir-thermal-structure.html","kind":"history","section":"history","keywords":"LK-20260716-flir-thermal-structure 2026-07-16 98 refactor compatible labkit_FLIRThermal_app Image Measurement App structure","text":"FLIR Thermal separates durable annotations from decoded sources id: LK-20260716-flir-thermal-structure date: 2026-07-16 sequence: 98 type: refactor compatibility: compatible component: `labkit_FLIRThermal_app` | `1.4.2 -> 1.4.3` scope: Image Measurement scope: App structure Context FLIR Thermal split static metadata across files, spread a version-1 project over a generic lifecycle package, grouped decoded records, durable annotations, and numerical reading updates under `+appState`, and read nested portable reference fields in session, actions, and presentation. Its startup callback also selected an output directory before the user loaded a source. Decision and rationale Use the compact Runtime V2 contract while preserving the boundary between decoded radiometric data and the lightweight annotations safe to persist. Assign each remaining helper to the capability it describes instead of moving thermal semantics into the framework. Changes Consolidated product metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated durable project creation and validation in `projectSpec.m` and moved selected-image reconstruction to root `createSession.m`. Moved decoded item shape to `+sourceFiles`, point/ROI updates to `+analysisRun`, and persistent per-image readings to `+thermalAnnotations`. Removed separate metadata files, generic lifecycle/state packages, and the redundant App startup callback. Replaced all direct portable-reference field access with the Runtime source path accessor. User and data impact Radiometric decoding, Celsius conversion, range controls, point and ROI readings, viewport-preserving overlays, project reopen, and exports keep their existing behavior. Empty launch no longer chooses an output folder; adding sources still establishes the same source-adjacent default. Developers can find transient decoded matrices, scientific readings, and durable annotations under their actual owners without learning a generic state layer. Compatibility and migration The durable payload remains version 1 with identical fields and defaults. Existing projects require no migration. The removed App-specific debug line is replaced by the Runtime's standard debug-startup message. Validation Five unit methods cover radiometric import, thermal values, calibration diagnostics, range controls, independent ROIs, rendering mappings, manifests, and exported matrices. Two hidden GUI workflows cover multi-file selection, session caching, managed region interaction, project save/load reconstruction, shared ranges, and batch export. Evidence [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Thermal Library](../../../../libraries/thermal/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Image Measurement Apps still use generic lifecycle/state packages and will be reviewed individually. Automated GUI tests do not replace manual judgment of pointer feel, visual calibration, or camera-specific accuracy."},{"title":"FLIR display tuning","url":"history/records/2026/07/LK-20260703-flir-display-tuning.html","kind":"history","section":"history","keywords":"LK-20260703-flir-display-tuning 2026-07-03 36 feat compatible labkit_CSC_app labkit_FLIRThermal_app labkit_FLIRThermal_app labkit_FLIRThermal_app FLIR display tuning","text":"FLIR display tuning id: LK-20260703-flir-display-tuning date: 2026-07-03 sequence: 36 type: feat compatibility: compatible component: `labkit_CSC_app` | `1.3.6 -> 1.3.7` component: `labkit_FLIRThermal_app` | `1.2.4 -> 1.2.5` component: `labkit_FLIRThermal_app` | `1.2.5 -> 1.2.6` component: `labkit_FLIRThermal_app` | `1.2.6 -> 1.2.7` scope: FLIR display tuning Context The initial FLIR renderer used a fixed color transfer. Images with a narrow or skewed temperature distribution could therefore hide useful contrast even when the numeric range was correct. CSC voltage/current CSV output also needed clearer cycle organization for direct downstream use. Decision and rationale Separate the displayed color mapping from the temperature values. Apply gamma only while rendering the normalized image and expose it as a user control; keep the temperature matrix, point readings, ROI means, and exports numerically unchanged. Refine CSC CSV structure at its result writer rather than in the UI. Changes `labkit_FLIRThermal_app` `1.2.4 -> 1.2.7` `labkit_CSC_app` `1.3.6 -> 1.3.7` Refined CSC CV export. Added FLIR gamma color mapping and made gamma adjustable. User and data impact FLIR users could reveal contrast in hotter or cooler parts of an image without changing the underlying temperatures. The same gamma was used in displayed and exported color renderings. CSC users received a more directly usable voltage/current table. Compatibility and migration Existing FLIR inputs remained valid, and gamma affected rendered color only. Temperature matrices and measurement values did not require conversion. Validation The commits extended CSC export assertions, FLIR rendering and output tests, and the FLIR layout test for the gamma control. Exact historical commands were not recorded. Evidence Main commits `ee5b8f79`, `65dbf5ae`, and `f076561e`. Known limitations and follow-up Gamma changes visual contrast only. Quantitative interpretation must use the temperature values and scale, not colors sampled from the rendered image."},{"title":"Facade contract baseline and release validation hardening","url":"history/records/2026/06/LK-20260623-facade-contract-baseline-and-release-validation-hardening.html","kind":"history","section":"history","keywords":"LK-20260623-facade-contract-baseline-and-release-validation-hardening 2026-06-23 11 ci compatible labkit.biosignal labkit.dta labkit.rhs labkit.ui labkit.ui labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_CurvatureMeasurement_app Facade contract baseline and release validation hardening","text":"Facade contract baseline and release validation hardening id: LK-20260623-facade-contract-baseline-and-release-validation-hardening date: 2026-06-23 sequence: 11 type: ci compatibility: compatible component: `labkit.biosignal` | `new -> 1.0.0` component: `labkit.dta` | `new -> 1.0.0` component: `labkit.rhs` | `new -> 1.0.0` component: `labkit.ui` | `new -> 2.0.0` component: `labkit.ui` | `2.0.0 -> 2.1.0` component: `labkit.ui` | `2.2.0 -> 2.2.1` component: `labkit_DICPostprocess_app` | `1.0.0 -> 1.0.1` component: `labkit_DICPreprocess_app` | `1.0.0 -> 1.0.1` component: `labkit_CurvatureMeasurement_app` | `1.0.0 -> 1.0.1` scope: Facade contract baseline and release validation hardening Context Reusable packages exposed public MATLAB functions, but apps had no machine-checkable way to state which API versions they required. Release and CI checks also needed one consistent build-task entry path. Decision and rationale Give each reusable package version metadata and let apps declare compatible ranges that are checked at launch and in tests. Route MATLAB CI shards through the same build tasks used by maintainers so release validation exercises the documented commands. Changes `labkit.biosignal` `1.0.0` `labkit.dta` `1.0.0` `labkit.rhs` `1.0.0` `labkit.ui` `2.0.0 -> 2.2.1` DIC Pre/Post and Curvature `1.0.0 -> 1.0.1` Release tags `v2.4.1` and `v2.4.2` Added facade contract metadata and requirement checks. Hardened app lifecycle and release validation contracts. Routed MATLAB CI shards through build tasks. User and data impact An incompatible app/package combination could fail with a direct requirement message instead of producing a later missing-function error. Existing project and result data were unaffected. Compatibility and migration The contract metadata described the existing public facades and was additive for app callers. Apps with an incompatible declared requirement now failed at launch with a version diagnostic instead of continuing unpredictably. Validation The listed commits introduced contract, lifecycle, and CI build-task tests. Release tags `v2.4.1` and `v2.4.2` identify the shipped checkpoints; exact local commands were not recorded. Evidence Main commits `a25b79f9`, `3673e548`, `49d9f41b`, and `7e39b558`. Known limitations and follow-up App and launcher display versions were added in the separate version-metadata change later the same day."},{"title":"Figure Studio adopts one product definition","url":"history/records/2026/07/LK-20260716-figure-studio-single-definition.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-single-definition 2026-07-16 79 refactor compatible labkit_FigureStudio_app labkit.ui LabKit Core App structure","text":"Figure Studio adopts one product definition id: LK-20260716-figure-studio-single-definition date: 2026-07-16 sequence: 79 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.1 -> 0.2.2` component: `labkit.ui` | `7.2.0 -> 7.2.1` scope: LabKit Core scope: App structure Context Figure Studio declared its command, names, family, App version, and LabKit requirement in two files separate from the runtime definition. Its entrypoint joined all three factories even though they described one product. Decision and rationale Use Figure Studio as the first reviewed App migration to the single-definition contract. This validates the reduced structure on an App that also accepts a typed axes handoff, without changing its plotting or export behavior. Changes Moved exact product metadata and the UI requirement into `definition.m`. Reduced the public entrypoint to one definition plus its existing typed request adapter. Removed the redundant `requirements.m` and `version.m` files. Taught the version guard to use `AppVersion` in a migrated definition while comparing the first migration against the previous `version.m` baseline. Fixed the shared source reconciler so it creates the canonical empty source array without first constructing an invalid empty-ID placeholder record. Added `services.results.emptyOutputs()` and used it for Figure Studio's variable-length export manifest instead of constructing an invalid empty-ID placeholder output. User and data impact Launch, axes handoff, style controls, project payload, FIG reading, plot snapshot extraction, and all export formats retain their existing behavior. Maintainers now update one product contract instead of three files. Compatibility and migration The App command and project ID are unchanged. App version metadata requests still return the same fields through the public entrypoint. Existing project payloads remain at version 1 and require no data migration. Validation Focused tests cover ordinary launch, axes handoff, style changes, project round-trip, exports, launcher discovery, requirements/version requests, and version/history governance. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) documents the unchanged workflow and new definition ownership. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the shared product contract. Known limitations and follow-up Figure Studio still has a meaningful version-1 project schema, rebuildable plot session cache, and startup resource installation. These will move to the projectSpec/action capability model after the framework supports that model; they were not deleted merely to reduce file count."},{"title":"Figure Studio consolidates its project schema","url":"history/records/2026/07/LK-20260716-figure-studio-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-project-spec 2026-07-16 82 refactor compatible labkit_FigureStudio_app LabKit Core Project lifecycle","text":"Figure Studio consolidates its project schema id: LK-20260716-figure-studio-project-spec date: 2026-07-16 sequence: 82 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.2 -> 0.2.3` scope: LabKit Core scope: Project lifecycle Context Figure Studio's version-1 project creation and validation lived in separate files under a generic `+appLifecycle` package. The package name described a framework phase rather than the durable capability those functions owned. Decision and rationale Consolidate the complete durable schema behind `projectSpec.m`. Keep `createSession.m` separate at the App package root because it performs a different job: rebuilding transient selection and decoded plot cache from a validated project. Changes Added one project declaration with local create and validate functions. Moved session reconstruction to one explicitly named package-root entry. Removed the three-file generic `+appLifecycle` package. Left meaningful startup, action, presentation, source, style, and export capabilities unchanged. User and data impact Figure loading, axes handoff, styling, project save/load, session restoration, and exports behave unchanged. A maintainer can now understand the entire durable schema in one file. Compatibility and migration The App command, project ID, payload version, fields, validation, and source record format are unchanged. Existing Figure Studio projects need no migration. Validation The existing unit and hidden GUI suites cover FIG reading, source style, project round-trip, axes handoff, canvas resizing, quick export, and data package export through the consolidated project declaration. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The startup hook still owns axes handoff and resize-resource installation. It will be evaluated with other App startup hooks before any shared mount/action capability is introduced."},{"title":"Figure Studio derives its publication preset from the nine-panel reference","url":"history/records/2026/07/LK-20260721-figure-studio-nine-panel-calibration.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-nine-panel-calibration 2026-07-21 155 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio derives its publication preset from the nine-panel reference id: LK-20260721-figure-studio-nine-panel-calibration date: 2026-07-21 sequence: 155 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.1 -> 0.6.4` scope: LabKit Core Context The previous Figure Studio publication profile was described as a visual calibration even though its dimensions had been taken from a separate manual FIG example. It also allowed native figure chrome to reduce the exported drawable height. Decision and rationale Use only the maintained 3-by-3 scientific reference. Normalize every panel by its detected axes frame, measure typography and strokes in pixels, and select one robust profile from all nine panels. Keep those measurements app-local and size the chromeless export client area directly so the configured inner frame survives raster and vector export. Changes Recalibrated the reference plot frame, typography, semantic strokes, frame, grid, legend border, and legend token length from panels A through I. Set the data stroke to 6.5 pt after registered raster checks matched panel A exactly and reduced the remaining one-pixel deficits in panels E and H. Removed menu and toolbar chrome from temporary export figures and preserved the complete figure bounds through `exportgraphics`. Ignored zero-area text extents when computing outer margins so native log rulers cannot inflate an export canvas to tens of thousands of pixels. Added the calibrated 900 px plot width to the selectable sizes and made it the new-project default. Retained source token geometry for **FIG default** while applying the measured long token only to the LabKit preset. Replaced dynamic-array analyzer suppressions with bounded collection and documented the 72-to-96-PPI range used by cross-platform visual metrics. User and data impact New LabKit-style projects open at the nine-panel reference scale. Existing scientific values, limits, source graphics, FIG files, and saved projects are not recalculated. Exports retain the requested inner-frame aspect instead of losing native-window height. Compatibility and migration The style record remains extensible and existing projects stay readable. Switching an existing document to **LabKit figure** opts into the new profile; **FIG default** continues to use source presentation values. Validation Pixel-frame, OCR glyph-height, colored-stroke, and registered-overlay checks against all nine reference panels. Focused Figure Studio result-file and project tests. Hidden-GUI Figure Studio workflow tests. Native logarithmic-axis margin regression. Linux-compatible visual-ratio and analyzer-policy regressions. Repository `changedFast` validation. Evidence After width registration, the nine detected inner-frame height errors range from -1.71 to +2.16 pixels; panel A is exact. Shared OCR glyph heights have a median scale error below one percent. Reference and generated comparison rasters remain untracked validation artifacts. Known limitations and follow-up Raster antialiasing varies slightly by renderer and platform. Exact scientific curve coordinates are source data and are not part of the style preset."},{"title":"Figure Studio edits one native subplot panel at a time","url":"history/records/2026/07/LK-20260721-figure-studio-native-panel-editing.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-native-panel-editing 2026-07-21 153 feat compatible labkit_FigureStudio_app Figure Studio","text":"Figure Studio edits one native subplot panel at a time id: LK-20260721-figure-studio-native-panel-editing date: 2026-07-21 sequence: 153 type: feat compatibility: compatible component: `labkit_FigureStudio_app` | `0.4.0 -> 0.6.0` scope: Figure Studio Context Figure Studio reconstructed a limited portable snapshot from the first axes in a FIG. This could omit composite graphics such as traditional `boxplot` groups, gave no reliable way to choose a subplot from a mixed figure, and made export canvas controls look like live plot-resize controls. Decision and rationale Treat an opened FIG as a transient native document resource and select one axes from it by an ordered subplot panel choice. The selected axes remains the authoritative hierarchy for preview and editable/image export; the portable snapshot remains an explicit secondary data-package representation. This gives ordinary MATLAB charts a broader preservation path without promising that an arbitrary custom graphics class is portable. Use a fixed, visually calibrated single-panel default (1600 by 1333, 6:5). The workbench now displays a raster rendering of that real export canvas, fit as one image into the available workspace. Resizing the window never feeds back into style sizing; supersampling remains export-only. Provide an explicit automatic X/Y-limit recovery action instead of trying to infer limits during unrelated style changes. Changes Added ordered mixed-FIG subplot-panel selection; preview, styling, limits, and exports all operate on the selected axes only. Added private native-source cloning for popout handoff and FIG import, with cleanup through the existing document-resource lifecycle. Preserved native child hierarchies in preview and editable FIG export, including traditional grouped `boxplot` graphics and hidden-handle visual children. Preserved source tick labels and tick configuration when copying native axes. Replaced free-form preview resizing with source/fixed export-size choices, aspect choices, export supersampling, and a stable true-export preview. Added **Recalculate X/Y limits** and project persistence for explicit limit overrides. Migrated project schema 3 to remember the selected panel index. User and data impact Existing projects migrate automatically. A saved mixed FIG project restores the same selected panel when its source is available. Popout projects retain a portable snapshot after reload; their private native source is never saved. No scientific calculation, source file, or source axes is modified. Compatibility and migration Schema 1--3 projects migrate to schema 4. Schema 3 adds `panelIndex = 1`; earlier migrations retain their existing style and limit conversions. Validation Project-schema tests cover migrations through schema 4. Hidden-GUI tests cover four-panel FIG selection, native boxplot preservation for FIG import and popout handoff, export, limit recovery, and a stable export-canvas preview through workspace resizing. Focused visual review checks a generated single-panel scientific probe at the default 1600-by-1333 reference size. Evidence The focused test artifacts and generated local visual probe are supporting evidence. No external laboratory data was used. Known limitations and follow-up Developer-led visual review remains necessary for unusual custom charts, native dialogs, and publication-specific annotation overlap. Portable data packages intentionally support fewer object types than native FIG export."},{"title":"Figure Studio fixes publication-frame preview and axis controls","url":"history/records/2026/07/LK-20260721-figure-studio-plot-frame-preview.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-plot-frame-preview 2026-07-21 154 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio fixes publication-frame preview and axis controls id: LK-20260721-figure-studio-plot-frame-preview date: 2026-07-21 sequence: 154 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.0 -> 0.6.1` scope: LabKit Core Context Figure Studio had treated a requested output size as a whole figure canvas. That made labels and titles consume the configured plot area, and resizing the preview could make the displayed typography unrelated to the eventual export. Copied UIAxes also needed a portable reconstruction path for reliable export. Decision and rationale Treat the requested width and aspect as the inner axes frame. Keep all publication-style calibration within Figure Studio, then calculate an outer figure from the live rendered text extents so visual styling cannot reduce the configured data area. Changes Defined the plot width and aspect controls as the inner axes frame; export now calculates an outer figure around that fixed frame from visible labels, ticks, titles, legends, and annotations. Calibrated the LabKit publication preset from the maintained single-panel visual measurements, including category-specific typography and strokes. Kept the preview as an interactive axes, reflowing its display scale when its allocated area changes without changing saved/export settings. Added explicit X/Y minimum and maximum controls, plus visible-data recalculation with a finite-data envelope expanded by 50 percent. Rebuilt UIAxes sources through the portable renderer for conventional export, retaining displayed scientific axis exponents. User and data impact Existing Figure Studio projects remain compatible. Existing style sizes retain their meaning as a plot frame; the enclosing export figure may grow to avoid clipping external text. No scientific calculations or source figure data are changed. Compatibility and migration The project schema migrates existing style records without changing their stored data. UIAxes exports use the portable display snapshot only for the screen-specific aspect geometry that cannot be transferred to normal axes. Validation Focused Figure Studio result-file and source-axes tests. Hidden-GUI Figure Studio layout tests, including UIAxes export and preview reflow behavior. Pixel-scale visual metrics for the calibrated single-panel output. Evidence The calibrated profile fixes the inner frame at 1237 by 942 pixels. Its generated output reports that same axes size while the enclosing figure grows only for required outer text and annotations. Known limitations and follow-up Manual review remains appropriate for native MATLAB graphics that cannot be represented by the portable package."},{"title":"Figure Studio names its post-layout initialization capability","url":"history/records/2026/07/LK-20260716-figure-studio-initializer.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-initializer 2026-07-16 109 refactor compatible labkit_FigureStudio_app LabKit Core App structure","text":"Figure Studio names its post-layout initialization capability id: LK-20260716-figure-studio-initializer date: 2026-07-16 sequence: 109 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.4 -> 0.2.5` scope: LabKit Core scope: App structure Context Figure Studio was the only public App with a generic `startup.m` file. Its definition already declared the function through Runtime V2's optional `Start` capability, but the filename obscured who invoked it and made the function look like an independent lifecycle entrypoint. Decision and rationale Keep the Runtime-owned `Start` hook because Figure Studio genuinely needs a post-layout phase: it consumes an optional axes handoff, resolves the managed preview axes, and registers a cleanup-owned resize resource. Rename the App callback to `initializeWorkbench` so its capability and invocation boundary are explicit. Do not move GUI/service work into the GUI-free session factory or reintroduce App-owned timers and readiness state. Changes Renamed the Start callback from generic `startup` to `initializeWorkbench` and declared the new handle in `definition`. Documented the callback timing and the request, preview, resource, dialog, workflow, and debug services supplied by Runtime V2. Tightened the App structure guardrail so compact Apps cannot add a generic `startup.m` file outside their declared capability vocabulary. Advanced the Figure Studio App version to 0.2.5. User and data impact Normal launch, default output folder selection, popout axes handoff, source style adoption, preview canvas resize behavior, debug logging, project data, and exports are unchanged. No project migration is required. Compatibility and migration The public command and Runtime definition contract are unchanged. `startup` was an App-internal callback referenced only by the definition; callers should launch the App rather than invoke either callback directly. Validation The Figure Studio hidden GUI suite covers ordinary launch, style controls, FIG loading, preview resize, quick/package export, axes handoff, project save/load, stable canvas sizing, and popout-to-Studio transfer. It also asserts that the definition exposes the semantic initializer handle. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up The Start capability remains necessary for post-layout resources; removing it would require a different framework service phase rather than a file move. App and agent guidance still need a repository-wide terminology audit."},{"title":"Figure Studio preserves common graphics and styles semantic categories","url":"history/records/2026/07/LK-20260720-figure-studio-semantic-restyling.html","kind":"history","section":"history","keywords":"LK-20260720-figure-studio-semantic-restyling 2026-07-20 145 feat compatible labkit_FigureStudio_app labkit.app Figure Studio App Framework GUI validation Test runner","text":"Figure Studio preserves common graphics and styles semantic categories id: LK-20260720-figure-studio-semantic-restyling date: 2026-07-20 sequence: 145 type: feat compatibility: compatible component: `labkit_FigureStudio_app` | `0.3.1 -> 0.4.0` component: `labkit.app` | `1.2.0 -> 1.2.1` scope: Figure Studio scope: App Framework GUI validation scope: Test runner Context Figures sent from Apps, reopened from FIG files, or recreated from Figure Studio data packages could omit common composite graphics such as bars, error bars, areas, and rectangles. Legends were reconstructed only from display names, and the preset exposed one broad line-width control. This made restyling incomplete and could enlarge unrelated text enough to overlap data. Separately, the official hidden-GUI policy was applied while native App windows were constructed but the final show operation could make direct-entrypoint tests visible. Decision and rationale Preserve the supported visible plot structure before applying a presentation preset. Style text and strokes by semantic role so users can establish a coherent baseline and then refine categories that have different visual weights. A preset may style an existing legend, but it retains source placement by default because moving or creating a legend can cover data. Keep GUI-test visibility in the private native adapter. App authors and test methods should not maintain a second hiding convention, and no new public API is needed for a framework-owned launch consistency rule. Keep broad tests in one MATLAB process. A complete local comparison showed that three workers increased both wall-clock and CPU time, so portability work around child-process orchestration would preserve cost without user benefit. Changes Added bar, error-bar, area, and display-only rectangle extraction and reconstruction for previews, FIG imports, and portable recreation scripts. Preserved constant-line labels without interpreting scientific text as a MATLAB line specification. Preserved existing legend text, visibility, placement, orientation, columns, font, interpreter, and border through every supported reconstruction path. Preserved explicit axis ticks, tick labels, rotations, locations, and tick geometry instead of asking MATLAB to infer them again during reconstruction. Included visible hidden-handle graphics in snapshots while retaining their exclusion from automatic legend discovery. Made the LabKit preset the initial style for direct plot handoff while retaining the captured source style as **FIG default**. Added separate controls for title, axis-label, tick, annotation, legend, data, uncertainty, graphic-boundary, reference, and axes styling, plus source/horizontal/45-degree X tick labels. Calibrated the LabKit preset against a representative standard Figure: a 720-by-600 reference canvas, 24/20/15 pt semantic text tiers, 1.1/1.2 pt semantic strokes, Helvetica when available, boxed axes and legend, and a 600 dpi export at the default scale. Scaled text and strokes dynamically from each style's reference canvas while compensating for preview fitting, and reduced UI steps to 0.5 pt for fonts, 0.1 pt for strokes, and 10 px for canvas dimensions. Migrated schema 1 projects to the expanded semantic style model while preserving every previously saved style value. Made the final native show operation honor hidden and minimized GUI validation modes. Bound heartbeat work at timer creation so path-isolation contracts cannot remove a method needed by an active progress callback. Removed internal worker planning, partitioning, process launch, shard selectors, and speculative JUnit shard estimates. Local and CI broad tasks now share the same single-process runner. User and data impact Existing schema 1 documents migrate automatically to schema 2, keeping saved values and supplying defaults only for newly introduced style categories. Supported figures retain more visible content, while unsupported MATLAB chart classes continue to produce explicit warnings. Normal App launches remain visible; only official hidden or minimized validation modes change final window presentation. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Result-file tests exercise extraction, FIG reopening, recreation-script execution, legend metadata, constant-line labels, semantic styling, and idempotent preset application. Figure Studio source-axis and hidden-GUI suites cover source defaults, LabKit handoff defaults, the new control surface, preview stability, and popout handoff. Project tests cover schema 1 style migration. Framework GUI coverage verifies that the final show operation retains hidden mode, and a direct DIC Preprocess entrypoint test exercises the full launch path. A same-machine cold-process comparison measured 470 seconds wall time and 876 seconds CPU time for the serial headless gate versus 490 seconds wall time and 1,215 seconds CPU time for three workers. Worker artifacts also confirmed that heartbeat updates survived the isolated-path contract. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led visual review remains responsible for aesthetic judgment, unusual third-party chart classes, native window behavior, and plot-specific overlap after manual legend placement."},{"title":"Figure Studio preserves overlays above source images","url":"history/records/2026/07/LK-20260722-figure-studio-image-overlay-stacking.html","kind":"history","section":"history","keywords":"LK-20260722-figure-studio-image-overlay-stacking 2026-07-22 156 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio preserves overlays above source images id: LK-20260722-figure-studio-image-overlay-stacking date: 2026-07-22 sequence: 156 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.4 -> 0.6.5` scope: LabKit Core Context Figure Studio copied every visible child from native MATLAB axes, but reversed the source child vector before copying it into the interactive preview. Because MATLAB stores axes children from front to back and `copyobj` preserves vector order, an opaque image became the frontmost child and hid line, marker, and text overlays that had been drawn above it. Decision and rationale Pass the native axes child order through unchanged. The source axes remains the authority for stacking, and Figure Studio must not infer a new order from graphics classes or special-case images. Changes Preserved native front-to-back child order when copying an axes into the interactive preview. Added a synthetic image, line, and text stack regression at the renderer boundary. Added a hidden-GUI regression covering both FIG-file import and axes handoff. User and data impact Image-backed plots now show their visible overlays in Figure Studio and in preview-derived exports. Source files, scientific values, coordinates, object styles, and project data are unchanged. Compatibility and migration No project migration is required. Existing projects receive the corrected stacking when their native source is rebuilt. Validation Exact source-axes stacking regression. Hidden-GUI FIG import and axes-handoff stacking regression. Visual inspection of an authorized local diagnostic FIG through both paths; the real file remains untracked and is not a test fixture. Evidence Both real-source paths retained all 12 visible graphics children with the opaque image last and the 11 overlay children in front. Temporary preview rasters confirmed the overlays were visible. Known limitations and follow-up Custom graphics classes still depend on MATLAB supporting their native parent transition. The portable data package remains narrower than the native preview and export path."},{"title":"Figure Studio removes source-reference knowledge","url":"history/records/2026/07/LK-20260716-figure-studio-source-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-source-boundary 2026-07-16 94 refactor compatible labkit_FigureStudio_app LabKit Core Apps Runtime source boundary","text":"Figure Studio removes source-reference knowledge id: LK-20260716-figure-studio-source-boundary date: 2026-07-16 sequence: 94 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.3 -> 0.2.4` scope: LabKit Core Apps scope: Runtime source boundary Context Figure Studio read both the current path and stored filename from the nested portable source reference. It repeated those reads in session reconstruction, actions, file-list presentation, and selection lookup. Decision and rationale Resolve source paths once through Runtime V2 at each workflow boundary. Derive the displayed filename from the resolved path so the App needs no knowledge of any portable-reference field. Changes Migrated FIG session reconstruction, action loading, file presentation, and selection lookup to `labkit.ui.runtime.sourcePaths`. Derived the opened-file status name through `fileparts`. Removed every direct portable-reference field read from Figure Studio. Preserved imported axes data, default style adoption, source order, current selection, editing, and export behavior. User and data impact FIG selection and display behave unchanged. Figure Studio now owns only how it uses a resolved FIG file; Runtime V2 owns how that file remains portable. Compatibility and migration No project or source-record field changed. Existing Figure Studio projects require no migration. Validation Source-axes and result-file unit tests cover import and export semantics. The hidden GUI suite covers empty startup, FIG addition and selection, style presentation, save/load, and output workflows. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The repository-wide portable-reference guard remains deferred until all remaining App families have migrated."},{"title":"File-panel layout stabilization","url":"history/records/2026/06/LK-20260630-file-panel-layout-stabilization.html","kind":"history","section":"history","keywords":"LK-20260630-file-panel-layout-stabilization 2026-06-30 19 fix compatible labkit.ui labkit.ui File-panel layout stabilization","text":"File-panel layout stabilization id: LK-20260630-file-panel-layout-stabilization date: 2026-06-30 sequence: 19 type: fix compatibility: compatible component: `labkit.ui` | `3.2.3 -> 3.2.4` component: `labkit.ui` | `3.2.4 -> 3.2.5` scope: File-panel layout stabilization Context The single-file panel allocated too much height to its rows and could render inconsistently as the containing app resized. This was especially noticeable in file-heavy apps, where the panel competed with analysis controls and plots. Decision and rationale Give the panel explicit, compact row sizing and protect that geometry with GUI layout tests. The change stayed in the shared file-panel builder because the same visual defect appeared wherever the component was used. Changes `labkit.ui` `3.2.3 -> 3.2.5` Stabilized and compacted single file-panel layout. User and data impact File names and actions occupied less vertical space, leaving more room for the workflow below them. The change affected layout only; selections and loaded file data were unchanged. Compatibility and migration App definitions and selected files required no conversion. The shared panel kept the same controls and callbacks with revised row geometry. Validation Both commits extended `GuiLayoutUiDeclarativeAppTest` with the expected panel geometry. Exact historical test commands were not recorded. Evidence Main commits `7f8df1cd` and `02b2f1b6`. Known limitations and follow-up Later runtime generations replaced this private UI 3.x builder, but retained the principle that shared components own and test their responsive geometry."},{"title":"File-panel migration","url":"history/records/2026/06/LK-20260624-file-panel-migration.html","kind":"history","section":"history","keywords":"LK-20260624-file-panel-migration 2026-06-24 12 refactor breaking labkit.dta labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app File-panel migration","text":"File-panel migration id: LK-20260624-file-panel-migration date: 2026-06-24 sequence: 12 type: refactor compatibility: breaking component: `labkit.dta` | `1.0.0 -> 2.0.0` component: `labkit.ui` | `2.2.1 -> 3.0.0` component: `labkit_DICPostprocess_app` | `1.0.1 -> 1.2.0` component: `labkit_DICPreprocess_app` | `1.0.1 -> 1.2.0` component: `labkit_ChronoOverlay_app` | `1.0.0 -> 1.2.0` component: `labkit_CIC_app` | `1.0.0 -> 1.2.0` component: `labkit_CSC_app` | `1.0.0 -> 1.2.0` component: `labkit_EIS_app` | `1.0.0 -> 1.2.0` component: `labkit_VTResistance_app` | `1.0.0 -> 1.2.0` component: `labkit_BatchImageCrop_app` | `1.0.0 -> 1.2.0` component: `labkit_CurvatureMeasurement_app` | `1.0.1 -> 1.2.0` component: `labkit_FocusStack_app` | `1.0.0 -> 1.2.0` component: `labkit_ImageEnhance_app` | `1.0.0 -> 1.2.0` component: `labkit_ImageMatch_app` | `1.0.0 -> 1.2.0` component: `labkit_NerveResponseAnalysis_app` | `1.0.0 -> 1.2.0` component: `labkit_ResponseReviewStats_app` | `1.0.0 -> 1.2.0` component: `labkit_RHSPreview_app` | `1.0.0 -> 1.2.0` component: `labkit_ECGPrint_app` | `1.0.0 -> 1.2.0` scope: File-panel migration Context Apps implemented file selection through separate task-input adapters, which produced inconsistent lists, selection events, and status feedback. The DTA session helper also coupled parsing to that older UI model. Decision and rationale Move supported apps to one reusable file-panel model and make DTA loading a GUI-free file/curve API. Accept the breaking package versions because retaining both task-input and file-panel paths would preserve ambiguous callback behavior. Changes `labkit.dta` `1.0.0 -> 2.0.0` `labkit.ui` `2.2.1 -> 3.0.0` All supported apps moved from `1.0.x` into the `1.2.0` workflow line. Replaced task inputs with file panels. Removed the old DTA session helper surface. User and data impact File lists, current selection, add/remove actions, and status display became consistent across the migrated apps. App code written against the removed task inputs or DTA session helpers required an update. Compatibility and migration This was a breaking workflow migration. Older app code expecting task inputs or the removed DTA session helpers needed migration. Validation Commit `b145c904` migrated app GUI workflows and package compatibility tests to the new file-panel and DTA APIs. Evidence Main commit `b145c904`. Known limitations and follow-up This first file-panel generation was later refined for append behavior, native dialog edge cases, and Runtime V2 events."},{"title":"Focus Stack adopts the compact App contract","url":"history/records/2026/07/LK-20260716-focus-stack-structure.html","kind":"history","section":"history","keywords":"LK-20260716-focus-stack-structure 2026-07-16 95 refactor compatible labkit_FocusStack_app Image Measurement App structure","text":"Focus Stack adopts the compact App contract id: LK-20260716-focus-stack-structure date: 2026-07-16 sequence: 95 type: refactor compatibility: compatible component: `labkit_FocusStack_app` | `1.5.1 -> 1.5.2` scope: Image Measurement scope: App structure Context Focus Stack split static metadata across `requirements.m` and `version.m`, spread a version-1 project over a generic lifecycle package, grouped three calculation concepts under `+appState`, and ran a startup callback only to fill an output path and emit an App-specific debug line. It also duplicated the portable-reference path loop in session, actions, and presentation. Decision and rationale Use the compact Runtime V2 contract: one product definition, one durable project specification, and one explicit session factory. Put calculation result shape, presets, and run fingerprinting beside the computation. Resolve output folders only when file registration or export actually needs them. Changes Consolidated command metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated project creation and validation in `projectSpec.m`. Moved transient image reconstruction to root `createSession.m`. Moved three `+appState` helpers into their owning `+analysisRun` capability. Removed the metadata files, generic lifecycle/state packages, and redundant App startup callback. Replaced all direct portable-reference reads with the Runtime path accessor. User and data impact Focus loading, registration, fusion, previews, duplicate-run detection, project reopen, and exports keep the same scientific behavior. A new App no longer mutates its durable output folder during startup. After inputs are selected, output defaults remain source-adjacent as before. The App package loses six structural files and one generic package while its real workflow capabilities become easier to locate. Compatibility and migration The durable payload remains version 1 with identical fields, defaults, and validation. Existing projects require no migration. The removed startup debug line is replaced by the Runtime's standard debug-startup message. Validation Unit tests cover the definition/project/session contract, numerical fusion, registration, deterministic run fingerprints, summaries, readers, and invalid inputs. The hidden GUI test covers debug startup, file and folder import, fusion, presentation, export manifest, save/load reconstruction, and append. Evidence [Focus Stack](../../../../apps/image-measurement/focus-stack/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The remaining Image Measurement Apps still use the older structural packages and will be reviewed individually rather than copied from this App blindly."},{"title":"Framework guides and function reference keep distinct routes","url":"history/records/2026/07/LK-20260717-framework-guide-and-function-routes.html","kind":"history","section":"history","keywords":"LK-20260717-framework-guide-and-function-routes 2026-07-17 134 fix compatible Documentation information architecture","text":"Framework guides and function reference keep distinct routes id: LK-20260717-framework-guide-and-function-routes date: 2026-07-17 sequence: 134 type: fix compatibility: compatible scope: Documentation information architecture Context An initial Framework documentation alignment also classified individual `labkit.ui.*` and `labkit.contract.*` API pages as Framework pages. Entering those pages from the global Functions index then changed the active top-level area, which mixed conceptual ownership with page type and made return paths hard to predict. Decision and rationale Keep conceptual and workflow guides under Framework. Keep every exact MATLAB function contract under Functions. Visible API group labels still explain that `labkit.ui` is the Framework implementation, but the top navigation remains stable while browsing function reference pages. Changes Moved the canonical compatibility guide to `framework/contracts.html`. Kept a tracked legacy page at `libraries/contracts/index.html` that links to the new guide and the Functions reference. Restored Functions ownership for all individual UI and contract API pages. Updated framework, library, and regression-test links to the canonical guide. User and data impact Readers now follow one predictable route for Framework guides and another for exact function syntax. Existing contract-guide bookmarks still reach a compatibility page. MATLAB code and scientific data are unchanged. Compatibility and migration The old guide URL remains as a compatibility landing page. Public MATLAB symbols remain unchanged. Validation Renderer regression verifies the Framework guide route, legacy landing page, and Functions-active runtime API page. `docsCheck` verifies the complete site. Evidence The generated Framework landing page links directly to `framework/contracts.html`, while `labkit.ui.runtime.launch` highlights Functions and labels its sibling group Framework Runtime. Known limitations and follow-up The legacy page is a visible handoff rather than an HTTP redirect because the tracked static-site renderer does not currently emit redirect responses."},{"title":"Framework owns UI and compatibility documentation","url":"history/records/2026/07/LK-20260717-framework-documentation-ownership.html","kind":"history","section":"history","keywords":"LK-20260717-framework-documentation-ownership 2026-07-17 133 docs compatible App Framework documentation","text":"Framework owns UI and compatibility documentation id: LK-20260717-framework-documentation-ownership date: 2026-07-17 sequence: 133 type: docs compatibility: compatible scope: App Framework documentation Context The documentation described `labkit.ui` as the App Framework but presented its public API and the compatibility contracts through the generic Functions area. The visible information architecture therefore disagreed with the framework terminology used by app development guides. Decision and rationale Make Framework the documentation owner for `labkit.ui.*` and `labkit.contract.*`. Compatibility requirements belong in the Framework because App definitions and startup consume them. Keep the stable `labkit.contract` MATLAB namespace because it checks UI and domain facades alike; moving it below `labkit.ui` would create a misleading dependency and break existing App definitions. Changes Added compatibility contracts as a Framework sidebar branch. Classified UI and contract API pages under the Framework top navigation. Replaced raw UI package headings with visible Framework API labels while retaining fully qualified MATLAB symbols. Removed the separate Contracts entry from the Functions guide table. User and data impact Readers now enter framework runtime, layout, plotting, interaction, debugging, and compatibility material through one consistent Framework route. MATLAB calls, saved projects, scientific results, and URLs remain compatible. Compatibility and migration No code migration is required. Existing `labkit.ui.*` and `labkit.contract.*` symbols are unchanged. Validation Renderer regression tests verify Framework ownership for the contracts guide and a runtime API page. `docsCheck` rebuilds and compares the complete site. Evidence Generated UI and contract pages highlight Framework in the product navigation, and the Framework landing sidebar links the compatibility guide. Known limitations and follow-up The global API reference still indexes all public symbols together so exact MATLAB functions remain searchable from one place."},{"title":"Framework public APIs become an explicit last resort","url":"history/records/2026/07/LK-20260720-public-api-escalation-order.html","kind":"history","section":"history","keywords":"LK-20260720-public-api-escalation-order 2026-07-20 144 docs compatible Architecture guidance Agent skills","text":"Framework public APIs become an explicit last resort id: LK-20260720-public-api-escalation-order date: 2026-07-20 sequence: 144 type: docs compatibility: compatible scope: Architecture guidance scope: Agent skills Context A convenient framework helper can appear attractive for one App-specific need, but every public name becomes a long-lived compatibility and documentation commitment. Conversely, forcing unrelated behavior into an existing API can turn a focused contract into an ambiguous option bucket. Decision and rationale Use a consistent escalation order for new App behavior: keep it App-local, extend an existing focused API when the addition is natural, add private framework/runtime capability when shared implementation does not require an App-authored contract, and add a new public API only for stable use by multiple Apps or when the nearest existing API would otherwise lose a clear single purpose. This makes a new public API the final solution without prohibiting one when a genuine reusable boundary has emerged. Changes Added the escalation order to the root, framework, and App agent contracts. Updated the App builder and boundary guard skills to require evidence for multiple consumers or an anti-bucket clarity need before introducing a new public name. Updated the documentation maintainer skill to distinguish documentation of an existing focused extension from review of a genuinely new public surface. User and data impact Runtime behavior, public signatures, App calculations, projects, and exports are unchanged. The rule affects future design choices and review evidence. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Skill package validation covers every edited skill. Documentation history rendering and consistency checks cover this record. Repository searches confirm the escalation order appears at the three ownership layers and in the relevant skills. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up When a proposed public API crosses the stated threshold, record its consumers, ownership rationale, alternatives considered, and compatibility contract in the component history."},{"title":"Gait Analysis active-swing workflow","url":"history/records/2026/07/LK-20260716-gait-analysis-active-swing-workflow.html","kind":"history","section":"history","keywords":"LK-20260716-gait-analysis-active-swing-workflow 2026-07-16 71 feat breaking labkit_GaitAnalysis_app Video Marker input contract treadmill swing segmentation gait visualization and exports","text":"Gait Analysis active-swing workflow id: LK-20260716-gait-analysis-active-swing-workflow date: 2026-07-16 sequence: 71 type: feat compatibility: breaking component: `labkit_GaitAnalysis_app` | `1.1.1 -> 2.0.0` scope: Video Marker input contract scope: treadmill swing segmentation scope: gait visualization and exports Context The migrated Gait app accepted loosely shaped coordinate files but did not recover the legacy workflow's step segmentation, per-step skeleton report, or complete translation and angle measurements. A coordinate file alone also could not prove its frame rate, skeleton order, or calibration. Decision and rationale Treat a current Video Marker payload-version-2 project or autosave as the sole file input. It is the first durable artifact that owns coordinates, timing, skeleton, calibration, and annotation provenance together. Segment the legacy treadmill active swing from a prominent foot-X maximum (lift-off) to the following minimum (landing), including a final completed swing that has no later lift-off. Changes Load only the named `labkitProject` variable and reject generic tables, arbitrary MAT variables, legacy marker payloads, and missing frame rate. Show all overlaid skeleton trajectories immediately after import. Detect active swings with app-owned prominence, peak-height, and temporal separation logic; compute cycle/stance measures when a following lift-off exists. Present one selected swing with connected landmarks, joint/segment traces, five endpoint translations, and joint minimum, maximum, and range of motion. Export per-frame, coordinate, step, summary, and provenance tables with lift-off/landing and `swing_time_s` terminology. Migrate version-1 Gait option names and invalidate its scientifically incompatible cached result. User and data impact Users first inspect all trajectories, then run analysis and move between segmented swings. Existing Gait project settings are migrated, but old cached results are recalculated. Older or generic pose files must be converted by opening and saving the source in current Video Marker. Compatibility and migration This is an intentional input and output-schema break. The durable project migration preserves equivalent thresholds while renaming stride/contact-era fields to active-swing terms. Video Marker project payload version 2 is required. Validation Focused unit tests cover source metadata recovery, rejection of incomplete and legacy MAT files, lift-off/landing pairs, retention of the final completed swing, migration of old option names, scientific table fields, and exports. The focused GUI test covers trajectory orientation, analysis, step preview, export, and project reopen. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) documents the source, calculation, visualization, and export contracts. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the durable payload used as input. Known limitations and follow-up The detector represents image-kinematic treadmill events, not force-plate contact. Frame annotation status is preserved but is not yet an automatic step exclusion rule."},{"title":"Gait Analysis app","url":"history/records/2026/07/LK-20260714-gait-analysis-app.html","kind":"history","section":"history","keywords":"LK-20260714-gait-analysis-app 2026-07-14 55 feat compatible labkit_GaitAnalysis_app Gait Analysis app","text":"Gait Analysis app id: LK-20260714-gait-analysis-app date: 2026-07-14 sequence: 55 type: feat compatibility: compatible component: `labkit_GaitAnalysis_app` | `new -> 1.0.0` scope: Gait Analysis app Context Existing gait work used a script chain after pose tracking: import coordinates, smooth marker traces, make per-step figures, compute step and joint metrics, select useful steps, and export tables for downstream statistics. That work belongs in its own app family because the downstream task is not image annotation, signal import, or electrochemistry; it is gait-specific pose analysis from already tracked coordinates. Decision and rationale Add an independent Gait Analysis app instead of extending Video Marker or recreating a model-training workflow. The app accepts several coordinate-table shapes, keeps gait event detection and metric definitions app-local, and exports simple CSV tables that can be consumed by plotting or statistical programs. Changes Added `labkit_GaitAnalysis_app` under the new Gait family. Added CSV/TSV/TXT and MAT pose-coordinate import, including generic `point_x`/`point_y` and LabKit `point__x`/`point__y` column shapes. Added smoothing, foot-relative step-event detection, hip/knee/ankle angle calculation, segment lengths, per-step translations, stride length, step time, ROM, summary metrics, and trajectory/angle/step-event previews. Added CSV set export for frame metrics, step metrics, summary metrics, and per-frame coordinates that keep raw pixel columns alongside optional scale-calibrated and first-frame-origin-shifted columns. User and data impact Users can analyze gait from multiple pose-coordinate sources without tying the workflow to a specific tracking model. The exported tables are plain CSV and separate frame-level, coordinate, step-level, and summary data for downstream overlay, plotting, or statistics. Compatibility and migration The app is additive and does not change Video Marker, image measurement apps, public `+labkit` facades, or existing exports. Existing script outputs can be imported when they provide wide coordinate columns or MAT `coords` and `pointNames`. Validation `GaitAnalysisTest` covers coordinate import, synthetic step detection, metric tables, MAT import, coordinate export calibration/origin semantics, coordinate CSV readback, and CSV set export. `GuiLayoutGaitAnalysisTest` covers the hidden GUI launch and semantic control contract. These tests were added with the app in commit `49863964`. Evidence Initial Gait Analysis app `49863964`. Known limitations and follow-up The first version analyzes one tracked subject at a time and does not perform tracking, model training, group-level statistics, EMG/CAP synchronization, multi-limb phase analysis, or automatic step quality classification."},{"title":"Gait Analysis keeps schema history in one project contract","url":"history/records/2026/07/LK-20260716-gait-analysis-structure.html","kind":"history","section":"history","keywords":"LK-20260716-gait-analysis-structure 2026-07-16 104 refactor compatible labkit_GaitAnalysis_app Gait App structure","text":"Gait Analysis keeps schema history in one project contract id: LK-20260716-gait-analysis-structure date: 2026-07-16 sequence: 104 type: refactor compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.3 -> 2.0.4` scope: Gait scope: App structure Context Gait Analysis spread creation, validation, session reconstruction, and two ordered schema upgrades through a generic lifecycle package. Its generic state package actually contained four analysis concepts: defaults, source-derived options, result construction, and duplicate-run fingerprints. Session and presentation code also inspected the Runtime's nested source-reference fields. Decision and rationale Concentrate project history behind one `projectSpec.m`, keep transient pose reconstruction in root `createSession.m`, and assign all former state helpers to `+analysisRun`. This preserves the real complexity—Video Marker parsing, step segmentation, kinematics, QC, and exports—while removing structural categories that did not explain the workflow. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderer, and debug capability in `definition.m`. Replaced two public migration files with one migration callback that selects the version-1 or version-2 transformation from `fromVersion`. Moved default options, pose-derived option resolution, empty results, and deterministic task fingerprints from `+appState` to `+analysisRun`. Replaced direct portable-reference access with semantic `sourcePaths` lookup in session reconstruction and presentation. Removed generic lifecycle/state packages and separate requirement/version files, and updated GUI-free documentation calls to the owning package. User and data impact Current Video Marker MAT loading, full-trajectory inspection, active-swing segmentation, one-step review, joint angles, translations, timing, QC, duplicate-run detection, CSV export, project save, and project reopen retain their behavior. The image-coordinate Y direction and time-series coordinate direction remain deliberately distinct. Developers can now follow project history through one entry and find all analysis policy under the package that owns the scientific workflow. Compatibility and migration Durable schema version 3 is unchanged. Version-1 option renames and result invalidation still run before the version-2 source collection upgrade. Runtime V2 now owns the loop and validates the resulting current payload. Validation Focused tests cover current Video Marker input, timing/scale/role extraction, active-swing segmentation, final-swing retention, gait parameters, CSV round-trip, and both historical migrations. The hidden GUI workflow covers MAT import, trajectory preview, analysis, export, project save, and decoded pose reconstruction after reopen. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not establish scientific validity for recordings outside the documented treadmill model or replace manual inspection of tracked points and segmented events. Neurophysiology and wearable Apps with generic lifecycle packages remain scheduled for the same review."},{"title":"Gait consumes the Video Marker file contract without a sibling App dependency","url":"history/records/2026/07/LK-20260717-gait-app-path-isolation.html","kind":"history","section":"history","keywords":"LK-20260717-gait-app-path-isolation 2026-07-17 124 fix compatible labkit_GaitAnalysis_app Gait App packaging Video Marker file contract","text":"Gait consumes the Video Marker file contract without a sibling App dependency id: LK-20260717-gait-app-path-isolation date: 2026-07-17 sequence: 124 type: fix compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.7 -> 2.0.8` scope: Gait scope: App packaging scope: Video Marker file contract Context Gait Analysis correctly parsed saved Video Marker MAT documents in production, but its synthetic debug writer created those documents by calling `video_marker.projectSpec`, `video_marker.skeletonDefinition`, and `video_marker.frameAnnotations`. Tests added every App root to the MATLAB path, while the launcher adds only the selected App root. Debug startup could therefore pass tests and fail in a real isolated launch. Decision and rationale Treat the saved `labkitProject` payload as the boundary between the producer and consumer. Gait owns its parser, diagnostics, and synthetic consumer fixture. Video Marker owns serialization and schema validation. One explicit integration test may load both Apps to prove their current file contracts agree, but neither Gait production nor debug code may call the Video Marker package. Changes Rebuilt the Gait debug sample from the documented payload fields without loading Video Marker source code. Split consumer parser tests from a producer-consumer integration test that constructs a current Video Marker project, validates it, saves it, and reads it through Gait. Added an isolated-path debug test using only the repository root and Gait App root. Added a repository guardrail that rejects executable calls from one App package into a sibling App package while allowing documentation references and integration tests. Documented the saved-data boundary in the App development guide and Gait manual. User and data impact Gait debug startup and its packaged App behavior no longer depend on every public App being present on the MATLAB path. Users continue to open the same current Video Marker project and autosave MAT files; parsing, timing, calibration, gait calculations, and exports are unchanged. Developers receive an immediate guardrail failure if a shared test path hides a new sibling-App runtime dependency. Compatibility and migration No project or output migration is required. Video Marker payload version 2 remains the accepted input contract. Validation Focused validation covers isolated debug sample generation, consumer parsing, current Video Marker producer-to-Gait import, Gait calculations and hidden GUI workflow, and the App package boundary guardrail. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [App Development](../../../../development/build-apps/app-development.md#cross-app-data-contracts) Known limitations and follow-up The integration test protects the current saved schema, not scientific validity of manually marked trajectories. Those data and workflow checks remain App-owned."},{"title":"Gait trajectory image-coordinate preview","url":"history/records/2026/07/LK-20260716-gait-image-coordinates.html","kind":"history","section":"history","keywords":"LK-20260716-gait-image-coordinates 2026-07-16 67 fix compatible labkit_GaitAnalysis_app trajectory preview","text":"Gait trajectory image-coordinate preview id: LK-20260716-gait-image-coordinates date: 2026-07-16 sequence: 67 type: fix compatibility: compatible component: `labkit_GaitAnalysis_app` | `1.1.0 -> 1.1.1` scope: trajectory preview Context Marker coordinates use the image convention with an upper-left origin and Y increasing downward. Gait Analysis plotted those values on MATLAB's default Cartesian axes, so the trajectory preview appeared vertically flipped relative to the source video. Decision and rationale Preserve the imported coordinate values and scientific calculations. Reverse only the trajectory preview's Y axis so its visual orientation matches Video Marker and Image Marker. Time-series angle and step plots explicitly retain the conventional upward Y direction. Changes Render trajectory previews with a reversed Y axis. Restore the normal Y direction when switching to Angles or Steps. Document the preview coordinate convention. User and data impact Tracked motion now appears in the same orientation as the source image. Stored coordinates, calculations, tables, and exports are unchanged. Compatibility and migration The change is display-only and requires no project or data migration. Validation The hidden Gait Analysis workflow test checks both trajectory and time-series axis directions while exercising source load and analysis. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) documents the image-coordinate preview convention. Known limitations and follow-up The preview assumes imported marker coordinates follow the documented image coordinate convention. Generic coordinate files with a Cartesian convention must be converted by their producer before import."},{"title":"Headless validation follows current documentation and App contracts","url":"history/records/2026/07/LK-20260719-headless-validation-repair.html","kind":"history","section":"history","keywords":"LK-20260719-headless-validation-repair 2026-07-19 137 fix compatible labkit_TTestWizard_app Continuous integration Documentation validation Statistics","text":"Headless validation follows current documentation and App contracts id: LK-20260719-headless-validation-repair date: 2026-07-19 sequence: 137 type: fix compatibility: compatible component: `labkit_TTestWizard_app` | `1.0.0 -> 1.0.1` scope: Continuous integration scope: Documentation validation scope: Statistics Context The first main-branch validation after the path-derived documentation change still asserted several retired documentation paths. The same run exposed T-Test Wizard code-quality findings and a framework GUI test file that had grown beyond its review budget. Decision and rationale Align validation with the current path-owned documentation structure and keep Runtime-owned empty session buckets out of App factories. Resolve the static analysis findings without changing calculations, export values, or visible workflow behavior. Split the workspace-specific GUI coverage by capability so each test file remains focused. Changes Updated documentation guardrails to use the current testing, Runtime, and complete-App guide paths. Removed an empty `view` placeholder that Runtime already supplies. Replaced dynamic array growth in group-label and group-reassignment helpers with bounded collection. Centralized the box-plot label and documented the plot-jitter and significance-display constants. Split workspace-page and dynamic-table GUI coverage into its own focused test file. User and data impact T-Test Wizard calculations, project data, CSV values, plots, and workflows are unchanged. The App version advances to 1.0.1 to record the internal validation repair. Compatibility and migration Existing version-1 projects remain compatible and require no data migration. Runtime continues to provide `selection`, `workflow`, `view`, and `cache` in the final session state. Validation Focused contract and T-Test core tests cover the original headless failures. Hidden GUI tests cover the T-Test workflow and the extracted Runtime workspace coverage. Documentation rendering and changed-file gates validate the final main-branch handoff. Evidence [T-Test Wizard](../../../../apps/statistics/ttest-wizard/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Testing LabKit](../../../../development/maintain-and-release/testing.md) Known limitations and follow-up Interactive native-dialog behavior remains a developer-led manual validation responsibility; hidden GUI automation does not replace it."},{"title":"History records use one canonical authored format","url":"history/records/2026/07/LK-20260721-canonical-history-record-format.html","kind":"history","section":"history","keywords":"LK-20260721-canonical-history-record-format 2026-07-21 149 docs compatible Project History Documentation Search","text":"History records use one canonical authored format id: LK-20260721-canonical-history-record-format date: 2026-07-21 sequence: 149 type: docs compatibility: compatible scope: Project History scope: Documentation Search Context History records mixed a transport-oriented schema marker with several related but inconsistent metadata conventions. Authors had to infer valid fields and values from renderer internals and agent guidance. Decision and rationale Use one compact, authored record format without a schema field. A single public format page defines every legal field, value, ordering rule, and narrative section so a record can be written and reviewed from repository documentation. Changes Rewrote every existing record into the canonical metadata form, preserved its identity, date, sequence, narrative, and component history links, and added a strict renderer parser. Scopes now feed the documentation search index. User and data impact Readers retain the complete history timeline and component links while gaining more useful search terms. This documentation-only change does not alter saved projects, experimental data, or App behavior. Compatibility and migration The source format changes incompatibly for record authors: remove `schema` and replace an introduction entry with a `component` transition using `new ->`. Existing records are migrated in this same change, so no compatibility reader is retained. Validation The documentation renderer validates every migrated record, rebuilds the site, and documentation guardrails verify the canonical source and generated output. Evidence The renderer parser is the executable enforcement of the published format; the record format page provides its human-readable counterpart. Known limitations and follow-up No known follow-up is required. Future history changes use this format directly rather than adding compatibility metadata variants."},{"title":"Image App project validation ownership","url":"history/records/2026/07/LK-20260716-image-project-validation-ownership.html","kind":"history","section":"history","keywords":"LK-20260716-image-project-validation-ownership 2026-07-16 119 refactor compatible labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app image App project schemas App maintenance cost","text":"Image App project validation ownership id: LK-20260716-image-project-validation-ownership date: 2026-07-16 sequence: 119 type: refactor compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.4 -> 1.7.5` component: `labkit_CurvatureMeasurement_app` | `1.4.4 -> 1.4.5` component: `labkit_FLIRThermal_app` | `1.4.4 -> 1.4.5` component: `labkit_FocusStack_app` | `1.5.3 -> 1.5.4` component: `labkit_ImageEnhance_app` | `1.6.4 -> 1.6.5` component: `labkit_ImageMatch_app` | `1.6.4 -> 1.6.5` component: `labkit_VideoMarker_app` | `1.5.3 -> 1.5.4` scope: image App project schemas scope: App maintenance cost Context The seven image workflow validators repeated Runtime's canonical project-bucket checks and, in six cases, the standard portable source-record field list. Those checks obscured the App-specific rules that actually matter: crop task relationships, parameter ranges, annotation shapes, video metadata, skeleton dimensions, and result schemas. Decision and rationale Keep the existence of each App's required source collection and every domain-specific relationship in `projectSpec.m`. Rely on Runtime for the five canonical bucket structs and the internal shape of every supplied source record. This preserves strict malformed-project rejection while making each validator read as its App schema. Changes Removed 71 net lines of repeated Runtime structure checks from the seven image App project specifications. Preserved required `inputs.sources` fields and all item/source, annotation/source, skeleton/frame, parameter, and result constraints. Added a GUI-free contract that accepts all seven default projects and rejects each project when its App-required source collection is removed. User and data impact Valid projects, migrations, image calculations, annotations, and exports are unchanged. Malformed source records continue to fail in Runtime; malformed App fields continue to fail in the owning App validator. Compatibility and migration No payload format changed and no migration is required. The refactor changes which layer reports framework-owned structure failures, not which valid data the Apps accept. Validation The focused project-spec contract covers default acceptance and missing-source rejection for every affected App. Existing focused unit and hidden-GUI tests cover the domain relationships and representative load, edit, analyze, and export workflows. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines canonical project and source validation. Each affected [Image Measurement App](../../../../apps/image-measurement/README.md) manual lists its remaining project and session responsibilities. Known limitations and follow-up This change does not shorten action handlers or scientific workflow code. Those remain App-owned unless a separately measured repeated behavior proves to be stable and domain-neutral."},{"title":"Image Apps stop creating invalid placeholder outputs","url":"history/records/2026/07/LK-20260716-image-manifest-output-arrays.html","kind":"history","section":"history","keywords":"LK-20260716-image-manifest-output-arrays 2026-07-16 80 fix compatible labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_ImageEnhance_app labkit_ImageMatch_app Image Measurement Result manifests","text":"Image Apps stop creating invalid placeholder outputs id: LK-20260716-image-manifest-output-arrays date: 2026-07-16 sequence: 80 type: fix compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.1 -> 1.7.2` component: `labkit_FLIRThermal_app` | `1.4.1 -> 1.4.2` component: `labkit_ImageEnhance_app` | `1.6.1 -> 1.6.2` component: `labkit_ImageMatch_app` | `1.6.1 -> 1.6.2` scope: Image Measurement scope: Result manifests Context Four image Apps preallocated variable-length result arrays by first asking the Runtime output factory to create a record with an empty ID. Runtime V2 rejects empty result IDs, so otherwise valid export workflows could stop before their standard manifest was written. Decision and rationale Use the canonical empty result array introduced by `labkit.ui` and append only validated real outputs. This preserves strict IDs without requiring each App to duplicate the manifest struct shape. Changes Updated Batch Crop crop outputs. Updated FLIR image, colorbar, and temperature-CSV outputs. Updated Image Enhance batch outputs. Updated Image Match batch outputs. User and data impact The exported images, numeric CSV data, filenames, roles, statuses, and manifest schemas are unchanged. Exports no longer fail during internal output-array initialization. Compatibility and migration No saved project or export schema migration is required. Existing result files remain readable. Validation Each affected App's hidden synthetic GUI export workflow verifies its domain output plus the standard LabKit manifest. Version and component-history guardrails cover all four products. Evidence [Batch Crop](../../../../apps/image-measurement/batch-crop/README.md) [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Image Match](../../../../apps/image-measurement/image-match/README.md) Known limitations and follow-up The Apps still need their separate single-definition and projectSpec reviews; this record only fixes the shared manifest construction defect."},{"title":"Image Enhance assigns state helpers to workflow capabilities","url":"history/records/2026/07/LK-20260716-image-enhance-structure.html","kind":"history","section":"history","keywords":"LK-20260716-image-enhance-structure 2026-07-16 99 refactor compatible labkit_ImageEnhance_app Image Measurement App structure","text":"Image Enhance assigns state helpers to workflow capabilities id: LK-20260716-image-enhance-structure date: 2026-07-16 sequence: 99 type: refactor compatibility: compatible component: `labkit_ImageEnhance_app` | `1.6.2 -> 1.6.3` scope: Image Measurement scope: App structure Context Image Enhance split static product metadata across files, spread a version-1 project over a generic lifecycle package, and placed decoded items, durable annotations, numerical histories, preview scaling, UI normalization, and export fingerprints together under `+appState`. Source reconstruction and presentation also read nested portable-reference fields directly. Decision and rationale Use the compact Runtime V2 definition and project contracts, then assign each remaining concept to the workflow capability that owns its behavior. Preserve the important preview/export boundary: pixel-radius parameters scale for the downsampled preview while full-size export continues to use source coordinates. Changes Consolidated product metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated durable creation and validation in `projectSpec.m` and moved transient reconstruction to root `createSession.m`. Assigned decoded items and lazy selected-image loading to `+sourceFiles`, histories and preview replay to `+analysisRun`, durable per-image data to `+enhancementAnnotations`, UI value clamping to `+userInterface`, and export fingerprints to `+resultFiles`. Removed separate metadata files, generic lifecycle/state packages, and the redundant App startup callback. Replaced direct portable-reference access with the Runtime path accessor. Corrected the GUI-free manual example to write the first returned image from the documented cell-array result. User and data impact Shared and per-image histories, white ROI calibration, pending previews, preview-coordinate radius scaling, original-resolution processing, duplicate export detection, project reopen, and outputs keep their existing behavior. Empty launch no longer chooses an output folder; importing sources retains the source-adjacent default. Developers can locate source decoding, calculations, durable annotations, and exports without learning one generic state package. Compatibility and migration The durable payload remains version 1 with identical fields and defaults, so existing projects require no migration. The Runtime's standard debug-startup message replaces the removed App-specific line. Validation The focused unit suite covers numerical tools, white ROI behavior, preview radius scaling, source reading, full-size exports, manifests, per-image steps, task fingerprints, and project/presenter contracts. The hidden GUI workflow covers file append and selection, independent histories, tool application, export, save/load, and transient cache reconstruction. Evidence [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Image Library](../../../../libraries/image/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Apps still using generic lifecycle/state packages will be reviewed individually. Automated GUI tests do not replace manual judgment of visual quality, ROI pointer feel, or enhancement suitability for quantitative images."},{"title":"Image Match adopts capability-owned structure","url":"history/records/2026/07/LK-20260716-image-match-structure.html","kind":"history","section":"history","keywords":"LK-20260716-image-match-structure 2026-07-16 96 refactor compatible labkit_ImageMatch_app Image Measurement App structure","text":"Image Match adopts capability-owned structure id: LK-20260716-image-match-structure date: 2026-07-16 sequence: 96 type: refactor compatibility: compatible component: `labkit_ImageMatch_app` | `1.6.2 -> 1.6.3` scope: Image Measurement scope: App structure Context Image Match split metadata and project lifecycle across eight structural files, used an App startup callback only for an output-folder default and debug line, and grouped unrelated source, analysis, and export structures under `+appState`. Session, actions, and presentation also read portable-reference path fields directly. Decision and rationale Adopt one definition, one project spec, and one session factory. Place each data constructor beside the capability that owns its meaning: loaded image items with source reading, match steps with analysis, and idempotent export tasks with result writing. Changes Consolidated command metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated version-1 project creation and validation in `projectSpec.m`. Moved selected-image reconstruction to root `createSession.m`. Replaced `+appState` with capability-owned source, analysis, and result functions. Removed the metadata files, generic lifecycle package, and redundant startup callback. Replaced all portable-reference field reads with the Runtime path accessor. Corrected the GUI-free manual example to use the real `applyPipeline` argument order and cell output. User and data impact Reference/source selection, lazy preview loading, match history, calculation, duplicate-export detection, project reopen, and export formats behave unchanged. A new project no longer writes an environment-specific output path during startup; source selection still establishes the same adjacent default. The directory now answers where an item, step, or export task belongs without requiring knowledge of a generic state layer. Compatibility and migration The durable payload remains version 1 with identical fields and validation. Existing Image Match projects require no migration. Validation Unit tests cover the definition/project/session contract, matching methods, pipeline replay, preview scaling, result tables, export formats, manifests, and task fingerprints. The hidden GUI suite covers reference/source loading, selection, history, preview, export, and save/load workflows. Evidence [Image Match](../../../../apps/image-measurement/image-match/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Image Measurement Apps retain older lifecycle or state packages and will be reviewed against their own capabilities."},{"title":"Image app workflow improvements","url":"history/records/2026/07/LK-20260701-image-app-workflow-improvements.html","kind":"history","section":"history","keywords":"LK-20260701-image-app-workflow-improvements 2026-07-01 27 feat compatible labkit_launcher labkit.image labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_FLIRThermal_app labkit_ImageEnhance_app labkit_ImageMatch_app Image app workflow improvements","text":"Image app workflow improvements id: LK-20260701-image-app-workflow-improvements date: 2026-07-01 sequence: 27 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.5 -> 1.1.6` component: `labkit.image` | `1.0.0 -> 1.1.0` component: `labkit.ui` | `3.2.10 -> 3.3.0` component: `labkit.ui` | `3.3.0 -> 3.3.1` component: `labkit_BatchImageCrop_app` | `1.4.0 -> 1.5.0` component: `labkit_BatchImageCrop_app` | `1.5.0 -> 1.5.1` component: `labkit_FLIRThermal_app` | `1.0.0 -> 1.1.0` component: `labkit_FLIRThermal_app` | `1.1.0 -> 1.1.2` component: `labkit_ImageEnhance_app` | `1.4.0 -> 1.4.1` component: `labkit_ImageMatch_app` | `1.4.0 -> 1.4.1` scope: Image app workflow improvements Context Image apps used full-resolution data for several previews even when the screen could not display that detail. Controls also accepted ranges that were awkward for the current image, and FLIR point and ROI measurements were not yet a complete direct-manipulation workflow. Decision and rationale Set an explicit preview budget, derive sensible control bounds from the loaded image, and preserve full-resolution data for calculations and exports. Expand the FLIR app around point and ROI temperature tools rather than adding more separate analysis dialogs. Changes `labkit.image` `1.0.0 -> 1.1.0` `labkit.ui` `3.2.10 -> 3.3.1` Batch Crop `1.4.0 -> 1.5.1` FLIR Thermal `1.0.0 -> 1.1.2` `labkit_launcher` `1.1.5 -> 1.1.6` Added preview-budget helpers. Improved image app range and preview controls. Improved image measurement workflows. User and data impact Large images produced bounded previews while exports continued to use source resolution. Batch Crop controls reflected the crop and scale being edited, and FLIR users could place temperature points or regions directly on the image and read their values in the detail panel. Compatibility and migration Existing image inputs and app exports remained valid. Preview budgeting changed display resolution only; calculations and exports continued to use the source data required by each workflow. Validation The two commits expanded Batch Crop export tests, FLIR operation and layout tests, image-facade preview tests, busy-state tests, scale-bar gesture checks, and launcher coverage. Exact historical commands were not recorded. Evidence Main commits `15a798ba` and `70bfcfd4`. Known limitations and follow-up Preview budgeting improved responsiveness but did not by itself remove every eager image read. Batch Crop file selection was made lazy in the following profiling work."},{"title":"Interactive recovery of missing project sources","url":"history/records/2026/07/LK-20260716-runtime-source-relink.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-source-relink 2026-07-16 66 fix compatible labkit.ui project loading portable source references","text":"Interactive recovery of missing project sources id: LK-20260716-runtime-source-relink date: 2026-07-16 sequence: 66 type: fix compatibility: compatible component: `labkit.ui` | `6.0.1 -> 6.0.2` scope: project loading scope: portable source references Context Saved projects could describe required external files with portable source records, but an unresolved path produced a framework error unless the owning app implemented the advanced `Project.RelinkSources` hook. Production apps using standard source records therefore had no common recovery interaction when a project moved between folders, drives, or operating systems. Decision and rationale Make missing-file recovery the default Runtime V2 behavior for canonical source records. The runtime already owns project loading, injected dialogs, and transactional state replacement, so it can provide one consistent prompt without duplicating native chooser code in every app. The advanced app hook remains available for nonstandard source schemas. Changes Report the saved filename and source role when automatic resolution fails. Let the user locate each missing required file with one native file chooser. Rebuild the selected source reference relative to the loaded project before constructing the app session. Mark a successfully repaired document as unsaved so the replacement path can be retained on the next save. Mark migrated payloads, snapshots, and declared legacy variables as unsaved; **Save State** then upgrades the opened MAT path to the current project format through the existing atomic replacement. Treat cancellation as an atomic load cancellation without changing the live project or view. User and data impact Projects whose external files remain in their recorded locations open exactly as before. When a required file moved, the user can locate it instead of seeing only an unresolved-source error. The runtime never searches arbitrary folders or silently substitutes a different file. Merely opening an old document does not rewrite it; saving the migrated document upgrades that same path. Compatibility and migration The change is compatible with current project envelopes, declared legacy imports, and existing custom `Project.RelinkSources` callbacks. Selecting a replacement updates only the candidate project; saving writes the current project format. Validation The hidden Runtime V2 project test covers successful default relinking, relative-reference rebuilding, dirty-document state, cancellation rollback, and the existing custom relink hook. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) documents the default missing-source interaction. Runtime source resolution reruns before fresh-session construction and state commit. Known limitations and follow-up The default flow asks once for each unresolved source. Apps with a specialized multi-file schema may continue to provide a custom relinking callback."},{"title":"LabKit name and the first multi-domain app families","url":"history/records/2026/05/LK-20260530-app-family-expansion.html","kind":"history","section":"history","keywords":"LK-20260530-app-family-expansion 2026-05-30 3 feat compatible historical project evolution","text":"LabKit name and the first multi-domain app families id: LK-20260530-app-family-expansion date: 2026-05-30 sequence: 3 type: feat compatibility: compatible scope: historical project evolution Context By the end of the electrochem extraction, the repository had reusable DTA operations and several app entry points, but its naming and package layout still reflected the original Gamry workbench. It was not yet clear whether the design could serve workflows with different data, interaction, and visualization needs. Decision and rationale Rename the project LabKit and treat apps, rather than one scientific domain, as the main deliverables. Keep a limited set of reusable packages underneath them, then exercise that structure with image registration, region editing, curvature measurement, wearable data import, and ECG viewing. The purpose was not to turn LabKit into one analysis program. It was to make a common MATLAB foundation useful to several independent laboratory tools without erasing the workflow decisions that made each tool understandable. Changes Renamed the workbench namespace to `labkit` and reduced the public package surface to app-facing UI and domain operations. Standardized the basic workbench shell and DTA file panels across the electrochem apps. Added DIC preprocessing and postprocessing workflows with iterative crop, mask, registration, zoom, and preview interactions. Added the Curvature Measurement app and reused its anchor-curve editor across image workflows where the interaction was genuinely the same. Added a biosignal facade and the ECG Print app, including wearable CSV import and signal-column diagnostics. Added MATLAB CI and reorganized tests by source responsibility. User and data impact LabKit now served electrochem, image, and biosignal work from distinct app entry points. Image users gained direct manipulation of regions and anchors; biosignal users gained a viewer for imported wearable recordings. Existing DTA files remained external inputs and were not converted into a central project format. Compatibility and migration The namespace rename was an implementation migration for repository code. The new image and biosignal apps were additive. Scripts that referenced the former workbench namespace needed to adopt `labkit` or an app entry point. Validation Focused DIC interaction tests, biosignal import tests, layout tests, and the first MATLAB CI workflow were added during this stage. The exact historical command used for each commit was not recorded. Evidence Public-surface cleanup and LabKit rename `9bd8ec8f` through `179885e1`. Unified workbench and file panels `2de7dd0f` through `1be52b9d`. DIC family `aa96ae88` through `fd808a56`. Curvature Measurement `9c1f3798`, `8bcde252`. Biosignal and ECG work `d7c31369` through `b7cabd52`. Initial MATLAB CI `5102640c`. Known limitations and follow-up The new apps exposed interaction problems that ordinary callback tests did not capture well, including popout axes, anchor editing, busy-state reentry, and image zoom ownership. Those issues drove the managed interaction work that followed before v1.0."},{"title":"Launcher adopts the App SDK diagnostic launch contract","url":"history/records/2026/07/LK-20260720-launcher-app-sdk-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260720-launcher-app-sdk-diagnostics 2026-07-20 139 feat compatible labkit_launcher LabKit Core App Framework","text":"Launcher adopts the App SDK diagnostic launch contract id: LK-20260720-launcher-app-sdk-diagnostics date: 2026-07-20 sequence: 139 type: feat compatibility: compatible component: `labkit_launcher` | `1.5.2 -> 1.6.0` scope: LabKit Core scope: App Framework Context The Launcher still passed the retired `RequestAdapter` startup seam after Apps had moved to `labkit.app.Definition`. Normal launch therefore failed before the selected App could create its window. Removing that argument restored startup, but temporarily also removed the user's diagnostic launch path. Decision and rationale Treat the App entrypoint and `Definition.launch` as the only Launcher-to-App runtime boundary. A normal launch uses the SDK defaults. A debug launch passes one typed `labkit.app.diagnostic.Options` value that requests verbose persisted events and the App-owned anonymous synthetic sample. The Launcher continues to read catalog metadata from the single app-owned `definition.m` source without executing the SDK. This preserves its recovery role when an installed framework is incomplete or damaged. Changes Replaced retired runtime-adapter injection with direct App SDK entrypoint calls for normal launch and profiling. Restored **Open Debug** using verbose typed diagnostics and each App's `BuildDebugSample` contract. Added one isolated session folder per debug launch under `artifacts/diagnostics/launcher/`, with the event stream, manifests, and anonymous fixture artifacts. Kept Launcher UI state in a Launcher-owned view record instead of the retired framework registry name. Updated Definition metadata discovery for current name-value syntax while retaining the older literal form for repair-oriented catalog compatibility. Excluded hidden class-folder implementation methods from public API and generated-documentation discovery. User and data impact Existing normal launch, profiling, packaging, update, and documentation actions keep their public behavior. Debug launch now creates explicit diagnostic artifacts and opens the App with its synthetic scenario; it no longer invokes the retired string-mode or request-adapter contracts. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Focused Launcher GUI, catalog, progress, profiling, and documentation contracts. App SDK diagnostic integration through DIC Preprocess, including `events.jsonl` and `sample-pack.json` evidence. Documentation source synchronization and public API discovery guardrails. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive validation should confirm the visible debug launch, the selected App's synthetic state, and the usefulness of the generated diagnostic bundle on the deployment MATLAB version. labkit.app.Definition Compile and launch one immutable App SDK contract."},{"title":"Launcher and maintainer tool manuals","url":"history/records/2026/07/LK-20260716-launcher-and-maintainer-manuals.html","kind":"history","section":"history","keywords":"LK-20260716-launcher-and-maintainer-manuals 2026-07-16 63 docs compatible labkit_launcher `docs/apps/labkit-core/launcher/` `docs/development/tools/` `docs/development/maintain-and-release/testing.md`","text":"Launcher and maintainer tool manuals id: LK-20260716-launcher-and-maintainer-manuals date: 2026-07-16 sequence: 63 type: docs compatibility: compatible component: `labkit_launcher` scope: `docs/apps/labkit-core/launcher/` scope: `docs/development/tools/` scope: `docs/development/maintain-and-release/testing.md` Context Launcher behavior was split between Getting Started, architecture notes, source comments, and historical records. Maintainer tools under `tools/` had callable MATLAB entry points but no coherent manual set; profiling details were embedded in the testing command matrix while packaging and codecheck behavior were mostly discoverable only from source. Decision and rationale Document the user-operated launcher beside LabKit Core apps, even though its self-repair implementation remains a root-level standalone file. Document source-checkout tools under Development because they support maintenance rather than app workflows. Keep the existing Testing page as the test-system owner, add a conceptual execution map there, and move profiler call details into the maintainer tool reference. This structure lets readers start from the product they operate or the maintenance task they intend, without mirroring every source directory or mixing tool APIs into reusable `labkit.*` function reference. Changes Added a LabKit Launcher manual covering window actions, programmatic calls, discovery, installation, recovery, tools, limitations, and development milestones. Added a maintainer-tool landing page and detailed references for Code Analyzer reports, app deployment packages, documentation builds, and performance profiling. Added a test-system execution map explaining build tasks, runner discovery, test layers, selectors, plugins, artifacts, and manual-validation boundaries. Connected Getting Started, Apps, LabKit Core, Development, Architecture, Documentation, Testing, Release, the root README, and generated navigation to the new manuals. User and data impact Users can now understand launcher controls and history from the Apps section. Maintainers can copy direct MATLAB calls and inspect every supported option and output without reading tool implementation files. No app workflow, scientific calculation, saved project, input file, or exported result changes. Compatibility and migration This is an additive documentation change. Existing page URLs remain valid. The new launcher and tool pages are generated from Markdown and included in search; no handwritten HTML or third-party documentation package is added. Validation Documentation contracts verify source ownership, generated launcher history, tool entry-point coverage, local links, search indexing, and byte-for-byte agreement between structured sources and the tracked site. Evidence `docs/apps/labkit-core/launcher/README.md` owns the launcher manual. `docs/development/tools/` owns direct maintainer-tool references. `ProjectSurfaceDocumentationTest` protects placement and generated content. Known limitations and follow-up Private app documentation remains in each private repository. Internal private helpers beneath tool folders are intentionally described through their public entry point rather than receiving standalone reference pages."},{"title":"Launcher app version history","url":"history/records/2026/07/LK-20260713-launcher-app-version-history.html","kind":"history","section":"history","keywords":"LK-20260713-launcher-app-version-history 2026-07-13 53 feat compatible labkit_launcher Launcher app version history","text":"Launcher app version history id: LK-20260713-launcher-app-version-history date: 2026-07-13 sequence: 53 type: feat compatibility: compatible component: `labkit_launcher` | `1.3.0 -> 1.4.0` scope: Launcher app version history Context The structured changelog could be parsed by maintainers, but launcher users could not inspect the history of the selected app. Earlier normalization also attached broad release descriptions without recording every app version event, which made histories such as CIC appear to begin years of versions too late. Decision and rationale Expose component-filtered history in the launcher and make exact component events the durable lookup key. A user should be able to move from the current app catalog directly to its evolution record without reading Git history or understanding changelog internals. Changes Added `labkit_launcher(\"history\", appCommand)` for programmatic history lookup and a Version History viewer for the selected launcher app. Added explicit component introduction events and reconstructed every tracked launcher, facade, and app version transition from `origin/main` history. Added continuous-history validation so missing introductions, gaps, and current-version mismatches fail the release guardrail. User and data impact Launcher users can inspect dated version transitions, compatibility, rationale, impact, and evidence for the selected app. App calculations and user data are unchanged. Compatibility and migration The launcher command and button are additive. Existing list, version, package, maintenance, and app-launch workflows remain available. Validation `LauncherGuiTest` covers programmatic filtering and the hidden GUI viewer; `ChangelogGuardrailTest` validates complete version chains against current metadata. These checks were included in the combined mainline change `4a6c41dd`. Evidence Component history, launcher viewer, and changelog validation were included in `4a6c41dd`. Historical component events were reconstructed from version-file changes on the then-current `origin/main` history. Known limitations and follow-up History begins when version metadata was first tracked for each component. Behavior before that point can only be inferred from older source commits and is not assigned invented version numbers."},{"title":"Launcher code-analysis export","url":"history/records/2026/07/LK-20260701-launcher-code-analysis-export.html","kind":"history","section":"history","keywords":"LK-20260701-launcher-code-analysis-export 2026-07-01 29 feat compatible labkit_launcher Launcher code-analysis export","text":"Launcher code-analysis export id: LK-20260701-launcher-code-analysis-export date: 2026-07-01 sequence: 29 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.6 -> 1.2.0` scope: Launcher code-analysis export Context The launcher offered a Code Analyzer action, but its earlier implementation carried substantial custom export code. That made a maintenance tool harder to maintain than the MATLAB analysis it was exposing. Decision and rationale Use MATLAB's native issue representation and export path, and keep the launcher responsible only for choosing the target and destination. This reduced custom conversion logic while preserving a launcher-level entry point for the report. Changes `labkit_launcher` `1.1.6 -> 1.2.0` Exported launcher Code Analyzer issues natively. User and data impact The launcher could export Code Analyzer findings in a MATLAB-supported form without requiring a separate script. This action inspected source code only and did not start or modify an app project. Compatibility and migration Existing launcher actions remained available. Newly exported issue files used MATLAB's native representation rather than the removed custom conversion. Validation `LauncherGuiTest` gained coverage for the native issue-export path and the implementation in `8fd3ddff` removed more launcher code than it added. Evidence Main commit `8fd3ddff`. Known limitations and follow-up Code analysis remained a maintainer tool. It did not become a prerequisite for launching an app or a replacement for the repository's automated checks."},{"title":"Launcher installation and App startup expose actionable progress","url":"history/records/2026/07/LK-20260726-launcher-install-and-startup-feedback.html","kind":"history","section":"history","keywords":"LK-20260726-launcher-install-and-startup-feedback 2026-07-26 160 fix compatible labkit_launcher Launcher double-click startup feedback Duplicate App launch prevention Standalone install and repair target selection Stable main and release-tag download selection","text":"Launcher installation and App startup expose actionable progress id: LK-20260726-launcher-install-and-startup-feedback date: 2026-07-26 sequence: 160 type: fix compatibility: compatible component: `labkit_launcher` | `1.7.0 -> 1.7.1` scope: Launcher double-click startup feedback scope: Duplicate App launch prevention scope: Standalone install and repair target selection scope: Stable main and release-tag download selection Context Opening an App from the Launcher was synchronous. Although the callback assigned an opening message before calling the App entry point, MATLAB could enter the expensive startup call before repainting the Launcher. Users therefore saw no visible acknowledgement, could interpret a legitimate startup as a hang, and could issue repeated double-clicks without knowing which stage was active. Existing GUI coverage asserted only the final opened state. The standalone `labkit_launcher.m` also claimed it could bootstrap an otherwise empty folder, while its safety check and regression test rejected that exact case. Its repair window exposed neither a target folder nor a download-source choice, and did not distinguish a new installation from replacement of an existing one. Decision and rationale Treat startup acknowledgement as part of the Launcher interaction contract. Publish and paint a small number of honest stages before the expensive App entry point, disable launch surfaces while the action is active, and preserve the concrete entry-point failure when startup does not complete. Stages identify the current ownership boundary without inventing percentage progress that the Launcher cannot measure. Keep the standalone entry self-contained but make its narrow responsibility complete: choose and validate a target before network access, distinguish installation from repair, offer stable/main/explicit-tag archives, confirm replacement, validate the extracted candidate, and retain transactional rollback. The installed Launcher continues to own rich version browsing and App catalog behavior. Changes Added visible path-preparation and App-window-initialization stages. Changed the launch button text and pointer while startup is active. Disabled the App table and all action buttons until startup finishes. Restored the normal controls after success or failure. Extended the native Launcher test to inspect status and control state at the instant the selected App entry point begins. Replaced the one-button repair window with target, source, a fetched published-version selector, confirmation, truthful operation stages, and status details. The UI neither requires users to type repository tags nor claims percentage progress for work it cannot measure. Allowed safe installation into a new, empty, or standalone-launcher-only folder while rejecting filesystem roots, Git checkouts, and unrelated nonempty folders before download. Added current-main and selected published-release downloads without importing the installed version manager into the standalone file. User and data impact Users receive immediate acknowledgement after a button press or row double-click, can see whether startup is preparing the path or waiting in the named App entry point, and cannot accidentally queue another launch through the Launcher while the first is active. Scientific data and App projects are unchanged. A user who downloads only `labkit_launcher.m` can now choose where to install, understand whether the operation is a new installation or repair, and select a published stable version or deliberate development source. Unsafe target folders fail before network or replacement activity. Compatibility and migration The launcher command, catalog, table fields, visual layout, update tools, and App entry points are unchanged. The patch is compatible with existing installations and Apps. The standalone window adds choices but preserves latest stable as its recommended default. Validation The focused hidden-GUI Launcher specification verifies the starting message, disabled table, wait pointer, temporary button label, selected command, final opened message, and restoration of controls. Documentation consistency and launcher version/history guardrails cover the published contract. Bootstrap specifications verify standalone UI choices, root-only and selected empty-folder installation, stable-release fallback, exact main and tag archive URLs, candidate validation, Git and unrelated-folder rejection, preservation, rollback, and actionable network failure. Evidence The direct-main fix commit, focused Launcher dispatch result, generated documentation check, and exact main-push CI run are the delivery evidence. Known limitations and follow-up The Launcher can identify that control has entered a named App entry point, but cannot report finer App-owned initialization phases until that App creates its Runtime diagnostics. Native dialog appearance, folder-picker behavior, and perceived responsiveness remain manual checks."},{"title":"Launcher manager and stale callback fix","url":"history/records/2026/06/LK-20260626-launcher-manager-and-stale-callback-fix.html","kind":"history","section":"history","keywords":"LK-20260626-launcher-manager-and-stale-callback-fix 2026-06-26 13 fix compatible labkit_launcher labkit_launcher labkit.ui Launcher manager and stale callback fix","text":"Launcher manager and stale callback fix id: LK-20260626-launcher-manager-and-stale-callback-fix date: 2026-06-26 sequence: 13 type: fix compatibility: compatible component: `labkit_launcher` | `1.0.0 -> 1.1.0` component: `labkit_launcher` | `1.1.0 -> 1.1.1` component: `labkit.ui` | `3.0.0 -> 3.0.1` scope: Launcher manager and stale callback fix Context The launcher could update LabKit but did not offer an explicit choice among a recent release, a tag, and the main branch. Separately, image drag tools could leave old callbacks attached after an interaction ended. Decision and rationale Add a managed version selector backed by an install manifest, and make image interaction cleanup restore or release every temporary callback. Both changes reduce hidden state that otherwise survives beyond the user's selected action. Changes `labkit_launcher` `1.0.0 -> 1.1.1` `labkit.ui` `3.0.0 -> 3.0.1` Added the launcher version manager and managed-manifest requirement. Released stale image drag callbacks. User and data impact Users could deliberately install a supported release or development revision. Ending an image drag no longer left the figure responding to an obsolete tool. Saved app data did not change. Compatibility and migration Existing launcher installations remained usable. Managed installations needed the launcher manifest for version replacement; image editors required no saved- data conversion after their stale callbacks were released. Validation The listed commits added launcher-manager/manifest coverage and regression checks for image callback cleanup. Exact historical commands were not recorded. Evidence Main commits `fe8654c9`, `ef89cf77`, and `3d23b7f1`. Known limitations and follow-up Version selection still depended on network access for remote revisions; already installed local versions remained usable offline."},{"title":"Launcher reads the single App definition","url":"history/records/2026/07/LK-20260716-launcher-definition-metadata.html","kind":"history","section":"history","keywords":"LK-20260716-launcher-definition-metadata 2026-07-16 78 refactor compatible labkit_launcher App discovery Product metadata ownership","text":"Launcher reads the single App definition id: LK-20260716-launcher-definition-metadata date: 2026-07-16 sequence: 78 type: refactor compatibility: compatible component: `labkit_launcher` | `1.5.0 -> 1.5.1` scope: App discovery scope: Product metadata ownership Context The launcher discovered entrypoints independently from App runtime creation, but obtained versions by parsing a separate package `version.m`. That kept the old metadata file alive even after Runtime V2 gained a single definition contract. Decision and rationale Keep discovery lightweight and self-contained while changing its source of truth. The launcher now reads `AppVersion` and `Updated` literals from the App's `definition.m`; it does not execute the definition or start the App. Changes Preferred `definition.m` product metadata during public and private App discovery. Added a synthetic private-App catalog test with no `version.m` file. Retained a documented internal fallback only for Apps awaiting migration. User and data impact Launcher catalogs continue to show version and update dates. A migrated App does not need a second metadata file solely for discovery. Compatibility and migration The change is compatible. Existing Apps remain discoverable through the temporary fallback; migrated Apps use only their definition. The fallback is removed after the last App migration. Validation The launcher catalog test creates an isolated App definition, points private discovery at it, and verifies the returned version and date without a `version.m` file. Evidence [LabKit Launcher](../../../../apps/labkit-core/launcher/README.md) explains definition-backed discovery. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the App product metadata fields. Known limitations and follow-up P-code packaging metadata will be audited with the deployment workflow before the transitional parser fallback is removed."},{"title":"Launcher update reliability","url":"history/records/2026/07/LK-20260701-launcher-update-reliability.html","kind":"history","section":"history","keywords":"LK-20260701-launcher-update-reliability 2026-07-01 25 fix compatible labkit_launcher labkit_launcher Launcher update reliability","text":"Launcher update reliability id: LK-20260701-launcher-update-reliability date: 2026-07-01 sequence: 25 type: fix compatibility: compatible component: `labkit_launcher` | `1.1.3 -> 1.1.4` component: `labkit_launcher` | `1.1.4 -> 1.1.5` scope: Launcher update reliability Context The self-contained launcher could install a ZIP release, but the replacement path performed more filesystem work than necessary and duplicated logic for locating, unpacking, and replacing the workbench. Slow replacement made a successful update look stalled; duplicated recovery paths made failures harder to reason about. Decision and rationale Minimize the files touched during an update and give ZIP replacement one straight-line implementation. Preserve the existing installation until the download and unpack steps have produced a usable replacement. Changes `labkit_launcher` `1.1.3 -> 1.1.5` Sped up launcher zip updates. Simplified launcher zip replacement. User and data impact ZIP updates completed with less waiting and fewer intermediate operations. A failure before replacement left the existing checkout available, while a successful update continued to present the same launcher entry point. Compatibility and migration Existing managed installations remained updateable. The replacement algorithm changed internally and did not require users to reinstall a working checkout. Validation Both commits extended `LauncherGuiTest` around download, replacement, and recovery behavior. The exact historical commands were not recorded. Evidence Main commits `ebf86cf2` and `becf9391`. Known limitations and follow-up The updater still depended on the release artifact being trustworthy. Later release work added stronger tag-to-asset verification and update diagnostics."},{"title":"Legacy import and first app workbench","url":"history/records/2026/05/LK-20260528-initial-app-workbench-foundation.html","kind":"history","section":"history","keywords":"LK-20260528-initial-app-workbench-foundation 2026-05-28 1 feat compatible historical project evolution","text":"Legacy import and first app workbench id: LK-20260528-initial-app-workbench-foundation date: 2026-05-28 sequence: 1 type: feat compatibility: compatible scope: historical project evolution Context LabKit began with a direct import of older MATLAB analysis and GUI code. The electrochem workflows mixed file parsing, calculations, session state, plots, and exports inside large entry scripts, and several root commands exposed the same legacy implementation through different paths. Decision and rationale Separate the imported code by responsibility before adding new features. Move Gamry parsing and pulse detection into reusable DTA operations; keep CIC, CSC, VT resistance, EIS, and chrono calculations with their workflows; then expose each workflow through one package-backed app command. Extract shared UI pieces only after the app behavior is visible in more than one workflow. Changes Imported the legacy MATLAB tree without rewriting its scientific formulas. Extracted chrono, EIS, and CV/CT DTA parsers plus pulse detection and named synthetic fixtures. Separated chrono alignment/export, CIC, CSC, VT resistance, and EIS result operations from their GUI assembly. Added named app entry points and package-backed runners, then removed the duplicate root legacy entry points and legacy GUI directory. Introduced the first shared two-pane/tabbed shells, file panels, plot controls, log panels, and axes helpers used by the electrochem apps. User and data impact Users gained named electrochem app commands instead of opening the old GUI scripts directly. Calculations and exported values were intended to remain unchanged while code ownership moved; laboratory files remained external inputs rather than being copied into a central LabKit database. Compatibility and migration Component/app version files did not exist yet. Old root GUI entry points were removed as package-backed app commands became the supported launch path. Validation The first regression evidence included named DTA fixtures, calculation tests, an optional GUI smoke runner, and GUI compatibility checks. A single standard test command had not yet been established. Evidence Initial import `5973bde0`. Parser and calculation extraction from `fc70f9b2` through `9f7c6e4e`. App entrypoint and package migration from `40f46561` through `4d58066c`. First shared app-shell work from `f05f4a35` through `61a8cb87`. Known limitations and follow-up This stage improved structure but still contained several broad shared UI helpers and session abstractions that were narrowed during the following DTA facade and app-boundary work."},{"title":"Legacy import preserves portable references without exposing their schema","url":"history/records/2026/07/LK-20260716-opaque-source-import.html","kind":"history","section":"history","keywords":"LK-20260716-opaque-source-import 2026-07-16 102 feat compatible labkit.ui App Framework Project portability","text":"Legacy import preserves portable references without exposing their schema id: LK-20260716-opaque-source-import date: 2026-07-16 sequence: 102 type: feat compatibility: compatible component: `labkit.ui` | `7.4.2 -> 7.4.3` scope: App Framework scope: Project portability Context The GUI-free source-record factory accepted filepaths, but an App importing an older MAT variable could already receive a portable reference containing the only usable relative location. Recreating the record from its original path would discard that portability; preserving it required the importer to copy the Runtime's private nested reference schema. Decision and rationale Keep one public source factory and allow its source value to be either a normal filepath or an existing portable reference passed as one opaque struct. The Runtime validates and canonicalizes the latter. Apps still never construct or read nested reference fields. Changes Extended `labkit.ui.runtime.sourceRecord` with an opaque-reference overload. Added strict validation for the supported portable-reference schema, scalar text fields, filename, and usable relative or original path. Canonicalized imported references so unrelated legacy fields do not enter a current project. Documented the distinction between normal filepath creation and legacy reference preservation. User and data impact Moved legacy projects retain their relative source fallback during upgrade. App importers no longer need to duplicate Runtime serialization fields or choose between architectural ownership and data portability. Compatibility and migration Existing filepath calls and injected source-record services are unchanged. The overload accepts only the current supported reference schema; malformed or newer references fail before they can enter a project. Validation Focused Runtime tests cover normal path creation, opaque-reference canonicalization, relative-path preservation, missing fields, unsupported schema versions, source path access, and the public package surface. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Framework](../../../../framework/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up Opaque-reference import is intended for trusted project importers, not as a general App-defined serialization extension. Video Marker is the first legacy importer scheduled to consume the overload."},{"title":"Local validation uses one focused route and CI owns full coverage","url":"history/records/2026/07/LK-20260721-local-validation-routing.html","kind":"history","section":"history","keywords":"LK-20260721-local-validation-routing 2026-07-21 150 test compatible Test routing Continuous Integration","text":"Local validation uses one focused route and CI owns full coverage id: LK-20260721-local-validation-routing date: 2026-07-21 sequence: 150 type: test compatibility: compatible scope: Test routing scope: Continuous Integration Context The repository exposed both `changedFast` and a conservative `changed` task. For documentation, agent guidance, and CI workflow changes they selected the same complete project suite, so running both locally duplicated work that the cross-platform CI already repeated more comprehensively. Decision and rationale Keep one local changed-file task, `changedFast`, for a focused pre-push check. PR and main-push CI remain the complete validation gate because they run the full headless and hidden-GUI suites on every supported platform. Changes Removed the public conservative `changed` task and its runner entry. Guidance, templates, workflows, and CI helpers now route to their owning documentation or CI guardrails instead of the complete project suite. Broad GUI candidate selection remains reduced to representative workflows for the local route. User and data impact App behavior, saved projects, analysis results, and laboratory data do not change. Contributors receive faster local feedback while the same complete platform coverage remains mandatory before delivery. Compatibility and migration Use `buildtool changedFast` for the local pre-push checkpoint. The retired `buildtool changed` command is intentionally unavailable; use CI results for complete validation rather than substituting another local broad run. Validation Build-task catalog and route-contract tests verify the single public local route and its owner-specific selections. The changed-file gate exercises the selected guardrails, while CI continues to run full headless and hidden-GUI coverage. Evidence The CI workflow invokes `headless` and `gui` on Linux, macOS, and Windows. The validation planner records a reason for every selected local guardrail. Known limitations and follow-up Local `changedFast` is intentionally not a substitute for CI or manual App review. If a new shared surface lacks an owning test route, add that route rather than broadening unrelated local checks."},{"title":"MATLAB-compatible image conversion API","url":"history/records/2026/07/LK-20260713-matlab-compatible-image-conversion.html","kind":"history","section":"history","keywords":"LK-20260713-matlab-compatible-image-conversion 2026-07-13 47 refactor breaking labkit.image labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app MATLAB-compatible image conversion API","text":"MATLAB-compatible image conversion API id: LK-20260713-matlab-compatible-image-conversion date: 2026-07-13 sequence: 47 type: refactor compatibility: breaking component: `labkit.image` | `1.2.0 -> 2.0.0` component: `labkit_DICPostprocess_app` | `1.3.5 -> 1.3.6` component: `labkit_BatchImageCrop_app` | `1.6.7 -> 1.6.8` component: `labkit_CurvatureMeasurement_app` | `1.3.4 -> 1.3.5` component: `labkit_FocusStack_app` | `1.4.8 -> 1.4.9` component: `labkit_ImageEnhance_app` | `1.5.7 -> 1.5.8` component: `labkit_ImageMatch_app` | `1.5.7 -> 1.5.8` scope: MATLAB-compatible image conversion API Context The `toDouble`, `toLuma`, and `toRgbDouble` names combined class conversion, channel shaping, and clipping in ways that differed from familiar MATLAB APIs. Decision and rationale Use MATLAB-compatible names and call contracts for replacement functions, and keep orthogonal RGB shaping explicit so users do not need to learn a composite LabKit normalization rule. Changes Added base-MATLAB `labkit.image.im2double` and `labkit.image.rgb2gray`. Kept channel shaping in `ensureRgb` and made clipping explicit at call sites. Removed the ambiguous conversion helpers and centralized Rec.601 ownership. User and data impact Base-MATLAB users receive familiar image conversion behavior without hidden toolbox requirements. Existing app image results retain their intended ranges and channel shapes through explicit pipelines. Compatibility and migration This is a breaking facade rename. External callers replace removed helper names with `im2double`, `rgb2gray`, and `ensureRgb` as separately needed. Validation Image facade, downstream app, toolbox-shadow, and base-MATLAB ownership tests cover class conversion, luma values, and representative workflows. Evidence The current API contracts are documented in the [Image Library](../../../../libraries/image/README.md). Commit `e3f71c2d` aligned the conversion functions with MATLAB call behavior. Known limitations and follow-up The compatibility layer intentionally covers the LabKit-used MATLAB contracts, not every Image Processing Toolbox function. labkit.image.im2double Convert image data to double using MATLAB's im2double contract. labkit.image.rgb2gray Convert RGB data using MATLAB's rgb2gray call contract."},{"title":"Managed image interactions and diagnostic tracing","url":"history/records/2026/06/LK-20260604-managed-image-interactions.html","kind":"history","section":"history","keywords":"LK-20260604-managed-image-interactions 2026-06-04 4 feat compatible historical project evolution","text":"Managed image interactions and diagnostic tracing id: LK-20260604-managed-image-interactions date: 2026-06-04 sequence: 4 type: feat compatibility: compatible scope: historical project evolution Context Image and ECG apps had made the workbench broader, but they also revealed a class of GUI failures that did not appear in parser or calculation tests. An anchor editor could re-enter a callback, a scale bar could lose ownership, an axes popout could stop reflecting the source, and a long operation could leave controls in an ambiguous state. Decision and rationale Move repeatable interaction mechanics into the UI foundation while leaving image-specific geometry and workflow state in each app. Give long-running actions an explicit busy-state guard, make image axes and tools managed resources, and record callback progress in diagnostic traces that could be inspected after a failure. Changes Added axes popout support and corrected image aspect and menu refresh behavior. Added shared UI control helpers and a reusable ECG peak detector facade. Added the Focus Stack app with automatic and manual image selection. Improved anchor insertion, curvature controls, and scale-bar ownership. Added a busy-state guard to prevent overlapping actions. Introduced a managed image-axes runtime, layered UI facades, and app debug trace logging. Split large app entry points into app-owned private workflow helpers rather than widening the public framework API. User and data impact Users gained Focus Stack and more predictable image editing. Popout views, anchors, scale bars, and long operations provided clearer feedback and were less likely to leave an app in a broken intermediate state. Debug traces made it possible to distinguish a slow or failed callback from an unresponsive window. The changes affected UI state and presentation; they did not intentionally alter existing scientific results. Compatibility and migration Apps gradually moved from direct axes and interaction setup to the managed UI facades. App-private geometry helpers remained private, so the framework did not promise a public API for every interaction detail. Validation The period added public API option documentation, app-private helper tests, interaction regression coverage, and focused checks for UI layout ownership. No single historical validation command covers the complete stage. Evidence Axes popout and control helpers `1e9022c4` through `2136009f`. ECG detector and filtering work `e8450652` through `adb7dfbf`. Focus Stack `08518e91`, `cc4cdf92`. Busy guard `ac36bd54`. Image interaction and scale-bar work `f58b8559` through `5304fa20`. Managed axes, traces, and layered UI facades `27bdbdd8`, `e86e4ea3`, `da0663f1`, `c4cae633`. Known limitations and follow-up The interaction layer was still evolving and the test infrastructure mixed custom runners with newer MATLAB tests. The v1.0 stabilization pass addressed test entry points, package ownership, and remaining compatibility aliases."},{"title":"Managed scientific and conversion constants","url":"history/records/2026/07/LK-20260713-managed-calculation-constants.html","kind":"history","section":"history","keywords":"LK-20260713-managed-calculation-constants 2026-07-13 48 refactor compatible labkit.dta labkit.rhs labkit.biosignal labkit_ChronoOverlay_app labkit_CSC_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Managed scientific and conversion constants","text":"Managed scientific and conversion constants id: LK-20260713-managed-calculation-constants date: 2026-07-13 sequence: 48 type: refactor compatibility: compatible component: `labkit.dta` | `2.0.0 -> 2.0.1` component: `labkit.rhs` | `1.0.0 -> 1.0.1` component: `labkit.biosignal` | `1.0.0 -> 1.0.1` component: `labkit_ChronoOverlay_app` | `1.3.5 -> 1.3.6` component: `labkit_CSC_app` | `1.3.9 -> 1.3.10` component: `labkit_NerveResponseAnalysis_app` | `1.3.4 -> 1.3.5` component: `labkit_ResponseReviewStats_app` | `1.3.4 -> 1.3.5` scope: Managed scientific and conversion constants Context Scientific coefficients, device-format gains, unit conversions, and numeric tolerances were sometimes left as unexplained literals or repeated across callers, making origin and change impact difficult to audit. Decision and rationale Give each semantic calculation constant one named owner and a nearby source or purpose comment, while exempting ordinary indices and explicit UI geometry that do not encode scientific meaning. Changes Named and documented Rec.601, sRGB/CIE, DTA/RHS gains, SI conversions, tolerances, and empirical policies across facades and apps. Centralized repeated CSC charge-density, CIC display-unit, and curvature tolerance contracts. Added repository-wide magic-number and rectangle-interaction guardrails. User and data impact Calculation results are preserved, but future changes now expose the constant's meaning and provenance instead of silently editing an unexplained literal. Compatibility and migration No user migration is required. Public function call contracts and output schemas are unchanged by this record. Validation `MagicNumberGovernanceTest`, rectangle governance, module compatibility tests, and affected app tests cover centralized ownership and preserved numeric behavior. Evidence Source comments use the `Constant:` marker and guardrail diagnostics name the unmanaged file and line. Commit `125338c0` introduced the calculation-constant checks and the corresponding source annotations. Known limitations and follow-up The scanner targets semantically suspicious precision and notation; review is still required for simple integers or short decimals whose meaning is hidden."},{"title":"Migration helper cleanup","url":"history/records/2026/06/LK-20260630-migration-helper-cleanup.html","kind":"history","section":"history","keywords":"LK-20260630-migration-helper-cleanup 2026-06-30 23 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_ImageEnhance_app labkit_RHSPreview_app labkit_RHSPreview_app historical project evolution","text":"Migration helper cleanup id: LK-20260630-migration-helper-cleanup date: 2026-06-30 sequence: 23 type: refactor compatibility: compatible component: `labkit.ui` | `3.2.8 -> 3.2.9` component: `labkit_DICPostprocess_app` | `1.2.3 -> 1.2.4` component: `labkit_BatchImageCrop_app` | `1.3.7 -> 1.3.8` component: `labkit_BatchImageCrop_app` | `1.3.8 -> 1.3.9` component: `labkit_ImageEnhance_app` | `1.3.4 -> 1.3.5` component: `labkit_RHSPreview_app` | `1.2.2 -> 1.2.3` component: `labkit_RHSPreview_app` | `1.2.3 -> 1.2.4` scope: historical project evolution Context Several apps still split one small decision across multiple temporary helper files created during earlier package migrations. That made simple state and preview behavior harder to follow without creating a reusable API. Decision and rationale Consolidate related values behind one clearly named operation and delete pass-through helpers whose only purpose was reducing file length. Preserve the visible app behavior while making each calculation or state summary traceable from its caller. Changes DIC Post, Batch Crop, and RHS Preview patch bumped. Retired migration helper debt. Consolidated RHS preview window bounds, Batch Crop scale state, and Image Enhance export helpers. User and data impact No workflow or result format changed. The cleanup reduced internal indirection in Image Enhance export, Batch Crop scale summaries, RHS Preview window bounds, and a small set of DIC/electrochem helpers. Compatibility and migration The consolidated helpers preserved the app-facing state and result structures. No project or export format changed as the duplicate implementations were removed. Validation Commits `7f73b71b`, `e3349af6`, `733fb951`, `98a2b02c`, and `391540a7` added or updated focused tests for each consolidated operation. Evidence Main commits `7f73b71b`, `e3349af6`, `733fb951`, `98a2b02c`, and `391540a7`. Known limitations and follow-up This was a behavior-preserving cleanup. Later workflow-first packages replaced some of the package names shown in the historical commits."},{"title":"Minimal App contract and current authoring manuals","url":"history/records/2026/07/LK-20260716-minimal-app-contract.html","kind":"history","section":"history","keywords":"LK-20260716-minimal-app-contract 2026-07-16 115 docs compatible labkit-project App authoring Runtime V2 contract guardrails","text":"Minimal App contract and current authoring manuals id: LK-20260716-minimal-app-contract date: 2026-07-16 sequence: 115 type: docs compatibility: compatible component: `labkit-project` scope: App authoring scope: Runtime V2 contract guardrails Context Runtime V2 already supplied default project, session, action, presenter, and startup capabilities, but the complete-App tutorial and several current manuals still taught the retired split metadata and generic lifecycle package shape. The App structure guardrail also prohibited those files only when an App already owned `projectSpec.m`, leaving static Apps unprotected. Decision and rationale Make progressive capability the documented and tested authoring contract. A static App consists of a thin entrypoint, one definition, and one data-only layout. Actions, presentation, durable project state, transient reconstruction, and startup are added independently only when the product needs them. Changes Rewrote the complete-App tutorial around the three-file starting point and one `projectSpec.m` create/validate/migrate entry. Updated architecture, framework, private-App, library, testing, release, and history manuals plus scoped App rules and the App-builder skill to use definition-owned metadata and progressive optional capabilities. Added a hidden-GUI launch test whose definition omits every optional Runtime V2 component. Corrected the tutorial's numeric field declaration after the real launch test exposed its implicit text-control mismatch. Made the App package guardrail reject retired metadata, lifecycle, state, startup, and per-version migration files for every App shape. User and data impact App behavior and scientific results do not change. A developer can begin with three source files and add each larger architectural concept only when a real workflow requires it. Public and private App documentation now teach the same contract. Compatibility and migration This change documents and protects the already migrated Runtime V2 structure. It does not alter saved project payloads or remove the temporary low-level legacy project-migration reader used by framework tests. Validation Focused contract and GUI tests cover both the forbidden retired structures and an actual launch from the minimal definition. A documentation contract checks the tutorial, scoped App rules, and App-builder skill together. Documentation consistency checks and a regenerated site validate navigation and history metadata. Evidence [Build a Complete App](../../../../development/build-apps/complete-app.md) gives the progressive file-by-file tutorial. [Architecture](../../../../development/build-apps/architecture.md) defines the current package shape and ownership boundaries. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) specifies every optional component and callback. Known limitations and follow-up The minimal contract proves framework defaults, not that every complex App has already reached its lowest maintainable complexity. App-by-App workflow and framework-boundary audits continue separately."},{"title":"Minimal App definitions","url":"history/records/2026/07/LK-20260716-minimal-app-definition.html","kind":"history","section":"history","keywords":"LK-20260716-minimal-app-definition 2026-07-16 76 feat compatible labkit.ui App authoring Runtime V2 definition defaults","text":"Minimal App definitions id: LK-20260716-minimal-app-definition date: 2026-07-16 sequence: 76 type: feat compatibility: compatible component: `labkit.ui` | `7.0.0 -> 7.1.0` scope: App authoring scope: Runtime V2 definition defaults Context Runtime V2 already normalized missing project and session buckets, but every definition still had to provide project create/validate callbacks, at least one action, and a presenter. A static App therefore needed lifecycle files and placeholder functions that owned no behavior. Decision and rationale Make lifecycle components proportional to product behavior. Every App still declares a stable ID, title, and semantic layout. The framework supplies empty canonical state and no-op dynamic behavior until the App opts into durable data, transient cache, actions, or presentation. Changes Made `Project`, `Actions`, and `Present` optional in `labkit.ui.runtime.define`; `CreateSession` was already optional. Added a framework-owned version-1 empty project specification and empty presenter model. Allowed an empty action registry when no layout event or startup phase references an action. Added a GUI contract test that launches a definition containing only `Id`, `Title`, and `Layout` and verifies both canonical state roots. Rewrote App-development entry guidance around capability tiers instead of a fixed list of placeholder files. User and data impact Existing Apps behave unchanged. A new static App does not need `createProject.m`, `createSession.m`, `validateProject.m`, `definitionActions.m`, or a presenter. Those components are introduced only when the App gains corresponding behavior. Compatibility and migration The change is additive within UI 7. Existing complete definitions remain valid. Saved project and source schemas do not change. Validation The minimal-definition GUI test exercises real launch, canonical project and session creation, layout construction, empty presentation, and readiness. Definition validation and public documentation guardrails cover both minimal and full definitions. Evidence [App Framework](../../../../framework/README.md) introduces the minimal definition. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) lists required and optional definition components. [App Development](../../../../development/build-apps/app-development.md) maps files to capabilities instead of treating them as universal boilerplate. Known limitations and follow-up Apps that own a project schema still use explicit project specification. The next lifecycle cleanup consolidates each App's project factory, validator, and ordered migration steps behind one `projectSpec.m` entry point rather than scattered version-step files."},{"title":"Multi-app launcher packages","url":"history/records/2026/07/LK-20260709-multi-app-launcher-packages.html","kind":"history","section":"history","keywords":"LK-20260709-multi-app-launcher-packages 2026-07-09 42 feat compatible labkit_launcher Multi-app launcher packages","text":"Multi-app launcher packages id: LK-20260709-multi-app-launcher-packages date: 2026-07-09 sequence: 42 type: feat compatibility: compatible component: `labkit_launcher` | `1.2.7 -> 1.3.0` scope: Multi-app launcher packages Context The deployment tool could package one app, but laboratory workflows often used a small related set. Creating one ZIP per app duplicated the shared LabKit runtime, while manually merging ZIP files risked inconsistent entry files and manifests. The launcher's selected row also could not double as a multi-select without making Open and Debug ambiguous. Decision and rationale Add a separate Package checkbox to each launcher row. Keep the ordinary row selection for Open and Debug, and let checked rows produce one bundle with one entry file per app and a manifest describing the complete package. Changes `labkit_launcher` `1.2.7 -> 1.3.0` Project deployment tooling, multi-app bundle support. Added an independent `Package` checkbox column to the launcher app table so users can choose multiple apps without changing the row selected for Open or Debug. `Package Checked` and `Checked P-code` now create one zip containing every checked app, one direct entry file per app, and a multi-app manifest. Kept single-app package names, result fields, and manifest schema compatible when only one app is supplied to `packageLabKitApp`. User and data impact Users could distribute a related group of apps in one source or P-code ZIP without including the rest of the workbench. Opening and debugging still acted on the highlighted app, so preparing a package did not change normal launcher navigation. Compatibility and migration Existing direct calls that package one app continue to produce the original single-app package contract. Validation The commit expanded deployment unit tests, launcher packaging GUI coverage, selection helpers, changed-file routing, and build-task efficiency checks. The exact historical command was not recorded. Evidence Mainline commit `8a23a52`. Known limitations and follow-up Bundles intentionally included selected apps and their LabKit dependencies, not tests, full documentation, or unrelated private workspaces. P-code packages still required a compatible MATLAB runtime."},{"title":"Nerve Response Analysis uses fixed source identities","url":"history/records/2026/07/LK-20260716-nerve-response-structure.html","kind":"history","section":"history","keywords":"LK-20260716-nerve-response-structure 2026-07-16 107 refactor compatible labkit_NerveResponseAnalysis_app Neurophysiology App structure","text":"Nerve Response Analysis uses fixed source identities id: LK-20260716-nerve-response-structure date: 2026-07-16 sequence: 107 type: refactor compatibility: compatible component: `labkit_NerveResponseAnalysis_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context Nerve Response Analysis split one project schema across generic lifecycle files and kept two additional lifecycle helpers to filter and replace source records by role. Product metadata remained in separate requirement/version functions, while session and presentation code read nested portable-reference fields directly. Decision and rationale Use `filterRecord` and `protocol` as the two stable App-owned source IDs and matching roles. Runtime V2 already supports semantic path lookup by ID and an injected upsert service, so role-filter wrappers duplicate framework behavior without adding scientific meaning. Keep event detection, recording analysis, CAP measurement, presentation, and result writing in their existing concrete workflow packages. Changes Consolidated command identity, display metadata, version, requirements, project, session, layout, actions, presenter, renderer, and debug capability through the single definition. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved parsed JSON and transient workflow reconstruction to root `createSession`. Replaced App-owned role filtering/replacement helpers with `sourcePaths` and the injected `upsertSource` service. Strengthened validation so the two source IDs are unique, supported, and match their declared roles. Removed the generic lifecycle package and separate requirement/version files. User and data impact Filter/protocol selection, run limits, event and train detection, CAP metrics, issue handling, previews, output-folder defaults, JSON export, manifest output, reset, and project reopening are unchanged. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still combine their separate filter and protocol records into the canonical source collection. Runtime V2 invokes the single migration entry and owns iteration. Current version-2 files already written with the documented fixed IDs load without data transformation. Validation Focused unit tests cover migration, source identity validation, event trains, differential/common-mode calculations, CAP metrics, filter labels, and the legacy keep-column input. The hidden GUI workflow covers filter selection, analysis, counts/issues presentation, JSON and manifest export, project save, reset, reopen, and reanalysis. Evidence [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not establish that a selected protocol, stimulation source, or timing window is physiologically correct. RHS Preview still uses the older split lifecycle structure and requires its own independent convergence review."},{"title":"One project migration entry per App","url":"history/records/2026/07/LK-20260716-single-project-migration-entry.html","kind":"history","section":"history","keywords":"LK-20260716-single-project-migration-entry 2026-07-16 81 feat compatible labkit.ui Runtime V2 projects App authoring","text":"One project migration entry per App id: LK-20260716-single-project-migration-entry date: 2026-07-16 sequence: 81 type: feat compatibility: compatible component: `labkit.ui` | `7.2.1 -> 7.3.0` scope: Runtime V2 projects scope: App authoring Context Runtime V2 already applied project migrations sequentially, but each App had to expose a cell array containing every historical version-step function. Schema growth therefore increased files and wiring even though the framework owned the actual migration loop. Decision and rationale Give each persistent App one migration entry. `Migrate(project,fromVersion)` upgrades exactly one step; Runtime V2 supplies each missing version in order, validates every intermediate payload, and continues to the current version. Changes Added the scalar `Project.Migrate` callback. Kept per-step serialization validation, newer-version rejection, atomic load behavior, final project validation, session rebuild, and dirty upgrade state unchanged. Converted the framework's version-1-to-3 round-trip fixture to one callback with two version cases. Retained `Project.Migrations` only as a temporary bridge for existing Apps. User and data impact Saved projects behave as before. App maintainers can keep create, validate, and every schema transition as local functions in one `projectSpec.m` instead of maintaining a growing migration-file registry. Compatibility and migration The contract is additive within UI 7. Existing Apps continue to load during family-sized migration. An App must not declare both migration forms. Validation The Runtime V2 project test opens a version-1 payload in a version-3 App, executes both steps through one callback, verifies final data and source relinking, saves the current payload version, and checks failure atomicity. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents the project declaration and loop. [App Development](../../../../development/build-apps/app-development.md) describes the single project file. Known limitations and follow-up Existing Apps still need their lifecycle functions consolidated into `projectSpec.m`. The legacy migration-array bridge will be removed after the last family is migrated."},{"title":"Output folder prompts","url":"history/records/2026/06/LK-20260630-output-folder-prompts.html","kind":"history","section":"history","keywords":"LK-20260630-output-folder-prompts 2026-06-30 20 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Output folder prompts","text":"Output folder prompts id: LK-20260630-output-folder-prompts date: 2026-06-30 sequence: 20 type: feat compatibility: compatible component: `labkit.ui` | `3.2.5 -> 3.2.6` component: `labkit_DICPostprocess_app` | `1.2.0 -> 1.2.1` component: `labkit_DICPreprocess_app` | `1.2.0 -> 1.2.1` component: `labkit_BatchImageCrop_app` | `1.3.3 -> 1.3.4` component: `labkit_FocusStack_app` | `1.2.1 -> 1.2.2` component: `labkit_ImageEnhance_app` | `1.3.1 -> 1.3.2` component: `labkit_ImageMatch_app` | `1.3.1 -> 1.3.2` component: `labkit_NerveResponseAnalysis_app` | `1.2.1 -> 1.2.3` component: `labkit_ResponseReviewStats_app` | `1.2.1 -> 1.2.2` scope: Output folder prompts Context Exporting from different apps opened `uigetdir` directly, chose starting folders differently, and could not substitute a noninteractive chooser in a test. The repeated dialog code also made cancellation handling inconsistent. Decision and rationale Provide one output-folder prompt that selects a safe default, returns an explicit cancellation flag, remembers a successful folder, and accepts an injected chooser for tests. Apps would still decide when an output folder was needed and what they wrote there. Changes `labkit.ui` `3.2.5 -> 3.2.6` DIC apps, Batch Crop, Focus Stack, Image Enhance/Match, Nerve Response, and Response Review patch bumped. Added `promptOutputFolder`. Migrated output-folder prompts with chooser injection and safe defaults. User and data impact Output dialogs began in a useful folder and cancellation returned cleanly to the app. The selected folder was remembered as a preference; no output was created until the owning app performed its export. Compatibility and migration Existing output locations and exported file formats remained valid. Apps moved from direct folder dialogs to the shared prompt without changing what they wrote after selection. Validation Commit `c5055b98` added `AppHookHelpersTest` coverage for defaults, cancellation, successful selection, and chooser injection, then updated the affected app compatibility checks. Evidence Main commit `c5055b98`. Known limitations and follow-up Runtime V2 later moved dialog access into injected services, preserving the same separation between app decisions and platform dialog mechanics."},{"title":"Owner-minimal validation routing compiles canonical test groups","url":"history/records/2026/07/LK-20260722-owner-minimal-validation-routing.html","kind":"history","section":"history","keywords":"LK-20260722-owner-minimal-validation-routing 2026-07-22 157 test compatible Test routing Test ownership","text":"Owner-minimal validation routing compiles canonical test groups id: LK-20260722-owner-minimal-validation-routing date: 2026-07-22 sequence: 157 type: test compatibility: compatible scope: Test routing scope: Test ownership Context Changed-file validation could widen an App capability change to a family-level test folder, depend on implicit contract discovery, or execute the same test through overlapping folder routes. The migration ledger recorded the required zero-debt replacement: explicit App owners, framework consumer closure, and one execution of each selected official test. Decision and rationale Treat route selection and execution as separate concerns. Preserve every semantic owner route with its reason, discover the matching official tests, then union canonical identities into one non-GUI and one hidden-GUI execution group. This keeps coverage reviewable without paying for duplicate execution. Changes Moved App-specific tests into explicit `appContract`, `workbench`, source capability, `smoke`, and `isolatedPath` owners; moved cross-App audits to project ownership. Removed family-root and overlap-based route suppression. Added canonical test-union compilation, deterministic feature-tagged smoke selection, direct facade-consumer routing, and safe all-smoke fallback when required feature metadata is absent. Preserved physical suite-folder case during selector discovery and made an isolated App sample derive the repository root from the shared test setup, so owner paths behave consistently on Linux, macOS, and Windows. Focused ordinary App-manual, history, and library-documentation routes while retaining complete contracts for renderer and policy changes. Added dynamic guards for every public App's owned contracts and smoke proof, valid source-capability scopes, unique canonical execution, and focused documentation routes. Test classes also reject migrated test-shaped methods stranded in non-discoverable static-private blocks. User and data impact No App behavior, saved project, scientific result, or export changes. Local `changedFast` feedback is smaller and deterministic; CI remains the complete headless and hidden-GUI validation gate. Compatibility and migration No saved-data migration is required. The active test-routing migration ledger is retired; future routing changes must extend the durable ownership rules and their guardrails rather than restore compatibility fallbacks. Validation Focused build-routing guardrails, App ownership contracts, and moved cross-App contracts. Same-machine MATLAB R2025a before/after benchmark for a representative Figure Studio source-capability change. One final `buildtool changedFast` run for the completed diff. Evidence The planner prints all semantic routes and the canonical execution count, including the number of repeated selections removed. The final local gate and CI provide the executable evidence for the exact merged diff. For a change to the Figure Studio `sourceAxes/copyToPreview` capability, the pre-migration planner at the retained mainline baseline selected 46 test executions across three runner groups: 45 unique tests plus one repeated isolated-path contract. Those groups took 120.396 seconds after 5.284 seconds of planning. The migrated planner retained four explicit semantic routes, selected 11 unique tests with no repeated execution, and compiled them into one headless and one hidden-GUI group. Those groups took 51.134 seconds after 6.212 seconds of planning. Test execution time fell by 69.262 seconds (57.5%) while retaining the source-capability unit tests, the owning App's isolated path contract, its launch smoke test, and the relevant source-axes GUI tests. Known limitations and follow-up Hidden GUI smoke tests validate bounded launch/layout behavior, not native dialogs, pointer feel, visual quality, or scientific interpretation; those remain manual review responsibilities."},{"title":"Preview-area per-axis wheel zoom","url":"history/records/2026/07/LK-20260709-preview-area-per-axis-wheel-zoom.html","kind":"history","section":"history","keywords":"LK-20260709-preview-area-per-axis-wheel-zoom 2026-07-09 44 feat compatible labkit.ui Preview-area per-axis wheel zoom","text":"Preview-area per-axis wheel zoom id: LK-20260709-preview-area-per-axis-wheel-zoom date: 2026-07-09 sequence: 44 type: feat compatibility: compatible component: `labkit.ui` | `5.0.3 -> 5.0.4` scope: Preview-area per-axis wheel zoom Context The preview area applied the same two-dimensional wheel zoom to every registered axes. That was appropriate for an image or main plot, but awkward for a narrow histogram or color-scale axes: horizontal zoom changed a dimension whose layout was meant to remain fixed. Decision and rationale Let a preview-area definition choose `xy`, `x`, or `y` wheel zoom for each axes. Preserve `xy` as the default so existing apps keep their behavior, while side axes can opt into only the dimension that carries data. Changes `labkit.ui` `5.0.3 -> 5.0.4` Added a `scrollZoomAxes` preview-area layout option so apps can declare whether each preview axis should mouse-wheel zoom in `xy`, `x`, or `y`. Preview-area side axes can now remain horizontally stable while still allowing app-selected vertical wheel zoom. User and data impact A histogram or temperature scale could remain horizontally stable while still supporting vertical exploration. Main images and plots continued to zoom in both dimensions unless their app explicitly chose another mode. Compatibility and migration Existing preview areas keep default `xy` wheel zoom unless they opt into another per-axis setting. Validation The commit extended UI layout unit tests and axes-workbench GUI coverage for the default and per-axis modes. The exact historical command was not recorded. Evidence Mainline commit `3c143eb`. Known limitations and follow-up The option controlled wheel navigation only. Toolbar zoom, pan, linked axes, and app-specific limit policies remained separate concerns."},{"title":"Profiling and validation speedups","url":"history/records/2026/07/LK-20260702-profiling-and-validation-speedups.html","kind":"history","section":"history","keywords":"LK-20260702-profiling-and-validation-speedups 2026-07-02 30 ci compatible labkit_launcher labkit_launcher labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_ECGPrint_app Profiling and validation speedups","text":"Profiling and validation speedups id: LK-20260702-profiling-and-validation-speedups date: 2026-07-02 sequence: 30 type: ci compatibility: compatible component: `labkit_launcher` | `1.2.0 -> 1.2.1` component: `labkit_launcher` | `1.2.1 -> 1.2.2` component: `labkit.ui` | `3.4.0 -> 3.4.1` component: `labkit.ui` | `3.4.1 -> 3.4.2` component: `labkit_BatchImageCrop_app` | `1.6.0 -> 1.6.1` component: `labkit_ECGPrint_app` | `1.3.0 -> 1.3.1` scope: Profiling and validation speedups Context Startup and file-selection complaints could not be resolved reliably from wall clock impressions alone. At the same time, focused changes paid avoidable test discovery cost, GUI tests waited longer than the behavior required, and Batch Crop read every selected image before the user requested a preview or export. Decision and rationale Add a profiler that records launcher, startup, callback, and close costs with source attribution. Use its evidence to remove repeated UI updates and eager image reads. Route changed-file validation to the smallest owning suites and bound GUI waits without weakening their behavioral assertions. Changes `labkit_launcher` `1.2.0 -> 1.2.2` `labkit.ui` `3.4.0 -> 3.4.2` `labkit_BatchImageCrop_app` `1.6.0 -> 1.6.1` `labkit_ECGPrint_app` `1.3.0 -> 1.3.1` Added LabKit profiling and build-managed test routing to the launcher. Reduced GUI profiling overhead and deferred Batch Crop image reads until preview/export. Compressed validation runtime with bounded GUI waits. User and data impact Maintainers could launch a profiled target and receive a summarized report instead of interpreting MATLAB's raw profile table. Selecting many Batch Crop files returned sooner because images were decoded only for the current preview or final export. Routine validation also completed with less discovery and wait overhead. Compatibility and migration Profiling and test routing were maintainer tools. Batch Crop kept its task and export schemas; selected images were simply decoded later in the workflow. Validation The profiling tool received a dedicated unit suite. Later commits added launcher profiler coverage, deferred-read tests, debug-trace mirroring checks, routing guardrails, and bounded GUI-wait tests. Commit `25912c54` records successful `buildtool changed`, integration validation, and remote MATLAB tests. Evidence Main commits `c07dfc0a`, `74025fee`, `eadcca82`, `25912c54`, and `fcfc36d8`. Known limitations and follow-up These changes reduced measured work but did not eliminate the white window shown before all startup setup completed. The following startup pass changed when windows were painted and when scroll navigation was installed."},{"title":"Project restore distinguishes missing sources from damaged sources","url":"history/records/2026/07/LK-20260717-strict-project-session-restore.html","kind":"history","section":"history","keywords":"LK-20260717-strict-project-session-restore 2026-07-17 125 fix compatible labkit.ui labkit_FigureStudio_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageMatch_app labkit_ImageEnhance_app labkit_NerveResponseAnalysis_app Runtime project restore App session reconstruction","text":"Project restore distinguishes missing sources from damaged sources id: LK-20260717-strict-project-session-restore date: 2026-07-17 sequence: 125 type: fix compatibility: compatible component: `labkit.ui` | `7.4.6 -> 7.4.7` component: `labkit_FigureStudio_app` | `0.2.8 -> 0.2.9` component: `labkit_FLIRThermal_app` | `1.4.6 -> 1.4.7` component: `labkit_FocusStack_app` | `1.5.5 -> 1.5.6` component: `labkit_ImageMatch_app` | `1.6.6 -> 1.6.7` component: `labkit_ImageEnhance_app` | `1.6.6 -> 1.6.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.6 -> 1.4.7` scope: Runtime project restore scope: App session reconstruction Context Runtime already resolved missing required source paths and offered interactive relinking before it rebuilt an App session. Several session factories then caught every decoder exception and returned an empty cache. A file that still existed but was damaged, unsupported, or exposed a programming error could therefore look like an unresolved source or an empty project. Decision and rationale Keep path resolution and cancellation in Runtime, and make reconstruction of an existing source strict. A session factory may accept an explicitly absent optional source, but it must not reinterpret an exception from an existing file. Runtime wraps reconstruction failures with `inputs.sources` IDs, roles, and filenames, records the exception in diagnostics, and preserves the prior state and presentation. Changes Removed broad exception recovery from Figure Studio, Focus Stack, Image Match, Image Enhance, FLIR Thermal, and Nerve Response Analysis session reconstruction. Made FLIR restore use strict thermal decoding while retaining skip-and-report behavior for interactive batch import. Added Runtime diagnostic context and a field-specific `ProjectSessionRestoreFailed` error without replacing the decoder cause. Added atomic corrupt-source, diagnostic-delivery, cross-App factory, and no-broad-catch regression coverage. User and data impact Missing required files still open the relink workflow, and cancelling it leaves the current project untouched. Existing corrupt or unsupported files now stop the load with the relevant project source identities and filenames instead of showing an empty preview. The prior document remains usable. App developers can leave ordinary decoder failures uncaught in `createSession.m`; Runtime owns the user-visible load failure and diagnostic record. Compatibility and migration No saved-project migration is required. Project envelopes and source-reference formats are unchanged. This change only corrects failure handling while reconstructing transient state. Validation Focused validation covers successful project round trips, missing-source relink, cancellation rollback, existing corrupt source rollback, diagnostic delivery, all affected session factories, and the repository guardrail that forbids broad catches in `createSession.m`. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md#session-actions-presentation-and-renderers) [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Focus Stack](../../../../apps/image-measurement/focus-stack/README.md) [Image Match](../../../../apps/image-measurement/image-match/README.md) [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) Known limitations and follow-up Automated hidden GUI tests verify state rollback and semantic error delivery; they do not replace manual review of native dialog wording or damaged third-party file variants."},{"title":"Protected image enhancement workflows","url":"history/records/2026/06/LK-20260629-protected-image-enhancement-workflows.html","kind":"history","section":"history","keywords":"LK-20260629-protected-image-enhancement-workflows 2026-06-29 16 feat compatible labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"Protected image enhancement workflows id: LK-20260629-protected-image-enhancement-workflows date: 2026-06-29 sequence: 16 type: feat compatibility: compatible component: `labkit_ImageEnhance_app` | `1.2.2 -> 1.3.0` component: `labkit_ImageMatch_app` | `1.2.1 -> 1.3.0` scope: historical project evolution Context The existing appearance adjustments could change a subject together with its background. Image Enhance and Image Match needed modes that protected subject detail while correcting bright backgrounds and tone. Decision and rationale Add subject-preserving enhancement and protected tone matching as explicit methods with their own parameters, descriptions, and tests. Keep these methods inside the image apps because they encode workflow-specific decisions rather than generic image primitives. Changes Image Enhance `1.2.2 -> 1.3.0` Image Match `1.2.1 -> 1.3.0` Added Subject-preserving enhance to Image Enhance. Added Protected tone to Image Match. Added calculation and GUI coverage for both methods. User and data impact Users could brighten or normalize a background with less change to the main subject. The new methods were optional; existing histories and other matching methods retained their previous behavior. Compatibility and migration Existing enhancement steps and Image Match inputs remained valid. The new protected methods were additional choices and did not rewrite saved source images. Validation Commit `1768dd57` added focused Image Enhance and Image Match unit tests and updated both GUI workflow tests. Evidence Main commit `1768dd57`. Known limitations and follow-up The protected methods reduce unwanted subject changes but cannot infer semantic foreground perfectly. Their result still requires visual review."},{"title":"Public API help is complete, discoverable, and behavior checked","url":"history/records/2026/07/LK-20260717-complete-public-api-help-contract.html","kind":"history","section":"history","keywords":"LK-20260717-complete-public-api-help-contract 2026-07-17 127 docs compatible labkit.biosignal labkit.dta labkit.rhs labkit.thermal labkit.image labkit.ui labkit_DICPreprocess_app labkit_DICPostprocess_app labkit_CSC_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Public MATLAB help Generated API reference Documentation contract tests","text":"Public API help is complete, discoverable, and behavior checked id: LK-20260717-complete-public-api-help-contract date: 2026-07-17 sequence: 127 type: docs compatibility: compatible component: `labkit.biosignal` | `1.0.2 -> 1.0.3` component: `labkit.dta` | `2.0.2 -> 2.0.3` component: `labkit.rhs` | `1.0.2 -> 1.0.3` component: `labkit.thermal` | `1.1.1 -> 1.1.2` component: `labkit.image` | `2.0.1 -> 2.0.2` component: `labkit.ui` | `7.5.0 -> 7.5.1` component: `labkit_DICPreprocess_app` | `1.5.7 -> 1.5.8` component: `labkit_DICPostprocess_app` | `1.4.6 -> 1.4.7` component: `labkit_CSC_app` | `1.4.7 -> 1.4.8` component: `labkit_BatchImageCrop_app` | `1.7.6 -> 1.7.7` component: `labkit_FLIRThermal_app` | `1.4.7 -> 1.4.8` component: `labkit_ImageMatch_app` | `1.6.7 -> 1.6.8` component: `labkit_VideoMarker_app` | `1.5.6 -> 1.5.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.7 -> 1.4.8` component: `labkit_ResponseReviewStats_app` | `1.4.6 -> 1.4.7` scope: Public MATLAB help scope: Generated API reference scope: Documentation contract tests Context LabKit's generated function pages came from MATLAB help, but the contract test covered a hand-maintained subset of packages and only checked broad section presence. A public function, implemented option, default, legal value, failure mode, or related API could therefore be omitted while the documentation suite still passed. Several App calculation pages exposed exactly those gaps. Decision and rationale Treat the callable public surface as discoverable source truth. The contract now scans every non-private function below `+labkit` plus each App API declared in the API catalog. It derives standard option reads from implementation code and requires those fields to have documented values and defaults, while required options remain explicitly distinguishable from optional ones. Every public function also states failure behavior and a `See also` relationship. This keeps a generated page useful for standalone MATLAB callers instead of merely repeating a short summary. Changes Completed help for the Contract, Biosignal, DTA, RHS, Thermal, Image, UI, and cataloged App calculation surfaces. Added exact option/default/legal-value checks, package-wide discovery, failure-behavior checks, and related-API checks to the documentation contract. Added a synthetic regression proving the guard detects an undocumented option, an omitted default, a vague value domain, missing failure behavior, and a missing related-API link. Made `labkit.image.resizeToFit` reject unsupported interpolation method names instead of silently treating them as bilinear. Linked owning library and App manuals to the detailed generated reference. User and data impact Readers can now determine valid calls, defaults, option domains, outputs, empty-result behavior, errors, and adjacent functions from the generated page for a public symbol. Developers adding a public function or a standard option read receive a focused contract failure when the corresponding help is incomplete. Compatibility and migration No saved project or data migration is required. Existing valid calls are unchanged. A call to `labkit.image.resizeToFit` with a method other than `\"bilinear\"` or `\"nearest\"` now raises `labkit:image:InvalidResizeMethod`; such values were never part of the documented contract. Validation The exact `PublicApiDocumentationContractTest` suite passes all eight tests, including execution of cataloged examples and the new synthetic defect regression. Image facade unit tests cover both supported resize methods and the unsupported-method error. Generated-site consistency and changed-scope validation are run after this record and its component manuals are rendered. Evidence [Public API index](../../../../reference/README.md) [LabKit App Framework](../../../../framework/README.md) [App catalog](../../../../apps/README.md) [Documentation maintenance](../../../../development/maintain-and-release/documentation.md) Known limitations and follow-up Implementation-derived option discovery intentionally recognizes the repository's standard option access patterns; unusual dynamic field access still requires review. Private helpers remain outside the public-page contract, and interactive `Typical Call:` sketches are not executed as file-independent examples. labkit.image.resizeToFit Resize an image to fit within maximum row/column limits."},{"title":"Public library help contracts and reference validation","url":"history/records/2026/07/LK-20260716-public-api-help-contracts.html","kind":"history","section":"history","keywords":"LK-20260716-public-api-help-contracts 2026-07-16 62 docs compatible labkit.biosignal labkit.dta labkit.image labkit.rhs labkit.thermal public MATLAB help generated API reference documentation contract tests","text":"Public library help contracts and reference validation id: LK-20260716-public-api-help-contracts date: 2026-07-16 sequence: 62 type: docs compatibility: compatible component: `labkit.biosignal` | `1.0.1 -> 1.0.2` component: `labkit.dta` | `2.0.1 -> 2.0.2` component: `labkit.image` | `2.0.0 -> 2.0.1` component: `labkit.rhs` | `1.0.1 -> 1.0.2` component: `labkit.thermal` | `1.1.0 -> 1.1.1` scope: public MATLAB help scope: generated API reference scope: documentation contract tests Context The public library reference identified functions and basic syntax but did not consistently explain option values, units, returned structures, failure behavior, or the distinction between executable examples and calls that need a user-supplied file. Readers still had to inspect implementation files to use several GUI-free APIs correctly. Decision and rationale Treat each public MATLAB help block as the source-level call contract and render that contract into one API page per function. Detailed library validation is kept separate from project-navigation validation so both remain focused and within the repository's effective-code-line budget. The patch versions record a compatible documentation-contract improvement. Function signatures, calculations, returned data, and error identifiers are unchanged. Changes Expanded public help with syntax, inputs, outputs, option names and legal values, defaults, units, errors, examples or typical calls, and related APIs. Distinguished self-contained `Example:` sections from interactive or file-dependent `Typical Call:` sections. Added generated-reference checks for thermal conversion and rendering, DTA schemas and pulse modes, and RHS lazy reads and waveform units. Corrected stale biosignal documentation links in package guidance and an implementation contract. Made label-ownership and package-boundary scans ignore prose-only substring collisions while continuing to reject literals and forbidden MATLAB tokens in executable code. User and data impact Users can discover the supported call contract through both `help` and the generated HTML reference without launching an app. No file format, saved project, calculation, or exported value changes. Compatibility and migration This is a patch-level compatible change. Existing calls require no migration. Validation Documentation consistency checks validate generated sections and executable examples. Project guardrails validate version/history ownership, literal ownership, package boundaries, and effective code-line budgets. Evidence Public help blocks and generated API pages are the maintained contract. Known limitations and follow-up Interactive calls and calls requiring laboratory files remain `Typical Call:` sketches until the repository provides synthetic, non-sensitive fixtures that make them executable examples."},{"title":"RHS Preview separates source roles from reference mechanics","url":"history/records/2026/07/LK-20260716-rhs-preview-structure.html","kind":"history","section":"history","keywords":"LK-20260716-rhs-preview-structure 2026-07-16 108 refactor compatible labkit_RHSPreview_app Neurophysiology App structure","text":"RHS Preview separates source roles from reference mechanics id: LK-20260716-rhs-preview-structure date: 2026-07-16 sequence: 108 type: refactor compatibility: compatible component: `labkit_RHSPreview_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context RHS Preview split one durable project across generic lifecycle files and used two additional lifecycle helpers to select or replace records by role. It also constructed variable filter-source IDs itself and read Runtime-owned reference fields in session, actions, and presentation code. Product metadata remained in separate requirement/version functions. Decision and rationale Keep the three App-owned roles because they express real workflow meaning: one preview recording, one optional protocol, and an ordered collection of filter recordings. Use Runtime V2 for portable path access, fixed-source upserts, and stable reconciliation of the variable collection. Keep only the small App-owned role-to-path selection policy under `sourceFiles`; do not introduce a new framework public role API for one App-specific collection shape. Changes Consolidated command identity, display metadata, version, requirements, project, session, layout, actions, presenter, renderer, and debug capability through the single definition. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved indexing, initial preview reads, filter discovery, and transient view reconstruction to root `createSession`. Added `sourceFiles.pathsForRole` to select App-owned source roles while delegating reference decoding to `labkit.ui.runtime.sourcePaths`. Replaced fixed-source construction with `upsertSource` and variable filter ID construction with `reconcileSources`, preserving filter order and stable identities across rediscovery. Removed the generic lifecycle package and separate requirement/version files. User and data impact RHS indexing, lazy waveform windows, channel selection, managed ROI/scroll interaction, protocol editing, multi-file filter labels, refresh/removal, exports, manifests, reset, and project reopening retain their behavior. New filter selections use Runtime-generated stable IDs; those IDs are internal project identities and do not change exported recording paths or labels. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still combine their separate recording, protocol, and filter source fields before validation. Existing version-2 source IDs and ordering are preserved when their resolved files remain selected. Runtime V2 owns migration iteration and later saves remain version 2. Validation Focused unit tests cover source migration, role-path ordering, summary/detail models, channel-role drafts, filter labels, and preview bounds. The hidden GUI workflow covers indexing and drawing a synthetic RHS file, channel tables, two-file filter discovery, protocol/filter export, manifests, project save, reset, reopen, and reconstruction of both preview and filter sources. Evidence [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not assess native file-dialog feel, physical channel-role correctness, or waveform interpretation. The remaining special startup path and repository guidance still require separate convergence review."},{"title":"Release validation gate and GUI CI hardening","url":"history/records/2026/07/LK-20260708-release-validation-gate-and-gui-ci-hardening.html","kind":"history","section":"history","keywords":"LK-20260708-release-validation-gate-and-gui-ci-hardening 2026-07-08 40 ci compatible historical project evolution","text":"Release validation gate and GUI CI hardening id: LK-20260708-release-validation-gate-and-gui-ci-hardening date: 2026-07-08 sequence: 40 type: ci compatibility: compatible scope: historical project evolution Context Release-candidate tags did not yet require one explicit summary gate over the headless, coverage, and GUI jobs. Some GUI assertions also depended on exact pixel ordering or timing that varied across CI display backends. Decision and rationale Require every release test job before publication, while testing semantic grid structure instead of platform-specific pixel rounding. Increase the shared GUI idle allowance so slower hosted displays can finish registered UI work. Changes Project validation workflow, no component version change. Release candidate tags now run the full MATLAB test workflow gate before publication: headless tests, coverage, GUI tests, and a release summary gate. GUI layout tests now assert structural grid contracts instead of platform-dependent flattened pixel ordering or width comparisons. Shared GUI test idle waiting allows slower CI display backends more time to finish registered UI work. User and data impact Published release candidates gained a single pass/fail validation signal. Application behavior and data formats did not change; the GUI suite became less sensitive to harmless platform layout differences. Compatibility and migration No known manual migration. Validation Commit `f359518` updated CI policy tests, declarative UI tests, launcher GUI tests, and shared wait behavior. Evidence Mainline commit `f359518`. Known limitations and follow-up The gate covers automated checks only. Interactive workflow feel and scientific visual review remain outside hosted CI."},{"title":"Release validation includes Base MATLAB compatibility","url":"history/records/2026/07/LK-20260717-release-validation-hardening.html","kind":"history","section":"history","keywords":"LK-20260717-release-validation-hardening 2026-07-17 129 ci compatible scheduled and release-candidate validation","text":"Release validation includes Base MATLAB compatibility id: LK-20260717-release-validation-hardening date: 2026-07-17 sequence: 129 type: ci compatibility: compatible scope: scheduled and release-candidate validation Context LabKit already exposed `buildtool baseMatlab`, but GitHub Actions did not run the broad MATLAB product-ownership analysis. A release candidate could therefore pass headless, coverage, and GUI jobs while the explicit Base MATLAB compatibility gate remained a local-only check. Decision and rationale Run the existing public `baseMatlab` task in scheduled and manually dispatched workflows, and require it before a validated release tag can be created. Ordinary pull requests and main pushes retain the smaller headless gate because the static call scan and representative fallback workflows already run there. Changes Added a dedicated Base MATLAB compatibility job with JUnit, active-test, log, and artifact reporting. Added that job to the release-test dependency gate. Corrected the CI contract test so it discovers actual `tasks:` values and checks them against the executable build-task catalog. Updated artifact upload jobs to the current Node.js 24 action generation. User and data impact No App behavior or saved data changes. A release tag is now blocked when MATLAB resolves production source to an undeclared MathWorks product. Compatibility and migration The public `buildtool baseMatlab` command is unchanged. GitHub-hosted runners meet the action runtime requirement; no self-hosted runner migration is introduced. Validation The CI policy contract verifies job scope, public-task ownership, diagnostics, timeouts, parent checkout depth, and release-gate dependencies. The Base MATLAB task remains covered by its own dependency guardrail. Evidence `.github/workflows/matlab-tests.yml` is the executable release gate; `tests/cases/contract/project/ci/CiValidationPolicyGuardrailTest.m` protects its routing and dependency structure. Known limitations and follow-up The product-ownership analysis depends on MATLAB's dependency report and cannot replace manual scientific or interactive App validation."},{"title":"Response Review Stats uses one project contract","url":"history/records/2026/07/LK-20260716-response-review-structure.html","kind":"history","section":"history","keywords":"LK-20260716-response-review-structure 2026-07-16 105 refactor compatible labkit_ResponseReviewStats_app Neurophysiology App structure","text":"Response Review Stats uses one project contract id: LK-20260716-response-review-structure date: 2026-07-16 sequence: 105 type: refactor compatibility: compatible component: `labkit_ResponseReviewStats_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context Response Review Stats kept project creation, validation, its one source-schema migration, and session reconstruction in four generic lifecycle files. Session and presentation code also read the Runtime's nested source path directly, and the reset action depended on the generic lifecycle package. Decision and rationale Use the standard compact Runtime V2 structure: one definition, one project contract, and one root session factory. Metric alignment, measurement, summaries, source parsing, exports, and presentation already had clear owners and remain unchanged. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderer, and debug capability in `definition.m`. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec.m`. Moved transient metric/alignment reconstruction to root `createSession.m` and reused it for the reset action. Replaced direct portable-reference reads with semantic `sourcePaths` lookup. Removed the generic lifecycle package and separate requirement/version files. User and data impact Analysis JSON and segment CSV loading, automatic window recomputation, aligned preview, metric summaries, reset, output-folder choice, CSV export, and result manifest behavior are unchanged. Developers now see the entire durable schema history through one entry instead of four lifecycle files. Compatibility and migration Durable schema version 2 is unchanged. Version-1 projects still move their singular source to the canonical source collection before validation and the next save. Validation Focused tests cover source migration, segment parsing, time-grid alignment, measurement, pair-group summaries, and default-grid selection. The hidden GUI workflow loads representative metrics, presents them, and exports the CSV and manifest through real Runtime callbacks. Evidence [Response Review And Stats](../../../../apps/neurophysiology/response-review-stats/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) Known limitations and follow-up Automated checks do not validate biological interpretation of a chosen baseline/noise window. The larger RHS Preview and Nerve Response Analysis Apps still require independent ownership review."},{"title":"Runtime V2 lifecycle ownership across the app fleet","url":"history/records/2026/07/LK-20260715-runtime-v2-app-migration.html","kind":"history","section":"history","keywords":"LK-20260715-runtime-v2-app-migration 2026-07-15 59 refactor breaking labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app `docs/` `docs/ui-runtime-redesign.md` runtime migration guidance","text":"Runtime V2 lifecycle ownership across the app fleet id: LK-20260715-runtime-v2-app-migration date: 2026-07-15 sequence: 59 type: refactor compatibility: breaking component: `labkit.ui` | `5.2.0 -> 6.0.0` component: `labkit_DICPostprocess_app` | `1.3.6 -> 1.4.0` component: `labkit_DICPreprocess_app` | `1.4.0 -> 1.5.0` component: `labkit_ChronoOverlay_app` | `1.3.6 -> 1.4.0` component: `labkit_CIC_app` | `1.3.8 -> 1.4.0` component: `labkit_CSC_app` | `1.3.10 -> 1.4.0` component: `labkit_EIS_app` | `1.3.4 -> 1.4.0` component: `labkit_VTResistance_app` | `1.3.8 -> 1.4.0` component: `labkit_GaitAnalysis_app` | `1.0.0 -> 1.1.0` component: `labkit_BatchImageCrop_app` | `1.6.8 -> 1.7.0` component: `labkit_CurvatureMeasurement_app` | `1.3.5 -> 1.4.0` component: `labkit_FLIRThermal_app` | `1.3.0 -> 1.4.0` component: `labkit_FocusStack_app` | `1.4.9 -> 1.5.0` component: `labkit_ImageEnhance_app` | `1.5.8 -> 1.6.0` component: `labkit_ImageMatch_app` | `1.5.8 -> 1.6.0` component: `labkit_VideoMarker_app` | `1.2.0 -> 1.3.0` component: `labkit_FigureStudio_app` | `0.1.5 -> 0.2.0` component: `labkit_NerveResponseAnalysis_app` | `1.3.5 -> 1.4.0` component: `labkit_ResponseReviewStats_app` | `1.3.5 -> 1.4.0` component: `labkit_RHSPreview_app` | `1.3.4 -> 1.4.0` component: `labkit_ECGPrint_app` | `1.3.5 -> 1.4.0` scope: `docs/` scope: `docs/ui-runtime-redesign.md` scope: runtime migration guidance Context App lifecycle, callback ordering, UI-derived state, project persistence, graphics resources, and result packaging were implemented repeatedly across apps. Migrating only the lifecycle names would have preserved the main author cost: each app would still need to understand raw controls, figure callbacks, and multiple competing state copies. Decision and rationale Make Runtime V2 the only write path and give the framework ownership of launch, startup, the FIFO event queue, atomic state commits, deterministic presentation, dialogs, interaction resources, project persistence, and result manifests. Apps retain explicit project/session/presentation/result contracts and all domain calculations, workflow decisions, plots, schemas, and exports. This deliberately optimizes ownership and learning cost rather than total app line count. Explicit app contracts can add code in a large scientific app, but they replace hidden closure state and callback plumbing with one inspectable model. The remaining 36 public UI functions have distinct reviewed contracts; the earlier 32-function planning target was not forced through vague APIs. Changes Migrated all twenty public apps to Runtime V2 definitions, standard launch, canonical project/session state, semantic events, pure presentation, and managed resources. Standardized current project writes and result manifests while retaining app-owned payload migrations and existing output files. Retired Runtime V1 writes and removed the public control-registry mutation, standalone editor/runtime, preview mutation, dialog, title, and dispatch compatibility surfaces. Kept supported V1 snapshots and named legacy app projects as read-only import formats; subsequent saves use the current project codec. Updated app requirements to `labkit.ui >=6 <7` and made UI 6 the breaking public facade boundary. Replaced Video Marker's optional `vision.PointTracker` branch with one deterministic app-owned multiscale patch matcher, retaining confidence, constant-velocity fallback, subpixel coordinates, and forward-cache behavior without Computer Vision Toolbox. Added traceable temporary MathWorks-product debt declarations: rapid app work must ship a base-MATLAB fallback plus deterministic and Toolbox-parity tests, and product analysis must continue to see the dependency until replacement. Re-audited already migrated apps, removed five dead or presenter-only helper files, and updated helper-quality classification from retired role packages to the current workflow-first lifecycle/state/result/UI boundaries. Corrected Runtime V2 interaction event construction so paired-anchor cell payloads remain one scalar semantic event. DIC Preprocess point matching now accepts alternating reference/moving points, enables alignment after two complete pairs, and completes the in-workbench rigid registration flow. Hardened the same scalar-envelope invariant for app-managed Runtime V2 resources and `runBusy` callback capture, including legitimate cell-form MATLAB callbacks and cell-valued resource payloads. Made project replacement invalidate the prior presentation cache before the fresh session is rendered, so opening an unchanged project still rebuilds app-owned preview graphics and other ephemeral visual resources. Corrected shared file-event index decoding for R2025a string results, so CIC and the other multi-file V2 apps can select and remove imported items. Stopped presentation commits from rerunning unchanged preview requests, and made DIC point-label updates preserve image handles and zoom viewports. Routed anchor-editor wheel input to the shared image zoom implementation without the retired wrapper's same-name recursion. Deferred CIC and VT batch DTA decoding until a file becomes visible or the batch is exported, so multi-file selection no longer blocks on every source. Let Gait Analysis read current Video Marker project/recovery envelopes and legacy Video/Image Marker autosaves directly as pose-coordinate inputs. Added interaction-mode subtitles for curve anchors, point marking, paired anchors, scale references, and fixed point slots; restored axes context menus after renderer resets; and promoted screenshot/project commands to top-level window entries. Moved Video Marker's Session controls to the top of the Video page, added an `Open MAT` project shortcut, and made New setup explicitly cancel, save, or discard before clearing the current project. Reorganized human documentation into getting-started, app workflow, public API, development, and focused-guide layers. Split app authoring from the user catalog, added the missing contract API reference, and made a project guardrail verify that every public `+labkit` function remains indexed under its owning facade. User and data impact App entrypoint names, scientific calculations, workflow decisions, plots, and existing export filenames remain stable. Current projects have a consistent save/reopen path, external sources use portable references, and result exports include a standard manifest. Runtime errors and modal interactions now pass through framework services, which also makes hidden validation deterministic. Compatibility and migration The migrated app versions require `labkit.ui >=6 <7`; they must not be copied into a UI 5 checkout. Supported V1 snapshots and documented legacy projects remain importable but are not written again. After import, save a new current project if continued editing or recovery is required. Validation Focused Runtime V2, project, interaction-hub, DIC point-matching, CIC, Figure Studio, app-boundary, and public-surface tests passed. The latest repair checkpoint additionally passed CIC/VT GUI workflows (2/2), Gait/app compatibility tests (21/21), Curvature and Video Marker GUI methods (6/6), focused framework menu/interaction tests (8/8), and three targeted interaction-hint methods (3/3). A real local legacy Video Marker autosave was read successfully without copying it into the repository. The documentation hierarchy checkpoint passed relative-link validation and 44/44 focused documentation, app-structure, build-task, and history guardrails. The Phase-6 `buildtool changedFast` checkpoint passed 15 framework GUI tests, 284 headless tests with one environment-assumption skip, and six representative GUI workflows. The final broad gate and any manual pointer or visual checks are reported with the merge handoff rather than embedded as mutable history. Video Marker replacement tests exercise integer and subpixel translation, flat-patch fallback, repeated-input determinism, prediction caching, and coordinate parity with `vision.PointTracker` when that product is installed. `buildtool baseMatlab` confirms the production source resolves only to MATLAB. Evidence `4454ca30` introduced the Runtime V2 kernel; the app migrations then ran from Chrono Overlay (`5fe76ee4`) through Figure Studio (`21936e8d`). `1ef46bfd` removed the retired UI runtime, and `8e109ff4` completed the framework contracts used by the migrated apps. The commits listed in the migration sequence above preserve the individual app checkpoints; this record explains their shared architectural result. Known limitations and follow-up Explicit domain contracts mean that large apps do not necessarily have fewer production lines. Further extraction is justified only by repeated mechanics, not by file-size targets. Pointer feel, drag ergonomics, and scientific visual judgment still require the documented manual app review before merge."},{"title":"Runtime diagnostics and workflow repair converge on one product path","url":"history/records/2026/07/LK-20260726-runtime-diagnostics-and-workflow-repair.html","kind":"history","section":"history","keywords":"LK-20260726-runtime-diagnostics-and-workflow-repair 2026-07-26 159 feat breaking labkit_launcher labkit.app labkit.dta labkit.image labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app Always-on session diagnostics and semantic App events Clean startup and explicit synthetic-input generation Launcher repair boundary and preserved product interaction Batch Crop native ROI interaction and preview resolution Explicit DTA units and EIS impedance display units","text":"Runtime diagnostics and workflow repair converge on one product path id: LK-20260726-runtime-diagnostics-and-workflow-repair date: 2026-07-26 sequence: 159 type: feat compatibility: breaking component: `labkit_launcher` | `1.6.0 -> 1.7.0` component: `labkit.app` | `1.2.4 -> 2.0.0` component: `labkit.dta` | `2.0.3 -> 3.0.0` component: `labkit.image` | `2.0.2 -> 2.0.3` component: `labkit_DICPostprocess_app` | `1.5.1 -> 1.6.0` component: `labkit_DICPreprocess_app` | `1.6.1 -> 1.7.0` component: `labkit_ChronoOverlay_app` | `1.5.1 -> 1.6.0` component: `labkit_CIC_app` | `1.5.1 -> 1.6.0` component: `labkit_CSC_app` | `1.5.1 -> 1.6.0` component: `labkit_EIS_app` | `1.5.2 -> 1.6.0` component: `labkit_VTResistance_app` | `1.5.1 -> 1.6.0` component: `labkit_GaitAnalysis_app` | `2.1.1 -> 2.2.0` component: `labkit_BatchImageCrop_app` | `1.8.1 -> 1.9.0` component: `labkit_CurvatureMeasurement_app` | `1.5.1 -> 1.6.0` component: `labkit_FLIRThermal_app` | `1.5.1 -> 1.6.0` component: `labkit_FocusStack_app` | `1.6.1 -> 1.7.0` component: `labkit_ImageEnhance_app` | `1.7.1 -> 1.8.0` component: `labkit_ImageMatch_app` | `1.7.1 -> 1.8.0` component: `labkit_VideoMarker_app` | `1.6.1 -> 1.7.0` component: `labkit_FigureStudio_app` | `0.6.5 -> 0.7.0` component: `labkit_NerveResponseAnalysis_app` | `1.5.1 -> 1.6.0` component: `labkit_ResponseReviewStats_app` | `1.5.1 -> 1.6.0` component: `labkit_RHSPreview_app` | `1.5.1 -> 1.6.0` component: `labkit_TTestWizard_app` | `1.2.0 -> 1.3.0` component: `labkit_ECGPrint_app` | `1.5.1 -> 1.6.0` scope: Always-on session diagnostics and semantic App events scope: Clean startup and explicit synthetic-input generation scope: Launcher repair boundary and preserved product interaction scope: Batch Crop native ROI interaction and preview resolution scope: Explicit DTA units and EIS impedance display units Context Debug launching had combined synthetic data, diagnostic persistence, and App startup into one mode, while ordinary incidents often left too little useful history. App-owned Log tabs and status/error adapters competed with a partial framework recorder. Batch Crop also exposed the practical consequence of testing construction without native interaction: its ROI could be visible yet fail to respond, and ordinary images were unnecessarily downsampled. The root Launcher had simultaneously accumulated installed-product behavior, repair implementation, and maintenance-tool logic in one rescue file. DTA inputs also carried ambiguous unit inference into downstream electrochemistry workflows. Decision and rationale Every normal App launch now owns one clean Runtime session with an always-on, privacy-bounded event journal. Runtime instruments framework operations; Apps add stable semantic events through `CallbackContext.log`. Trace capture, session viewing, and bundle export are runtime tools, while synthetic input generation is a separate explicit developer action that never loads its output or changes the open project. The public App SDK advances to 2.0 because launch diagnostic options and the old status, error, checkpoint, count, recorder, and debug-sample contracts were removed rather than kept as misleading compatibility aliases. Each migrated App requires `labkit.app >=2 <3`. The root Launcher remains a small self-contained repair entry. The installed launcher keeps the established catalog, status, App information, double-click launch, version browsing, and visual identity, while independent maintenance operations live under `tools/`. Changes Added canonical session events, durable bounded journals, active-session leases, degradation reporting, operation/result versus rollback semantics, a live session viewer, trace control, and diagnostic bundle export. Migrated all public Apps to semantic user/developer events, removed duplicate Log tabs, and exposed explicit anonymous synthetic-input generation from the ordinary Tools menu. Kept default launches data-free and action-free; hidden GUI tests now own their visibility fixture instead of relying on a test tag. Restored Batch Crop ROI body drag, center drag, and click-to-place behavior through managed native interaction, while preserving the viewport and using full-resolution previews unless an explicit high-resolution budget applies. Made DTA item units explicit, rejected unknown pulse modes, and added EIS impedance display choices from milliohms through megohms with kilohms as the default. Split Launcher rescue, installed composition, version management, cleanup, and documentation responsibilities without removing product capabilities. User and data impact Users can diagnose a problem after it occurs in an ordinary session, export a sanitized history, and deliberately generate anonymous reproduction inputs without restarting in a special mode. App status remains concise, while developer detail is available through Diagnostics. Existing scientific source files are not rewritten. Batch Crop keeps source resolution for ordinary images and provides responsive native ROI editing. EIS changes only display and export scaling selected by the user; calculations retain canonical ohms. Launcher repair remains explicit and never downloads or replaces files merely because it was opened. Compatibility and migration `labkit.app` 2.0 intentionally removes the former launch/debug and diagnostic adapter APIs. App source using those APIs must migrate to `BuildSyntheticSample` and semantic `CallbackContext.log` events. Saved App project schemas and their declared migrations are unchanged. `labkit.dta` 3.0 replaces ambiguous numeric unit inference with explicit unit metadata. Consumers must use declared canonical values. `labkit.image` 2.0.3 removes library-owned default megapixel policy; Apps choose preview budgets according to their own workflow. Validation Focused framework suites cover event schema, RNG preservation, operation outcomes, multi-process leases, privacy allowlists, projection degradation, viewer behavior, bundle export, Runtime instrumentation, and repository architecture. All 63 public hidden native App smoke cases pass. Every migrated App family has focused workflow coverage, including Batch Crop native ROI and preview-resolution contracts, EIS units, DTA parity, Launcher dispatch/repair, and synthetic-input state neutrality. Evidence The `debug-repair` branch contains purpose-based implementation and focused-test commits. Final evidence is the branch-review validation result, pull-request CI, merged-main CI, and developer-led manual checks recorded in the pull request. Known limitations and follow-up Hidden GUI tests do not establish pointer feel, dialog usability, visual quality, or the usefulness and privacy of exported incident bundles. Those remain explicit manual acceptance gates before merge."},{"title":"Runtime exposes GUI-free source-record creation","url":"history/records/2026/07/LK-20260716-gui-free-source-records.html","kind":"history","section":"history","keywords":"LK-20260716-gui-free-source-records 2026-07-16 100 feat compatible labkit.ui Runtime V2 sources App project migration","text":"Runtime exposes GUI-free source-record creation id: LK-20260716-gui-free-source-records date: 2026-07-16 sequence: 100 type: feat compatibility: compatible component: `labkit.ui` | `7.4.1 -> 7.4.2` scope: Runtime V2 sources scope: App project migration Context Runtime V2 owned the portable-reference schema and exposed a stable path accessor, but source-record creation was available only through callback-bound project services. GUI-free project migrations and legacy importers therefore could not create canonical sources without copying private reference fields. Batch Crop and Video Marker both contained such schema copies. Decision and rationale Expose the canonical source record, not the portable-reference implementation. `labkit.ui.runtime.sourceRecord` accepts stable App identity, semantic role, path, and required status, then delegates the nested representation to the Runtime. The injected callback service uses the same factory. Changes Added the GUI-free `labkit.ui.runtime.sourceRecord` factory with validation and complete MATLAB help. Routed `services.project.sourceRecord`, `upsertSource`, and `reconcileSources` through the same public contract. Extended source tests to cover factory output and invalid inputs. Updated the framework API map and portable-source guidance. User and data impact User project behavior is unchanged. App authors can now implement project migrations and legacy imports without opening a UI callback or constructing runtime-owned reference fields. Current file paths remain available only through `sourcePaths`. Compatibility and migration The addition is compatible within UI 7. Existing source records and injected services retain their shape and behavior. Apps should replace copied portable reference construction when their project/import code is migrated. Validation Focused Runtime source tests cover empty arrays, canonical creation, resolved path lookup, ordered/optional ID lookup, invalid references, and invalid factory arguments. Package-surface, version, documentation, and history guardrails protect the new public contract. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Framework](../../../../framework/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up Existing Apps that still copy or read portable-reference fields must migrate to `sourceRecord` and `sourcePaths` before the legacy source boundary is fully retired."},{"title":"Runtime owns portable source-reference details","url":"history/records/2026/07/LK-20260716-source-path-accessor.html","kind":"history","section":"history","keywords":"LK-20260716-source-path-accessor 2026-07-16 91 feat compatible labkit.ui Runtime source boundary App maintenance cost","text":"Runtime owns portable source-reference details id: LK-20260716-source-path-accessor date: 2026-07-16 sequence: 91 type: feat compatibility: compatible component: `labkit.ui` | `7.3.0 -> 7.4.0` scope: Runtime source boundary scope: App maintenance cost Context Runtime V2 owned source creation, rebasing, relinking, and validation, but an App still had to read `reference.originalPath` whenever it loaded or displayed a source. This exposed the nested persistence representation throughout session factories, actions, presenters, and workflow packages. Several Apps then added their own identical path loops or ID lookup functions. Decision and rationale Keep source `id`, `role`, and `required` as stable App-facing project data, but treat the nested portable reference as a runtime-owned value. Add one pure `labkit.ui.runtime.sourcePaths` accessor that works before UI construction and therefore serves session creation as well as actions and presentation. Passing UI-bound injected services into `CreateSession` was rejected: it would couple otherwise pure state reconstruction to figure, queue, dialog, and resource-service construction merely to read a path. Changes Added `sourcePaths(sources)` for paths in source order. Added `sourcePaths(sources,ids)` for strict lookup in requested ID order. Defined empty, malformed-reference, and unknown-ID behavior in executable public help and unit tests. Stopped documenting portable-reference fields as an App read contract. Added the accessor to the guarded public UI runtime surface. User and data impact Saved projects and file resolution behave unchanged. App code can now read, compare, load, and present source paths without knowing how a portable reference stores its current or relative location. Compatibility and migration The addition is compatible within UI 7. Existing direct field reads continue to work temporarily, but tracked Apps will move to the accessor during their family-by-family Runtime V2 consolidation. No project migration is required. Validation Unit tests cover source order, requested ID order, empty results, missing IDs, and invalid runtime references. Package-surface and documentation guardrails cover the new public contract. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Complete App Tutorial](../../../../development/build-apps/complete-app.md) Known limitations and follow-up Existing App-local path loops remain until their owning App commits are migrated and behavior-tested. Once all consumers use this accessor, a contract guard will prevent production Apps from reading portable-reference fields."},{"title":"Runtime uses one source contract for launch, migration, and layout","url":"history/records/2026/07/LK-20260717-retired-runtime-compatibility.html","kind":"history","section":"history","keywords":"LK-20260717-retired-runtime-compatibility 2026-07-17 126 refactor breaking labkit.ui Runtime launch contract Project payload migration Semantic layout validation","text":"Runtime uses one source contract for launch, migration, and layout id: LK-20260717-retired-runtime-compatibility date: 2026-07-17 sequence: 126 type: refactor compatibility: breaking component: `labkit.ui` | `7.4.7 -> 7.5.0` scope: Runtime launch contract scope: Project payload migration scope: Semantic layout validation Context UI Runtime had already introduced one-definition App launch, one version-aware `Project.Migrate` callback, and presenter-owned managed interactions. Transitional branches still accepted separate requirements and version factories, ordered `Project.Migrations` cells, and a `toolPanel` layout kind whose public constructor no longer existed. No public or accepted private App used those branches; framework tests and stale guidance were their only remaining consumers. Decision and rationale Keep one writable source contract for each capability. `launch` receives one definition factory and reads product metadata and requirements from that definition. Runtime calls the App's single `Migrate(project,fromVersion)` callback for each missing payload step. Layout sections contain supported semantic controls, while presenter interaction specs own axes tools. This removes source-level compatibility that could hide an incomplete App migration. Read-only support for current saved project envelopes, declared legacy MAT imports, and older supported payload versions remains intact. Changes Removed requirements/version factory dispatch from `runtime.launch` and added a direct diagnostic for the retired call form. Rejected `Project.Migrations` and removed its project-reader fallback. Removed `toolPanel` validation, sizing, building, diagnostics, and current manual guidance. Converted Runtime tests to complete single definitions and added regression checks for both retired contracts. User and data impact Current Apps and their project files behave the same. App developers have one place to review identity, version, requirements, layout, and optional runtime capabilities. Source code still using a three-factory launch or `Project.Migrations` now fails immediately with migration guidance instead of silently taking a legacy path. Compatibility and migration This is a source-contract cleanup within UI 7. Call `labkit.ui.runtime.launch(@app.definition,varargin{:})`, move product metadata and `Requirements` into `definition.m`, and replace migration callback cells with one callback that switches on `fromVersion`. No saved-data rewrite is required. Validation Focused Runtime GUI tests cover single-definition launch, rejection of the retired launch form, ordered project migration and atomic load, rejection of `Project.Migrations`, managed interactions, and startup progress. Contract tests cover App layout structure and public surface drift. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) [App development](../../../../development/build-apps/app-development.md) [Public API reference](../../../../reference/README.md) Known limitations and follow-up Historical records retain their original descriptions of the transitional APIs. They document how the architecture evolved and are not current usage guidance."},{"title":"Runtime-only P-code app packages","url":"history/records/2026/07/LK-20260708-runtime-only-p-code-app-packages.html","kind":"history","section":"history","keywords":"LK-20260708-runtime-only-p-code-app-packages 2026-07-08 41 refactor compatible historical project evolution","text":"Runtime-only P-code app packages id: LK-20260708-runtime-only-p-code-app-packages date: 2026-07-08 sequence: 41 type: refactor compatibility: compatible scope: historical project evolution Context Single-app P-code packages included a P-coded copy of the full launcher even though their purpose was to run one protected app. That exposed maintenance and packaging actions that were meaningful only in a source checkout. Decision and rationale Make P-code output a minimal runtime package with a direct app entry script. Keep the full launcher in source and source-package distributions, where its installation, profiling, and packaging tools are available. Changes Project deployment tooling, no component version change. `Package P-code` now creates a runtime-only single-app package instead of shipping a P-coded LabKit launcher and launcher maintenance tools. P-code package manifests and README instructions point users to the direct `run_` entry file. P-code packaging no longer requires `labkit_launcher.m` or `labkit_launcher.p` to exist in the package root being used as the runtime source. User and data impact Recipients of a P-code package start the protected app with `run_` and no longer see unrelated launcher maintenance actions. The protected app and its data formats are unchanged. Compatibility and migration Users of P-code packages should run `run_` from the unzipped package instead of `labkit_launcher`. Source packages still include and support the launcher. Validation Commit `75f63f1` expanded `PackageLabKitAppToolTest` for runtime-only package contents and updated the generated package instructions. Evidence Mainline commit `75f63f1`. Known limitations and follow-up P-code packages intentionally omit source-oriented launcher features. Users who need installation or packaging tools should use a source or source-package distribution."},{"title":"Runtime-owned project shape validation","url":"history/records/2026/07/LK-20260716-runtime-owned-project-shape-validation.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-owned-project-shape-validation 2026-07-16 117 refactor compatible labkit.ui labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app Runtime V2 project validation electrochemistry App maintenance cost","text":"Runtime-owned project shape validation id: LK-20260716-runtime-owned-project-shape-validation date: 2026-07-16 sequence: 117 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.4 -> 7.4.5` component: `labkit_ChronoOverlay_app` | `1.4.4 -> 1.4.5` component: `labkit_CIC_app` | `1.4.4 -> 1.4.5` component: `labkit_CSC_app` | `1.4.4 -> 1.4.5` component: `labkit_EIS_app` | `1.4.4 -> 1.4.5` component: `labkit_VTResistance_app` | `1.4.4 -> 1.4.5` scope: Runtime V2 project validation scope: electrochemistry App maintenance cost Context Runtime already validated canonical project buckets and portable source records after actions committed. During project restore, however, the App validator ran before that framework validation. Every persistent App consequently repeated the same bucket and source-record checks so malformed loaded projects would fail safely. Decision and rationale Validate framework-owned project structure before invoking App-owned project validation on both initial state and project restore. App validators should describe only their own schema and scientific rules. This gives canonical shape and source records one owner while retaining strict validation at the load boundary. Changes Added one private Runtime project-shape validator shared by restore and semantic state validation. Runtime now rejects a malformed loaded payload before its App validator can inspect domain fields. Removed 67 net lines of repeated canonical bucket and source-record checks from the five electrochemistry `projectSpec.m` files. Kept each App's legal choices, numeric bounds, result fields, and other domain-specific validation unchanged. User and data impact Valid projects and scientific results are unchanged. Malformed projects now receive the framework's consistent invalid-state error for canonical structure failures instead of an App-specific bucket or source error. No payload migration is required. Compatibility and migration The accepted project schema is unchanged. This is a validation-ownership change, not a data-format change, and current or older supported project versions continue through the same migration callbacks. Validation The Runtime project GUI test loads a payload missing a canonical bucket and verifies atomic rejection before App validation. Focused electrochemistry GUI tests exercise load, analysis, plotting, and export paths with the reduced App validators. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the framework and App validation boundary. [App Development](../../../../development/build-apps/app-development.md) tells App authors which validation remains in `projectSpec.m`. Each affected [Electrochemistry App](../../../../apps/electrochemistry/README.md) manual identifies its domain-owned validator responsibilities. Known limitations and follow-up Other App families still repeat some canonical structure checks. They will move to the same boundary only after their role and cross-field constraints are separated from the generic checks and covered by focused tests."},{"title":"Runtime-owned session defaults across App families","url":"history/records/2026/07/LK-20260716-runtime-owned-session-defaults.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-owned-session-defaults 2026-07-16 116 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Runtime V2 session construction App maintenance cost","text":"Runtime-owned session defaults across App families id: LK-20260716-runtime-owned-session-defaults date: 2026-07-16 sequence: 116 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.3 -> 7.4.4` component: `labkit_DICPostprocess_app` | `1.4.3 -> 1.4.4` component: `labkit_DICPreprocess_app` | `1.5.4 -> 1.5.5` component: `labkit_GaitAnalysis_app` | `2.0.4 -> 2.0.5` component: `labkit_BatchImageCrop_app` | `1.7.3 -> 1.7.4` component: `labkit_CurvatureMeasurement_app` | `1.4.3 -> 1.4.4` component: `labkit_FLIRThermal_app` | `1.4.3 -> 1.4.4` component: `labkit_FocusStack_app` | `1.5.2 -> 1.5.3` component: `labkit_ImageEnhance_app` | `1.6.3 -> 1.6.4` component: `labkit_ImageMatch_app` | `1.6.3 -> 1.6.4` component: `labkit_VideoMarker_app` | `1.5.2 -> 1.5.3` component: `labkit_FigureStudio_app` | `0.2.5 -> 0.2.6` component: `labkit_NerveResponseAnalysis_app` | `1.4.3 -> 1.4.4` component: `labkit_ResponseReviewStats_app` | `1.4.3 -> 1.4.4` component: `labkit_RHSPreview_app` | `1.4.3 -> 1.4.4` component: `labkit_ECGPrint_app` | `1.4.3 -> 1.4.4` scope: Runtime V2 session construction scope: App maintenance cost Context Runtime V2 already adds the canonical `selection`, `workflow`, `view`, and `cache` session buckets after an App factory returns. Its workflow service also creates `logLines` on the first log operation. Fifteen App factories still repeated empty buckets or initialized that framework-owned log, making their state declarations longer and obscuring which transient fields were genuinely App-specific. Decision and rationale Treat canonical session shape and workflow-log initialization as Runtime control-plane responsibilities. An App session factory now returns only the selection, workflow, view, and cache fields that express its own workflow. This preserves explicit App state while removing framework boilerplate. Changes Removed empty canonical session buckets from affected App factories. Removed every App-owned `logLines` initializer; the injected workflow service remains the only writer and lazy initializer. Removed Image Enhance's direct log-panel presenter binding; Runtime commits the workflow log to every declared log panel. Added `services.project.newState()` and moved full-project reset actions onto that Runtime-owned creation and normalization path. Added a fleet-wide structure guard that rejects empty canonical buckets and direct workflow-log initialization in `createSession.m`. Updated every affected App manual and patch version. User and data impact There is no workflow, scientific, persistence, or saved-data change. Runtime normalization produces the same complete in-memory session before validation, presentation, or the first callback. Existing project files remain compatible. Compatibility and migration The change is source-compatible within Runtime V2 and requires no project payload migration. Direct tests of an App's raw session factory should inspect only App-owned fields; tests that need canonical empty buckets should create state through Runtime. Validation The fleet structure guard covers every public App factory. Focused App unit and hidden-GUI suites verify restored state, initial presentation, workflow logs, and representative source-backed sessions across the affected families. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines canonical session normalization and workflow logging. [App Development](../../../../development/build-apps/app-development.md) requires `createSession.m` to rebuild only App-specific transient data. Each affected App manual describes its remaining session-owned fields. Known limitations and follow-up Most factories still perform necessary source decoding or reconstruction. They should not be removed merely to reduce file count. Further simplification requires evidence that a repeated reconstruction pattern is domain-neutral and belongs behind a stable Runtime service."},{"title":"Searchable MATLAB-generated documentation site and launcher workflow groups","url":"history/records/2026/07/LK-20260715-documentation-site.html","kind":"history","section":"history","keywords":"LK-20260715-documentation-site 2026-07-15 60 feat compatible labkit_launcher `docs/` `site/` `tools/docs/` `buildfile.m`","text":"Searchable MATLAB-generated documentation site and launcher workflow groups id: LK-20260715-documentation-site date: 2026-07-15 sequence: 60 type: feat compatibility: compatible component: `labkit_launcher` | `1.4.0 -> 1.5.0` scope: `docs/` scope: `site/` scope: `tools/docs/` scope: `buildfile.m` Context The documentation had become a set of large Markdown pages with no generated site, global search, code-entity pages, or enforced relationship between the public MATLAB surface and its reference material. Launcher actions were also presented as a mostly flat list, making routine app launch, installation, maintenance, profiling, and packaging appear equally related. Decision and rationale Keep authoring in tracked Markdown, structured navigation/catalog metadata, and MATLAB help blocks, then use one repository-owned MATLAB compiler to emit the tracked static site. Bind every non-private `labkit.*` API and each explicitly cataloged app-owned scientific API to its source declaration. Keep private helpers out of detailed reference pages. Group launcher actions by user intent and make documentation regeneration a first-level maintenance action. This follows the separation used by MATLAB and Qt documentation while keeping the build offline and dependency-free. Changes Added a MATLAB-only static documentation compiler, responsive site chrome, global client-side search, source links, related-API links, and deterministic generated-tree comparison. Added structured page and app-owned API catalogs. Generated reference now covers reusable library functions plus explicitly supported GUI-free app calculation entry points, while rejecting `private/` paths. Added public documentation build and consistency tasks and project guardrails for catalog coverage, search visibility, and generated output. Reorganized launcher buttons into Run Apps, Versions and Install, Development and Maintenance, and Package and Publish groups. Added Update Documentation to the launcher. It rebuilds `site/`, opens the generated home page, and reports narrative-page, API-page, file, and output location details. User and data impact Users can browse and search documentation without a MATLAB documentation server, move between related public functions, and regenerate the same site after source changes. Existing app data, projects, calculations, and exports are unchanged. Launcher maintenance actions are easier to distinguish from normal app launch and installation tasks. Compatibility and migration The feature is additive. Existing Markdown links remain valid in the source repository. `site/` is generated and must not be edited manually. Source-only or P-code app packages that omit documentation tools show the launcher action disabled with an explanatory tooltip. Validation The compiler generated 20 narrative pages, 133 public API pages, and 157 tracked files. Focused documentation/build guardrails passed after verifying a byte-for-byte rebuild. Hidden-GUI launcher layout, documentation generation, and missing-tool behavior tests passed 3/3. Evidence `tools/docs/renderLabKitDocs.m` and `docs/site.json` define the deterministic build and navigation contract. `docs/catalogs/api.json` defines the supported app-owned public API surface. Initial searchable site and launcher integration `bdeba572`. Per-app documentation publication `82734a47`. Later information-architecture rebuild `8b55e2b9`. Known limitations and follow-up The first generated site still contains transitional narrative pages that must be split into app-family, per-app, framework, and focused API guides. Cataloged scientific APIs need richer standalone examples and algorithm/unit sections before the documentation rewrite is complete."},{"title":"Session diagnostics expose actions and teardown failures","url":"history/records/2026/07/LK-20260726-actionable-session-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260726-actionable-session-diagnostics 2026-07-26 161 fix compatible labkit.app Session log filter and action terminology Runtime close and resource-cleanup diagnostics","text":"Session diagnostics expose actions and teardown failures id: LK-20260726-actionable-session-diagnostics date: 2026-07-26 sequence: 161 type: fix compatibility: compatible component: `labkit.app` | `2.0.0 -> 2.0.1` scope: Session log filter and action terminology scope: Runtime close and resource-cleanup diagnostics Context The live session viewer exposed internal `Audience` and `Root` terminology without explaining their meaning. Root-action choices were bare `op-*` identifiers, so users could not tell which click or workflow event a correlated call chain represented. The TRACE-disabled notice also implied that ordinary lifecycle evidence might be missing even though only TRACE-severity records were disabled. Runtime callback and startup failures were instrumented, but application resource cleanup ran before recorder close without its own operation boundary. A cleanup exception could therefore prevent orderly adapter and journal teardown while leaving no canonical lifecycle failure. Decision and rationale Present diagnostic concepts in user-task language while retaining stable machine identifiers underneath. Reader intent becomes **View**, root correlation becomes **Action**, and each action choice combines time, a safe semantic message or callback alias, and its `op-*` identifier. Treat Runtime close as an ordinary instrumented lifecycle operation. Attempt independent resource and native cleanup even after one fails, persist the terminal close result, close the journal, and only then return the original or combined cleanup exception. Changes Replaced the viewer's Audience wording with Useful, Everything, User workflow, and Developer details choices plus explanatory tooltips. Replaced Root wording with Action and generated readable correlation labels without changing the underlying root-action filter value. Clarified that TRACE-off sessions still capture DEBUG lifecycle boundaries and every warning or error. Added `runtime.close.started`, `runtime.close.completed`, and `runtime.close.failed` operation records. Made sibling cleanup continue after a resource or adapter failure and guaranteed recorder close before the cleanup exception is returned. User and data impact Users can select a recognizable action instead of guessing which `op-*` value belongs to an incident. Default views remain concise, while earlier DEBUG callback boundaries remain available without having enabled TRACE. Scientific state, project data, App callbacks, and saved schemas are unchanged. Diagnostic records remain sanitized and use the existing bounded journal. Compatibility and migration This is a compatible App SDK patch. Public App-author APIs and the event schema are unchanged. The projection adds a private display-label field, and existing root-action identifiers remain stable for filtering and exported bundles. Validation Focused projection specifications verify audience filtering, readable action labels, and accurate TRACE limitations. Hidden-GUI viewer specifications verify the visible terminology and separate label/value behavior. Runtime logging specifications inject an application-resource cleanup failure and require both the failed close operation and orderly session close in memory and the durable journal. Evidence The focused framework results, generated documentation check, changed-file gate, and exact main-push CI run are the delivery evidence. Known limitations and follow-up No logger can synthesize a failed terminal record for native events that never reach Runtime, exceptions deliberately swallowed without logging, or a process that hangs or terminates abruptly. The last retained boundary and journal lease state remain the evidence in those cases."},{"title":"Shared image facade","url":"history/records/2026/06/LK-20260630-shared-image-facade.html","kind":"history","section":"history","keywords":"LK-20260630-shared-image-facade 2026-06-30 24 feat compatible labkit.image labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"Shared image facade id: LK-20260630-shared-image-facade date: 2026-06-30 sequence: 24 type: feat compatibility: compatible component: `labkit.image` | `new -> 1.0.0` component: `labkit_BatchImageCrop_app` | `1.3.9 -> 1.4.0` component: `labkit_CurvatureMeasurement_app` | `1.2.3 -> 1.2.4` component: `labkit_FocusStack_app` | `1.2.5 -> 1.3.0` component: `labkit_ImageEnhance_app` | `1.3.5 -> 1.4.0` component: `labkit_ImageMatch_app` | `1.3.5 -> 1.4.0` scope: historical project evolution Context Batch Crop, Focus Stack, Image Enhance, and Image Match each carried similar code for supported extensions, path normalization, image reads, preview sizing, and basic enhancement. Small differences between those copies produced inconsistent file and display behavior. Decision and rationale Introduce `labkit.image` for GUI-free operations with neutral meaning, then move the image apps to that shared API. Keep crop geometry, focus fusion, matching, protected enhancement, and export schemas in their respective apps. Changes `labkit.image` `1.0.0` Batch Crop, Curvature, Focus Stack, Image Enhance, and Image Match advanced within their image-facade adoption lines. Added a GUI-free image facade for file input, display normalization, basic processing, and preview support. Adopted that facade across image-measurement apps. User and data impact Supported image selection, display-name handling, preview normalization, and common enhancement primitives became consistent across the migrated apps. Scientific app outputs and source files did not change format. Compatibility and migration Image apps kept their entry points and exports while adopting the shared operations. App code that called the retired local helpers needed to use the image facade or the owning app operation. Validation Commit `7023e87e` added a dedicated `LabKitImageFacadeTest` suite and updated app compatibility, package-boundary, public-surface, and focused image-app tests. Evidence Main commit `7023e87e`. Known limitations and follow-up The first facade release covered common MATLAB image formats and basic processing only. Thermal decoding and workflow-specific algorithms remained outside `labkit.image`."},{"title":"Single-click DIC rigid point matching","url":"history/records/2026/07/LK-20260713-dic-rigid-point-editor.html","kind":"history","section":"history","keywords":"LK-20260713-dic-rigid-point-editor 2026-07-13 50 feat compatible labkit.ui labkit_DICPreprocess_app Single-click DIC rigid point matching","text":"Single-click DIC rigid point matching id: LK-20260713-dic-rigid-point-editor date: 2026-07-13 sequence: 50 type: feat compatibility: compatible component: `labkit.ui` | `5.0.4 -> 5.1.0` component: `labkit_DICPreprocess_app` | `1.3.6 -> 1.4.0` scope: Single-click DIC rigid point matching Context DIC manual rigid matching had draggable points but maintained a separate pointer implementation and required a less consistent placement workflow than the ROI-center anchors used by related reconstruction workflows. Decision and rationale Extend the existing app-neutral anchor editor with a discrete point mode, then keep moving/fixed pair order, numbering, minimum pair count, and rigid-fit policy inside DIC. Changes Added `mode=\"points\"`: one blank click appends a point, dragging refines it, no connecting curve is drawn, and deletion remains under explicit controls. Migrated the DIC modal to two shared point-mode editors while preserving ordered moving/fixed pairs, labels, undo, cancel, and acceptance rules. Retained toolbox-free image display and rigid alignment behavior. User and data impact Feature placement now follows the same direct click-and-drag model as Imager ROI anchors. Point coordinates and the resulting rigid transform keep their existing N-by-2 pixel-coordinate contract. Compatibility and migration The default anchor-editor curve mode is unchanged. DIC exports and transform math are unchanged; this is an additive interaction improvement. Validation UI anchor-editor tests cover discrete point append and no-path behavior. The DIC GUI workflow covers toolbox-free modal cancellation and app launch wiring. Evidence Primary sources are `labkit.ui.interaction.anchorEditor` and `dic_preprocess.userInterface.selectRigidPointPairs`. Commit `392a073e` introduced their shared DIC rigid-point interaction. Known limitations and follow-up Automated hidden-GUI tests cannot judge pointing ergonomics; final interaction feel still requires a short manual placement-and-drag check."},{"title":"Single-definition App product contract","url":"history/records/2026/07/LK-20260716-single-definition-contract.html","kind":"history","section":"history","keywords":"LK-20260716-single-definition-contract 2026-07-16 77 feat compatible labkit.ui App authoring Runtime V2 launch metadata","text":"Single-definition App product contract id: LK-20260716-single-definition-contract date: 2026-07-16 sequence: 77 type: feat compatibility: compatible component: `labkit.ui` | `7.1.0 -> 7.2.0` scope: App authoring scope: Runtime V2 launch metadata Context Every App repeated product metadata and facade requirements in `version.m` and `requirements.m`, while `definition.m` separately described the product's runtime behavior. Thin entrypoints had to join all three factories, and the launcher and release guardrails treated the duplicate files as separate facts. Decision and rationale Make `definition.m` the one App product contract. Product identity, version, requirements, layout, and optional runtime capabilities now have one owner. The public entrypoint can delegate through `launch(@definition, varargin{:})`. Changes Added `Command`, optional `DisplayName`, `Family`, `AppVersion`, `Updated`, and `Requirements` fields to `labkit.ui.runtime.define`. Added single-definition launch and lightweight `version` and `requirements` request handling. Kept the previous three-factory launch form only as a temporary bridge while existing Apps move in reviewed family-sized changes. Extended the minimal real-GUI launch test to obtain all metadata from the same definition. User and data impact App users see no workflow or scientific change. App authors no longer need to keep three product declarations synchronized after their App is migrated. Compatibility and migration The addition is compatible within UI 7. Existing definitions continue to run during the migration window. Each App will move its exact metadata and facade ranges into `definition.m` before its redundant files are removed. The bridge is not a permanent supported architecture. Validation Focused runtime tests exercise normal launch plus `version` and `requirements` requests from one minimal definition. Documentation, facade-version, and App contract tests protect the metadata shape during the family migrations. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents the single definition and field meanings. [App Development](../../../../development/build-apps/app-development.md) shows the reduced static App file set. Known limitations and follow-up Existing Apps, launcher static discovery, release version guards, tutorials, and the App-builder skill still need family-by-family migration. The legacy launch bridge is removed only after those consumers no longer use it."},{"title":"Source-adjacent Video Marker autosave","url":"history/records/2026/07/LK-20260716-source-adjacent-video-autosave.html","kind":"history","section":"history","keywords":"LK-20260716-source-adjacent-video-autosave 2026-07-16 69 fix compatible labkit.ui labkit_VideoMarker_app Runtime V2 project recovery Video Marker autosave location","text":"Source-adjacent Video Marker autosave id: LK-20260716-source-adjacent-video-autosave date: 2026-07-16 sequence: 69 type: fix compatibility: compatible component: `labkit.ui` | `6.0.3 -> 6.0.4` component: `labkit_VideoMarker_app` | `1.4.0 -> 1.4.1` scope: Runtime V2 project recovery scope: Video Marker autosave location Context The first explicit Runtime V2 autosave action wrote only the framework's hidden recovery generation. Video Marker already had a product contract for a visible, stable autosave beside the source video, and users select those MAT files directly in downstream workflows such as Gait Analysis. Decision and rationale Keep generic debounced recovery framework-owned, but let an app provide a deterministic autosave destination when that location is part of its workflow. Video Marker derives the destination from the source video and never asks the user to choose it. Changes Added `services.project.saveAutosave(state,filepath)` without changing named project ownership or dirty status. Restored the visible `Video Marker Autosaves` folder and stable `.video_marker.autosave.mat` filename. Enabled **Save autosave** only after a video is open and report write failures through the standard diagnostics and app-parented alert services. User and data impact Clicking **Save autosave** immediately overwrites the source video's current autosave using the current `labkitProject` envelope. It opens no location dialog. The file remains easy to locate, copy, and load into Gait Analysis. Compatibility and migration The filename and source-adjacent folder match the earlier Video Marker autosave convention. Generic Runtime recovery remains available independently. Validation The focused Video Marker GUI test opens a synthetic video, verifies the button is enabled, writes the expected source-adjacent MAT, and confirms the runtime still has no named project path. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the exact no-dialog destination. [Runtime and lifecycle](../../../../framework/guides/runtime.md) distinguishes generic recovery from an app-targeted autosave. Known limitations and follow-up The source folder must be writable. A write failure leaves the current project untouched and is shown to the user."},{"title":"Startup responsiveness","url":"history/records/2026/07/LK-20260702-startup-responsiveness.html","kind":"history","section":"history","keywords":"LK-20260702-startup-responsiveness 2026-07-02 31 perf compatible labkit_launcher labkit.ui Startup responsiveness","text":"Startup responsiveness id: LK-20260702-startup-responsiveness date: 2026-07-02 sequence: 31 type: perf compatibility: compatible component: `labkit_launcher` | `1.2.2 -> 1.2.3` component: `labkit.ui` | `3.4.2 -> 3.4.4` scope: Startup responsiveness Context Profiling showed that a substantial part of perceived startup delay occurred after MATLAB had created the window but before it allowed the operating system to paint it. Launcher discovery and preview scroll setup also ran before their results were needed, leaving users with a blank or apparently frozen window. Decision and rationale Paint the launcher and app shell as soon as their basic structure exists, then defer discovery and preview navigation setup until the UI can remain responsive. Preserve the same controls and app catalog once initialization finishes. Changes `labkit_launcher` `1.2.2 -> 1.2.3` `labkit.ui` `3.4.2 -> 3.4.4` Painted launcher and app windows earlier. Deferred launcher app discovery and lazy preview scroll setup. User and data impact The window appeared and responded earlier, making initialization visible instead of presenting a long white-screen interval. App discovery and scroll navigation still completed automatically; the change moved their timing rather than removing those capabilities. Compatibility and migration App definitions and saved data were unchanged. The launcher and runtime altered only when discovery and interaction setup occurred during startup. Validation Launcher profiler assertions checked the earlier paint point, while UI axes layout tests covered deferred scroll navigation in `7d4ef11e`. Evidence Main commit `7d4ef11e`. Known limitations and follow-up Earlier painting improved perceived responsiveness, but it also made progress communication important. Later runtime work introduced explicit startup stages and lifecycle-managed readiness reporting."},{"title":"Structured documentation history","url":"history/records/2026/07/LK-20260715-component-owned-history.html","kind":"history","section":"history","keywords":"LK-20260715-component-owned-history 2026-07-15 61 docs breaking `docs/history/records/` `tools/docs/` `tools/release/` `CHANGELOG.md`","text":"Structured documentation history id: LK-20260715-component-owned-history date: 2026-07-15 sequence: 61 type: docs compatibility: breaking scope: `docs/history/records/` scope: `tools/docs/` scope: `tools/release/` scope: `CHANGELOG.md` Context Project evolution was stored in one 3,571-line root changelog. App, framework, library, validation, launcher, and release decisions were mixed together, while the new documentation site already had component pages capable of presenting the relevant history in context. Decision and rationale Store each change explanation once as an independent Markdown page in the chronological `docs/history/records///` tree. Let the component metadata, rather than the file's physical folder, associate a record with each affected app, framework, or library. The documentation compiler then generates both component-specific history lists and a searchable project-wide index. Changes Split the existing recorded history into one Markdown file per Change ID and placed every public record under one chronological source tree. Removed the root aggregate changelog, duplicated current-version lookup, and the dedicated release-history parser. Replaced the launcher's custom version-history window with a direct link to the selected App's generated Documentation and History page. Routed version governance through owned component docs, distributed history, generated-site consistency, and source version metadata. User and data impact Users see instructions, APIs, scientific context, and relevant changes from one app or library page. Source readers can find every public history record under `docs/history/records/`. Search still reaches the complete historical record. No project files, calculations, or experimental data formats change. Compatibility and migration Links that targeted the old root changelog must move to the generated Project History page or a component page. The old parser and launcher history mode are intentionally removed rather than maintained as compatibility aliases. Validation The documentation compiler validates unique history identities, required metadata, source pages, generated links, search indexing, and deterministic output. Project release guardrails validate the distributed record set and component aggregation. Evidence `docs/history/README.md` defines the human entry point. `tools/docs/private/loadLabKitDocumentation.m` discovers history pages. `DocumentationHistoryGuardrailTest` protects the distributed model. Known limitations and follow-up Some older records retain the wording used when their changes were originally made. Component metadata, rather than folder placement, determines where each record is shown."},{"title":"Structured evolution records","url":"history/records/2026/07/LK-20260713-structured-evolution-records.html","kind":"history","section":"history","keywords":"LK-20260713-structured-evolution-records 2026-07-13 52 docs compatible repository changelog and release governance","text":"Structured evolution records id: LK-20260713-structured-evolution-records date: 2026-07-13 sequence: 52 type: docs compatibility: compatible scope: repository changelog and release governance Context The previous `Unreleased` and `Pending` model mixed delivery state with historical meaning and required records to be moved after merge. Its prose was useful to humans but had no reliable machine contract. Decision and rationale Use stable, status-free records with a small parseable metadata header and required explanatory sections. Git branches, PRs, tags, and evidence identify delivery state without duplicating it in the changelog. Changes Added schema-v1 metadata for Change ID, date, type, compatibility, component introductions and version transitions, and unversioned repository scopes. Added a base-MATLAB parser and release guardrail for schema integrity. Normalized every historical entry into the same schema-v1 record contract. User and data impact Users and maintainers can search one durable ID and obtain the reason, impact, compatibility, validation, and evidence for a change without reconstructing it from commit messages. No scientific data or runtime behavior changes. Compatibility and migration Existing links to `CHANGELOG.md` remain valid. Automation that searched the old `Unreleased` or `Version History` headings must use the parser and stable Change IDs. Validation `ChangelogGuardrailTest` parses every schema-v1 record and checks current version lookup coverage and release-policy documentation. Evidence The parser lives at `tools/release/parseLabKitChangelog.m`; the normalized historical baseline was checked against the dated mainline log and release tags. Known limitations and follow-up Some older records do not name exact test commands because that evidence was not recorded at the time; they state that limitation instead of inventing it."},{"title":"T-Test Wizard adds table selection and first-versus-each comparisons","url":"history/records/2026/07/LK-20260718-ttest-wizard-and-table-workspace.html","kind":"history","section":"history","keywords":"LK-20260718-ttest-wizard-and-table-workspace 2026-07-19 135 feat compatible labkit_TTestWizard_app labkit.ui Statistics CSV exchange App Framework presentation","text":"T-Test Wizard adds table selection and first-versus-each comparisons id: LK-20260718-ttest-wizard-and-table-workspace date: 2026-07-19 sequence: 135 type: feat compatibility: compatible component: `labkit_TTestWizard_app` | `new -> 1.0.0` component: `labkit.ui` | `7.5.1 -> 7.6.0` scope: Statistics scope: CSV exchange scope: App Framework presentation Context Laboratory comparison data appeared as manually maintained workbook blocks, same-shape files, and rectangular App exports containing identifiers beside numeric metrics. Schema-first mapping made unfamiliar sources harder to use. The required workflow also included several treatments compared with one control, while the first implementation boundary held only A and B. Runtime presentation could update table values but not their spreadsheet headers or collapse unused workspace panels. An App therefore could not show an unfamiliar table faithfully while giving each wizard step the full workspace. Decision and rationale Display CSV, TSV, XLSX, and XLS sources as visible cell grids. Let the user append each numeric selection as an ordered durable group. The first group is the explicit reference; one chosen t-test specification is then applied to every later group versus that first group. Keep Data, Test/Plot, and Export controls short. Put Data and Plot in user-selectable right-side workspace pages, merge result review with the test, and keep completed comparisons as immutable snapshots. Plot controls consume those snapshots and cannot recalculate the result family. Keep CSV as a low-constraint rectangular interchange boundary. Known producers may add conveniences later, but visible cell selection remains the fallback. Changes Added the Statistics family and T-Test Wizard 1.0.0. Added direct visible-cell selection from CSV, TSV, XLSX, and XLS, including worksheet selection, numeric-text acceptance, mixed-cell feedback, and groups captured from different sources. Added an ordered `Group`/`Value` observation editor, manual paste/editing, existing-group capture choices, first-appearance reference ordering, and batch reassignment or deletion of selected observation rows. Added layered spreadsheet-header suggestions for new group names and coalesced completed-selection delivery so range dragging does not repeatedly refresh the App. Added first-versus-each Welch, pooled equal-variance, and paired t-tests with two-sided and directional alternatives. Added Base MATLAB Student-t probabilities and intervals, stable per-result failure statuses, and immutable result snapshots. Added reference-style bar and box plots with pastel groups, explicit numeric axes, standard x-axis tick labels, boxed grid-free axes, and stacked `NS/*/**/***/****` brackets. Added multi-column group CSV and multi-row result CSV exports. Added project schema version 2 migration from durable A/B vectors to two ordered groups. Extended Runtime presentation so result tables can update headers and hidden rows collapse. Coalesced rapid table-selection events before App presentation and replaced stale standalone file-context titles instead of appending duplicate suffixes. Preserved tick labels, limits, and graphics stacking order when plots are popped into standalone figures, keeping error bars above their bars, and added a copied-figure control for switching X-axis group labels between angled and horizontal presentation. Responsive popout margins keep enlarged ticks, axis labels, and titles inside the standalone window. Extended `labkit.ui.layout.workspace` to accept two or more tab pages without adding a parallel workspace-specific public constructor. User and data impact Users can select any number of groups from an unfamiliar readable table without defining a schema or removing unrelated text. Running creates one comparison row for every group after the first. Paired testing follows displayed row order for each comparison with the first group. The App exports ordinary CSVs instead of requiring another program to decode a project MAT file. Imported sources are not modified. Compatibility and migration The App and Statistics family are additive. Version-1 T-Test Wizard projects migrate A to group 1 and B to group 2, preserving labels, values, and source metadata. An earlier completed A/B result remains a one-row result family. `labkit.ui` remains compatible with `>=7 <8`. Existing presentation structs that omit dynamic table headers and visibility behave as before. No multiple-comparison correction is silently introduced. This preserves the meaning of each requested t-test and makes the limitation explicit. Validation Unit tests cover mixed-cell extraction, source preservation, Welch/pooled/ paired reference values, first-versus-each ordering, result-family identity, and multi-group CSV round trips. A focused framework GUI check proves rapid selections commit only the final range and repeated file-context presentation does not duplicate plot titles. A hidden App GUI workflow covers three captured ranges, batch group changes, two comparisons with the first group, equal-height source/editor tables, native Data/Plot pages, compact stale-plot messaging, bar/box selection, boxed grid-free rendering, and both exports. Framework GUI tests cover native workspace pages, invalid page composition, dynamic table headers, missing-cell display, full-width previews, collapsed hidden rows, and invalid presentation diagnostics. Representative private workbook structures informed the visual and workflow requirements without copying paths, filenames, identifiers, or values into the repository. Evidence [T-Test Wizard](../../../../apps/statistics/ttest-wizard/README.md) [Statistics Apps](../../../../apps/statistics/README.md) [Simple Scientific CSV Exchange](../../../../development/data-and-designs/scientific-csv-interchange.md) [T-Test Wizard design](../../../../development/data-and-designs/group-comparison-app-design.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Version 1.0.0 does not infer experimental units, independence, pairing, exclusions, or groups from filenames. It does not apply multiple-comparison correction, ANOVA, nonparametric tests, regression, or mixed modeling. Automatic numeric-column suggestions remain a possible convenience only after real use demonstrates a stable need."},{"title":"T-Test plots gain truthful limits and an explicit view reset","url":"history/records/2026/07/LK-20260720-ttest-plot-viewport-and-density.html","kind":"history","section":"history","keywords":"LK-20260720-ttest-plot-viewport-and-density 2026-07-20 143 feat compatible labkit_TTestWizard_app Statistics Apps App Framework","text":"T-Test plots gain truthful limits and an explicit view reset id: LK-20260720-ttest-plot-viewport-and-density date: 2026-07-20 sequence: 143 type: feat compatibility: compatible component: `labkit_TTestWizard_app` | `1.1.1 -> 1.2.0` scope: Statistics Apps scope: App Framework Context T-Test Wizard used the absolute data magnitude as a minimum padding span, so box plots with a narrow nonzero range could receive excessive empty space. Bar-chart zoom could move the zero baseline away from the y-axis edge, and the App had no visible action for returning to the fitted view. Two short status summaries also occupied detail-panel height. Decision and rationale Fit limits from the graphics that carry statistical meaning: distribution or bar extents, optional observations, standard-deviation error bars, and significance brackets. Keep zero at the appropriate edge for one-sided bar data because truncated bar baselines distort magnitude; mixed-sign data must instead show zero inside the axis. Keep box plots distribution-fitted. Expose an App-owned reset action backed by the framework's general transient plot-view revision. Use the framework's line-count hint for short summaries rather than adding T-Test-specific pixel sizes. Changes Corrected the fitted span and padding used by bar, box, and annotation geometry. Kept zero at the lower or upper y-limit for positive or negative bar plots across programmatic, toolbar, and wheel-driven limit changes. Added **Reset plot view** without recalculating tests or changing project data. Refined box fill, outlines, whiskers, observation overlays, bar opacity, error-bar caps, and outward ticks. Reduced **What will run** and **Result family** to two-line summary panels with the framework's summary reading size. User and data impact Project schema 2, completed comparison values, CSV outputs, formulas, and test choices are unchanged. T-Test Wizard 1.2.0 requires `labkit.app` 1.2 or later for compact summaries and one-shot plot viewport revisions. Existing projects open without migration. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation The T-Test hidden-GUI workflow checks fitted limits, zero-edge persistence, box-plot independence, compact summary sizing, and explicit view reset. Framework contract tests cover plot viewport preservation and revision resets. Documentation rendering and history consistency cover the versioned change. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led visual review should compare box, bar, error-bar, and bracket appearance at typical desktop sizes and with real scientific label lengths."},{"title":"Test catalog cutover replaces the legacy test tree","url":"history/records/2026/07/LK-20260723-test-catalog-cutover.html","kind":"history","section":"history","keywords":"LK-20260723-test-catalog-cutover 2026-07-23 158 test breaking Test catalog Test authoring Cross-platform validation","text":"Test catalog cutover replaces the legacy test tree id: LK-20260723-test-catalog-cutover date: 2026-07-23 sequence: 158 type: test compatibility: breaking scope: Test catalog scope: Test authoring scope: Cross-platform validation Context The former test tree mixed stage folders, route wrappers, shared helpers, and executor policy. Authors had to infer where a specification belonged and which selector would exercise it. On Windows, an additional case-sensitive path comparison could reject valid catalog specifications before any behavior ran. Decision and rationale Make production ownership, behavioral contract, and execution environment the only selection model. Keep framework mechanics private in `labkittest`; keep only genuinely cross-owner synthetic data in a named fixture package. A test path comparison must be case-insensitive because Windows resolves an existing path with filesystem casing that need not match the configured root spelling. Changes Replaced the old runner and `tests/cases` tree with exact catalog specifications under `tests/specs`. Added semantic authoring and execution entry points: `explain`, `createSpec`, and `run`. Removed unreferenced legacy GUI, App-contract, repository, and assertion helpers from the former `tests/shared` bucket. Moved the remaining cross-owner synthetic DTA, thermal, RHS, chrono, and gait inputs into `tests/+testfixtures` and qualified every consumer. Normalized catalog filesystem paths before ownership comparison and added a Windows regression for differently cased specification-root input. Updated the current testing manual, scoped test rules, migration ledger, and generated documentation site. User and data impact No App behavior, saved project, scientific calculation, export schema, or laboratory data changes. Test authors gain a path-free insertion workflow and machine-readable run artifacts. All fixtures remain synthetic. Compatibility and migration This intentionally removes the unsupported legacy test folders, runner, and selector model. Contributors must use `tests/specs`, `labkittest` semantic selectors, and `tests/+testfixtures` where a synthetic input is genuinely cross-owner. No data migration is required. Validation Catalog/build regression: 23 tests passed. DTA, RHS, and thermal synthetic-fixture regressions: 8 tests passed. Complete headless catalog: 202 tests passed. Documentation render and `docsCheck`: 372 generated files current. PR CI was inspected; Windows separator handling and the shell-sensitive clean-checkpoint revision syntax are covered by catalog regressions and require the updated commit to run remotely. POSIX CI also exposed an interactive Git pager inherited by MATLAB; planner queries now use `git --no-pager` so the progress watchdog can never stall on a pager. Its case-sensitive filesystem also verified that semantic owner matching must resolve the physical specification-directory spelling exactly. Evidence The catalog writes exact identities and semantic reasons into each run's `plan.json`; the final headless `summary.json` records 202 passed identities with no failures or incomplete tests. The PR workflow independently executes headless and hidden-GUI profiles on Linux, macOS, and Windows. Known limitations and follow-up Hidden-GUI checks remain structural and do not replace manual validation of native dialogs, visual quality, pointer behavior, real-data suitability, or scientific interpretation. The PR must receive green cross-platform CI before merge."},{"title":"Thermal facade and FLIR app","url":"history/records/2026/07/LK-20260701-thermal-facade-and-flir-app.html","kind":"history","section":"history","keywords":"LK-20260701-thermal-facade-and-flir-app 2026-07-01 26 feat compatible labkit.thermal labkit.ui labkit_FLIRThermal_app Thermal facade and FLIR app","text":"Thermal facade and FLIR app id: LK-20260701-thermal-facade-and-flir-app date: 2026-07-01 sequence: 26 type: feat compatibility: compatible component: `labkit.thermal` | `new -> 1.0.0` component: `labkit.ui` | `3.2.9 -> 3.2.10` component: `labkit_FLIRThermal_app` | `new -> 1.0.0` scope: Thermal facade and FLIR app Context FLIR radiometric JPEGs contain both a visible preview and embedded thermal data plus camera metadata. Treating them as ordinary images loses the temperature meaning, while placing all parsing and conversion code inside one GUI would make the scientific operations unusable from MATLAB scripts. Decision and rationale Create a thermal library for reading radiometric files, converting raw sensor values to temperature, and rendering temperature images. Build the FLIR Thermal Postprocess app on those public operations, keeping file lists, range controls, measurement state, and export flow in the app. Changes `labkit.thermal` `1.0.0` `labkit.ui` `3.2.9 -> 3.2.10` `labkit_FLIRThermal_app` `1.0.0` Added the thermal facade. Added the FLIR Thermal Postprocess app. User and data impact Users gained a FLIR app with file browsing, temperature display, range control, summary values, and export-oriented views. The same thermal functions could be called without opening the GUI, so scripts could inspect temperature matrices and metadata directly. Compatibility and migration The thermal facade and FLIR app were additive. Existing image apps and files were unchanged; scripts could begin using `labkit.thermal` without a project- file migration. Validation The change added thermal-facade and app-operation unit suites, a FLIR layout suite, package-surface checks, and a synthetic radiometric JPEG writer. No laboratory image was committed as a fixture. Evidence Main commit `977c9457`. Known limitations and follow-up Camera-specific metadata support depended on the formats represented by the synthetic and available local samples. Later changes refined display ranges, preview cost, and point and ROI measurement tools."},{"title":"Tool-panel hosts and image app fixes","url":"history/records/2026/06/LK-20260629-tool-panel-hosts-and-image-app-fixes.html","kind":"history","section":"history","keywords":"LK-20260629-tool-panel-hosts-and-image-app-fixes 2026-06-29 18 fix compatible labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_ImageEnhance_app labkit_ImageMatch_app Tool-panel hosts and image app fixes","text":"Tool-panel hosts and image app fixes id: LK-20260629-tool-panel-hosts-and-image-app-fixes date: 2026-06-29 sequence: 18 type: fix compatibility: compatible component: `labkit.ui` | `3.2.0 -> 3.2.2` component: `labkit.ui` | `3.2.2 -> 3.2.3` component: `labkit_BatchImageCrop_app` | `1.3.2 -> 1.3.3` component: `labkit_CurvatureMeasurement_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.3.0 -> 1.3.1` component: `labkit_ImageMatch_app` | `1.3.0 -> 1.3.1` scope: Tool-panel hosts and image app fixes Context Reusable interaction tools were being attached to generic empty sections, while several image apps had separate problems with file-entry identity, reported output size, and White ROI updates. Decision and rationale Add a semantic `toolPanel` host for composed tools and fix the image-app behaviors at their actual data owners. Normalize file entries and regenerate ids deterministically so presentation updates do not change source identity. Changes `labkit.ui` `3.2.0 -> 3.2.3` Batch Crop, Curvature, Image Enhance, and Image Match patch bumped where layouts or image-app behavior changed. Hardened file-panel entry normalization and deterministic ID regeneration. Fixed output-size reporting and White ROI responsiveness. Added semantic `toolPanel` hosts. User and data impact White ROI controls responded to the selected image, Batch Crop reported output dimensions consistently, and reusable tools occupied a stable named layout area. Existing source and result formats were unchanged. Compatibility and migration Existing file entries and image results remained readable. Deterministic ids were regenerated in memory, and the new layout host did not alter saved data. Validation The listed commits updated file-panel, tool-host, and focused image-app tests. Exact historical commands were not recorded. Evidence Main commits `f2189aef`, `77084fbe`, and `871739cd`. Known limitations and follow-up The first `toolPanel` API was later replaced by the current semantic layout and managed-interaction model."},{"title":"Traceable FLIR temperature calibration","url":"history/records/2026/07/LK-20260713-flir-calibration-provenance.html","kind":"history","section":"history","keywords":"LK-20260713-flir-calibration-provenance 2026-07-13 49 feat compatible labkit.thermal labkit_FLIRThermal_app Traceable FLIR temperature calibration","text":"Traceable FLIR temperature calibration id: LK-20260713-flir-calibration-provenance date: 2026-07-13 sequence: 49 type: feat compatibility: compatible component: `labkit.thermal` | `1.0.0 -> 1.1.0` component: `labkit_FLIRThermal_app` | `1.2.8 -> 1.3.0` scope: Traceable FLIR temperature calibration Context A successful Celsius conversion did not reveal whether emissivity and environmental values came from the file or from model defaults, which could make an absolute temperature appear more certain than its metadata justified. Decision and rationale Preserve conversion provenance with the thermal record and show fallback use in the app, rather than silently treating every parameter as measured. Changes Added optional conversion diagnostics with correction mode, defaulted fields, parameter sources, and fallback status. Stored diagnostics in thermal record metadata and surfaced warnings in FLIR file status and details. Documented embedded calibration requirements and environmental fallbacks. User and data impact Users can distinguish radiometric values based entirely on embedded metadata from values affected by fallback assumptions before interpreting temperatures. Compatibility and migration Existing one-output conversion calls remain valid. The diagnostics output and record metadata are additive; app workflows need no migration. Validation Thermal facade and FLIR app tests cover metadata sources, fallback reporting, and one-output compatibility. Evidence The model and provenance contract are documented in `docs/api/thermal.md`; branch checkpoint `7391e293` carries the implementation before the final squash merge. Known limitations and follow-up Diagnostics describe source and fallback use; they do not estimate a physical uncertainty interval for a particular camera, surface, or environment."},{"title":"Traceable and base-MATLAB CI validation","url":"history/records/2026/07/LK-20260713-traceable-base-matlab-ci.html","kind":"history","section":"history","keywords":"LK-20260713-traceable-base-matlab-ci 2026-07-13 51 ci compatible GitHub Actions and MATLAB validation routing","text":"Traceable and base-MATLAB CI validation id: LK-20260713-traceable-base-matlab-ci date: 2026-07-13 sequence: 51 type: ci compatibility: compatible scope: GitHub Actions and MATLAB validation routing Context CI failures and stalls could end without enough information to identify the active test, and ordinary success on a toolbox-rich development machine did not prove that base-MATLAB users could run representative workflows. Decision and rationale Make the existing official runner publish progress and active-test state, and add a distinct compatibility gate that combines static calls, product ownership, and toolbox-shadowed behavior. Changes Added per-test progress, heartbeat, active-test, timeout-summary, and artifact publication behavior to CI. Added `buildtool baseMatlab` and representative toolbox-shadow workflows. Improved changed-file routing to target direct consumers while retaining explicit owners such as the launcher GUI suite. Corrected result aggregation so assumption-filtered tests remain visible as skipped without being misreported as shard failures. User and data impact Base-MATLAB compatibility is now an explicit supported path, and failed or stalled CI runs provide enough state to identify the last active test. Runtime scientific outputs are not changed by this record. Compatibility and migration Existing build tasks remain available. Maintainers can add `baseMatlab` to local validation without installing or uninstalling toolboxes. Validation CI policy, build-task framework, changed-routing, toolbox dependency, and representative workflow tests guard the new behavior. Evidence The command matrix is in `docs/development/maintain-and-release/testing.md`; CI uploads official runner logs and active-test artifacts. Commit `28ff8edb` made MATLAB failures traceable, `37bd7fd5` enforced Base MATLAB compatibility, and `2c9b8792` preserved launcher validation ownership. Known limitations and follow-up Shadow tests cover known dependency risks and cannot simulate every licensed toolbox combination; MATLAB product-ownership analysis remains the broad check."},{"title":"UI 5 facade redesign, app migration, and plot refresh","url":"history/records/2026/07/LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh.html","kind":"history","section":"history","keywords":"LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh 2026-07-06 38 refactor breaking labkit_launcher labkit.ui labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app UI 5 facade redesign, app migration, and plot refresh","text":"UI 5 facade redesign, app migration, and plot refresh id: LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh date: 2026-07-06 sequence: 38 type: refactor compatibility: breaking component: `labkit_launcher` | `1.2.3 -> 1.2.4` component: `labkit.ui` | `4.2.0 -> 4.2.1` component: `labkit.ui` | `4.2.1 -> 5.0.0` component: `labkit_DICPostprocess_app` | `1.3.3 -> 1.3.4` component: `labkit_DICPreprocess_app` | `1.3.3 -> 1.3.4` component: `labkit_ChronoOverlay_app` | `1.3.3 -> 1.3.5` component: `labkit_CIC_app` | `1.3.5 -> 1.3.7` component: `labkit_CSC_app` | `1.3.7 -> 1.3.9` component: `labkit_EIS_app` | `1.3.3 -> 1.3.4` component: `labkit_VTResistance_app` | `1.3.5 -> 1.3.7` component: `labkit_BatchImageCrop_app` | `1.6.5 -> 1.6.6` component: `labkit_CurvatureMeasurement_app` | `1.3.3 -> 1.3.4` component: `labkit_FLIRThermal_app` | `1.2.7 -> 1.2.8` component: `labkit_FocusStack_app` | `1.4.4 -> 1.4.5` component: `labkit_ImageEnhance_app` | `1.5.4 -> 1.5.5` component: `labkit_ImageMatch_app` | `1.5.4 -> 1.5.5` component: `labkit_FigureStudio_app` | `new -> 0.1.0` component: `labkit_FigureStudio_app` | `0.1.0 -> 0.1.1` component: `labkit_FigureStudio_app` | `0.1.1 -> 0.1.2` component: `labkit_FigureStudio_app` | `0.1.2 -> 0.1.4` component: `labkit_NerveResponseAnalysis_app` | `1.3.3 -> 1.3.4` component: `labkit_ResponseReviewStats_app` | `1.3.3 -> 1.3.4` component: `labkit_RHSPreview_app` | `1.3.3 -> 1.3.4` component: `labkit_ECGPrint_app` | `1.3.4 -> 1.3.5` scope: UI 5 facade redesign, app migration, and plot refresh Context The declarative runtime had made app structure more consistent, but its public packages still reflected the order in which features had been extracted: `app`, `spec`, `view`, `tool`, and `diag` mixed lifecycle, layout, plotting, interaction, and diagnostics. App authors often had to know implementation history to guess where an operation belonged. Plot behavior exposed the cost of that ambiguity. Apps independently cleared axes, fitted limits, translated image coordinates, registered previews, and handled empty states. After removing or changing a file, an electrochem plot could retain limits, legends, or annotations from the previous selection. Launcher operations also needed visible progress, and the growing collection of popout-figure actions no longer fit comfortably in every app. Figure cleanup, style adjustment, data export, and reconstruction were substantial enough to justify their own workflow. Decision and rationale Name the UI facade by responsibility: runtime, layout, controls, plots, interactions, and debugging. Move reusable axes mechanics into the plot layer, then migrate every supported app in one breaking version so old and new package names cannot coexist indefinitely. Give long launcher operations one busy and progress model, and move advanced figure work into Figure Studio rather than expanding the app shell further. Changes `labkit_launcher` `1.2.3 -> 1.2.4` `labkit.ui` `4.2.0 -> 5.0.0` `labkit_FigureStudio_app` `0.1.0 -> 0.1.4` `labkit_ChronoOverlay_app` `1.3.3 -> 1.3.5` `labkit_CIC_app` `1.3.5 -> 1.3.7` `labkit_CSC_app` `1.3.7 -> 1.3.9` `labkit_EIS_app` `1.3.3 -> 1.3.4` `labkit_VTResistance_app` `1.3.5 -> 1.3.7` `labkit_DICPreprocess_app` `1.3.3 -> 1.3.4` `labkit_DICPostprocess_app` `1.3.3 -> 1.3.4` `labkit_BatchImageCrop_app` `1.6.5 -> 1.6.6` `labkit_CurvatureMeasurement_app` `1.3.3 -> 1.3.4` `labkit_FLIRThermal_app` `1.2.7 -> 1.2.8` `labkit_FocusStack_app` `1.4.4 -> 1.4.5` `labkit_ImageEnhance_app` `1.5.4 -> 1.5.5` `labkit_ImageMatch_app` `1.5.4 -> 1.5.5` `labkit_RHSPreview_app` `1.3.3 -> 1.3.4` `labkit_NerveResponseAnalysis_app` `1.3.3 -> 1.3.4` `labkit_ResponseReviewStats_app` `1.3.3 -> 1.3.4` `labkit_ECGPrint_app` `1.3.4 -> 1.3.5` Reorganized the UI facade into `labkit.ui.runtime`, `layout`, `control`, `plot`, `interaction`, and `debug` so app authors can find lifecycle, data-only layout, control update, plot-area, pointer/tooling, and diagnostic APIs by responsibility. Replaced the old app/spec/view/tool/diag UI paths with UI 5 names and moved every app to `definition(..., \"Layout\", @buildWorkbenchLayout)` plus `labkit.ui.layout.*`, `labkit.ui.control.*`, `labkit.ui.plot.*`, `labkit.ui.interaction.*`, and `labkit.ui.debug.*`. Added framework-owned plot helpers for registered axes lookup, clearing, empty-state messages, fitted limits, canvas framing, image preview redraw, and data/fraction coordinate conversion. Reset electrochem plot axes and legends when files are cleared, removed, or redrawn so old ranges, markers, shaded windows, and annotations do not remain after the file list changes. Refit CIC, Chrono Overlay, CSC all-cycle, and VT Resistance redraws to the current plotted data instead of preserving stale manual zoom limits. Staggered CIC Emc/Ema marker labels with readable white backgrounds so key extrema labels are less likely to be hidden by voltage-step or window annotations. Added visible busy/progress feedback for launcher actions that can wait on file scans, artifact cleanup, app startup, profiling, GitHub version lookup, or update/install work. Added launcher and version-manager busy gates so repeated clicks do not start overlapping synchronous operations. Replaced top workbench utility buttons with native window utility menus. Changed workbench plot popout/copy/save actions to operate on every registered preview axes in a multi-axes app. Replaced icon-only popout figure tools with visible text buttons for font, plotted-line, axes, grid, and Studio handoff controls. Added the LabKit Core Figure Studio app for opening MATLAB `.fig` files, switching between the measured LabKit single-panel style and the imported FIG defaults, tuning font/line/grid parameters, and exporting visible graphics data packages with reconstruction scripts. Normalized imported FIG axes before applying Studio canvas and style constraints so source layout/aspect metadata and file-selection titles cannot collapse the preview. Centered the managed preview canvas in the app preview grid so styled FIG labels and plot content render in the visible canvas instead of the corner cell. Added a `labkit.ui.plot.fitCanvas` canvas-frame helper so fixed-size preview axes use the framework-owned preview grid policy instead of app-owned row/column layout code. User and data impact App plots now followed the current file selection: clearing or changing data also cleared stale ranges, legends, markers, and annotations. CIC kept Emc and Ema labels readable on dense traces, and multi-axes apps offered one consistent set of plot utilities. Launcher progress distinguished a long synchronous operation from a frozen MATLAB session and rejected repeated clicks while that operation was active. Figure Studio added a separate place to open a `.fig`, compare the imported style with a LabKit single-panel style, adjust visible graphics, and export the axes data with a reconstruction script. These operations worked on figure presentation and visible graphics; they did not replace the scientific export owned by the source app. For app authors, the breaking rename made API discovery more direct. Lifecycle code used `labkit.ui.runtime`, layout descriptions used `labkit.ui.layout`, and plot or interaction code no longer depended on a broad historical bucket. Compatibility and migration Breaking UI facade migration: app code must use the UI 5 package paths and require `labkit.ui >=5 <6`. Validation Commit `78f4827e` migrated 383 source and test files and added focused plot- helper and neurophysiology layout coverage. Launcher progress received its own GUI suite in `edbc79d8`. Figure Studio added GUI and result-export suites in `4c841d6f`, followed by source-axes import tests in `7edd619f`. Exact local commands for the combined release were not recorded. Evidence UI 5 facade and app migration `78f4827e`. Launcher progress and plot utilities `edbc79d8`. Figure Studio `4c841d6f` and source-axes cleanup `8aeee1f1`. FIG import normalization `7edd619f`. Known limitations and follow-up UI 5 clarified package ownership, but app code still performed much of its own lifecycle and rendering coordination. Runtime V2 later took explicit ownership of startup, callbacks, presenters, injected services, and serializable state."},{"title":"UI 7 public runtime boundary","url":"history/records/2026/07/LK-20260716-ui7-public-runtime-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-ui7-public-runtime-boundary 2026-07-16 75 refactor breaking labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app public API ownership Runtime V2 busy and persistence mechanics","text":"UI 7 public runtime boundary id: LK-20260716-ui7-public-runtime-boundary date: 2026-07-16 sequence: 75 type: refactor compatibility: breaking component: `labkit.ui` | `6.0.5 -> 7.0.0` component: `labkit_DICPostprocess_app` | `1.4.0 -> 1.4.1` component: `labkit_DICPreprocess_app` | `1.5.0 -> 1.5.1` component: `labkit_ChronoOverlay_app` | `1.4.0 -> 1.4.1` component: `labkit_CIC_app` | `1.4.0 -> 1.4.1` component: `labkit_CSC_app` | `1.4.0 -> 1.4.1` component: `labkit_EIS_app` | `1.4.0 -> 1.4.1` component: `labkit_VTResistance_app` | `1.4.0 -> 1.4.1` component: `labkit_GaitAnalysis_app` | `2.0.1 -> 2.0.2` component: `labkit_BatchImageCrop_app` | `1.7.0 -> 1.7.1` component: `labkit_CurvatureMeasurement_app` | `1.4.1 -> 1.4.2` component: `labkit_FLIRThermal_app` | `1.4.0 -> 1.4.1` component: `labkit_FocusStack_app` | `1.5.0 -> 1.5.1` component: `labkit_ImageEnhance_app` | `1.6.0 -> 1.6.1` component: `labkit_ImageMatch_app` | `1.6.0 -> 1.6.1` component: `labkit_VideoMarker_app` | `1.5.0 -> 1.5.1` component: `labkit_FigureStudio_app` | `0.2.0 -> 0.2.1` component: `labkit_NerveResponseAnalysis_app` | `1.4.1 -> 1.4.2` component: `labkit_ResponseReviewStats_app` | `1.4.1 -> 1.4.2` component: `labkit_RHSPreview_app` | `1.4.1 -> 1.4.2` component: `labkit_ECGPrint_app` | `1.4.1 -> 1.4.2` scope: public API ownership scope: Runtime V2 busy and persistence mechanics Context The UI 6 runtime exposed three implementation-level helpers alongside its App lifecycle: direct busy-state execution and the creation and resolution of portable file-reference structs. Production Apps did not need those entry points. Semantic callbacks already owned busy transactions, while injected project services and the save/load boundary owned source references. Keeping the helpers public created two competing ways to perform the same framework-owned work. It also made tests exercise a direct busy mode that no production App used. Decision and rationale UI 7 keeps lifecycle and semantic construction public while moving callback transactions and reference serialization into the runtime implementation. `labkit.ui.runtime.create` remains public: it has a distinct supported purpose for callers that already own a complete declarative layout and need only the workbench shell, without project lifecycle or action orchestration. Changes Removed `labkit.ui.runtime.runBusy` from the public facade. Layout actions and other semantic callbacks now reach one private transaction helper. Removed `createPortableFileReference` and `resolvePortableFileReference` from the public facade. Runtime save, load, recovery, and relink code continue to use those algorithms privately. Removed the unused direct-busy behavior that froze callbacks and graphics hit testing. Semantic transactions preserve callback-owned interaction changes. Updated all tracked Apps to declare the UI 7 compatibility range without changing their domain behavior or project payload versions. Replaced tests that fabricated references through a public helper with project-level save/move/load coverage or explicit legacy fixtures. User and data impact App users see no workflow or saved-data change. App developers initiate work through semantic actions and create source records through `services.project.sourceRecord`; the runtime owns the corresponding busy and serialization mechanics. Code that called the three removed UI 6 functions must move to those lifecycle contracts before adopting UI 7. Compatibility and migration The change is a deliberate public API break and therefore increments the UI facade major version. Every tracked App now requires `labkit.ui >=7 <8`. Runtime V2 project envelopes, App payload versions, and portable-reference field names are unchanged, so saved projects require no data migration. Validation Validation covers the exact UI package surface, semantic busy transactions, Runtime V2 project save/move/load and relink behavior, affected App migrations, App requirement/version consistency, generated API removal, and documentation history contracts. Evidence [UI Framework](../../../../framework/README.md) maps the supported public packages and runtime entry points. [Runtime and Data Model](../../../../framework/guides/runtime.md) explains semantic transactions, injected services, and portable source behavior. Known limitations and follow-up This boundary change does not redesign the lower-level declarative workbench builder. `labkit.ui.runtime.create` remains supported and will be judged by its own layout-only contract rather than by production App call count."},{"title":"UI diagnostics and release v3.0.0","url":"history/records/2026/06/LK-20260629-ui-diagnostics-and-release-v3-0-0.html","kind":"history","section":"history","keywords":"LK-20260629-ui-diagnostics-and-release-v3-0-0 2026-06-29 17 feat compatible labkit_launcher labkit.ui UI diagnostics and release v3.0.0","text":"UI diagnostics and release v3.0.0 id: LK-20260629-ui-diagnostics-and-release-v3-0-0 date: 2026-06-29 sequence: 17 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.2 -> 1.1.3` component: `labkit.ui` | `3.1.3 -> 3.2.0` scope: UI diagnostics and release v3.0.0 Context The diagnostic work completed during the 3.x UI cycle needed a stable release checkpoint, clearer validation documentation, and CI behavior that would not run duplicate workflows for the same change. Decision and rationale Publish the UI diagnostic improvements as `v3.0.0`, document how they are validated, and avoid redundant CI triggers. Keep the release compatible with the app data produced by the preceding 2.x workbench releases. Changes Release tag `v3.0.0` `labkit.ui` `3.1.3 -> 3.2.0` `labkit_launcher` `1.1.2 -> 1.1.3` Improved UI diagnostics and validation documentation. Published the v3.0.0 release line around UI diagnostics, validation docs, and duplicate CI avoidance. User and data impact Users gained a named release to install or restore, while failure reports contained better callback context. Project and scientific result formats did not change in this release record. Compatibility and migration The v3.0.0 release retained the preceding app data formats. Users could update or roll back the workbench without converting saved scientific results. Validation Commit `21eff4dc` carried the diagnostic and validation changes; tag commit `349a7549` published `v3.0.0`. Exact historical local commands were not recorded. Evidence Main commits `21eff4dc` and release tag commit `349a7549`. Known limitations and follow-up This record marks the release boundary; individual diagnostic mechanics are described in their earlier component history records."},{"title":"UI groups migration","url":"history/records/2026/07/LK-20260703-ui-groups-migration.html","kind":"history","section":"history","keywords":"LK-20260703-ui-groups-migration 2026-07-03 34 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app UI groups migration","text":"UI groups migration id: LK-20260703-ui-groups-migration date: 2026-07-03 sequence: 34 type: refactor compatibility: compatible component: `labkit.ui` | `3.4.5 -> 4.0.0` component: `labkit_DICPostprocess_app` | `1.3.1 -> 1.3.2` component: `labkit_DICPreprocess_app` | `1.3.1 -> 1.3.2` component: `labkit_ChronoOverlay_app` | `1.3.1 -> 1.3.2` component: `labkit_CIC_app` | `1.3.3 -> 1.3.4` component: `labkit_CSC_app` | `1.3.3 -> 1.3.4` component: `labkit_EIS_app` | `1.3.1 -> 1.3.2` component: `labkit_VTResistance_app` | `1.3.3 -> 1.3.4` component: `labkit_BatchImageCrop_app` | `1.6.3 -> 1.6.4` component: `labkit_CurvatureMeasurement_app` | `1.3.1 -> 1.3.2` component: `labkit_FLIRThermal_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.4.2 -> 1.4.3` component: `labkit_ImageEnhance_app` | `1.5.2 -> 1.5.3` component: `labkit_ImageMatch_app` | `1.5.2 -> 1.5.3` component: `labkit_NerveResponseAnalysis_app` | `1.3.1 -> 1.3.2` component: `labkit_ResponseReviewStats_app` | `1.3.1 -> 1.3.2` component: `labkit_RHSPreview_app` | `1.3.1 -> 1.3.2` component: `labkit_ECGPrint_app` | `1.3.2 -> 1.3.3` scope: UI groups migration Context The declarative runtime described actions as a mostly flat list with optional group hints. Apps with several phases could not reliably express which controls belonged together, and layout code had to infer more structure than the app had actually declared. Decision and rationale Make groups the primary unit of control layout. Each group would carry its title and controls, giving the runtime enough structure to render related actions together without learning app-specific labels or workflow rules. Changes `labkit.ui` `3.4.5 -> 4.0.0` All supported apps received patch bumps. Replaced action groups with UI groups. Moved the reusable UI contract into the 4.x line. User and data impact Apps retained their actions, but related controls appeared in explicit visual sections. App definitions became easier to scan because their grouping matched the panels users saw. Compatibility and migration App workflow definitions had to align with the new grouped UI contract. Validation The migration updated UI specification tests, basic-control and workbench GUI tests, app package structure checks, and all supported app definitions. The exact historical command was not recorded. Evidence Main commit `e81243a3`. Known limitations and follow-up This was a breaking definition-format change and established the UI 4.x line. UI 5 later renamed facade packages by responsibility but retained explicit groups as the layout model."},{"title":"UI utility snapshots and popout tools","url":"history/records/2026/07/LK-20260704-ui-utility-snapshots-and-popout-tools.html","kind":"history","section":"history","keywords":"LK-20260704-ui-utility-snapshots-and-popout-tools 2026-07-04 37 feat compatible labkit.ui UI utility snapshots and popout tools","text":"UI utility snapshots and popout tools id: LK-20260704-ui-utility-snapshots-and-popout-tools date: 2026-07-04 sequence: 37 type: feat compatibility: compatible component: `labkit.ui` | `4.1.0 -> 4.2.0` scope: UI utility snapshots and popout tools Context Users could tune controls and plots during a session, but restoring that work required repeating the settings manually. Popout axes were useful for visual inspection, yet copying data or recreating the plot in a script still required ad hoc commands. Decision and rationale Give the runtime a serializable snapshot contract for app state and expose save and load as workbench utilities. Extend popout axes with explicit styling, image copy, data export, and reconstruction-script actions derived from the graphics objects already present on the axes. Changes `labkit.ui` `4.1.0 -> 4.2.0` Added UI state snapshot save/load APIs. Added workbench utility controls. Improved axes popout export and copy tools. User and data impact Users could save a supported app state and restore it later from the workbench. Popout plots could be restyled, copied, exported as visible graphics data, or used to generate a MATLAB reconstruction script without changing the original app result. Compatibility and migration Apps that did not declare serializable state continued to run without snapshot support. Existing app data and figure files were not converted automatically. Validation The commit expanded app-runtime and axes-workbench GUI suites and added package- surface checks for the snapshot APIs. The exact historical command was not recorded. Evidence Main commit `0155cd12`. Known limitations and follow-up Snapshots covered state declared serializable by the app; they were not raw MATLAB workspace dumps. Figure Studio later provided a dedicated workflow for more extensive figure cleanup and export."},{"title":"VT Resistance consolidates its project contract","url":"history/records/2026/07/LK-20260716-vt-resistance-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-vt-resistance-project-spec 2026-07-16 90 refactor compatible labkit_VTResistance_app Electrochemistry Project lifecycle","text":"VT Resistance consolidates its project contract id: LK-20260716-vt-resistance-project-spec date: 2026-07-16 sequence: 90 type: refactor compatibility: compatible component: `labkit_VTResistance_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context VT Resistance split static product metadata across three files and its first-version project across a generic lifecycle package. Its session factory also encodes a real performance policy by loading only the first preview source. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because lazy DTA decoding and analysis is transient reconstruction, not lifecycle boilerplate. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved lazy preview restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Added a direct project/session contract assertion to the VT export suite. Kept pulse detection, steady windows, voltage definitions, resistance formulas, plots, and export schemas unchanged. User and data impact Launch, batch loading, selection, calculation, save/load, plotting, and CSV exports behave unchanged. The durable contract and lazy reconstruction policy are now explicit and adjacent. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing VT Resistance projects require no migration. Validation Unit tests cover project/session boundaries, pulse detection, window policies, raw and baseline-corrected resistance, failed rows, and CSV output. The hidden GUI workflow covers real launch, selection, calculation, save/load, plots, and export. Evidence [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The lazy session still reaches into portable-reference internals. The shared source-path boundary will replace this across all Apps in one framework change."},{"title":"VT Resistance removes source and workflow storage boilerplate","url":"history/records/2026/07/LK-20260716-vt-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-vt-runtime-source-reconciliation 2026-07-16 114 refactor compatible labkit_VTResistance_app Electrochemistry App structure Runtime adoption","text":"VT Resistance removes source and workflow storage boilerplate id: LK-20260716-vt-runtime-source-reconciliation date: 2026-07-16 sequence: 114 type: refactor compatibility: compatible component: `labkit_VTResistance_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context VT Resistance deliberately keeps batch source registration lazy, decoding only the selected preview until export. The App nevertheless generated and appended portable source records itself and wrote analysis messages directly into `session.workflow.logLines`. Its session factory consequently repeated empty workflow and view buckets owned by Runtime V2. Decision and rationale Keep lazy ordered path registration, selection, resistance analysis, plots, and exports in VT Resistance. Delegate the conversion from that ordered path set to stable portable records to `services.project.reconcileSources`. Runtime preserves retained identities and allocates new unique identities. Route analysis messages through `services.workflow.log` and return only App-specific selection and cache state from `createSession`. Changes Removed local source-ID generation, record append, and registration lookup. Reconciled ordered source paths after add, removal, and clear operations. Routed resistance-analysis messages through the Runtime workflow service. Removed empty workflow and view session boilerplate. Added GUI checks for Runtime-supplied buckets and stable, unique source IDs across batch add, save, reopen, removal, and re-addition. Advanced the VT Resistance App version to 1.4.4. User and data impact Lazy loading, current selection, resistance values, plots, logs, export columns, and project files retain their behavior. Retained file identities are stable and newly added files cannot collide with them. Scientific calculations and exported numeric values are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing portable source records retain their IDs when reopened. Validation The VT Resistance hidden GUI workflow covers three-source lazy registration, selection, whole-batch recomputation, export, save, clear, reopen, remove, re-add, and source identity. Focused resistance/export and repository contract tests cover the surrounding calculation and product behavior. Evidence [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up The broader App inventory must still be scanned for local source-record construction that represents migrations or semantic fixed slots rather than replaceable Runtime bookkeeping."},{"title":"Validated runtime identity contracts","url":"history/records/2026/07/LK-20260716-runtime-identity-contracts.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-identity-contracts 2026-07-16 65 fix compatible labkit.ui runtime definitions layouts and presentations projects and result manifests","text":"Validated runtime identity contracts id: LK-20260716-runtime-identity-contracts date: 2026-07-16 sequence: 65 type: fix compatibility: compatible component: `labkit.ui` | `6.0.0 -> 6.0.1` scope: runtime definitions scope: layouts and presentations scope: projects and result manifests Context Runtime ids connect app definitions, layout controls, actions, axes, interactions, durable source records, result outputs, and managed resources. Several registries previously accepted duplicate ids or dangling references until a callback ran. Recovery directory names also used MATLAB identifier normalization, which is not an injective mapping and could make two distinct app ids share storage. Decision and rationale Keep semantic ids developer-owned, but validate each namespace at the earliest transaction boundary. App ids now have one documented portable syntax and a collision-free filesystem encoding. References from layouts, presentations, and interactions must resolve before the runtime changes visible handles or managed resources. Same-scope resource replacement remains intentional: a resource id is an idempotent ownership slot, while resources that must coexist use distinct ids. Changes Rejected invalid app ids, duplicate preview-axis ids, duplicate durable source ids, and duplicate result-output ids. Rejected layout, interaction, and presentation references to unknown actions, controls, targets, axes, or renderers before committing UI changes. Encoded app recovery storage with the exact app id while retaining read-only discovery of recovery files written under the earlier normalized folder. Added a repository contract test requiring literal public app ids to be globally unique. User and data impact Valid current apps and saved projects behave as before. Configuration mistakes now fail with a specific runtime error instead of overwriting another object or appearing later as a broken callback. Existing recovery files remain discoverable and new recovery files cannot collide through name normalization. Compatibility and migration This patch is compatible for documented definitions. A private app with an invalid app id, duplicate semantic ids, or a dangling reference must correct that declaration. Its app id should otherwise remain unchanged because the id is part of saved-project and recovery compatibility. Validation Focused unit, contract, and hidden-GUI runtime tests cover invalid definitions, duplicate namespaces, dangling references, result manifests, and recovery storage behavior. Evidence `labkit.ui.runtime.define` documents the public identity and resource rules. Runtime validation rejects invalid identity graphs before commit. The app identity contract scans every public app definition. Known limitations and follow-up Identity validation protects one runtime and the public app catalog. External private-app catalogs remain responsible for running the same contract test in their own workspace."},{"title":"Version metadata baseline","url":"history/records/2026/06/LK-20260623-version-metadata-baseline.html","kind":"history","section":"history","keywords":"LK-20260623-version-metadata-baseline 2026-06-23 10 feat compatible labkit_launcher labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Version metadata baseline","text":"Version metadata baseline id: LK-20260623-version-metadata-baseline date: 2026-06-23 sequence: 10 type: feat compatibility: compatible component: `labkit_launcher` | `new -> 1.0.0` component: `labkit.ui` | `2.1.0 -> 2.2.0` component: `labkit_DICPostprocess_app` | `new -> 1.0.0` component: `labkit_DICPreprocess_app` | `new -> 1.0.0` component: `labkit_ChronoOverlay_app` | `new -> 1.0.0` component: `labkit_CIC_app` | `new -> 1.0.0` component: `labkit_CSC_app` | `new -> 1.0.0` component: `labkit_EIS_app` | `new -> 1.0.0` component: `labkit_VTResistance_app` | `new -> 1.0.0` component: `labkit_BatchImageCrop_app` | `new -> 1.0.0` component: `labkit_CurvatureMeasurement_app` | `new -> 1.0.0` component: `labkit_FocusStack_app` | `new -> 1.0.0` component: `labkit_ImageEnhance_app` | `new -> 1.0.0` component: `labkit_ImageMatch_app` | `new -> 1.0.0` component: `labkit_NerveResponseAnalysis_app` | `new -> 1.0.0` component: `labkit_ResponseReviewStats_app` | `new -> 1.0.0` component: `labkit_RHSPreview_app` | `new -> 1.0.0` component: `labkit_ECGPrint_app` | `new -> 1.0.0` scope: Version metadata baseline Context The repository had release tags and package contracts, but the launcher and individual apps did not display their own component versions. Decision and rationale Add lightweight `version` requests to every app and the launcher, show those versions in titles and the launcher catalog, and validate their format so a debug report can identify the exact component being run. Changes Release tag `v2.4.0` `labkit_launcher` `1.0.0` All supported apps `1.0.0` `labkit.ui` `2.1.0 -> 2.2.0` Added app and launcher version metadata. Added versioned titles, lightweight version requests, launcher catalog version display, and version guardrails. User and data impact Users and maintainers could read the launcher and app version without inspecting Git history. The change added metadata only and did not modify saved data or numerical results. Compatibility and migration Version requests and titles were additive. Existing app commands, inputs, and saved results continued to work without conversion. Validation Commit `d70c2607` added version-request, title, catalog, and format checks. Release tag `v2.4.0` identifies the shipped baseline. Evidence Main commit `d70c2607`. Known limitations and follow-up These component versions describe displayed app/library builds; compatibility between reusable packages is handled separately by `labkit.contract`."},{"title":"Video Marker app","url":"history/records/2026/07/LK-20260713-video-marker-app.html","kind":"history","section":"history","keywords":"LK-20260713-video-marker-app 2026-07-13 54 feat compatible labkit_VideoMarker_app Video Marker app","text":"Video Marker app id: LK-20260713-video-marker-app date: 2026-07-13 sequence: 54 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `new -> 1.0.0` scope: Video Marker app Context Image measurement workflows needed a generic video annotation tool for ordered 2D keypoints and skeleton connections. The app belongs with image measurement tools because it uses the same axes interaction, scale calibration, and export style, but the source media and frame navigation are video-specific. Decision and rationale Add a standalone Video Marker app instead of extending an existing image-only app or adding a public video facade. The first version keeps marker semantics app-local, uses existing UI interaction tools for ordered points and scale calibration, and avoids training, inference, multi-subject tracking, or per-point missing-state semantics. Changes Added `labkit_VideoMarker_app` under Image Measurement with video loading, indexed frame navigation, ordered keypoint placement, skeleton overlays, and previous-confirmed-frame inheritance as draft annotations. Added a self-describing marker CSV that stores pixel coordinates for every frame and can be imported back into the app for overlay or editing. Added a separate coordinate CSV export for continuous confirmed frame ranges, with selectable pixel or calibrated units, top-left or first-point origin, and up/down Y-axis convention. Added project MAT save/load and debug sample-pack coverage for synthetic video marker assets. User and data impact Users can manually annotate video frames without depending on a model-training workflow. Marker CSV remains the editable round-trip format; coordinate CSV is plain plotting data and does not mutate the saved marker annotations. Compatibility and migration The app is additive and does not change existing image measurement apps or public LabKit facades. No existing project files or exports require migration. Validation `VideoMarkerTest` covers skeleton parsing, frame inheritance, marker CSV round trip, coordinate transforms, and project save/load. `GuiLayoutVideoMarkerTest` covers the hidden GUI launch and semantic control contract. Both accompanied the app in commit `b41f5254`. Evidence Initial Video Marker app `b41f5254`. Known limitations and follow-up The first version is manual and single-subject. It intentionally does not perform automatic interpolation, optical flow, model inference, multi-camera synchronization, 3D annotation, or per-point missing/out-of-frame labeling."},{"title":"Video Marker concentrates project history behind one contract","url":"history/records/2026/07/LK-20260716-video-marker-structure.html","kind":"history","section":"history","keywords":"LK-20260716-video-marker-structure 2026-07-16 103 refactor compatible labkit_VideoMarker_app labkit_GaitAnalysis_app Image Measurement App structure","text":"Video Marker concentrates project history behind one contract id: LK-20260716-video-marker-structure date: 2026-07-16 sequence: 103 type: refactor compatibility: compatible component: `labkit_VideoMarker_app` | `1.5.1 -> 1.5.2` component: `labkit_GaitAnalysis_app` | `2.0.2 -> 2.0.3` scope: Image Measurement scope: App structure Context Video Marker had already separated skeleton, annotations, tracking, video IO, export, and UI capabilities, but its Runtime contract remained spread across seven generic lifecycle files plus separate requirement and version files. A path lookup wrapper also inspected the Runtime's nested portable-reference fields, and the legacy importer constructed that schema itself. Decision and rationale Keep the App's real persistence requirements—schema migration, old MAT import, and current-frame resume—but expose them together through one `projectSpec.m`. Keep decoded-video reconstruction in root `createSession.m`. Use the Runtime's source factory and accessor instead of App-owned reference adapters. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderers, and debug capability in `definition.m`. Concentrated create, validate, migrate, legacy import, resume creation, and resume application behind one project-spec entry. Replaced the ordered migration-cell declaration with the Runtime's single migration callback. Replaced App-side source-reference construction and nested-field lookup with `sourceRecord` opaque import and `sourcePaths` semantic lookup. Removed the generic lifecycle package, source-path wrapper, and separate requirement/version files. Updated Gait's synthetic Video Marker fixture to construct the current payload through the project contract rather than a retired lifecycle file. User and data impact Skeleton setup, continuous point marking, prediction, calibration, coordinate export, explicit autosave, save-before-new confirmation, recovery, source relinking, and old MAT upgrade retain their behavior. The saved current-frame resume remains navigation convenience; durable coordinates and provenance are unchanged. Developers now find all project-history policy in one file and do not need to learn the Runtime's portable-reference representation. Compatibility and migration Current schema version 2 and the recognized old `videoMarkerProject` variable are unchanged. Version-1 payloads still derive durable video metadata, and old portable references retain their relative path. New saves continue to use the current `labkitProject` envelope. Validation Focused unit tests cover skeleton editing, annotation provenance, deterministic motion tracking, Toolbox parity, CSV and coordinate semantics, metadata, migration, legacy import, validation, and resume. Five hidden GUI workflows cover launch, continuous marking, viewport preservation, explicit autosave, new-project choices, named save/load, recovery, and old-format upgrade. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) Known limitations and follow-up Automated GUI checks do not establish tracking validity for every recording or replace manual judgment of point placement and prediction quality. Remaining Apps with generic lifecycle packages will be reviewed independently."},{"title":"Video Marker predictive frame navigation","url":"history/records/2026/07/LK-20260714-video-marker-predictive-navigation.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-predictive-navigation 2026-07-14 58 feat compatible labkit_VideoMarker_app labkit.ui `docs/development/build-apps/architecture.md` `docs/api/ui.md` runtime dependency policy","text":"Video Marker predictive frame navigation id: LK-20260714-video-marker-predictive-navigation date: 2026-07-14 sequence: 58 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `1.1.0 -> 1.2.0` component: `labkit.ui` | `5.1.1 -> 5.2.0` scope: `docs/development/build-apps/architecture.md` scope: `docs/api/ui.md` scope: runtime dependency policy Context The first tracking implementation exposed separate interpolation and previous-frame buttons. Its integer grayscale block search could only model a small translation, and coordinate interpolation did not follow the actual trajectory of articulated points. Users still had to request every estimate manually. Decision and rationale Make prediction an automatic part of forward navigation. Use a mature pyramidal Kanade-Lucas-Tomasi tracker with forward/backward rejection for short-range point propagation, and treat every complete human edit as a new anchor. This matches the app's correction-oriented workflow without claiming unattended long-term or occlusion tracking. Keep the implementation MATLAB-local. Project policy now forbids incidental third-party runtimes, Python environments, model downloads, and first-run network installation; additional dependencies require an explicit architecture decision. Changes Removed the user-facing Track and Interpolate actions. Added automatic forward prediction for adjacent frames and forward jumps. Replaced integer block matching with cropped four-level pyramidal KLT, forward/backward reliability checks, subpixel coordinates, and a constant-velocity fallback for rejected points. Added manual/predicted frame provenance and per-point confidence to the recoverable project payload while preserving marker CSV coordinates. Added reusable portable external-file references to `labkit.ui.runtime` and used them for both Video Marker projects and video-adjacent autosaves. Added a reusable field-labeled manual relinking fallback for malformed or unresolved references; app imports retain file-format validation ownership. Project and autosave payloads now prefer a path relative to the MAT file, fall back to the original path or same-folder filename, and offer video relinking when no automatic candidate exists. Made manual edits immutable anchors that reset subsequent prediction; only predicted drafts may be refreshed by the tracker. Added the repository runtime-dependency boundary to the root constitution, app rules, and human architecture documentation. User and data impact After users mark a complete frame, moving forward presents editable predicted points automatically. Correcting those points establishes a fresh anchor. Older projects are upgraded in memory with provenance defaults; marker and coordinate CSV formats are unchanged. Project/video directory trees can move between users, cloud-drive roots, and operating systems without editing paths. Compatibility and migration Existing Video Marker projects and autosaves remain readable. New project payloads contain additive provenance and confidence fields. Systems without the MATLAB KLT implementation retain coordinates through the motion fallback and remain manually editable; no third-party installation is attempted. Validation Synthetic tests cover subpixel translated-texture tracking, provenance upgrade, and marker CSV round trips. Hidden GUI tests cover automatic prediction on forward navigation and the absence of manual assist buttons. Local untracked evaluation used adjacent frames from a representative video across marker, eye, ear, nose, and toe textures; repository fixtures contain no real sample paths or pixels. Evidence Predictive marking and portable file references `6bfd74c8`. Known limitations and follow-up KLT is a short-range tracker. Low-texture, occluded, or strongly deforming points can be rejected and require human correction; that correction is the next anchor rather than an error state."},{"title":"Video Marker startup validation","url":"history/records/2026/07/LK-20260714-video-marker-startup-fix.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-startup-fix 2026-07-14 56 fix compatible labkit_VideoMarker_app `tests/shared/guiTestHelpers.m`","text":"Video Marker startup validation id: LK-20260714-video-marker-startup-fix date: 2026-07-14 sequence: 56 type: fix compatibility: compatible component: `labkit_VideoMarker_app` | `1.0.0 -> 1.0.1` scope: `tests/shared/guiTestHelpers.m` Context Video Marker declared its preview axes as `video` but reset an empty preview through the nonexistent `raw` axes id. The runtime correctly converted that deferred startup exception into a visible failure status, but the structural GUI test continued because it checked controls before asserting that startup had completed successfully. Decision and rationale Use the declared `video` axes id in the app and make the shared standard workbench assertion wait for the startup lifecycle. A startup failure now fails the GUI test with the runtime diagnostic instead of leaving the test green after only verifying that the shell was constructed. Changes Corrected the empty-preview reset to target `videoAxes/video`. Added a shared startup-success assertion to standard workbench GUI checks. Added a synthetic regression proving deferred startup failures are rejected. User and data impact Video Marker opens normally before a video is selected. This change does not alter marker projects, coordinate exports, annotations, or measurement data. Compatibility and migration The fix is compatible with existing Video Marker projects and exported files. No user migration is required. Validation The Video Marker hidden GUI test covers the corrected initial preview path. The reusable declarative UI GUI test covers startup-failure detection in the shared helper. Both regressions were added in the focused startup repair commits below. Evidence Video Marker startup repair `48319203`. Focused GUI startup-lifecycle regression split `c3c6ce06`. Known limitations and follow-up Automated startup checks validate lifecycle completion and diagnostics; they do not replace manual review of video rendering or point-drag interaction."},{"title":"Video Marker visual skeleton setup and continuous marking","url":"history/records/2026/07/LK-20260714-video-marker-continuous-marking.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-continuous-marking 2026-07-14 57 feat compatible labkit.ui labkit_VideoMarker_app Video Marker visual skeleton setup and continuous marking","text":"Video Marker visual skeleton setup and continuous marking id: LK-20260714-video-marker-continuous-marking date: 2026-07-14 sequence: 57 type: feat compatibility: compatible component: `labkit.ui` | `5.1.0 -> 5.1.1` component: `labkit_VideoMarker_app` | `1.0.1 -> 1.1.0` scope: Video Marker visual skeleton setup and continuous marking Context Video Marker required comma-separated keypoint names, manually encoded edge text, a start/finish edit toggle, and a separate confirm action. Those controls made skeleton setup error-prone and added modes that did not match the direct click-and-drag interaction already supported by the point editor. Decision and rationale Make skeleton definition the first explicit step, represent keypoints and connections as structured controls, and keep point editing active for the whole video session. Frame navigation is the save boundary; complete frames confirm automatically and inherited frames remain drafts until edited. Changes Replaced long skeleton strings with an editable ordered-keypoint table, mutually filtered connection endpoint selectors, and add/remove/reorder actions. Added editable skeleton presets led by the legacy iliac-to-foot five-point leg chain, while keeping blank custom construction available. Added a connect-in-order shortcut that fills all adjacent point connections without removing other user-defined edges. Added the domain-neutral `labkit.ui.control.setItems` API for dynamically updating selectable control choices without firing app callbacks. Added `labkit.ui.plot.replaceOverlay` so image apps can replace one named overlay layer without clearing peer graphics, view limits, or callbacks. Added hidden-test-safe `labkit.ui.runtime.confirm` for app-owned two-choice recovery and confirmation workflows. Removed start/finish and confirm controls; blank clicks add points in order, dragging refines them, and frame changes save and inherit coordinates. Made point edits refresh only the skeleton overlay so zoom, wheel handling, and the active continuous-marking session survive each placed point. Preserved the current X/Y viewport across frame navigation so users can mark the same zoomed ROI through consecutive frames. Added draft-only marking assists for temporal interpolation between nearest confirmed frames and lightweight local block matching from the immediately previous confirmed frame. Added atomic change-driven project-compatible autosave in a visible subfolder beside the source video, plus matching-video restore/start-new choices. Added a new-setup action that explicitly clears the current session before a different skeleton is declared. User and data impact Skeleton setup is visible and validated before video loading. Existing marker CSV, coordinate CSV, and project MAT schemas remain unchanged. Point order is still the coordinate-column identity contract. Compatibility and migration Existing marker CSV and project files remain readable. The workflow changes are UI-only; users no longer press Apply skeleton, Start point edit, or Confirm frame. Video Marker now requires `labkit.ui >=5.1.1 <6`. Validation Unit tests cover skeleton presets, edits, and edge remapping. Hidden GUI workflow tests cover preset application, table-based setup, mutually filtered connection choices, automatic editor activation, consecutive point placement with preserved zoom/scroll callbacks, complete-frame confirmation, frame inheritance, and draft-state preservation. UI framework tests cover dynamic selectable items. Evidence Continuous-marking and skeleton workflow redesign `bcba1833`. Known limitations and follow-up Automated hidden-GUI tests verify callbacks and state transitions but cannot judge visual density or pointer ergonomics; those still need a short manual review with a representative video."},{"title":"v1.0: app-owned packages and a standard test platform","url":"history/records/2026/06/LK-20260606-v1-foundation.html","kind":"history","section":"history","keywords":"LK-20260606-v1-foundation 2026-06-06 5 refactor compatible historical project evolution","text":"v1.0: app-owned packages and a standard test platform id: LK-20260606-v1-foundation date: 2026-06-06 sequence: 5 type: refactor compatibility: compatible scope: historical project evolution Context The pre-release work had produced many useful apps and library functions, but the repository still carried large entry points, custom test routing, UI compatibility aliases, and helper ownership that was difficult to explain. Releasing that state as a stable foundation required more than assigning a version number. Decision and rationale Make each app package responsible for its runner, callbacks, presenters, and private workflow helpers. Use MATLAB's official test platform as the common runner, retain focused GUI gesture tests where automation was meaningful, and remove compatibility abstractions that no longer represented supported code. Changes Added the official MATLAB test-platform skeleton and migrated project guardrails and app suites to it. Decomposed image, DIC, electrochem, and ECG entry points into app-owned packages and private workflow helpers. Added GUI gesture coverage and CI test artifacts. Centralized the build-task catalog and separated scheduled coverage from faster routine checks. Added Batch Image Crop with a base-MATLAB rotation path. Completed DIC package migration and removed toolbox-only unit dependencies from its tested paths. Removed obsolete UI compatibility aliases and specialized dual-plot shell APIs in favor of structured form specifications and app-owned plot controls. User and data impact The v1.0 foundation provided a coherent set of named apps backed by repeatable tests. Batch Image Crop joined the image family, while existing electrochem, DIC, curvature, focus, and ECG workflows retained their app entry points. The release concentrated on ownership and validation. It did not introduce a single shared project-file format or combine the apps into one monolithic GUI. Compatibility and migration Repository code using removed UI aliases or dispatch routers needed to call the supported app and UI contracts. User-facing app entry points remained the intended launch mechanism. Validation MATLAB unit suites, architecture guardrails, GUI gesture tests, build-task checks, CI JUnit summaries, and scheduled coverage were present by the end of the stage. Tag `v1.0` points to `0bb83a6e`. Evidence Test-platform rewrite `7ad36886` through `83c8fcc1`. Build entry points and task catalog `12aa66af` through `7cc209ab`. Batch Image Crop `0731f6f0`, `5a76eb31`. App-owned package migrations `c4ae5074` through `76146c3d`. UI API cleanup `65a8c65d` through `0f428eb8`. v1.0 tag target `0bb83a6e`. Known limitations and follow-up v1.0 still required users to know individual app commands and used the first generation of the UI runtime. The next major release added a launcher and migrated apps to UI 2.0."},{"title":"v2.0: launcher, image apps, and UI 2.0","url":"history/records/2026/06/LK-20260615-v2-launcher-and-ui2.html","kind":"history","section":"history","keywords":"LK-20260615-v2-launcher-and-ui2 2026-06-15 6 feat compatible historical project evolution","text":"v2.0: launcher, image apps, and UI 2.0 id: LK-20260615-v2-launcher-and-ui2 date: 2026-06-15 sequence: 6 type: feat compatibility: compatible scope: historical project evolution Context The first release had stable app commands, but discovery was poor: users had to know which command to run. Apps also used a mixture of first-generation UI patterns, which made startup, resizing, debug launch, and preview behavior less consistent than the underlying package boundaries. Decision and rationale Add a launcher as the visible entrance to the workbench and migrate the app families to a second-generation UI contract. Preserve separate app entry points and app-owned workflows; the launcher would discover and start them rather than turn them into screens of one large application. Changes Added the LabKit launcher and localized MATLAB project metadata. Added Image Enhance and Image Match workflows and semantic tab resizing. Supplied a base-MATLAB path for image matching instead of requiring an Image Processing Toolbox implementation. Migrated LabKit apps to UI 2.0 and added a starter template for new apps. Stabilized responsive layout behavior and default text-panel sizing. Completed cold-start migration, simplified debug launch, and persisted debug artifacts for later inspection. Refined DIC Postprocess overlay export at the release boundary. User and data impact Users could browse and start supported apps from one launcher while retaining the option to call an app directly. Image users gained enhancement and matching workflows. Debug sessions left evidence that could be inspected after a startup or callback problem. Existing scientific file formats and app-specific saved data remained owned by their apps. Compatibility and migration App implementations moved to UI 2.0, but the release preserved the model of separate named app entry points. Code that depended on first-generation UI implementation details needed to adopt the new runtime contracts. Validation The release used the reorganized MATLAB suite, GUI layout regressions, cold- start checks, and focused DIC overlay tests. Tag `v2.0` points to `4bc7343f`. Evidence Launcher `7c825ef8`. Image apps and base-MATLAB matching `5cdc31e2`, `c838635c`. UI 2.0 migration `c2658605`. Starter app and layout stabilization `6c8b57ab`, `78ca39e0`. Startup and debug work `90c8869f` through `2153bd08`. v2.0 tag target `4bc7343f`. Known limitations and follow-up The launcher did not yet update itself, and large file panels and image tools still exposed inconsistent busy, zoom, and path-selection behavior. Those cross-app concerns became the focus of the 2.1 through 2.3 releases."},{"title":"v2.2 to v2.3.1: self-contained launch and image workflow refinement","url":"history/records/2026/06/LK-20260621-v2-2-v2-3-image-workflows.html","kind":"history","section":"history","keywords":"LK-20260621-v2-2-v2-3-image-workflows 2026-06-21 8 feat compatible historical project evolution","text":"v2.2 to v2.3.1: self-contained launch and image workflow refinement id: LK-20260621-v2-2-v2-3-image-workflows date: 2026-06-21 sequence: 8 type: feat compatibility: compatible scope: historical project evolution Context The launcher and updater existed, but installation still assumed repository context. At the same time, ordinary image work exposed small but costly interaction problems: selecting several files, preserving zoom while placing a marker, padding a crop without shifting its source coordinates, or returning to duplicate-crop mode after editing scale. Decision and rationale Make the launcher usable as a self-contained entry artifact and refine the high-frequency image workflows in place. These releases favored fewer steps and stable spatial state over adding more general-purpose dialogs or toolbars. Changes Made the launcher self-contained and added an update button backed by the release updater. Appended file-panel selections and added multi-file choose-and-load behavior. Improved Image Match speed and separated its reference input from moving images. Preserved Batch Crop source coordinates when padding, reduced padding seams, and refined the padding workflow. Added physical scale mode to Batch Crop and restored scale and duplicate-task interactions after edits. Preserved image-tool zoom while placing or editing overlays. Hardened image-tool ownership and changed the README to present the launcher as the primary user entry. User and data impact Users could begin from a compact launcher, select several files in one action, and edit image overlays without losing their current view. Batch Crop gained physical scaling while retaining the relationship between the visible crop and the source image. Padding and scale metadata affected exported crop interpretation, so regression work explicitly protected source coordinates and resumed interaction modes. Compatibility and migration The changes were compatible additions and fixes to the v2 workflows. Release tags identify the incremental baselines: `v2.2.0` at `c904baca`, `v2.3.0` at `f2ed23c2`, and `v2.3.1` at `83d03e7a`. Validation Focused tests covered path-list cleanup, Batch Crop scale and padding contracts, preview interactions, and image-tool ownership. The exact command for each historical commit was not recorded. Evidence Self-contained launcher and updater button `a1938db8`, `c904baca`. File selection and Image Match work `5aa10909`, `471b79c2`, `ebdbf9db`. Batch Crop padding `54c8fe43` through `f2ed23c2`. Zoom and physical scale `632191b9` through `2b9690fc`. Ownership hardening and primary launcher guidance `8328248a`, `83d03e7a`. Known limitations and follow-up Some previews still repeated expensive processing, EIS log-axis navigation was fragile, and the release process did not yet prove that downloadable artifacts matched their tags. v2.3.2 and v2.3.3 addressed those issues."},{"title":"v2.3.2 and v2.3.3: preview performance and release contracts","url":"history/records/2026/06/LK-20260623-v2-3-performance-and-release-contracts.html","kind":"history","section":"history","keywords":"LK-20260623-v2-3-performance-and-release-contracts 2026-06-23 9 fix compatible historical project evolution","text":"v2.3.2 and v2.3.3: preview performance and release contracts id: LK-20260623-v2-3-performance-and-release-contracts date: 2026-06-23 sequence: 9 type: fix compatibility: compatible scope: historical project evolution Context By v2.3.1, app discovery and image interaction were substantially better, but users could still experience redundant preview processing and unstable axes limits. Separately, a working release download was not sufficient evidence that the downloaded launcher came from the intended tag. Decision and rationale Separate preview computation from rendering so an interaction does not repeat scientific or image processing unnecessarily. Stabilize axes behavior at the app boundary, then make release assets reproducible and add facade contract checks that describe which library calls apps may rely on. Changes Stabilized EIS zoom on logarithmic axes and reset plot limits when coordinate choices changed. Reduced repeated image preview processing and separated preview preparation from display updates. Documented the boundary between preview state and exported results. Added pixel-radius controls for scale previews. Required release assets to be reproduced from their Git tag. Improved GitHub onboarding and issue templates. Added facade contract checks and hid windows created by automated GUI tests. User and data impact Image apps responded more quickly when controls changed without requiring new processing. EIS navigation became more predictable, and scale previews exposed their pixel radius directly. Release users gained a stronger link between the published tag and the launcher they downloaded. The preview/export separation clarified that a faster display refresh must not silently change exported numerical or spatial results. Compatibility and migration The changes corrected existing v2 behavior without introducing a new user data format. Tags `v2.3.2` and `v2.3.3` point to `29669ca6` and `a7e7dfb1`, respectively. Validation Focused EIS and image-preview tests accompanied the fixes. Release guidance added tag-derived asset checks, and facade tests checked supported app-to- library calls. Automated GUI windows were hidden to reduce test interference. Evidence EIS axes fixes `6c8e6540`, `579bc56a`. Preview processing separation `cc65e844`, `2369ffd3`. Preview/export contract and scale controls `d0d46f6e`, `29669ca6`. Reproducible release and onboarding `1832f46a` through `0c5243a9`. Facade and GUI-test contracts `a25b79f9`, `20ea41b8`. Known limitations and follow-up Component-specific version metadata was introduced only after these releases, so the early record relies on repository tags and commit evidence. Later history pages can name both the repository release and the affected component version."},{"title":"VT Resistance","url":"apps/electrochemistry/vt-resistance.html","kind":"app","section":"apps","keywords":"apps electrochemistry vt resistance VT Resistance","text":"VT Resistance VT Resistance estimates cathodic and anodic steady resistance from a biphasic voltage transient and reports the mean of their absolute values. Requirements And Launch The app requires a chrono DTA curve with valid time, voltage, and current columns. labkit_VTResistance_app Inputs And Batch Behavior Add one or more chrono `.DTA` files. The transient session decodes and analyzes the registered batch so shared setting changes update every result together. No electrode-area normalization is performed because the reported quantity is electrical resistance in ohms. Saved projects preserve source order and portable identity through removal, later additions, and reopen. Basic Workflow Add files and select a representative transient. Choose pulse detection, steady-window policy, and voltage definition. Inspect detected phase windows and current/voltage traces. Review cathodic, anodic, and average resistance. Export the batch result CSV. Analysis Parameters Parameter Default Alternatives Pulse detection Metadata first, then auto Metadata only; Auto from Im only Steady window Full pulse median Center 60% median Resistance voltage Baseline-corrected dV/I Raw Vf/I The center-60% policy uses the interval from 20% to 80% of each detected phase to reduce onset and offset transients. Plot marker and shading choices affect display only. Calculation Semantics After finite-sample filtering and pulse detection, the app calculates median current and voltage in the chosen cathodic and anodic steady windows. The cathodic baseline is the median pre-pulse voltage and the anodic baseline is the median post-pulse voltage, with explicit finite fallbacks. Baseline-corrected mode uses: R_cath = (V_cath,steady - V_cath,baseline) / I_cath,steady R_anod = (V_anod,steady - V_anod,baseline) / I_anod,steady R_average = mean(abs(R_cath), abs(R_anod)) Raw mode replaces each voltage difference with its steady absolute voltage. Division by a missing, non-finite, or effectively zero current returns `NaN`; the app does not invent a finite resistance. Output Schema The CSV contains source identity, steady currents and voltages, baseline voltages and windows, raw and baseline-corrected resistance fields, selected resistance values, absolute cathodic/anodic resistance, average resistance, pulse-detection method, and status. A result manifest records the common batch parameters and output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message); options = struct( ... \"pulseMode\", \"Metadata first, then auto\", ... \"windowMode\", \"Full pulse median\", ... \"voltageMode\", \"Baseline-corrected dV/I\"); result = vt_resistance.analysisRun.computeResistance(item, options); assert(result.ok, result.message); Errors And Limitations At least five finite `T`, `Vf`, and `Im` samples are required. Each selected steady window must contain at least two samples. Resistance is invalid when steady current is zero or non-finite. The result is a transient-derived estimate and does not replace an impedance model or compensate wiring/contact resistance automatically. Related Topics [Electrochemistry family](../README.md) [CIC](../cic/README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md) vt_resistance.analysisRun.computeResistance Estimate cathodic and anodic resistance from a VT pulse."},{"title":"Gait Apps","url":"apps/gait/index.html","kind":"app family","section":"apps","keywords":"apps gait Gait Apps","text":"Gait Apps Gait apps turn frame-ordered landmark coordinates into auditable kinematic tables. Annotation stays in the image or video marker workflow; this family owns role mapping, coordinate calibration, temporal interpretation, gait metrics, quality checks, and result export. Choose An App Task App Place and revise landmarks on video frames [Video Marker](../image-measurement/video-marker/README.md) Convert completed landmarks into gait metrics [Gait Analysis](gait-analysis/README.md) Data Flow The current Video Marker records one ordered set of named points per frame and saves coordinates, skeleton, timing, and calibration in its project. Gait Analysis validates that project and normalizes the saved payload to a frame-by-point-by-two coordinate array. The user maps point names to iliac, hip, knee, ankle, and foot roles. The app smooths coordinates, calculates joint angles and segment lengths, detects steps, applies quality rules, and builds export tables. Gait Analysis reads current Video Marker project and autosave files directly. It never rewrites the annotation project and does not accept generic MAT or coordinate-table substitutes that lack the required source metadata. Use The Calculation Without The App The app-owned calculation entry point is `gait_analysis.analysisRun.computeGait`. It accepts normalized pose data from `gait_analysis.sourceFiles.readPoseFile` and the same option structure used by the app. See the [Gait Analysis manual](gait-analysis/README.md) for the data shape, defaults, outputs, and example. Related Documentation [Image Measurement apps](../image-measurement/README.md) [App catalog](../README.md) [App Framework](../../framework/README.md) gait_analysis.analysisRun.computeGait Calculate joint, segment, swing, and step-quality measurements. gait_analysis.sourceFiles.readPoseFile Read one current Video Marker MAT project for gait analysis."},{"title":"Gait Analysis","url":"apps/gait/gait-analysis.html","kind":"app","section":"apps","keywords":"apps gait gait analysis Gait Analysis","text":"Gait Analysis Gait Analysis 2 converts a current Video Marker project into independently segmented treadmill swing steps, per-frame kinematics, per-step gait parameters, visual step reports, and reproducible CSV outputs. Loading and analysis are deliberately separate: loading first shows the complete tracked skeleton trajectory; analysis then enables one-step-at-a-time review. Open Gait Analysis From the LabKit launcher, select **Gait Analysis** and choose **Open**. From a source checkout, run: labkit_GaitAnalysis_app Input Contract The only file input is a current Video Marker `labkitProject` MAT document or autosave. The MAT file is the analysis source of truth and the cross-App data contract; Gait Analysis does not call the Video Marker package at runtime. The saved fields are described by [Video Marker project and session state](../../image-measurement/video-marker/README.md#project-and-session-state). Gait Analysis reads: frames-by-points-by-2 pixel coordinates; point IDs, point names, and skeleton edges; frame annotation status and source when present; video frame count, frame rate, duration, width, and height; scale calibration and physical unit when calibrated. The app does not reopen the original video to obtain scientific metadata. Video Marker payloads older than the embedded-video-metadata contract must be opened and saved with the current Video Marker before analysis. Gait Analysis rejects generic coordinate tables and arbitrary MAT variables because they do not jointly own timing, skeleton, calibration, and annotation provenance. `computeGait` still accepts an in-memory normalized pose for deterministic tests and programmatic calculations. The Video Marker document is stored as a portable project source. Older Gait Analysis projects are upgraded on load. Coordinates use image convention: the origin is at the upper left and Y increases downward. The skeleton preview preserves that convention. Angle and length time series use conventional plot axes. Project And Session State The pose source, analysis options, computed tables/events, and export record are saved. Decoded pose data and the currently selected step are reconstructed after load. Projects from the older stride-naming contract invalidate results whose scientific meaning changed, so rerun the analysis before export. Two-Stage Workflow 1. Load And Inspect All Trajectories Choose **Open Video Marker MAT**. Before any analysis runs, the workspace overlays every recorded skeleton and each named point trajectory. This view is intended to expose gross tracking failures, missing landmarks, unexpected skeleton order, and coordinate orientation before derived numbers are produced. The source summary reports frame count, point count, embedded frame rate, source format, and unit. Role fields are populated from matching source point names; `iliac_crest` is accepted for the iliac role. 2. Analyze And Review One Step Choose **Run analysis**. Select a row in the step table or use **Previous step** and **Next step**. The workspace then shows only that step: all skeleton poses from lift-off through landing, with point trajectories; hip, knee, and ankle angle traces; iliac-hip, hip-knee, knee-ankle, and ankle-foot length traces. The skeleton plot annotates swing duration, step length, iliac/hip/knee/ankle/ foot translations, and each joint's minimum, maximum, and range of motion. This is the app version of the earlier per-step gait figure; the figures no longer need to be generated as an intermediate image set merely to inspect each step. Step Segmentation The default treadmill detector operates on the smoothed foot X coordinate. A lift-off candidate is a local maximum whose prominence is at least the configured threshold and whose height is at least the configured mean-minus-sigma standard-deviation floor. Candidates closer than the minimum interval retain the higher peak. Every accepted lift-off is independently paired with the lowest subsequent foot X value before the next lift-off, or before the recording ends. That following minimum is the landing event. This pairing is important: a completed last swing remains a valid step even when the recording ends before another lift-off. Earlier contact-to-contact implementations lost that final step. These are image-kinematic treadmill events, not force-plate contact measurements. When a later lift-off exists, the app additionally derives cycle time, stance time, cadence, and duty factor. Those values are legitimately unavailable for the final independently complete swing if no next lift-off was recorded. Parameters Parameter Default Meaning Iliac, hip, knee, ankle, foot point Matching role name Exact, case-insensitive source point assigned to each anatomical role. Frame rate `0` until supplied by the source Used only when the source has no usable per-frame time vector. Current Video Marker projects populate it automatically. Pixels per unit `1` Pixel density for physical outputs. A valid Video Marker calibration replaces it automatically. Unit name `px` Physical unit label associated with pixels per unit. First-frame first point as origin Off Shifts scaled coordinate exports; raw pixel coordinates remain unchanged. Smooth window 5 frames Centered finite-value mean applied independently to each point and axis. Minimum foot-X prominence 20 source units Minimum lift-off peak prominence inherited from the legacy treadmill workflow. Peak-height sigma 2 Requires a lift-off peak to be at least mean foot X minus this many standard deviations, matching the legacy treadmill detector. Minimum interval 0.2 s Minimum time between lift-off peaks; converted to frames from source timing. Minimum swing 3 frames Minimum accepted inclusive lift-off-to-landing span and timing-free separation fallback. Maximum swing 300 frames Maximum accepted inclusive lift-off-to-landing span. Minimum step length 1 output unit Minimum two-dimensional foot endpoint displacement. Maximum hip translation 1,000,000 output units Upper endpoint-displacement QC rule; the default normally leaves it inactive. Changing any parameter invalidates the previous result. Repeating a run with the same source and parameters is skipped when its deterministic fingerprint is unchanged. Calculations For each frame the app calculates unsigned 0-180 degree hip, knee, and ankle angles from adjacent segment vectors and four Euclidean segment lengths. A nonfinite or zero-length vector produces `NaN`; the app does not invent an angle. For each lift-off-to-landing step it calculates: swing duration; two-dimensional foot endpoint displacement, reported as step length; two-dimensional endpoint translation for iliac, hip, knee, ankle, and foot; minimum, maximum, and range of motion for hip, knee, and ankle; cycle time, stance time, cadence per minute, and duty factor when the next lift-off exists; validity and a concrete rejection reason for duration, step length, or hip translation rules. The former scripts sometimes called foot displacement `stride_length` even though only one active swing was measured. Version 2 uses `step_length` so the column name matches the calculation. Outputs **Export CSV set** writes: `_frames.csv`: frame/time, step membership, event flags, three joint angles, four segment lengths, and scaled point coordinates; `_coordinates.csv`: raw pixel and scaled/origin-shifted coordinates; `_steps.csv`: event boundaries, timing, cadence/duty factor, step length, five point translations, joint extrema/ROM, validity and reason; `_summary.csv`: source geometry, counts, valid-step means, and global joint extrema; `_gait.labkit.json`: provenance manifest for inputs, parameters, outputs, and valid-step count. Use Without The GUI pose = gait_analysis.sourceFiles.readPoseFile(\"marker-project.mat\"); opts = gait_analysis.analysisRun.optionsForPose( ... pose, gait_analysis.analysisRun.defaultOptions()); result = gait_analysis.analysisRun.computeGait(pose, opts); writetable(result.stepTable, \"steps.csv\"); `computeGait` performs no UI or file writes. Its result contains `frameTable`, `coordinateTable`, `stepTable`, `summaryTable`, paired `events`, normalized `options`, `ok`, and `message`. Limitations Kinematic lift-off and landing are not substitutes for force or pressure measurements. The default detector reflects lateral treadmill motion and assumes the foot travels toward increasing X before returning toward decreasing X. Smoothing and prominence affect event boundaries and must be reported with scientific results. Tracking confidence and manual/predicted frame status are preserved from Video Marker but are not yet automatic step-exclusion rules. Group statistics and between-animal inference are outside this single- recording app; exported step tables are the boundary for that workflow. Related Functions And Apps `gait_analysis.sourceFiles.readPoseFile` `gait_analysis.analysisRun.defaultOptions` `gait_analysis.analysisRun.optionsForPose` `gait_analysis.analysisRun.computeGait` [Video Marker](../../image-measurement/video-marker/README.md) [Gait apps](../README.md) gait_analysis.analysisRun.computeGait Calculate joint, segment, swing, and step-quality measurements. gait_analysis.sourceFiles.readPoseFile Read one current Video Marker MAT project for gait analysis."},{"title":"Image Measurement Apps","url":"apps/image-measurement/index.html","kind":"app family","section":"apps","keywords":"apps image measurement Image Measurement Apps","text":"Image Measurement Apps The Image Measurement family turns image and video data into repeatable crops, calibrated measurements, thermal readings, fused images, appearance pipelines, and landmark coordinates. Scientific and workflow-specific choices remain app-owned; generic image IO and primitives come from `labkit.image` and `labkit.thermal`. Choose An App Goal App Main result Apply repeatable crop geometry to many images [Batch Image Crop](batch-crop/README.md) same-size crop images and manifest Measure a fitted circular arc and traced length [Curvature Measurement](curvature/README.md) radius, curvature, length, overlay Decode and measure radiometric FLIR images [FLIR Thermal](flir-thermal/README.md) Celsius matrix, readings, rendered image Fuse multiple focal planes [Focus Stack](focus-stack/README.md) all-in-focus image and depth index map Apply an ordered enhancement pipeline [Image Enhance](image-enhance/README.md) enhanced images and processing history Transfer appearance from a reference image [Image Match](image-match/README.md) matched images and match history Mark ordered landmarks across video [Video Marker](video-marker/README.md) recoverable project and coordinate tables Shared Interaction Conventions Managed points, rectangles, scale references, and overlays remain editable without resetting the current zoom. A new source or project may start at a home view; ordinary marker placement, dragging, ROI resizing, and annotation refresh preserve the user's viewport. Canvas subtitles and action labels describe the active interaction mode. Image coordinates use `[x y]` with `x` as column and `y` as row. Rectangle geometry uses `[x y width height]` unless a linked API states that it accepts two corners. Calibrated measurements always retain the pixel-domain source geometry needed for audit. Numeric And Display Boundaries Display palettes, axes zoom, overlay opacity, marker appearance, and preview downsampling do not change scientific arrays. Crop geometry, scale calibration, thermal conversion, fusion parameters, enhancement steps, and coordinate transforms do affect output and are stored in project state or result manifests. Related Modules [Image Library](../../libraries/image/README.md) [Thermal Library](../../libraries/thermal/README.md) [App Framework interactions](../../framework/README.md) [All Apps](../README.md)"},{"title":"Batch Image Crop","url":"apps/image-measurement/batch-crop.html","kind":"app","section":"apps","keywords":"apps image measurement batch crop Batch Image Crop","text":"Batch Image Crop Batch Image Crop defines one crop task per image, previews rotation and edge-continuous padding, and exports repeatable same-size crops in pixel or physical-scale mode. Requirements And Launch labkit_BatchImageCrop_app Inputs Use **Add images or folder** to load supported image files. A folder selection loads supported files from that folder. Each list row stores its own crop center, rotation, padding, and optional scale calibration. **Duplicate image** creates another task for the same source so multiple ROIs can be exported without loading duplicate files. Basic Workflow Load images and select a task. Set crop width and height or switch to physical mode. Click anywhere in the preview to place the crop center. Drag the center marker or anywhere inside the highlighted ROI to move it, or enter center X/Y. Set rotation and padding. Calibrate physical scale when required. Duplicate tasks for additional ROIs. Choose format and output folder, then export. The preview rectangle remains draggable by either its center marker or its interior; a click without dragging sets its center at that preview location, including inside the rectangle. Change crop width and height with their controls. Editing the ROI updates the selected task immediately; switching tasks restores that task's geometry. ROI refreshes preserve axes zoom. Pixel Mode Default crop dimensions are 1024 by 1024 pixels. Rotation defaults to 0 degrees and padding to 0%. Center controls use source-image coordinates. When crop geometry crosses the rotated image boundary, edge-continuous padding supplies pixels so every exported crop retains the requested dimensions. Physical Mode And Scale Physical mode specifies width and height in the selected unit (default `um`). Each source image requires a valid pixels-per-unit calibration. Measure a known reference line, enter its physical length, and verify the displayed pixels/unit. `targetPixelsPerUnit=0` selects an automatic common output scale; a positive value requests an explicit scale. The max-upsample warning defaults to 15%. It warns when a task would require substantial interpolation; it does not silently change the requested physical geometry. Crop and calibration units are converted explicitly. Scale Bar The scale-bar length defaults to 100 selected units, at Bottom right, in Black. Measure the reference first, then place the bar. The bar is an export overlay; its placement does not change crop geometry or scale calculations. Outputs Exports support PNG, TIFF, and JPEG. Each task produces one image at the planned output dimensions. The crop manifest records source, task identity, center, rotation, padding, source/output scale, requested physical geometry, format, and output filename. A second LabKit result JSON records project-wide parameters and identifies each output file. Project And Recovery Saved projects preserve one independent task per list row, so duplicated tasks can resolve to the same image while retaining separate crop geometry and calibration. Crop dimensions, physical-scale settings, output format, output folder, and scale-bar choices are also saved. Image pixels and preview caches are reconstructed from the sources after load rather than embedded in the project. Older projects are upgraded on open while preserving each task's center, rotation, padding, and calibration. If a source moved, the standard relinking flow asks for its new location. Use Without The GUI calibration = labkit.app.interaction.scaleCalibration(20, 10, \"um\"); items = struct(\"scaleCalibration\", calibration); physicalOptions = struct( ... \"physicalWidth\", 5, \"physicalHeight\", 3, ... \"scaleUnit\", \"um\", \"targetPixelsPerUnit\", 4); plan = batch_crop.cropGeometry.scalePlan(items, physicalOptions); imageData = reshape(uint8(1:100), 10, 10); cropOptions = struct(\"centerXY\", [5 5]); cropped = batch_crop.cropGeometry.cropScaledImage( ... imageData, cropOptions, plan); For pixel-domain work use `batch_crop.cropGeometry.cropImage`. Open the linked API pages for exact plan fields, interpolation, and padding behavior. Function Reference The generated crop-geometry pages document pixel coordinates, rounding, padding, scaling plans, per-image option fields, defaults, output metadata, failure behavior, and related APIs. Start with [`cropScaledImage`](../../../reference/api/batch_crop/cropGeometry/cropScaledImage.html) when reproducing a physical-scale export outside the GUI. Errors And Limitations Physical export requires a finite positive calibration for every task. Large upsampling cannot restore information absent from the source. Rotation and scale resampling can soften high-frequency detail. JPEG is lossy; use PNG or TIFF for quantitative downstream image analysis. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) batch_crop.cropGeometry.cropImage Rotate and crop an image at a fixed pixel size. batch_crop.cropGeometry.cropScaledImage Crop one calibrated image to a common physical output scale. batch_crop.cropGeometry.scalePlan Plan equal-size physical crops across calibrated images."},{"title":"Curvature Measurement","url":"apps/image-measurement/curvature.html","kind":"app","section":"apps","keywords":"apps image measurement curvature Curvature Measurement","text":"Curvature Measurement Curvature Measurement fits a circle to an ordered image curve, reports radius and curvature, measures traced arc length, and supports pixel-to-physical scale calibration. Requirements And Launch labkit_CurvatureMeasurement_app Basic Workflow The **Files + Analysis** tab contains the complete workflow: Choose an image in the **Image** section. Use **Measure reference pixels**, or enter **Reference pixels** directly, then enter the known reference length and unit. Configure and place the optional display scale bar. Use **Start curve edit** and place ordered anchors along the feature. Drag anchors to refine the trace; undo or clear as needed, then finish curve editing. Choose the densification settings, fit the circle, and measure curve length. Export the result CSV and overlay PNG. The edit buttons change to **Finish curve edit** or **Finish reference edit** while their managed interaction is active. Curve and reference edits are mutually exclusive. Anchor edits and result overlays preserve the current axes zoom. The **Summary + Results** tab reports curve length, radius, curvature, RMSE, fit center, and pixels per selected unit. **Details** explains the next valid step before a result exists and reports the current measurement afterward. **Tools > Diagnostics > Open Session Log...** records file, edit, fit, calibration, export, and runtime actions without consuming a workflow tab. The chosen image is saved as a portable project source. Older projects are upgraded on load without changing the curve, calibration, or result meaning. Curve Editing Curve points are ordered by placement. **Undo last point** removes the newest anchor and **Clear curve** removes the complete trace. Neighboring duplicate points are removed before numeric fitting. At least three distinct points are required for a circle fit; length requires at least two. Fit Parameters And Semantics **Densify before circle fit** is enabled by default with 300 dense points. Densification interpolates along the ordered trace before the least-squares circle fit; it changes sampling density, not the manually defined path. **Show dense fit points** is presentation-only. For fitted radius `r`: curvature = 1 / r Pixel-domain radius and curvature are always available. With calibration, radius and traced length are converted to the selected physical unit and curvature uses its reciprocal unit. Curve length is the sum of distances along the ordered polyline/densified curve specified by the API options; it is not the circumference of the fitted circle. Scale Calibration And Bar Measure the pixel distance spanning a known reference, enter its physical length, and choose `m`, `cm`, `mm`, `um`, or `nm`. Calibration stores reference pixels, reference length, unit, and pixels per unit. Scale-bar length defaults to 100 units; position defaults to Bottom right and color to Black. Placing or moving the display bar does not modify the fit. Outputs The CSV records point count, fit settings, center, radius, curvature, traced length, calibration, units, and status. The overlay PNG contains the source image, ordered curve, optional dense samples, fit residuals, fitted circle, center, and configured scale bar when available. Each export writes its standard result manifest with source and parameter provenance. Use Without The GUI points = [100 80; 130 58; 165 52; 200 66; 225 95]; fit = curvature.analysisRun.computeCurvatureFit(points, ... struct(\"densify\", true, \"densePointCount\", 300)); lengthResult = curvature.analysisRun.computeCurveLength(points, struct()); Errors And Limitations Collinear or nearly collinear points do not define a stable finite circle. Calibration uncertainty propagates directly to physical radius and length. A circle fit summarizes a curved segment; it does not prove the feature is truly circular. Inspect residuals and the overlay. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) curvature.analysisRun.computeCurvatureFit Fit a circle and curvature to traced image points. curvature.analysisRun.computeCurveLength Measure the polyline length of traced image points."},{"title":"FLIR Thermal","url":"apps/image-measurement/flir-thermal.html","kind":"app","section":"apps","keywords":"apps image measurement flir thermal FLIR Thermal","text":"FLIR Thermal FLIR Thermal decodes radiometric FLIR JPEG/RJPEG files, displays calibrated temperature maps, measures rectangular ROI hot/cold/mean values, and exports rendered images with Celsius data. Requirements And Launch A `.jpg`, `.jpeg`, or `.rjpg` extension alone is insufficient; the file must contain a readable FLIR raw thermal record and calibration metadata. labkit_FLIRThermal_app Inputs And Navigation Use **Add FLIR files or folder** to load one or more radiometric images. The selected row is the current image; Previous/Next change selection without discarding per-image display range or measurement annotations. Source files are read-only. Basic Workflow Load radiometric files and inspect the decoded min/max/metadata summary. Choose a palette and color mapping. Set a range preset, per-image range, or shared group range. Choose ROI hot spot, ROI cold spot, or ROI mean, then drag its rectangle. Optionally click the image for an independent manual point reading. Review the numeric results and markers. Export the current image or the full batch. Placing or dragging a reading ROI and refreshing its marker preserves the current zoom. The reading is recalculated from the thermal matrix, not from screen colors. Temperature Conversion `labkit.thermal.readFile` extracts the raw sensor matrix and converts it to Celsius. Environment-corrected conversion uses Planck constants plus available emissivity, object distance, atmospheric/window transmission, reflected temperature, and related metadata. Missing environmental fields may use documented defaults and are listed in `record.metadata.temperatureConversion`; required Planck constants are never invented. Absolute temperature accuracy depends on camera calibration and environmental assumptions. Preserve conversion diagnostics with reported measurements. Display Controls Palettes are Turbo, Iron, Hot, Parula, and Gray. Mapping modes are Linear, Log, and Gamma; gamma defaults to 2.2 and is limited to 0.1-5. Display ranges are stored per image. Actions can set each image independently, apply one shared group range, fit the current image, or round configured bounds. Palette, mapping, gamma, and color limits affect only mapping into display colors. They do not transform the Celsius matrix or exported temperature values. ROI Readings The rectangular ROI includes both boundary pixels after its two corners are rounded and clamped to the thermal matrix. Hot and cold readings report value and `[x y]` location. Mean ignores non-finite pixels and also records ROI geometry and finite pixel count. The marker is a display annotation; moving it does not change source data. Outputs Current or batch export writes PNG, TIFF, or JPEG rendered thermal images, matching color scale graphics, Celsius CSV matrices, measurement values, a batch CSV summary, and the standard `flir_thermal.labkit.json` result manifest. The clean image export excludes interactive toolbar chrome. Numeric temperature outputs remain Celsius regardless of palette or mapping mode. Project And State Saved projects keep portable source references, display parameters, export settings, and lightweight per-image ranges and readings. Raw sensor matrices and decoded Celsius matrices are loaded again from the selected source when a project opens. Missing source files use the standard relinking flow rather than relying on a local absolute path. An existing source that is no longer a readable radiometric file aborts the restore and preserves the current document. Batch import may still report and skip rejected selections before they become project sources. An empty launch does not choose an output directory. Adding files establishes the source-adjacent default; **Choose folder** remains available before export. Use Without The GUI record = labkit.thermal.readFile(\"capture.rjpg\"); temperatureC = record.temperatureC; point = flir_thermal.analysisRun.pointTemperatureReading( ... temperatureC, [120 80]); [hot, cold, meanReading] = ... flir_thermal.analysisRun.roiTemperatureMeanReading( ... temperatureC, [90 60], [169 129]); [imageHot, imageCold] = ... flir_thermal.analysisRun.extremeTemperatureReadings(temperatureC); Function Reference The generated pages for [`pointTemperatureReading`](../../../reference/api/flir_thermal/analysisRun/pointTemperatureReading.html) and [`extremeTemperatureReadings`](../../../reference/api/flir_thermal/analysisRun/extremeTemperatureReadings.html) specify coordinate order, clamping, nonfinite/empty behavior, returned fields, conversion failures, and related measurement APIs. Errors And Limitations Non-radiometric JPEG files are rejected even when their extension is valid. Coordinates refer to the decoded thermal matrix, not a separately scaled visible-light image. An ROI containing no finite temperature pixels cannot return a finite mean. JPEG export is visual and lossy; keep Celsius data for quantitative work. Related Topics [Thermal Library](../../../libraries/thermal/README.md) [Image Measurement family](../README.md) [API Reference](../../../reference/README.md) flir_thermal.analysisRun.extremeTemperatureReadings Locate finite global thermal extrema. flir_thermal.analysisRun.pointTemperatureReading Read the nearest calibrated thermal pixel. flir_thermal.analysisRun.roiTemperatureMeanReading Measure an inclusive rectangular thermal ROI."},{"title":"Focus Stack","url":"apps/image-measurement/focus-stack.html","kind":"app","section":"apps","keywords":"apps image measurement focus stack Focus Stack","text":"Focus Stack Focus Stack fuses at least two focal planes into one all-in-focus image using multilevel Laplacian focus evidence and exports a focus-depth index map. Requirements And Launch Inputs should show the same field of view at different focus positions. labkit_FocusStack_app Inputs Add selected image files or choose a folder. Remove blurred, displaced, or otherwise invalid frames before running. The first image defines working geometry; differently sized inputs are resized and the result records how many images required resizing. Basic Workflow Load at least two focal planes. Remove frames that do not belong to the stack. Start with the Balanced preset. Enable auto-registration when the stack has small rigid camera motion. Tune detail scale, blend radius, and uncertain blend only when artifacts are visible. Run the stack and inspect both fused image and focus map. Export fused PNG, focus-map PNG, and summary CSV. Fusion Parameters Parameter Default Meaning Preset Balanced coordinated starting values; Crisp favors fine detail, Smooth favors seam suppression, Noisy favors grain tolerance Auto-register off register images to the middle image before fusion Detail scale 31 px odd local window for focus-energy evidence Blend radius 4 px smooth focus-selection weights across boundaries Uncertain blend 5% blend competing planes where focus confidence is low The public calculation API also accepts `minConfidence` (default 0.05) and `pyramidLevels` (default 4, bounded by image geometry). Algorithm Images are normalized to grayscale or RGB double data on common geometry. A Gaussian/Laplacian pyramid is built for each plane. Local squared Laplacian detail energy chooses the strongest plane at each level. Confidence is the relative separation between the best and second-best evidence. High-confidence regions use the winning plane. Low-confidence regions blend normalized evidence weights; zero-evidence regions use equal weights. The weights are spatially smoothed by the level-scaled blend radius and the fused pyramid is reconstructed and clamped to `[0,1]`. The focus index is the winning input plane at full resolution. Focus coverage is the fraction of pixels assigned to each plane. The algorithm does not correct parallax, deformation, moving objects, illumination changes, or severe registration error. Outputs fused all-in-focus image; integer focus-depth index map; confidence map and per-plane focus coverage in the run result; summary CSV with geometry, parameters, confidence, coverage, registration, and resize information; result provenance manifest. Use Without The GUI images = {imread(\"z01.png\"), imread(\"z02.png\"), imread(\"z03.png\")}; options = struct(\"focusWindow\", 31, \"smoothRadius\", 4, ... \"minConfidence\", 0.05, \"pyramidLevels\", 4); result = focus_stack.analysisRun.computeFocusStack(images, options); assert(result.ok, result.message); imwrite(result.fused, \"stacked.png\"); Errors And Limitations Fewer than two images is an error. Reopening a saved stack is strict: an existing image that cannot be decoded aborts restore and leaves the current document unchanged. `minConfidence` must be finite and between 0 and 1. Resizing changes sampling and should not substitute for consistent capture. Auto-registration should be reviewed; a wrong registration can create a sharp-looking but geometrically false composite. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) focus_stack.analysisRun.computeFocusStack Fuse a stack of images with different focus planes."},{"title":"Image Enhance","url":"apps/image-measurement/image-enhance.html","kind":"app","section":"apps","keywords":"apps image measurement image enhance Image Enhance","text":"Image Enhance Image Enhance builds an ordered, reversible processing history for one image or a batch and exports the resulting images with the exact step sequence. Requirements And Launch All processing uses MATLAB and repository-owned code. labkit_ImageEnhance_app Inputs And Batch Mode Add supported image files or a folder. In **Batch shared processing** mode, one shared step history is applied to every source. When batch mode is off, each image keeps a separate history and optional white ROI. Selecting another image updates the preview without recalculating unrelated files. Basic Workflow Load images and select a representative source. Decide whether steps are shared across the batch. Choose a tool and set its controls. For White ROI calibration, draw the white-background ROI first. Apply the tool to history and inspect Enhanced, Original, or Before | After. Undo or reset history as needed. Choose output format/folder and export. Each **Apply tool** action saves one processing step. Panner changes preview the pending tool without changing the saved history until **Apply tool** is chosen. ROI creation and overlay refresh preserve the current zoom. Tools And Defaults Tool Primary control Secondary control Brightness/contrast Brightness 0% Contrast 15% Local contrast Clarity 30% Radius 12 px Sharpen Sharpen 35% Radius 1.5 px Hue/saturation Hue 0 degrees Saturation 10% White balance Strength 100% Temperature 0% White ROI calibration Strength 100% White target 92% Subject-preserving enhance Strength 70% Background target 90% Processing converts input to RGB double in `[0,1]`, applies steps in table order, and clamps the final image. Brightness, local contrast, sharpening, hue/saturation, and gray-world white balance call the linked `labkit.image` primitives. White ROI calibration requires a valid rectangle for every affected image. It builds a softened background mask from the rectangle and adjusts luminance toward the target while limiting subject changes. Subject-preserving enhance estimates a low-saturation bright background automatically and uses a whole image fallback only when no usable background support is found. History And Reproducibility The history table records step kind and settings in execution order. **Undo history** removes the latest applied step; **Reset history** returns to the normalized original. Source images are not modified. Shared and per-image histories are stored separately in project state so switching batch mode does not ambiguously merge them. Outputs Export supports PNG, TIFF, and JPEG. Each output is rendered from the source plus its effective stored pipeline, not from a downsampled preview. The export manifest records source, ordered steps, ROI geometry, batch mode, format, and output filename. A LabKit result JSON records the complete output set and the project parameters used to create it. Use Without The GUI steps = [ ... image_enhance.analysisRun.makeStep(\"Brightness/contrast\", 0, 15), ... image_enhance.analysisRun.makeStep(\"Local contrast\", 30, 12), ... image_enhance.analysisRun.makeStep(\"Sharpen\", 35, 1.5)]; output = image_enhance.analysisRun.applyPipeline(imread(\"source.png\"), steps); imwrite(output{1}, \"enhanced.png\"); Project And State Saved projects keep portable source references, shared and per-image step histories, white-reference ROIs, export settings, and compact result metadata. Full-size pixels and downsampled previews are loaded again from the selected source when a project opens rather than embedded in the project. An empty launch does not choose an output directory. Adding images establishes the source-adjacent default; **Choose folder** remains available before export. Errors And Limitations Unknown step labels are rejected. Reopening a project with an existing unreadable image aborts restore and preserves the current document instead of displaying an empty preview. White ROI calibration fails when a required image has no valid ROI. Enhancement changes pixel values and can invalidate quantitative intensity analysis; retain the source and manifest. JPEG export adds lossy compression after processing. Related Topics [Image Match](../image-match/README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) image_enhance.analysisRun.applyPipeline Replay an ordered non-destructive image enhancement history."},{"title":"LabKit Apps","url":"apps/index.html","kind":"overview","section":"apps","keywords":"apps LabKit Apps","text":"LabKit Apps LabKit apps are independent MATLAB tools for complete laboratory workflows. Choose an app by the result you need, then use its app page for supported inputs, controls, interaction behavior, calculations, outputs, recovery, and programmatic APIs. Start An App Open `labkit_launcher`, select one app, and choose **Open**. The launcher's **Documentation and History** action opens the selected app page. Source-checkout users can also call an app command after adding LabKit to the MATLAB path: labkit_launcher % Or, after repository path setup: labkit_CIC_app See [Getting Started](../getting-started/README.md) for installation, updating, version selection, and source-checkout setup. The [LabKit Launcher manual](labkit-core/launcher/README.md) documents its complete interactive and programmatic surface. Choose An App Task App Input Principal output Discover, install, launch, diagnose, and package LabKit apps [LabKit Launcher](labkit-core/launcher/README.md) Installed or source LabKit tree Running app, maintenance report, or deployment ZIP Register, crop, and mask image pairs for DIC [DIC Preprocess](dic/dic-preprocess/README.md) Reference and moving images Aligned images, crops, mask Render and summarize Ncorr strain results [DIC Postprocess](dic/dic-postprocess/README.md) Ncorr MAT, reference image, mask Strain overlays and summary CSV Overlay chrono voltage and current traces [Chrono Overlay](electrochemistry/chrono-overlay/README.md) Chrono DTA Plot and CSV Measure charge-injection capacity [CIC](electrochemistry/cic/README.md) Chrono DTA CIC result table and CSV Compare cyclic and time-domain CSC [CSC](electrochemistry/csc/README.md) CV/CT DTA Per-cycle CSC and CV data Inspect impedance curves [EIS](electrochemistry/eis/README.md) EIS DTA Nyquist/Bode plots and CSV Estimate voltage-transient resistance [VT Resistance](electrochemistry/vt-resistance/README.md) Chrono DTA Resistance table and CSV Convert tracked points into gait metrics [Gait Analysis](gait/gait-analysis/README.md) Current Video Marker project MAT Frame, step, coordinate, and summary tables Crop image batches at repeatable geometry [Batch Image Crop](image-measurement/batch-crop/README.md) Image files Same-size crops and manifest Measure curve radius, curvature, and length [Curvature Measurement](image-measurement/curvature/README.md) Image Overlay and measurement CSV Decode and measure radiometric images [FLIR Thermal](image-measurement/flir-thermal/README.md) FLIR radiometric image Temperature data, measurements, rendered image Fuse focal planes [Focus Stack](image-measurement/focus-stack/README.md) Aligned image stack Fused image and focus map Apply a repeatable enhancement pipeline [Image Enhance](image-measurement/image-enhance/README.md) Image files Enhanced images and manifest Match appearance to a reference image [Image Match](image-measurement/image-match/README.md) Source and reference images Matched images and manifest Annotate ordered landmarks across video [Video Marker](image-measurement/video-marker/README.md) Video Project MAT and coordinate CSV Restyle figures and export visible plot data [Figure Studio](labkit-core/figure-studio/README.md) FIG or popout axes Styled figure and plot data Inspect RHS recordings and define channels [RHS Preview](neurophysiology/rhs-preview/README.md) RHS file or folder Preview, protocol JSON, filter record Detect trains and measure neural responses [Nerve Response Analysis](neurophysiology/nerve-response-analysis/README.md) Filter record and protocol JSON Analysis JSON and CAP metrics Review aligned responses and statistics [Response Review and Stats](neurophysiology/response-review-stats/README.md) Analysis JSON or segment CSV Metrics CSV and summary Compare multiple groups with the first using t-tests and one mean/SD plot [T-Test Wizard](statistics/ttest-wizard/README.md) CSV, TSV, workbook, or entered values Result family, CSVs, and comparison plot Inspect ECG and measure segment SNR [ECG Print](wearable/ecg-print/README.md) MAT or delimited table Segment SNR CSV and waveform image Browse By Family [DIC](dic/README.md) - preparation and postprocessing around a DIC solver. [Electrochemistry](electrochemistry/README.md) - DTA-based chrono, CV/CT, impedance, charge, and resistance workflows. [Gait](gait/README.md) - pose-coordinate analysis and gait metrics. [Image Measurement](image-measurement/README.md) - calibrated image, thermal, annotation, crop, fusion, and appearance workflows. [LabKit Core](labkit-core/README.md) - the launcher and general MATLAB graphics tools. [Neurophysiology](neurophysiology/README.md) - RHS inspection, response analysis, and review. [Statistics](statistics/README.md) - explicit first-versus-each t-tests and result-based group plotting. [Wearable](wearable/README.md) - wearable biosignal workflows. How To Read An App Page Concrete app pages use a common MATLAB-style order: purpose, requirements, and launch command; supported inputs and the shortest successful workflow; controls and interaction behavior; calculation or algorithm semantics, units, and assumptions; outputs, project files, autosave, and recovery behavior; GUI-free MATLAB examples and public app-owned APIs; errors, limitations, troubleshooting, related topics, and history. The app page documents stable user-visible behavior. Exact callable syntax and data shapes live on the linked API reference pages. Internal callbacks and private implementation helpers are intentionally omitted. Shared framework contracts are documented once in the [App Framework](../framework/README.md), not repeated in every App page. An App page mentions shared behavior only when that App changes it or when the behavior is necessary to complete the App's workflow. Common App Behavior The App Framework owns lifecycle, busy state, file selection, state snapshots, screenshot actions, plot tools, and managed interactions. Apps own scientific choices, workflow-specific defaults, result schemas, and exports. See the [App Framework](../framework/README.md) for behavior shared across apps. Every App opens as a clean project. Use **Tools > Diagnostics** to inspect the current session history, enable future trace capture, or export a diagnostic bundle after a problem. Apps with declared sample generation expose **Tools > Developer Tools > Generate Synthetic Inputs...**; generation writes anonymous inputs without loading them or changing the open project. The [runtime guide](../framework/guides/runtime.md) defines these shared contracts. Action and input-selection buttons provide concise hover help. The shared **Tools** menu contains plot, screenshot, and project-state actions when the corresponding capability is available. Input data and exported results should remain outside the replaceable LabKit runtime folder. Apps do not overwrite source files unless an app page states an explicit in-place operation. Programmatic Use Important scientific and deterministic app operations can be called without opening the GUI. Each app page identifies its supported app-owned functions and links them to the generated [API Reference](../reference/README.md). Reusable file parsers and generic processing functions live in the public `labkit.*` libraries. MATLAB must be able to see both the repository root and the owning app root. The launcher prepares these paths automatically. In a source checkout, add them explicitly before calling an app package: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(repoRoot) addpath(fullfile(repoRoot, \"apps\", \"electrochem\", \"cic\")) help cic.analysisRun.computeCIC For another app, replace the final app path with the directory that directly contains its `+package` folder. Do not add every repository subfolder with `genpath`; app roots are independent and may contain same-named private or development files that should not become global MATLAB commands. Related Documentation [Getting Started](../getting-started/README.md) [LabKit Launcher](labkit-core/launcher/README.md) [App Framework](../framework/README.md) [API Reference](../reference/README.md) [App Development](../development/build-apps/app-development.md) [Component History](../history/README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics."},{"title":"Image Match","url":"apps/image-measurement/image-match.html","kind":"app","section":"apps","keywords":"apps image measurement image match Image Match","text":"Image Match Image Match transfers tone and color statistics from one reference image to one or more source images while preserving each source image's geometry. Requirements And Launch The app performs appearance matching only; it does not geometrically register images. labkit_ImageMatch_app Inputs Choose one reference image and add source images or a source folder. The reference supplies appearance statistics and is not exported as a matched source. All images are normalized to RGB double data in `[0,1]`; output retains the source height and width. Basic Workflow Choose the reference and source images. Select a matching method. Set overall, tone, and color strength. Apply the match to history. Inspect Matched, Original, and Before | After views. Undo/reset history or add another step. Export matched images. Changing method or strength previews the pending match; **Apply match** saves the settings as the next step in the image's processing history. Matching Methods Method Behavior Balanced robust white-point match followed by Lab-style tone/color transfer White balance match robust bright-neutral RGB white points Tone only quantile-match Lab lightness only Protected tone constrained luminance/background adjustment with shadow, highlight, and saturation guards Lab style quantile-match lightness and covariance-match Lab chroma Histogram independently quantile-match all three Lab channels Overall strength blends the matched result with the original. Tone and color strength separately blend lightness and chroma behavior where the selected method supports them. All three default to 100%. Robust statistics and covariance regularization handle flat or nearly single-color images, but a visually dissimilar reference can still produce an unhelpful result. Matching reproduces distributions, not semantic lighting or camera calibration. History And Outputs Each source is recomputed from its original plus the ordered match history. Undo removes the latest step and reset removes all steps. Export supports PNG, TIFF, and JPEG and writes an image manifest containing reference/source identity, method, strengths, history, format, and output filenames. Source and reference files are never overwritten. Use Without The GUI source = imread(\"source.png\"); reference = imread(\"reference.png\"); step = image_match.analysisRun.makeStep(\"Balanced\", 100, 100, 100); matched = image_match.analysisRun.applyMatch(source, reference, step); % Apply a stored sequence: outputs = image_match.analysisRun.applyPipeline({source}, step, reference); output = outputs{1}; Function Reference The generated pages for [`applyMatch`](../../../reference/api/image_match/analysisRun/applyMatch.html) and [`applyPipeline`](../../../reference/api/image_match/analysisRun/applyPipeline.html) document supported step fields and defaults, normalization and output geometry, empty-input behavior, failures, examples, and related APIs. Errors And Limitations An empty reference returns normalized source data in the calculation API, but the app requires a reference before enabling match actions. Reopening a project with an existing unreadable reference or source image aborts restore and preserves the current document. Matching never corrects position, scale, rotation, perspective, or parallax. Histogram and Lab matching can shift scientifically meaningful pixel values; retain original images for quantitative analysis. JPEG export is lossy. Related Topics [Image Enhance](../image-enhance/README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) image_match.analysisRun.applyMatch Transfer reference appearance statistics to one image. image_match.analysisRun.applyPipeline Replay ordered reference-match steps over source images."},{"title":"Video Marker","url":"apps/image-measurement/video-marker.html","kind":"app","section":"apps","keywords":"apps image measurement video marker Video Marker","text":"Video Marker Video Marker defines an ordered landmark skeleton, records coordinates across video frames, predicts forward positions between manual anchors, and saves a portable project with an explicit source-adjacent autosave copy. Requirements And Launch The app declares compatibility with `labkit.app` 1.x and uses image functions shipped with the same workbench. Video decoding uses MATLAB's available video support. Predictive navigation is implemented in repository-owned MATLAB code; no model weights or third-party runtime package are downloaded. labkit_VideoMarker_app Start Or Open A Project The **Session** panel appears first in the Video tab. **Open MAT** uses the same loader as the window's top-level Load State action and accepts an explicit project or compatible autosave. **Save autosave** immediately updates `Video Marker Autosaves/.video_marker.autosave.mat` beside the source video without asking for a path and without turning that recovery copy into the named project file. **New setup** offers Cancel, Save and start new, or Discard and start new; it never silently clears the current session. For a new project, choose a preset or add keypoints, edit their names, reorder them, and define connections. **Connect in order** links every adjacent pair. At least one keypoint is required before opening a new annotation session. The skeleton is locked after the video opens so coordinate column meaning remains stable. Marking Workflow Define the ordered skeleton and optional scale calibration. Open the video. Click blank image locations to place the next missing keypoint in order. Drag an existing marker to refine it. Use Undo last point or Clear frame points when needed. Move to the next frame; the current frame is saved automatically. Correct predicted frames periodically and export or save the project. Point marking remains active while a video is open. A complete edited frame is a manual anchor. Moving forward predicts every ordered point using cropped multiscale patch matching. The tracker compares mean-centered local patches by normalized correlation, refines accepted matches to subpixel coordinates, and reports confidence for each point. A rejected point retains its bounded motion- prior estimate. Predicted frames remain editable drafts. Editing a complete frame makes it a new manual anchor for later prediction. Jumping forward propagates through intermediate frames and does not overwrite existing manual anchors. Frame navigation and marker refresh preserve the current zoom. Opening a new video or project starts at its own home view. Scale And Coordinate Export Scale calibration stores a measured pixel distance, known length, and unit. The coordinate export options are: units: `pixels` or `calibrated_physical`; origin: `top_left_pixel_center` or `first_point`; Y axis: `up` or `down`; inclusive start and end frame. Marker CSV is the round-trip editing format. Coordinate CSV is the plotting-oriented table after optional calibration, origin shift, and Y-axis conversion. Raw pixel coordinates remain available in the project. Autosave, Recovery, And Portability **Save autosave** writes atomically to `Video Marker Autosaves` beside the source video. It is an explicit action, not a background timer. Autosave and named project MAT files contain the same project data: frame count, frame rate, duration, image dimensions, skeleton edges, annotation status/source, calibration, and durable coordinates. The video itself remains an external portable source and can be relinked without discarding annotations. When a project tree moves between folders, users, or operating systems, the relative reference is tried first. If no candidate exists, the app asks the user to locate the video without discarding skeleton or annotations. A compatible old Video Marker project or autosave opens through **Open MAT** or **Tools > Project State > Load State...**. Project And Session State Durable state consists of video metadata, the portable video source, skeleton, frame annotations and provenance, calibration, export parameters, and output manifest paths. The video reader, decoded frame, current interaction, selection, preview graphics, and frame cache are transient session resources. Only the current frame number is saved as navigation convenience; annotations remain the authoritative scientific data. Older compatible project and autosave formats are upgraded when opened. Save the upgraded project before moving it into another workflow. Outputs explicit project MAT and visible autosave MAT; marker CSV for round-trip editing; coordinate CSV for analysis and plotting; output manifests recording coordinate options and file roles. CSV dialogs start in a source-adjacent `video_marker` output folder. Result manifests are written beside the chosen CSV. Diagnostics And Synthetic Inputs Use **Tools > Diagnostics** to inspect the live session log, enable trace capture, or export a diagnostic bundle after a problem occurs. Use **Tools > Developer Tools > Generate Synthetic Inputs...** to create a synthetic video, a valid marking project, and declared marker/coordinate output targets without including user filenames or laboratory data. Generation does not load those inputs into the running App. Use Without The GUI previousFrame = peaks(61); nextFrame = circshift(previousFrame, [-2 3]); [nextPoints, confidence] = video_marker.motionEstimate.trackPoints( ... previousFrame, nextFrame, [31 31]); skeleton = video_marker.skeletonDefinition.fromText( ... \"hip, knee\", \"hip-knee\"); annotations = video_marker.frameAnnotations.emptyAnnotations(2, 2); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 1, [11 21; 31 41], \"confirmed\"); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 2, [13 25; 35 45], \"confirmed\"); videoInfo = struct(\"frameRate\", 10); options = video_marker.coordinateExport.options( ... \"startFrame\", 1, \"endFrame\", 2, \"unitMode\", \"pixels\", ... \"originMode\", \"first_point\", \"yAxisMode\", \"up\"); coordinateTable = video_marker.coordinateExport.buildTable( ... annotations, skeleton, videoInfo, struct(), options); Function Reference Use the generated [`buildTable`](../../../reference/api/video_marker/coordinateExport/buildTable.html) page for exact frame-range option types, defaults, coordinate transforms, calibration requirements, output columns, failures, and related marker APIs. The [`trackPoints`](../../../reference/api/video_marker/motionEstimate/trackPoints.html) page documents the repository-owned prediction contract separately. Errors And Limitations Prediction is an editing aid, not unattended long-duration tracking. Occlusion, blur, repeated texture, large deformation, and leaving the frame can invalidate patch matches; inspect and correct drafts. Calibrated export requires a valid positive scale calibration. Changing skeleton order after annotation would change coordinate meaning and is therefore prevented once a video session begins. Related Topics [Gait Analysis](../../gait/gait-analysis/README.md) [Image Measurement family](../README.md) [API Reference](../../../reference/README.md) video_marker.coordinateExport.buildTable Convert confirmed video annotations to a coordinate table. video_marker.motionEstimate.trackPoints Track ordered image points between two consecutive video frames."},{"title":"LabKit Core Apps","url":"apps/labkit-core/index.html","kind":"app family","section":"apps","keywords":"apps labkit core LabKit Core Apps","text":"LabKit Core Apps Core apps provide the LabKit workbench entry point and cross-domain tools that are useful to many workflows but do not own a scientific measurement or experiment-specific formula. Choose An App Task App Discover, install, launch, diagnose, and package apps [LabKit Launcher](launcher/README.md) Restyle a MATLAB figure and export its visible graphics [Figure Studio](figure-studio/README.md) Workbench Entry Point The launcher is documented here because users operate it as the first LabKit application. Its implementation remains a self-contained root file so it can repair a damaged installation without depending on the app framework. Figure Handoff Figure Studio can open tracked `.fig` files or receive a plot sent from a LabKit plot context menu. The lightweight framework popout is intended for inspection; Figure Studio provides the complete workflow for controlled canvas and style settings, raster/vector export, and a data-plus-recreation-script package. Figure Studio processes one selected axes at a time. A mixed FIG exposes its subplots as separate panels; Studio does not turn the composite layout into a single plot. It does not rerun the calculation that produced the original figure and does not claim that displayed graphics are a lossless replacement for the original scientific dataset. Related Documentation [Figure Studio manual](figure-studio/README.md) [LabKit Launcher manual](launcher/README.md) [App Framework](../../framework/README.md) [Architecture](../../development/build-apps/architecture.md)"},{"title":"Figure Studio","url":"apps/labkit-core/figure-studio.html","kind":"app","section":"apps","keywords":"apps labkit core figure studio Figure Studio","text":"Figure Studio Figure Studio restyles MATLAB figures, exports presentation copies, and extracts supported visible graphics into a portable data package. It changes presentation properties, not the calculation that produced the plot. Open Figure Studio From the launcher, select **Figure Studio** and choose **Open**. From a source checkout, run: labkit_FigureStudio_app A LabKit plot can also send its current axes to Figure Studio through the plot context menu. That handoff embeds a serializable plot snapshot in the project and immediately applies the **LabKit figure** preset. The source style remains available through **FIG default**. Project And Handoff A plot-context-menu handoff creates the same editable project state as loading a FIG file. Saved projects retain portable FIG sources, embedded plot snapshots, the selected subplot panel, style settings, and canvas settings. Decoded source graphics are rebuilt after load, and the default output folder follows the restored source. Load And Select Figures On **Figures**, choose **Add FIG files or scan folder**. The source list accepts MATLAB `.fig` files and keeps one current selection. Selecting another source opens its native graphics without rerunning the analysis. A mixed FIG is listed as ordered **Subplot panel** choices (top-left to bottom-right, with a title where one exists). Select one panel to preview, restyle, recalculate, and export exactly that axes; Studio never combines multiple panels into one output. The plot-context-menu handoff already represents one axes, so it provides one panel. **Clear figures** removes all sources from the project. For a source using **FIG default**, Figure Studio adopts its font, semantic line widths, annotations, legend, grid, and axes appearance. Selecting **LabKit figure** applies the calibrated single-panel publication hierarchy while retaining the source legend placement. Switching a text preset never changes the selected plot-frame width, aspect, or export scale. Style And Canvas Controls Group Controls Text Style preset, all/title/axis-label/tick/annotation font size, X tick-label angle, grid alpha, grid visibility Lines + Boundaries data, uncertainty, main-graphic boundary, reference-line, and axes line widths Legend source/on/off display, location, font size, columns, and border Plot frame reference, 6:5, 4:3, 16:9, 1:1, 3:2, or source aspect; a fixed plot width or source size; export supersampling; top/right frame visibility Changing **All font** updates title, axis-label, tick, annotation, and legend sizes together. Individual controls then provide category-level refinement. **LabKit figure** uses a calibrated 900-by-725 px reference **plot frame**: 45 pt title, axis-label, tick, annotation, and legend text; 6.5 pt data strokes; 2 pt uncertainty strokes; and 1.5 pt boundary, reference, and axes strokes. The default uses a complete top/right frame with no grid and no legend box. Legend samples use the reference's long line tokens. These editable baselines come from normalized pixel measurements across all nine panels of the maintained 3-by-3 visual reference, not from a single imported FIG. The configured width and aspect always describe the inside of the axes frame. Figure Studio calculates the enclosing figure's outer margins from the current title, labels, ticks, legend, and visible annotations, so changing a long label cannot silently shrink the data region. Empty ruler text is ignored, including the zero-area placeholders MATLAB exposes on logarithmic axes. Choose **Source size** or one of 640, 720, 900, 960, 1200, 1237, 1364, 1600, or 2400 px; an aspect choice sets the paired plot-frame height. The workbench preview is a real interactive axes, not a raster image. When its allotted screen area changes, Studio reflows only the display text and strokes; project settings and export proportions remain unchanged. This calibration is validated on both 72-PPI Linux renderers and 96-PPI desktop MATLAB so display-density differences do not restyle exports. **Export x** is raster supersampling only. **Top/right frame** only shows or hides those two axes edges; it never changes the plot-frame size. Font controls use 0.5 pt steps and line controls use 0.1 pt steps. The preset styles every existing category but does not create or move a legend by default, because either action can cover data. Use the Legend panel for those explicit layout changes. **X tick labels** retains the source angle, makes labels horizontal, or rotates them 45 degrees. PNG and JPG resolution is `300 * Export x`, with a minimum of 72 dpi. SVG uses vector content. **FIG default** records the source plot-frame ratio as its reference, so reopening a source does not rescale its original typography. If limits are stale after copying or editing, use **Recalculate X/Y limits** to fit the visible graphics, update the interactive viewport, and refresh the four limit inputs. The X/Y minimum and maximum controls accept values within the finite visible-data extent expanded by exactly 50% on both sides; they do not change the source calculation. Quick Exports The **Figures** tab provides: **Save FIG** for an editable MATLAB figure; **PNG** and **JPG** for raster output; **SVG** for vector output. Each quick export also writes a LabKit result manifest beside the selected file. The manifest records the source, style parameters, output identity, and summary metadata. Export A Data Package On **Export**, select an output folder and choose **Export data + script**. Figure Studio creates a package containing supported visible data, style and axes metadata, and a MATLAB recreation script. For preview, editable FIG export, and image export, Studio first copies the selected native axes hierarchy when MATLAB permits the parent transition. This preserves MATLAB graphics that can be copied into the target axes, including ordinary grouped charts such as `boxplot`. Native child stacking is retained, so lines, markers, and text drawn over an image remain above that image in both the interactive preview and exports. UIAxes sources are rebuilt into conventional export axes from the portable snapshot, retaining the displayed scientific axis exponents as well as visible data. The portable data package separately recognizes visible line, bar, error-bar, area, scatter, image, surface, patch, rectangle, text, and constant-line objects. Existing legend text, visibility, placement, orientation, column count, font, interpreter, and border are retained. Explicit axis ticks, tick labels, label rotations, axis locations, and tick geometry are retained as visible presentation metadata. Visible graphics with hidden handles, such as error bars or significance brackets intentionally excluded from legends, are also retained without adding legend entries. Portable-package unsupported graphics are skipped and reported as warnings; the original FIG remains the authoritative source for object types that cannot be represented in that portable package. The package represents displayed graphics in axes child order. It is intended for audit, handoff, and plot recreation, not for reconstructing hidden source data or rerunning the original analysis. Programmatic Data Extraction fig = openfig(\"result.fig\", \"invisible\"); cleanup = onCleanup(@() close(fig)); ax = findobj(fig, \"Type\", \"axes\", \"-depth\", 1); plotData = figure_studio.resultFiles.extractAxesData(ax(1)); `plotData.axes` contains titles, labels, ticks, tick labels, scales, directions, limits, grid and aspect settings, color order, font settings, and colormap when available. `plotData.objects` contains the graphics type, display name, coordinate/color data, style fields, and object metadata. `plotData.warnings` lists skipped objects. Errors And Limitations A FIG must contain a readable axes to enter the editing workflow. Reopening a project with an existing but damaged or unsupported FIG stops the restore and preserves the currently open document; it is not presented as an empty figure source. Invisible objects are not exported. A selected single axes is copied natively when MATLAB supports that parent transition. The portable data package remains deliberately narrower and is not a promise of pixel-identical reconstruction of every MATLAB chart class. Callbacks, application data, custom classes, and analysis provenance inside the source figure are not treated as portable scientific data. Some object-specific rendering semantics cannot be reproduced from a plain graphics snapshot. Related Functions And Documentation `figure_studio.resultFiles.extractAxesData` `figure_studio.resultFiles.exportAxesPackage` `figure_studio.resultFiles.createStyledFigure` [LabKit Core apps](../README.md) [Plotting framework](../../../framework/README.md)"},{"title":"LabKit Launcher","url":"apps/labkit-core/launcher/index.html","kind":"app manual","section":"apps","keywords":"apps labkit core launcher LabKit Launcher","text":"LabKit Launcher The LabKit Launcher is the installed workbench entry point. It discovers apps, prepares their MATLAB paths, checks requirements, starts App SDK sessions, manages installed versions, opens app documentation, and exposes source-checkout maintenance tools. It is intentionally self-contained so a single surviving `labkit_launcher.m` can repair an incomplete ZIP installation. Start The Launcher labkit_launcher The window paints before app discovery completes. Its status area first reports that the app list is loading, then shows the selected app, integrity problems, tool availability, or the active maintenance operation. Launcher Window Group Action Behavior Run Apps **Open Selected App** Checks the selected app requirements, adds the app root, and calls its App SDK entrypoint without retired runtime launch arguments. Run Apps **Refresh App List** Repeats public and configured private-app discovery without restarting the launcher. Run Apps **Documentation and History** Opens the generated manual for the selected app. Versions and Install **Latest** Installs the current `main` branch archive. Versions and Install **Release** Installs the latest stable GitHub release. Versions and Install **Versions** Opens the release, tag, and commit selector for deliberate upgrade or rollback. Development and Maintenance **Update Documentation** Regenerates `site/` with the repository-owned documentation compiler. Development and Maintenance **Run Code Analyzer** Scans the checkout and writes JSON and HTML Code Analyzer reports. Development and Maintenance **Profile Selected App** Starts the selected app under the MATLAB profiler and saves its report when the app closes. Development and Maintenance **Clean Artifacts** Removes ignored generated reports under `artifacts/`; it does not delete app projects or exported laboratory results. Package and Publish **Package Checked** Creates one source ZIP containing all checked apps and their runtime support. Package and Publish **Checked P-code** Creates a runtime-only ZIP with MATLAB source encoded as P-code. Double-clicking an app row is equivalent to selecting it and opening it normally. The checkbox column controls package membership; ordinary launch selection does not change the checked set. When startup begins, the launcher immediately disables its App table and actions, changes the open button to **Starting App...**, shows a wait pointer, and reports two explicit stages: preparing the selected App path, then initializing its window through the named entry point. This feedback is painted before the potentially slow App entry point runs. Duplicate clicks are ignored while startup is active. Completion restores the controls and reports the opened command; failure reports the failing identifier and message, with repair guidance only for structural installation failures. Every launch uses the same clean App path. Use the App's **Tools > Diagnostics** menu to inspect its live session log, enable trace capture, or export a diagnostic ZIP after a problem occurs. Apps that declare a synthetic input pack expose **Tools > Developer Tools > Generate Synthetic Inputs...**. Generation writes anonymous fixture files and a manifest into a new folder but does not load them or mutate the running project. Programmatic Calls The launcher exposes a small non-GUI surface: fig = labkit_launcher; apps = labkit_launcher(\"list\"); info = labkit_launcher(\"version\"); page = labkit_launcher(\"documentation\", \"labkit_CIC_app\"); Call Result `labkit_launcher` Opens the window; optionally returns its figure handle. `labkit_launcher(\"list\")` Returns the discovered app catalog as a table without opening the launcher window. `labkit_launcher(\"version\")` Returns launcher name, display name, semantic version, and update date. `labkit_launcher(\"documentation\", appCommand)` Returns the local generated HTML page for one discovered app command. Unsupported modes, empty documentation commands, extra inputs, and more than one requested GUI output raise `labkit_launcher:InvalidInput` or `labkit_launcher:TooManyOutputs` errors. Documentation lookup uses the discovered public App folder and the unique path-conventional manual at `docs/apps///README.md`. It does not require a separately maintained App catalog. App Discovery Public apps are discovered from `apps/**/labkit_*_app.m`. Each entry point delegates to one `definition.m`, which supplies display metadata, family, command, version, requirements, layout, and optional capabilities. The launcher reads version and update metadata from that definition without starting the App. Local private apps may be discovered from `private_apps/apps/` or paths named by `LABKIT_PRIVATE_APP_ROOTS`; their repositories and manuals remain private. The launcher adds only the paths needed for the selected app. It does not use `genpath` to expose every app helper globally. Refresh the app list after adding, removing, or updating an app while the launcher remains open. Installation And Recovery The downloaded `labkit_launcher.m` can begin by itself. When no installed Launcher is available, it opens a focused install/repair window with: an editable installation folder and native **Browse...** action; a clear new-installation, existing-installation, or unsafe-target result; **Latest stable release** (recommended), a GitHub-backed selector of published stable versions, and **Current main branch** (development); honest preparation, download, package-validation, installation, and outcome stages with current-action or error details; and confirmation before replacing an existing installation. The target is checked before any network request. A new install may use a nonexistent folder whose parent exists, an empty folder, or a folder containing only the standalone launcher and ordinary OS metadata. Repair accepts an existing LabKit installation. Filesystem roots, Git checkouts, files, missing parents, and non-LabKit folders containing unrelated content are rejected. After LabKit is installed, **Latest**, **Release**, and **Versions** in the full Launcher provide the richer source-checkout version workflow. Every downloaded archive is validated before replacement. Existing repairs preserve a recovery copy transactionally and retain known local workspace folders when required. Keep experimental data and exports outside the runtime folder because installed code is replaceable. Installed apps run offline unless their own inputs use a network location. Version discovery and update actions require network access. Source and P-code deployment packages have different contents; see [App Deployment Packages](../../../development/tools/deployment.md). Maintenance Tools Maintenance buttons are enabled only when their corresponding source-checkout tool is available. The same operations can be called directly from MATLAB: [Code Analyzer Reports](../../../development/tools/codecheck.md) [App Deployment Packages](../../../development/tools/deployment.md) [Documentation Build Tools](../../../development/tools/documentation.md) [Performance Profiling](../../../development/tools/profiling.md) These tools produce ignored artifacts. They are not app runtime APIs and do not alter scientific data. Development History The launcher began as the visible entry point for UI 2.0, then gained self-contained repair updates, version selection, private-app discovery, profiling and packaging actions, early startup feedback, and direct app documentation links. The generated **Change history** below lists every record whose metadata names `labkit_launcher` in linear sequence. Key milestones: [v2.0 launcher and UI 2.0](../../../history/records/2026/06/LK-20260615-v2-launcher-and-ui2.md) [Self-contained launch and image workflow refinement](../../../history/records/2026/06/LK-20260621-v2-2-v2-3-image-workflows.md) [Launcher manager and stale callback repair](../../../history/records/2026/06/LK-20260626-launcher-manager-and-stale-callback-fix.md) [Profiling and validation speedups](../../../history/records/2026/07/LK-20260702-profiling-and-validation-speedups.md) [Searchable documentation and launcher workflow groups](../../../history/records/2026/07/LK-20260715-documentation-site.md) Related Documentation [Getting Started](../../../getting-started/README.md) [LabKit Apps](../../README.md) [LabKit Core Apps](../README.md) [Architecture](../../../development/build-apps/architecture.md) [Private Apps](../../../development/maintain-and-release/private-apps.md) [Release Process](../../../development/maintain-and-release/release.md)"},{"title":"Neurophysiology Apps","url":"apps/neurophysiology/index.html","kind":"app family","section":"apps","keywords":"apps neurophysiology Neurophysiology Apps","text":"Neurophysiology Apps The neurophysiology apps cover three stages of an Intan RHS workflow: checking recordings, measuring stimulus-evoked responses, and reviewing the resulting measurements. Choose An App What you want to do App Check recording metadata and short waveform windows; assign channel roles [RHS Preview](rhs-preview/README.md) Find stimulation events and measure compound action potentials [Nerve Response Analysis](nerve-response-analysis/README.md) Recalculate or summarize measurements from an analysis or segment table [Response Review and Stats](response-review-stats/README.md) Recommended Workflow Open a representative recording in RHS Preview. Confirm the channels and inspect a short portion of the waveform. Save a protocol draft that records channel roles. If you are working with a set of recordings, label the files and save a filter record. Open the filter record in Nerve Response Analysis, add the protocol, and run the analysis. Export the analysis as JSON. This file contains the events, stimulus trains, response measurements, and any issues found while reading the recordings. Open that JSON in Response Review and Stats to inspect and export the measurement table. A compatible segment CSV can be used instead when you need to recalculate measurements from waveforms. Each stage saves its own parameters with the results. Keep those settings with any reported latency, amplitude, or signal-to-noise values. Use RHS Data In MATLAB Code The [RHS library](../../libraries/rhs/README.md) provides functions for reading recording metadata and selected waveform windows without opening an app. The individual app manuals show the calculation functions used for event detection, response measurement, alignment, and summary statistics. Related Documentation [RHS library](../../libraries/rhs/README.md) [Biosignal library](../../libraries/biosignal/README.md) [App catalog](../README.md)"},{"title":"Nerve Response Analysis","url":"apps/neurophysiology/nerve-response-analysis.html","kind":"app","section":"apps","keywords":"apps neurophysiology nerve response analysis Nerve Response Analysis","text":"Nerve Response Analysis Nerve Response Analysis reads the recording list prepared in RHS Preview, finds stimulation events, groups them into trains, and measures compound action potential responses in the assigned channels. Open Nerve Response Analysis From the launcher, select **Nerve Response Analysis** and choose **Open**. From a source checkout, run: labkit_NerveResponseAnalysis_app Analyze A Recording Set On **Setup**, choose the filter JSON created by RHS Preview. On **Protocol**, choose the protocol JSON that assigns channel roles. A protocol is optional, but strongly recommended. Set **Max recordings** or **Max duration** only when you want a shorter test run. A value of zero means no limit. Choose **Analyze Filtered Files**. On **Review**, inspect the counts and switch the plot between **Counts** and **Issues**. On **Export**, choose an output folder and then **Export Analysis**. Changing the filter, protocol, or limits clears the previous analysis so that an export cannot silently use outdated settings. The filter record and optional protocol are saved as distinct portable project sources. Older projects with separate source fields are upgraded on load. Project And Session State The durable project stores portable references for the filter record and optional protocol, the two run limits, and the last export paths. Parsed JSON, analysis tables, issue details, preview selection, log messages, and output-folder convenience are transient session state. Opening a project reparses its JSON sources but does not persist or silently reuse old analysis tables; choose **Analyze Filtered Files** to calculate them again. If an existing selected JSON file is malformed, project restore stops and preserves the current document. An absent optional protocol remains valid. What The Analysis Does For each readable recording, the app selects an event source from the protocol and recording metadata, reads the required waveform, and finds sharp changes using the absolute first difference. A robust noise estimate sets the default detection threshold. Nearby peaks are reduced according to the minimum event spacing, then grouped into trains by their time gaps and train rules. For every event and response channel, the app measures a pre-event baseline, baseline noise, positive and negative peaks, peak-to-peak amplitude, peak time, latency, and SNR. The response search starts after the pulse-blanking interval to avoid measuring the stimulation artifact itself. Detection and measurement settings are scientific choices. Confirm that the event source and timing shift match the physical stimulus before interpreting latency. Output Files **Export Analysis** writes: `nerve_response_analysis.json`, containing source information, the protocol, events, trains, response measurements, and issues; `nerve_response_analysis.labkit.json`, containing the selected inputs, run limits, output identity, and result counts. An unreadable recording or missing channel is recorded in the issues table so that other recordings can still complete. Detect Events In MATLAB Code sampleRate = 10000; timeSec = (0:1/sampleRate:0.12).'; eventSignal = zeros(size(timeSec)); pulseTimes = 0.03 + (0:4).' * 0.01; eventSignal(round(pulseTimes * sampleRate) + 1) = 5; eventOptions = struct( ... \"sourceId\", \"stim\", ... \"stimShiftSec\", 0, ... \"train\", struct(\"minDetectedPulses\", 5)); [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, eventSignal, eventOptions); `timeSec` and `eventSignal` must be equal-length numeric vectors. Options can set the threshold multiplier, minimum score, minimum peak spacing, train gap, minimum pulse count, maximum train duration, isolation rule, and reported stimulus-time shift. With no candidates, the function returns empty tables. Measure CAP Features In MATLAB Code metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, responseSignal, events.timeSec, metricOptions); `metricOptions` can set `baselineWindowSec`, `blankingAfterPulseSec`, and `searchEndAfterPulseSec`. The output contains one row per event. If the search window contains no samples, that row is kept with status `noSamples`. To process an RHS file or an entire filter record, use `nerve_response_analysis.analysisRun.analyzeRecording` or `nerve_response_analysis.analysisRun.analyzeSession`. Function Reference The generated pages for [`detectEventTrains`](../../../reference/api/nerve_response_analysis/analysisRun/detectEventTrains.html), [`measureCapMetrics`](../../../reference/api/nerve_response_analysis/analysisRun/measureCapMetrics.html), and [`analyzeSession`](../../../reference/api/nerve_response_analysis/analysisRun/analyzeSession.html) document protocol nesting, direct option defaults and legal values, table schemas, units, partial-recording failure policy, and related APIs. Errors And Limitations Time and signal vectors must have the same length. A detection peak marks an electrical transition; it is not automatically the physical onset of the delivered stimulus. Default timing windows are starting points, not universal physiological constants. Missing events produce empty results rather than fabricated measurements. Review the issues table before comparing counts across recordings. Related Functions And Apps `nerve_response_analysis.analysisRun.analyzeSession` `nerve_response_analysis.analysisRun.detectEventTrains` `nerve_response_analysis.analysisRun.measureCapMetrics` [RHS Preview](../rhs-preview/README.md) [Response Review and Stats](../response-review-stats/README.md) nerve_response_analysis.analysisRun.analyzeRecording Analyze one RHS recording according to a protocol. nerve_response_analysis.analysisRun.analyzeSession Analyze accepted RHS recordings and combine their results. nerve_response_analysis.analysisRun.detectEventTrains Detect pulse candidates and group them into trains. nerve_response_analysis.analysisRun.measureCapMetrics Measure event-locked CAP response features."},{"title":"Response Review And Stats","url":"apps/neurophysiology/response-review-stats.html","kind":"app","section":"apps","keywords":"apps neurophysiology response review stats Response Review And Stats","text":"Response Review And Stats Response Review and Stats opens a Nerve Response Analysis result or a segment table, displays the measurements, and exports a clean CSV for review or downstream statistics. Segment tables can also be aligned and measured again with new baseline and noise windows. Open Response Review And Stats From the launcher, select **Response Review Stats** and choose **Open**. From a source checkout, run: labkit_ResponseReviewStats_app Review An Analysis On **Setup**, choose an analysis JSON or segment CSV. The app loads the measurement rows immediately. If the input contains waveform segments, set the **Baseline** and **Noise** windows. Both ranges are expressed in seconds relative to alignment time. Choose **Refresh Metrics** when you want to reload the source explicitly. On **Review**, inspect the summary and details. Switch the plot between **Summary** and **Aligned**. On **Export**, choose a folder and select **Export Metrics**. Changing either time window recalculates a loaded segment input automatically. For an analysis JSON that already contains measurements, the app reads those measurements rather than recreating unavailable waveform data. Accepted Inputs JSON exported by Nerve Response Analysis. The app reads its `metrics` records. CSV that can be interpreted as waveform segments with time and values. When segments have different time grids, the app interpolates them onto a shared grid. If no interval is specified in code, it uses the median positive source interval. If the timestamps cannot provide one, it uses 0.0001 seconds. When no window is specified, the grid spans the intersection of the time ranges after each segment's alignment-time shift. Samples outside an aligned segment's original range remain `NaN`. The selected input is saved as a portable project source. Older projects are upgraded on load. Project And Session State The project saves the source, baseline/noise windows, and last export reference. Metric tables, aligned waveforms, and summaries are recalculated from that source after load. Measurements And Summary For each aligned segment, the calculation subtracts the baseline mean and reports positive and negative peaks, peak-to-peak amplitude, noise RMS, and SNR. The summary groups rows by `pairId` when available; otherwise it uses the segment name. Counts are included so exclusions and missing rows remain visible. Output Files **Export Metrics** writes: `response_review_metrics.csv`, one row per measurement; `response_review_metrics.labkit.json`, recording the input, time windows, output identity, row count, and group count. Align Segments In MATLAB Code localTime = (-0.003:0.001:0.012).'; firstValues = zeros(size(localTime)); firstValues(localTime == 0.004) = 2; firstValues(localTime == 0.008) = -1; secondValues = 0.8 * firstValues; segments = struct( ... \"timeSec\", {10 + localTime, 20 + localTime}, ... \"values\", {firstValues, secondValues}, ... \"name\", {\"first\", \"second\"}, ... \"alignTimeSec\", {10, 20}); options = struct( ... \"sampleIntervalSec\", 0.001, ... \"windowSec\", [-0.003 0.012], ... \"baselineWindowSec\", [-0.003 -0.001], ... \"noiseWindowSec\", [-0.003 -0.001], ... \"measurementWindowSec\", [0 0.010]); aligned = response_review_stats.analysisRun.alignSegments(segments, options); metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, options); summary = response_review_stats.analysisRun.summarizeMetrics(metrics); Each segment supplies `timeSec`, `values`, and `name`. An optional `alignTimeSec` shifts that segment to the common zero. `aligned.values` is a samples-by-segments matrix. Function Reference The generated pages for [`alignSegments`](../../../reference/api/response_review_stats/analysisRun/alignSegments.html) and [`measureAlignedSegments`](../../../reference/api/response_review_stats/analysisRun/measureAlignedSegments.html) define time-window option shapes and defaults, interpolation and empty-sample behavior, output table columns, failures, examples, and related APIs. Errors And Limitations Baseline and noise windows must overlap the available data to produce finite measurements. Interpolation makes grids comparable but cannot restore timing precision or bandwidth that is absent from the source. An empty input returns an empty result rather than a synthetic summary. Keep the chosen windows with any exported amplitude or SNR values. Related Functions And Apps `response_review_stats.analysisRun.loadMetrics` `response_review_stats.analysisRun.alignSegments` `response_review_stats.analysisRun.measureAlignedSegments` `response_review_stats.analysisRun.summarizeMetrics` [Nerve Response Analysis](../nerve-response-analysis/README.md) response_review_stats.analysisRun.alignSegments Interpolate segment traces to a shared time grid. response_review_stats.analysisRun.measureAlignedSegments Measure peak-to-peak, noise, and SNR per segment. response_review_stats.analysisRun.summarizeMetrics Compute grouped metric counts and finite means."},{"title":"RHS Preview","url":"apps/neurophysiology/rhs-preview.html","kind":"app","section":"apps","keywords":"apps neurophysiology rhs preview RHS Preview","text":"RHS Preview RHS Preview lets you inspect an Intan RHS recording without loading the entire waveform into memory. Use it to check channels, move through short waveform windows, choose the channels to plot, and prepare protocol or file-filter JSON for later analysis. Open RHS Preview From the LabKit launcher, select **RHS Preview** and choose **Open**. From a source checkout, run: labkit_RHSPreview_app Preview A Recording On **Setup**, choose one `.rhs` file. The app reads the header, lists the amplifier channels, selects up to eight channels, and displays a short waveform window. Drag **Pan** to move the window through the recording. Scroll over the plot to change the displayed duration around the pointer. Edit the plotted interval on the graph when you need to mark a smaller region. Choose **Zoom to ROI** to make that interval the current window. Choose **Refresh Preview** to reread the current window from disk. Only the displayed window is decoded. This keeps navigation practical for long recordings and does not change the source file. Choose Channels And Roles On **Protocol**, the channel table contains four columns: Column Purpose Plot Show or hide this channel in the stacked preview Role Assign its purpose in the experiment Label Enter a readable name for plots and later analysis Channel Intan channel name read from the recording You may open an existing protocol JSON before editing the table. Choose **Save Protocol Draft** to save the current assignments. A protocol is recommended for response analysis because it makes the stimulation, reference, and response channels explicit. Prepare A File Filter Use **Add RHS files or folder** to collect recordings for a later analysis. The file table lets you assign a label and comment to each recording. Remove individual entries or clear the list as needed, then choose **Save Filter Record**. The filter record identifies the selected files and their labels. It does not copy the RHS recordings or contain decoded waveforms. Saved Project Sources The preview recording, optional protocol, and filter recordings are saved as distinct portable project sources. Older projects that stored these sources separately are combined automatically on load. Project And Session State The durable project stores portable references for one preview recording, one optional protocol, and an ordered collection of filter recordings. It also stores preview settings, channel-role drafts, manual filter labels/comments, and compact export records. Header indices, decoded preview windows, table presentation state, current ROI and window position, status text, and log messages are transient session data. They are reconstructed from the project sources when a project is opened. Review Recording Information The **Review** tab shows the indexed duration, channel counts, current window, selected channels, and recent action. The preview plot uses a separate vertical band for each selected channel so traces do not obscure one another. Read An RHS Window Without The App [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); assert(status.ok, status.message) options = struct( ... \"family\", \"amplifier\", ... \"channels\", [\"A-000\", \"A-001\"], ... \"timeRangeSec\", [1.0 1.5]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", options); assert(status.ok, status.message) `indexFile` reads recording metadata. `readWindow` returns the selected time range together with the sample rate and channel information needed to interpret the waveform matrix. Errors And Limitations RHS Preview currently displays amplifier channels. A channel listed in a protocol must exist in the selected recording. A window is limited to the indexed recording duration; requests outside that range are clamped. Short reads reduce memory and waiting time, but repeated navigation still requires disk access. The app is for inspection and preparation. Use Nerve Response Analysis for stimulus detection and CAP measurements. Related Functions And Apps `labkit.rhs.indexFile` `labkit.rhs.readWindow` `rhs_preview.analysisRun.readPreviewWindow` [Nerve Response Analysis](../nerve-response-analysis/README.md) [RHS library](../../../libraries/rhs/README.md) rhs_preview.analysisRun.readPreviewWindow Read the requested RHS preview window from app state."},{"title":"Statistics Apps","url":"apps/statistics/index.html","kind":"app family","section":"apps","keywords":"apps statistics Statistics Apps","text":"Statistics Apps Statistics Apps start from explicit observations selected or entered by the user. They keep data preparation, hypothesis testing, completed results, and figure design as separate steps so a visual change cannot silently alter a test. Choose An App What you want to do App Main result Select two or more groups, compare each later group with the first, and draw a mean/SD plot [T-Test Wizard](ttest-wizard/README.md) ordered t-test family, group CSV, result CSV, and plot Table And CSV Boundary An input file does not need a LabKit schema. T-Test Wizard first shows CSV, TSV, XLSX, and XLS sources as spreadsheet cells, then lets the user choose the numeric ranges that have scientific meaning. The first captured group is the reference and every later group is tested against it. For exchange between Apps and scripts, prefer the [Simple Scientific CSV Exchange](../../development/data-and-designs/scientific-csv-interchange.md): one header row, one rectangular table, readable labels, and blank cells for missing values. This is a lowest-layer table contract, not a universal experiment schema. Related Documentation [T-Test Wizard manual](ttest-wizard/README.md) [Simple Scientific CSV Exchange](../../development/data-and-designs/scientific-csv-interchange.md) [T-Test Wizard design](../../development/data-and-designs/group-comparison-app-design.md) [All Apps](../README.md)"},{"title":"Digital Image Correlation Apps","url":"apps/dic/index.html","kind":"app family","section":"apps","keywords":"apps dic Digital Image Correlation Apps","text":"Digital Image Correlation Apps The DIC family prepares optical image pairs before correlation and turns Ncorr strain results into presentation images and summary measurements afterward. LabKit does not implement the correlation solver itself. Apps In This Family Stage App Use it to Main result Before correlation [DIC Preprocess](dic-preprocess/README.md) register a moving image to a reference, apply identical crop geometry, and build a mask prepared image pair and binary ROI mask After correlation [DIC Postprocess](dic-postprocess/README.md) map EXX/EYY strain fields onto the reference image and summarize the valid ROI overlay PNGs and strain summary CSV End-To-End Workflow Use DIC Preprocess to align each moving image with the reference. Apply a shared crop and save the prepared pair. Create and save the ROI mask. Run correlation in Ncorr using the prepared images and mask. Load the Ncorr MAT result, matching reference image, and mask into DIC Postprocess. Generate overlays, inspect valid coverage, and export the summary. The two apps deliberately exchange ordinary image and MAT files rather than a private in-memory object. This keeps the external solver boundary explicit and makes each stage independently repeatable. Coordinate And Image Conventions Interactive points and rectangles use MATLAB image coordinates: `x` is the column coordinate and `y` is the row coordinate. Registration and crop operations preserve array class and color channels where the operation permits. Masks are logical images aligned with the displayed reference domain. DIC Preprocess owns geometric edits. DIC Postprocess may resize strain and mask arrays to the reference-image domain for rendering, but it does not alter the original Ncorr MAT file. Related Modules [Image Library](../../libraries/image/README.md) [App Framework interactions](../../framework/README.md) [All Apps](../README.md)"},{"title":"T-Test Wizard","url":"apps/statistics/ttest-wizard.html","kind":"app","section":"apps","keywords":"apps statistics ttest wizard T-Test Wizard","text":"T-Test Wizard T-Test Wizard captures two or more numeric groups, compares every group after the first with the first group, and draws one publication-oriented mean and standard-deviation plot. The first group is always the reference group. Open T-Test Wizard From the LabKit launcher, select **T-Test Wizard** and choose **Open**. From a source checkout, run: labkit_TTestWizard_app The App requires Base MATLAB and the LabKit App Framework. It does not require Statistics and Machine Learning Toolbox. Workflow The left controls use three short pages: **Data** — open a table, choose where selected values go, or clear data. **Test & Plot** — choose one t-test, run all comparisons, review result rows, and adjust plot presentation. **Export** — save all group observations and result rows as separate CSVs. The right workspace has two user-selectable pages: **Data** keeps the opened source table above the editable analysis table, each using half of the workspace. **Plot** gives the completed statistical figure the full workspace. Changing data or test settings does not silently recalculate the plot. A compact status beside the plot controls reports **OUT OF DATE** until **Run / refresh comparisons** is used again. The short **What will run** and **Result family** summaries stay compact so the result table and plot controls receive most of the vertical space. Add Groups From A Table The Data step accepts `.csv`, `.tsv`, `.xlsx`, and `.xls`. Workbook worksheets can be selected from the dropdown. Headers and labels remain visible in the source grid. For each group: select its numeric cells; leave **Source selection to** at `(new group)`, or choose an existing group; choose **Add selected source cells**; repeat for every additional group. Finite numeric cells and numeric text are accepted. Blank and label cells are skipped with a visible count. `NaN` and infinities block that selection until they are excluded or corrected. Opening another file replaces the source grid but keeps all groups already captured. For a new group, the App proposes a name from the nearest useful header above the selection. It follows merged-style headings to the left and combines up to two useful levels, such as `Treatment A - Metric Y`. Dates, row numbers, and numeric summary cells are not used as names. The proposed label remains editable in the analysis table. The lower analysis table uses one row per observation: Column Meaning Group Editable category label; repeated labels belong to one group Value One finite numeric observation Type or paste directly into either column. Blank rows are available for manual entry without a source file. Group order follows the first appearance of each label in this table; the first group is the reference. To change the reference, reorder the corresponding rows and edit any cell so the new order is committed. The interface deliberately does not expose an unexplained `N` column. To relabel many observations at once, select their rows in the analysis table, choose an existing group under **Change selected rows to**, and use **Change selected rows**. Selection is committed when the completed table selection changes, so dragging across a range does not repeatedly rebuild the App while the pointer is still moving. Use **Delete selected rows** to remove only the selected observations; if every observation in a group is deleted, that empty group is removed. **Clear all data** remains the separate full-reset action. Choose The Tests One selected method is applied to every comparison: Choice Calculation and use Independent t-test — Welch Independent groups without an equal-variance assumption; default Independent t-test — equal variances Pooled-variance independent test Paired t-test Row-by-row differences between the first group and each later group The Question dropdown offers two-sided, first greater than comparison, or first less than comparison alternatives. Difference direction is always: first/reference group minus comparison group Alpha defaults to `0.05`. No multiple-comparison correction is applied. The App reports each requested t-test exactly as configured; whether correction, ANOVA, a mixed model, or another method is scientifically appropriate remains the user's responsibility. Every group needs at least two finite observations. Paired testing additionally requires each comparison group to have the same length as the first group. Pairing follows displayed row order and is never inferred from a filename, header, or equal length. Results **Run / refresh comparisons** creates `N - 1` immutable result snapshots for `N` groups. Each row reports: comparison and reference labels; sample counts; group means; reference-minus-comparison difference; t statistic, degrees of freedom, and exact p-value; `NS`, `*`, `**`, `***`, or `****`; stable success or failure status. The thresholds used for the shorthand plot labels are: Label Rule `NS` `p >= alpha` `*` `p < alpha` `**` `p < 0.01` `***` `p < 0.001` `****` `p < 0.0001` The exact p-value remains available in the result table and CSV. Editing groups or test settings afterward does not rewrite the completed family; rerun to create a new family. Plot Style The plot uses the copied completed-result observations, not live editor values. Choose **Mean bars with SD** or **Box plot** without recalculating the tests. Both styles use numeric y-axis ticks, ordinary horizontal x-axis tick labels, and the same annotation geometry. Figure Studio can adjust typography and label presentation after the plot is popped out. The bar visual contract is: one pastel bar per group; bar height equal to the group mean; dark, capped sample-standard-deviation error bars; a zero-anchored y-axis for all-positive or all-negative bars, including after wheel or toolbar zoom; black boxed axes on a white background; Helvetica labels and no grid; stacked brackets from the first bar to every later successful comparison; significance shorthand on each bracket. The first four colors are soft green, orange, blue, and yellow; the palette cycles for additional groups. The box style uses the same group colors with subtler fill, dark outlines, whiskers, and medians. When individual values are shown, the box chart's own outlier markers are suppressed so observations are not drawn twice. Optional controls overlay individual values, hide SD bars on bar plots, hide brackets, and edit the title or y-axis label. Initial limits include bars or boxes, visible individual values, error bars, and significance brackets. **Reset plot view** recomputes that fitted viewport without rerunning the tests or changing project data. Switching between bar and box styles also accepts the new style's fitted limits; ordinary redraws otherwise preserve the user's current zoom. Use **Plot > Pop out all plots** for a standalone MATLAB figure. The popout can be passed to [Figure Studio](../../labkit-core/figure-studio/README.md) for further visual styling without recalculating the tests. CSV Outputs **Export group data CSV** writes one group per column and pads unequal groups with blank cells: Row,Reference,Treatment 1,Treatment 2 1,1.2,1.8,1.1 2,1.4,1.7,1.2 **Export comparison results CSV** writes one row per later group. Columns are: Test, Alternative, Alpha, Reference Group, Comparison Group, N Reference, N Comparison, N Pairs, Mean Reference, SD Reference, Mean Comparison, SD Comparison, Difference Reference-Comparison, SE, T, DF, P, CI Lower, CI Upper, Significance, Status, Message The imported source is never modified or overwritten. Programmatic Use Run a complete first-versus-each family without opening the GUI: groups = struct( ... \"label\", {\"Reference\", \"Treatment 1\", \"Treatment 2\"}, ... \"values\", {[1.2 1.4 1.3], [1.8 1.7 2.0], [1.1 1.2 1.4]}); options = struct( ... \"method\", \"welch\", ... \"alternative\", \"two_sided\", ... \"alpha\", 0.05); results = ttest_wizard.testRun.runGroupTTests(groups, options); [`runGroupTTests`](../../../reference/api/ttest_wizard/testRun/runGroupTTests.html) returns one canonical [`runTTest`](../../../reference/api/ttest_wizard/testRun/runTTest.html) result per later group. Project Recovery Project schema version 2 stores ordered groups, test settings, plot settings, completed comparison results, and source references. Version-1 A/B projects migrate by preserving A as the first group and B as the second group. Assumptions And Limitations Observations must share a meaningful measurement unit. Independent tests assume independence within and between groups. Paired tests use the displayed row order for every first-versus-group pair. The App does not infer experimental units, exclusions, independence, biological replication, or the correct statistical model. No multiple-comparison correction, ANOVA, nonparametric test, regression, or mixed model is performed. Plot shorthand does not replace the exact p-value or establish scientific importance. Related Documentation [Statistics Apps](../README.md) [T-Test Wizard design](../../../development/data-and-designs/group-comparison-app-design.md) [Simple Scientific CSV Exchange](../../../development/data-and-designs/scientific-csv-interchange.md) [Figure Studio](../../labkit-core/figure-studio/README.md) [App Framework](../../../framework/README.md) ttest_wizard.testRun.runGroupTTests Compare every group after the first with the first group. ttest_wizard.testRun.runTTest Run one independent Welch, pooled, or paired t-test."},{"title":"Wearable Apps","url":"apps/wearable/index.html","kind":"app family","section":"apps","keywords":"apps wearable Wearable Apps","text":"Wearable Apps Wearable apps read physiological recordings, preserve their time and channel information, and provide focused analysis and reporting workflows. Choose An App What you want to do App Filter ECG, detect beats, inspect segment quality, and export a printable waveform [ECG Print](ecg-print/README.md) Use Biosignal Functions Directly The [Biosignal library](../../libraries/biosignal/README.md) provides the same building blocks used by ECG Print: recording import, channel selection, filtering, peak detection, event-centered segments, template construction, and segment measurements. Use those functions when you need a script or batch workflow rather than the app. Related Documentation [ECG Print manual](ecg-print/README.md) [Biosignal library](../../libraries/biosignal/README.md) [App catalog](../README.md)"},{"title":"ECG Print","url":"apps/wearable/ecg-print.html","kind":"app","section":"apps","keywords":"apps wearable ecg print ECG Print","text":"ECG Print ECG Print reads a wearable recording, filters one channel, detects beats, builds event-centered segments and a representative template, and reports signal quality over time. It can export the segment measurements and a printable waveform image. Open ECG Print From the LabKit launcher, select **ECG Print** and choose **Open**. From a source checkout, run: labkit_ECGPrint_app Supported Inputs Choose **Open recording** to load a MAT, CSV, TXT, or TSV biosignal file. MAT files can contain a supported timetable or recording structure. Delimited text files can be detected automatically or parsed with the controls under **Import Parsing**. For a difficult text file, choose **Preview file header**, then set: the line containing column names; whether a header is present; the time column and its unit; the signal columns; a fallback sample rate when time values are unavailable. Choose **Parse / refresh file** after changing import settings. The app reports the detected channels and lets you select one for analysis. The recording is saved as a portable project source. Older projects are upgraded on load. Project And Session State ECG Print stores only durable workflow state in its project file: the portable recording source; import, channel, ROI, filter, detector, segment, template, and view settings; the compact last-analysis summary and export records. Decoded recordings, signal arrays, events, segments, templates, measurements, header previews, and plot models are transient session data. They are rebuilt from the recording and durable parameters when a project is opened. This keeps saved projects portable and avoids duplicating large waveform caches. Analyze ECG Open and parse a recording. Select a channel and, if needed, set the start and end of the time region. When the end is not greater than the start, the full signal is used. Set the bandpass range and peak method. Set minimum peak distance, segment half-window, template count, and smoothing count. Choose **Analyze current ROI**. Review the four plots: waveform and peaks, template-noise RMS, template SNR, and the template with either a residual band or individual segments. The filter is applied to the full selected channel before the time region is cropped. This reduces boundary artifacts at the region edges. The **Files + Analysis** tab keeps the workflow in five ordered sections: **Recording**, **Import Parsing**, **Channel + ROI**, **Signal Processing + SNR**, and **Exports**. Bounded numeric settings use paired spinner-and-slider controls. **Summary + Results** contains the analysis summary and file-header preview. **Tools > Diagnostics > Open Session Log...** records the current workflow and earlier runtime context. The **ECG Preview** workspace keeps four vertically stacked time-series axes available on every tab. Analysis Parameters Parameter Default Meaning Fallback sample rate 2000 Hz Used only when import cannot derive time spacing Bandpass 0.5 to 40 Hz Applied before peak detection; the upper cutoff is kept below 45% of sample rate Peak method QRS streaming QRS streaming, Pan-Tompkins, or local peaks Peak distance 0.28 s Minimum interval between accepted detections Segment half-window 0.7 s Data retained before and after each event Template top N 30 Number of best segments used to build the template Smooth beats 15 Smoothing span used in exported per-segment trends Template plot Template + residual band Alternative view is template plus individual segments Peak polarity is selected automatically. The default detector threshold is 2.8 standard deviations inside the app calculation. Output Files **Export segment SNR CSV** writes `ecg_segment_snr.csv` and a matching `ecg_segment_snr.labkit.json` manifest. The CSV contains per-segment measurements and smoothed trends. **Export waveform PNG** writes `ecg_waveform.png` and `ecg_waveform.labkit.json`. The manifest records the source, import and analysis settings, output identity, event count, segment count, and summary. Analyze A Signal In MATLAB Code [recording, status] = labkit.biosignal.readRecording(\"ecg.csv\"); assert(status.ok, status.message) signal = labkit.biosignal.getChannel(recording, 1); cache = struct(\"signal\", signal); parameters = struct( ... \"lowCut\", 0.5, ... \"highCut\", 40, ... \"roiStart\", 0, ... \"roiEnd\", 0, ... \"peakMethod\", \"QRS streaming\", ... \"peakDistance\", 0.28, ... \"segmentWindow\", 0.7, ... \"templateTopN\", 30); cache = ecg_print.analysisRun.analyzeSignal(cache, parameters); The returned cache contains the working and filtered signals, detected events, segments, template, and measurements. For a more customized pipeline, call `labkit.biosignal.filterSignal`, `detectEcgPeaks`, `segmentByEvents`, `buildTemplate`, and `measureSegments` directly. Errors And Limitations Time or a valid sample rate is required to interpret filter frequencies and event spacing. Automatic parsing should be checked when a text file has long preambles, unusual headers, or mixed metadata rows. A failed **Parse / refresh file** keeps the selected source and header preview available so import settings can be corrected in place. Automatic peak polarity and thresholds still require visual review when morphology or noise changes within a recording. Filtering and detection settings must accompany any reported heart rate or signal-quality result. Related Functions And Documentation `ecg_print.analysisRun.analyzeSignal` `labkit.biosignal.readRecording` `labkit.biosignal.detectEcgPeaks` `labkit.biosignal.measureSegments` [Biosignal library](../../../libraries/biosignal/README.md) ecg_print.analysisRun.analyzeSignal Rebuild ECG analysis products from one decoded signal cache."},{"title":"Development","url":"development/index.html","kind":"overview","section":"development","keywords":"development Development","text":"Development Use these pages for source changes and maintenance. App users normally need only [Getting started](../getting-started/README.md) and the [app guide](../apps/README.md). Read By Task Task Page Understand repository and package ownership [Architecture](build-apps/architecture.md) Create or refactor an app [App development](build-apps/app-development.md) and [complete app tutorial](build-apps/complete-app.md) Exchange simple scientific tables or gradually improve an App CSV export [Simple scientific CSV exchange](data-and-designs/scientific-csv-interchange.md) Develop the proposed cell-selection-based t-test App [T-Test Wizard design](data-and-designs/group-comparison-app-design.md) Work with an ignored private app repository [Private apps](maintain-and-release/private-apps.md) Select tests, GUI checks, or profiling [Testing](maintain-and-release/testing.md) Call source-checkout packaging, profiling, codecheck, or documentation tools [Maintainer tools](tools/README.md) Maintain the documentation sources and generated site [Documentation system](maintain-and-release/documentation.md) Choose versions, tags, and release evidence [Release process](maintain-and-release/release.md) Reusable facade calls are documented separately in the [public API reference](../reference/README.md). Launcher behavior belongs with the [LabKit Core apps](../apps/labkit-core/README.md), not in maintainer-only development pages."},{"title":"App Development","url":"development/build-apps/app-development.html","kind":"guide","section":"development","keywords":"development build apps app development App Development","text":"App Development LabKit apps are first-class deliverables. Each app owns its scientific workflow, state, plots, result schema, and exports; the reusable framework owns lifecycle and domain-neutral UI mechanics. Create An App Start from the LabKit app template rather than copying a complete neighboring app. Use the smallest nearby app only as a workflow example. A static App begins with only the product entrypoint, definition, and layout it actually uses: apps///labkit__app.m apps///+/definition.m apps///+/+workbench/buildLayout.m `labkit.app.Definition` supplies empty project/session and default presentation behavior when optional components are omitted. Bind real business callbacks directly from the layout. Add `+workbench/present.m` for derived visible state, `createSession.m` for transient decoded/cache state, and `projectSpec.m` only when the App owns durable data. That single project file contains local create, validate, and migrate functions. Its migrate callback exists only after a saved project schema has actually changed and only for the older payload versions the App explicitly supports; Runtime owns the version loop. Current saves always write the current payload version. Use an exact declared `LegacyImports` variable adapter only for real pre-envelope user files, never as a shape-guessing fallback. Runtime and App architecture names remain versionless. Put facade/App compatibility in the existing version and requirement metadata, and put saved payload numbers only in `projectSpec` migration logic. Do not create version-named packages, files, functions, types, tests, or manual sections. The project validator owns only App-specific requirements: domain fields, legal choices and ranges, cross-field relationships, source roles, and scientific invariants. Runtime validates the five canonical project buckets and standard portable source records before the callback runs. Do not repeat those framework checks in each App. Create additional packages only for concrete workflows that need them, for example `+sourceFiles`, `+analysisRun`, `+resultFiles`, `+cropGeometry`, or `+thermalFrames`. Avoid generic buckets such as `+actions`, `+ops`, `+io`, `+helpers`, and `+utils`. Define The Runtime Contract `definition.m` returns one immutable `labkit.app.Definition`. It is the App's single product contract and names the public command, stable ID, display metadata, App version, compatible LabKit facades, and workbench. Project schema, session factory, presenter, post-layout start callback, and debug sample are opt-in capabilities. Callbacks and renderers are owned directly by their layout nodes. The complete field tables, callback signatures, canonical project/session buckets, presenter shape, and renderer contract are documented in [Runtime and Lifecycle](../../framework/guides/runtime.md#definition-component-contract). For a complete file-by-file implementation, follow [Build a Complete App](complete-app.md). The framework owns: startup, readiness, and busy state queued callback dispatch and rollback project save/load and recovery managed interactions and resources debug tracing and result manifests The app owns durable `state.project`, transient `state.session`, semantic callbacks, presentation models, and scientific behavior. Build The Workbench `+workbench/buildLayout.m` returns a data-only `labkit.app.layout.*` tree. Keep tabs, sections, and workspace pages in the same order users see them. For a complex App, compose capability-owned layout fragments instead of flattening every control into this file. It must not create graphics handles, read files, run calculations, mutate state, or schedule startup work. `+workbench/present.m` is the pure assembly bridge from canonical state to a complete `labkit.app.view.Snapshot`. Compose feature-owned fragments with `Snapshot.include`. Renderers live with the capability they draw, receive only axes and a prepared model, and do not own workflow decisions. Use the complete runtime value only at this assembly bridge and at callbacks referenced directly by layout signals. Name it `applicationState`, unpack `project` and `session` at the top, and call feature presenters with the exact values they display. A feature presenter should look like `present(results, selection, displayOptions)`, not `present(state)`. A direct callback is the transaction adapter. Its signature is `(applicationState, callbackContext)` for a button or `(applicationState, typedEventValue, callbackContext)` for a value-bearing signal. It may perform short, visible state mutation in workflow order, then delegate calculations and writes through explicit inputs. Do not pass the complete state or callback context into a generic second action layer. Name Workflow Code Use concrete verb-object names such as: +sourceFiles/readSourceFiles.m +analysisRun/collectAnalysisOptions.m +analysisRun/computeAnalysisResults.m +resultFiles/writeResultFiles.m Keep callback-local glue local when extracting it would hide state mutation or workflow order. Extract a helper when it owns a stable calculation, data shape, file boundary, state transition, or reusable interaction contract. File length alone is not an extraction reason. Data Loading And Task Lifecycle Register large file selections with the least data needed for the first useful preview. Decode the selected file on demand and defer full-batch work until the user runs or exports when the workflow permits it. Apps with preview, edit, run, or export stages should make dirty state and the last successful task explicit. Pure helpers build deterministic task snapshots and calculations; result writers receive explicit task data rather than reading UI handles. Cross-App Data Contracts Apps exchange saved, documented data contracts; production and debug code do not call a sibling App package. A consumer owns its parser and error language, so it can launch with only the framework and its own App root on the MATLAB path. A producer owns serialization and schema validation. Keep one producer-consumer integration test that invokes both Apps and proves the current saved format remains compatible; keep consumer unit and debug fixtures independent of the producer package so the shared test path cannot hide a runtime dependency. Ownership Check Keep these in the app: accepted formats, domain defaults, units, and thresholds formulas and scientific interpretation plots, labels, result columns, and export schemas workflow ordering, user messages, and logs Move code into `+labkit` only when it is domain-neutral, independent of app state, directly testable, used broadly, and makes the public API clearer. See [Architecture](architecture.md#reusable-extraction-rule). Version And Requirements `definition.m` owns supported LabKit facade ranges together with the App command, display name, family, semantic version, and last change date. There is no second metadata registry. App behavior changes update this definition, the App documentation, and component-owned history in the same coherent change. Validation Use focused app tests while editing, then follow the stable gates in [Testing](../maintain-and-release/testing.md). Automated GUI tests cover launch, layout, callbacks, debug plumbing, and synthetic workflows; visual quality and manual interaction feel still require a human MATLAB check. Related Reference [App catalog and workflows](../../apps/README.md) [App Framework](../../framework/README.md) [Architecture](architecture.md) [Testing](../maintain-and-release/testing.md) labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot."},{"title":"Architecture","url":"development/build-apps/architecture.html","kind":"guide","section":"development","keywords":"development build apps architecture Architecture","text":"Architecture [Development index](../README.md) | [Public API index](../../reference/README.md) | [LabKit Launcher](../../apps/labkit-core/launcher/README.md) | [Maintainer Tools](../tools/README.md) LabKit is an app-first MATLAB workbench. Apps are the deliverables; `+labkit` is the small reusable foundation they share. Project Model apps/ workflow-specific GUI apps and app-owned helpers +labkit/ reusable UI, image, thermal, DTA, RHS, and biosignal facades tests/ behavior tests, project contracts, GUI checks, shared helpers, and runner code docs/ human-facing usage, API, architecture, and validation docs tools/ maintainer diagnostics, deployment packagers, and report generators .github/scripts/ GitHub Actions-only helper scripts The repository root has no generic `scripts/` bucket. Keep automation beside its single consumer; promote it to a shared owner only when several stable consumers genuinely need the same operation. Apps should remain independently launchable. The reusable library should grow only when a helper is domain-neutral, app-facing, tested, and useful beyond one workflow. Runtime Dependency Boundary LabKit apps run from MATLAB and repository-owned code. Production apps and facades do not create Python or Conda environments, install third-party runtime packages, download model weights, or require a network connection on first use. This keeps source checkouts, offline packages, and restored lab systems reproducible. During rapid development, an app may temporarily accelerate a capability with a MathWorks Toolbox only when it also ships a repository-owned base-MATLAB implementation with comparable user-visible behavior. The app must remain usable without the product, and automated tests must exercise that fallback. When values feed scientific interpretation, branching, exports, or later calculations, identical inputs must be idempotent: pure calculations reproduce the same app-consumed values, while safely repeated stateful operations do not compound state or side effects. A parity test must compare app-consumed outputs against the Toolbox reference within a documented tolerance. Visual similarity is not sufficient evidence. Record each temporary Toolbox dependency with its source function, MathWorks product, responsible app, fallback tests, idempotency/parity evidence, and replacement plan. Dependency reports continue to show the Toolbox call while that debt is open. The debt closes when the repository implementation replaces the Toolbox branch and the branch is removed. Adding any third-party runtime remains an architecture and deployment decision requiring explicit approval; it is not an ordinary app-local implementation choice. Version Ownership Facade and App compatibility is expressed by the existing version metadata and dependency-requirement components. Saved-data schemas keep explicit numeric versions only inside their persistence and migration contracts. Release and history records preserve the versions needed as evidence. Architecture concepts themselves are versionless. Packages, folders, files, functions, classes, type tags, protocol names, tests, and current manuals use stable semantic names. An incompatible redesign replaces the owning contract and updates version metadata; it does not create a second version-named architecture. Runtime Entrypoints Users normally start with: labkit_launcher The launcher discovers public `apps/**/labkit_*_app.m` entry points. Public app command names are stable user entry points, for example `labkit_CIC_app`, `labkit_DICPreprocess_app`, `labkit_ECGPrint_app`, and `labkit_RHSPreview_app`. Source checkouts may also keep local private apps under an ignored `private_apps/apps/` workspace or roots named by `LABKIT_PRIVATE_APP_ROOTS`. Any developer can create that local workspace for their own private apps. The launcher can list and launch those apps with `Visibility` set to `private`, but public releases and CI checks cover only the public `apps/` tree. Keep each private workspace as a separate private Git repository rather than mixing private app files into the public repo history. The public structure guide is [private-apps.md](../maintain-and-release/private-apps.md); private app documentation belongs in the private workspace. The launcher keeps update, discovery, and repair logic self-contained: it uses native MATLAB UI and local helper functions so users can repair a damaged zip install even if packages, apps, docs, or scripts have been deleted. It configures the MATLAB path for app entry points. MATLAB desktop project metadata belongs to each developer's local workspace. Tools under `tools/` are source-checkout support utilities rather than app runtime APIs. The launcher may call a small, explicit subset for maintenance and deployment actions, such as profiling a selected app or packaging a single app for offline deployment. Single-app deployment packages include the launcher and only those launcher-needed tool folders, not the whole source checkout. Direct syntax, options, outputs, and artifact behavior are documented in [Maintainer Tools](../tools/README.md). Ownership Boundaries Area Owns App entry point Public launch name delegated to one runtime definition, including lightweight requirements/version/debug requests. App package App definition, workflow state, command handlers, presenters, calculations, summaries, exports, and app-local helpers. `labkit.app` App definition and launch, semantic layout and view snapshots, typed events, callback capabilities, projects, diagnostics, results, interactions, and private native runtime lifecycle. `labkit.image` GUI-free image file IO, display normalization, resizing, mean filtering, and basic enhancement primitives. `labkit.thermal` GUI-free thermal source-file parsing, raw thermal matrices, embedded calibration metadata, raw-to-temperature conversion, and thermal colormap rendering. `labkit.dta` GUI-free Gamry DTA discovery, loading, parsed curves, and pulse helpers. `labkit.biosignal` GUI-free recording import, channel extraction, filtering, events, segments, templates, and measurements. `labkit.rhs` GUI-free Intan RHS discovery, header parsing, block indexing, and lazy waveform window reads. Apps own experiment-specific vocabulary, thresholds, protocol roles, plots, result schemas, export formats, alerts, and log wording. Reusable facades own domain-neutral mechanics that multiple apps can share. App Package Shape The target app shape is workflow-first. Each app keeps one MATLAB package under its app folder: apps///labkit__app.m apps///+/definition.m apps///+/+workbench/buildLayout.m Those three files are a complete static App. Add only the capabilities the product needs: projectSpec.m durable create/validate/migrate contract createSession.m transient App-specific reconstruction +workbench/present.m dynamic snapshot assembly +/layoutSection.m feature-owned controls +/present.m feature-owned snapshot fragment +/draw.m feature-owned plot renderer `definition.m` is the single product contract. It owns identity, display metadata, App version, facade requirements, layout, and references to any optional capabilities. `projectSpec.m` is the one durable-schema entry and keeps its create, validate, and version-aware migrate functions local. Runtime owns the migration loop. Do not add separate `requirements.m`, `version.m`, generic `+appLifecycle` or `+appState` packages, or per-version migration files. Add workflow packages only when the App has that user-facing capability: +sourceFiles/ choosing, reading, validating, and previewing source data +analysisRun/ collecting options, computing results, and showing results +resultFiles/ choosing output folders, writing files, and summarizing exports +cropGeometry/ app-owned crop geometry operations +thermalFrames/ app-owned thermal frame queues and display choices +syntheticInputs/ app-owned clean-room synthetic input generation Create only the packages the app needs. Names should describe a workflow or domain capability that changes together, not a broad technical phase. Avoid generic `+actions`, `+renderers`, `+ops`, `+io`, `+ui`, `+userInterface`, `+view`, `+export`, `+helpers`, and `+utils` packages for new app code. Avoid fixed `+app` namespaces, family-level `private/` helpers, `*Workflow.m` string dispatchers, and `+core/dispatch.m` routers. `+state`, `+actions`, `+ui`, `+view`, `+ops`, `+io`, and `+export` packages were retired with the workflow-first migration. Current app work should follow the workflow-first shape. App SDK Boundary App GUIs use the explicit `labkit.app` SDK: Layer App-facing API Definition `labkit.app.Definition` owns identity, requirements, optional project/session boundaries, workbench, presentation, and launch. Layout `labkit.app.layout.*` owns semantic controls, containers, workspace pages, direct callbacks, bindings, and plot renderers. View `labkit.app.view.Snapshot` owns complete derived visible state and prepared renderer models. Callback boundary Typed `labkit.app.event.*` values and sealed, specifically named `labkit.app.CallbackContext` operations. Optional contracts `labkit.app.project.*`, `result.*`, and `dialog.*`. Reusable facades publish MATLAB-native contract versions through their `version()` APIs. Apps declare required facade ranges in the `Requirements` field of `definition.m`, and `labkit.contract` checks those ranges in tests and at launch. This is a same-repo maintenance guardrail; routine users still update LabKit as one repository. Apps publish `AppVersion` and `Updated` metadata from the same definition for the launcher and window title. App versions are not dependency constraints and do not belong in `labkit.contract`. Project guardrails check `X.Y.Z` format and require versioned code changes to increase the corresponding definition, launcher, or facade version. The [release guide](../maintain-and-release/release.md) explains how to select and publish the next version. Image workflows may use `labkit.image` for generic image file filters, source image reads, display-name normalization, RGB double conversion, preview-size fitting, mean filtering, basic enhancement primitives, and image writes. Apps still own processing step semantics, ROI/background policy, matching formulas, crop geometry, focus-stack algorithms, DIC behavior, export schemas, and user workflow text. Thermal workflows may use `labkit.thermal` for radiometric source reads, embedded calibration metadata, raw thermal matrices, Celsius conversion, and linear thermal palette rendering. Apps still own file queues, display-range defaults, log/gamma display-mapping controls, export manifests, colorbar placement, overlay-removal workflow wording, measurements, and user-facing decisions. Generic image IO and filters stay in `labkit.image`; thermal file parsing and raw-to-temperature mechanics stay in `labkit.thermal`. `definition.m` returns the app runtime contract. Layout nodes own concrete callbacks and renderers, so Apps maintain no parallel registries. The framework compiles the static graph, builds the shell, owns the transactional event queue, persistence, resources, diagnostics, and private native adapter. `+workbench/buildLayout.m` should read as the product's user workflow. Capability packages own their controls, state transitions, prepared view models, rendering, alerts, and wording. `+workbench/present.m` extracts exact state inputs and composes those feature fragments; it is not a second monolithic implementation of the App. The state funnel The runtime commits one canonical value containing `project` and `session`, but that value is an SDK transaction envelope, not the App's domain model. Keep it at the edge: layout signal -> capability action(applicationState, event, callbackContext) -> calculation(exact scientific inputs) -> writer(exact result and destination inputs) runtime presentation -> workbench.present(applicationState) -> capability.present(exact visible inputs) -> renderer(axes, prepared model) Only `createSession`, `workbench.present`, `OnStart`, and a function bound directly to a layout signal accept the complete envelope. Those functions name it `applicationState`, expose the typed event and `callbackContext` in their signatures, and unpack it immediately. Feature presenters, renderers, calculations, writers, and local helpers receive named narrow inputs. Do not introduce a second App object, service bag, callback registry, or feature-wide context type to hide these inputs. If a calculation needs five scientific values, list those five values. If several always travel together and form a stable app-owned concept, give that concept a semantic struct and validate it at its owner. Reusable Extraction Rule A helper may move into `+labkit` only when all of these are true: It can be named without experiment-specific vocabulary. It does not encode app-specific units, thresholds, plots, result columns, or export schemas. It does not read or mutate app state. It is independently testable. At least two real apps use it, or one facade clearly needs it. It makes the app-facing API clearer rather than broader and vaguer. If those conditions are not met, keep the helper app-local. Extraction Quality Line budgets are maintainability backstops, not architecture goals. A smaller file is only better when the responsibilities are clearer. Extracted helpers should own a coherent behavior contract: a stable data shape, an explicit side effect, a GUI-free calculation, an export boundary, a display model, or a reusable app-facing framework mechanism. Trivial label formatters, single boolean checks, constant lists, and one-call facade wrappers can remain local, nested, or inline when that makes the workflow easier to follow. There is no minimum useful helper length. Small public facades, factories, filters, defaults, and test-facing helpers are valid when the name protects a real contract. Conversely, a long extracted helper is not reusable merely because it moved out of `run.m`; it must improve ownership, testability, or the app-facing API. When reviewing helper organization, prefer evidence from the helper's boundary role, call sites, tests, side effects, and ownership over raw line count. Repository line budgets count effective MATLAB code lines: blank lines and comment-only documentation do not consume the budget. Physical line counts remain useful diagnostic context, but they must not discourage complete function help. Validation Boundary The default automated validation boundary is: buildtool headless This covers project contracts, reusable facade behavior, and non-GUI app helper behavior. GUI checks cover launch, layout, callback wiring, trace plumbing, reusable tool lifecycle, and hidden synthetic app workflows. Manual MATLAB review is still required for full interactive workflow feel. labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot."},{"title":"Build A Complete App","url":"development/build-apps/complete-app.html","kind":"guide","section":"development","keywords":"development build apps complete app Build A Complete App","text":"Build A Complete App This guide builds a small trace viewer with the production `labkit.app` contract. The example keeps durable settings in a project, rebuilds transient file data in a session, draws a plot, and exports a result. Each file has one visible responsibility. File Shape apps/examples/trace_viewer/ |-- labkit_TraceViewer_app.m `-- +trace_viewer/ |-- definition.m |-- projectSpec.m |-- createSession.m |-- +workbench/ | |-- buildLayout.m | `-- present.m |-- +sourceTrace/ | |-- readTrace.m | |-- layoutSection.m | |-- workspacePlot.m | |-- present.m | `-- draw.m `-- +resultFiles/ |-- layoutSection.m `-- exportTrace.m There is no handler table, renderer registry, or `+userInterface` bucket. `+workbench` is the product assembly boundary. Capability packages own their part of the user workflow. 1. Thin Entrypoint function varargout = labkit_TraceViewer_app(varargin) [varargout{1:nargout}] = trace_viewer.definition().launch(varargin{:}); end The entrypoint does not build state, controls, services, or figures. 2. One App Definition function app = definition() app = labkit.app.Definition( ... Entrypoint=\"labkit_TraceViewer_app\", ... AppId=\"examples.trace-viewer\", ... Title=\"Trace Viewer\", ... Family=\"Examples\", ... AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... ProjectSchema=trace_viewer.projectSpec(), ... CreateSession=@trace_viewer.createSession, ... Workbench=trace_viewer.workbench.buildLayout(), ... PresentWorkbench=@trace_viewer.workbench.present); end Definition is a readable inventory, not an execution script. It validates the static layout, direct callback signatures, plot renderers, target IDs, project schema, and presentation contract before native UI mutation. 3. Durable Project function schema = projectSpec() schema = labkit.app.project.Schema( ... Version=1, ... Create=@createProject, ... Validate=@validateProject); end function project = createProject() project = struct( ... \"inputs\", struct(\"sources\", struct([])), ... \"parameters\", struct(\"gain\", 1), ... \"results\", struct(\"lastExport\", [])); end function accepted = validateProject(project) accepted = isstruct(project) && isscalar(project) && ... isfield(project, \"inputs\") && isfield(project.inputs, \"sources\") && ... isfield(project, \"parameters\") && ... isnumeric(project.parameters.gain) && ... isscalar(project.parameters.gain) && ... isfinite(project.parameters.gain); end The project contains only durable App meaning. External files are portable source records owned by the runtime, not raw paths. 4. Transient Session function session = createSession(project, callbackContext) paths = callbackContext.resolveSourcePaths(project.inputs.sources); trace = struct(\"x\", [], \"y\", []); if ~isempty(paths) trace = trace_viewer.sourceTrace.readTrace(paths(1)); end session = struct(\"trace\", trace); end `createSession(project,callbackContext)` is the one reconstruction boundary. The runtime calls it after project restore and file-list source changes. Session data is reconstructible and is not written into the project envelope. 5. Product Layout function layout = buildLayout() controls = { ... trace_viewer.sourceTrace.layoutSection(), ... trace_viewer.resultFiles.layoutSection()}; workspace = labkit.app.layout.workspace( ... trace_viewer.sourceTrace.workspacePlot()); layout = labkit.app.layout.workbench( ... controls, Workspace=workspace); end The assembly reads in user order. A complex App can use tabs and workspace pages here without flattening every feature into one file. The source capability owns its controls: function section = layoutSection() section = labkit.app.layout.section(\"sourceTrace\", \"Trace\", { ... labkit.app.layout.fileList(\"traceFiles\", ... Label=\"Trace file\", ... SelectionMode=\"single\", ... Bind=\"project.inputs.sources\", ... SourceRole=\"trace\", ... SourceIdPrefix=\"trace\"), ... labkit.app.layout.slider(\"gain\", ... Label=\"Gain\", Limits=[0.1 10], ... Bind=\"project.parameters.gain\")}); end Standard fields and file lists need no App callback. Their bindings update canonical state transactionally. The result capability binds a real business action directly: function section = layoutSection() section = labkit.app.layout.section(\"resultFiles\", \"Results\", { ... labkit.app.layout.button(\"exportTrace\", ... \"Export trace\", @trace_viewer.resultFiles.exportTrace, ... Tooltip=\"Export the calibrated trace and its sampling metadata.\")}); end 6. Complete View Snapshot function view = present(applicationState) view = labkit.app.view.Snapshot(); view = view.include(trace_viewer.sourceTrace.present( ... applicationState.session.trace, ... applicationState.project.parameters.gain)); view = view.enabled(\"exportTrace\", ... ~isempty(applicationState.session.trace.x)); end `+workbench/present.m` is a short assembly boundary. It extracts exact inputs and composes feature fragments with `Snapshot.include`; it does not perform IO or calculation. function view = present(trace, gain) model = struct(\"x\", trace.x, \"y\", trace.y .* gain); view = labkit.app.view.Snapshot().renderPlot(\"tracePlot\", model); end 7. Feature-Owned Renderer function node = workspacePlot() node = labkit.app.layout.plotArea( ... \"tracePlot\", @trace_viewer.sourceTrace.draw); end function draw(axesById, model) ax = axesById.main; cla(ax); plot(ax, model.x, model.y); xlabel(ax, \"Time (s)\"); ylabel(ax, \"Signal\"); grid(ax, \"on\"); end The plot area references the concrete renderer. The model carries prepared App meaning; drawing does not read project state or dispatch workflow actions. 8. Direct Business Callback function applicationState = exportTrace( ... applicationState, callbackContext) trace = applicationState.session.trace; gain = applicationState.project.parameters.gain; choice = callbackContext.chooseOutputFile( ... {\"*.csv\", \"CSV files\"}, \"\"); if choice.Cancelled return; end tableValue = table(trace.x(:), trace.y(:) .* gain, ... VariableNames=[\"Time\", \"Signal\"]); writetable(tableValue, choice.Value); applicationState.project.results.lastExport = string(choice.Value); end The callback signature exposes its runtime boundary. For larger exports, delegate table construction and result packaging to functions that accept only the trace, gain, and destination they require. Validation Test readers, calculations, project validation, snapshot fragments, renderers, and exports directly with synthetic values. Construct `definition()` in a headless test to validate the whole static contract. Run the App's bounded hidden-GUI workflow after smaller tests are stable; native dialogs and visual quality still require developer-led interactive validation. Before merge, update the App version, owning manual, structured component history, generated documentation site, and the final branch validation gates. Related Documentation [App Development](app-development.md) [Architecture](architecture.md) [LabKit App SDK](../../framework/README.md) [Testing](../maintain-and-release/testing.md) labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.slider Add a bounded numeric slider. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.project.Schema Declare one durable App project contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"T-Test Wizard Design","url":"development/data-and-designs/group-comparison-app-design.html","kind":"guide","section":"development","keywords":"development data and designs group comparison app design T-Test Wizard Design","text":"T-Test Wizard Design [Development index](../README.md) | [App manual](../../apps/statistics/ttest-wizard/README.md) | [App development](../build-apps/app-development.md) | [Simple scientific CSV exchange](scientific-csv-interchange.md) Product Boundary T-Test Wizard answers: Given two or more ordered numeric groups, what are the configured t-test results for every later group versus the first, and how should that completed family be visualized? The App owns visible group selection, order, test settings, result rows, plot meaning, export schemas, and failure wording. It does not choose the scientifically correct test, infer independence or pairing, apply automatic multiple-comparison correction, or replace ANOVA and repeated-measures models. Field Value Display name T-Test Wizard Command `labkit_TTestWizard_app` App ID `ttest_wizard` Family Statistics Version 1.0.0 No new `+labkit` facade is involved. The multi-group policy stays App-local. Workflow Select or enter groups -> order groups with reference first -> choose one test specification -> run group 2..N versus group 1 -> review result family -> draw bar or box comparison plot -> export group and result CSVs The left controls are Data, Test & Plot, Export, and Log. The right workspace has native Data and Plot pages. Data stacks the opened source table and editable analysis table at equal height; Plot gives the figure the full workspace. A compact freshness field stays beside the plot controls. Result rows stay beside the test controls, removing a result-only navigation step. Data Boundary The source grid accepts readable CSV, TSV, XLSX, and XLS tables. Selection is schema-free: finite numeric cells and numeric text are copied in visible spreadsheet order; blanks and labels are skipped with counts; nonfinite numeric cells block the selected range. Each capture appends one durable group: group.label group.values group.sourceDisplayName group.sheet group.cellAddresses Only `label` and `values` participate in calculation identity. Source metadata supports traceability and remains App-owned. A source can be replaced without discarding captured groups. The ordered group editor has `Group` and `Value` columns with one observation per row. Repeated labels form one group, and first label appearance defines group order. The first group is the reference. Blank rows support manual entry. The capture dropdown contains `(new group)` plus current labels, so a source selection can create a group or append to an existing one without an unexplained count column. For new groups, the App scans upward from the selected numeric cells and combines at most two nonnumeric header levels. Looking left on the same header row supports merged-style spreadsheet headings. Dates, numeric summaries, and row numbers are excluded. The result is only an editable suggestion, not a calculation identity inferred from the source. The editor supports batch group changes and deletion of selected observation rows. Deleting the final observation removes the empty group; clearing all data remains a distinct action. Table selection is delivered after the completed selection changes so pointer dragging does not trigger repeated presentation commits. Statistical Contract `runTTest` remains the two-vector primitive. `runGroupTTests` calls it once for each later group: for k = 2:numel(groups) results(k - 1) = runTTest( ... groups(1).values, groups(k).values, optionsForTheseLabels); end Common rules: difference direction is reference minus comparison; result order matches group order after the reference; the same method, alternative, and alpha apply to the family; Welch uses Welch-Satterthwaite degrees of freedom; pooled testing uses the selected equal-variance model; paired testing analyzes displayed reference(k)-comparison(k) differences; paired length is checked independently for each later group; Student-t probabilities and confidence intervals use the Base MATLAB implementation already owned by the App; each comparison preserves stable failure status instead of fabricating a number; no p-value correction is applied. The completed result family stores copies of the reference vector and each comparison vector. Plot callbacks consume those copies and cannot recalculate the tests. Result And Plot Model Each result row includes the method, alternative, alpha, labels, sample counts, means, SDs, reference-minus-comparison estimate and interval, standard error, t statistic, degrees of freedom, exact p-value, status, and message. Significance shorthand is presentation metadata: p < 0.0001 -> **** p < 0.001 -> *** p < 0.01 -> ** p < alpha -> * otherwise -> NS The plot contract follows the supplied laboratory reference figure: white axes and canvas; black box and ticks, no grid; explicit numeric y ticks and ordinary horizontal x-axis tick labels; modest default typography intended for later Figure Studio adjustment; either one pastel mean bar with sample-SD error bar or one colored box per group; black edge, cap, and annotation strokes; stacked brackets from x=1 to x=2..N; shorthand label centered above each bracket; optional deterministic raw-value overlay. The first four colors are: #9DD39C #F5C38A #A9D8E8 #FFF89A They cycle deterministically for additional groups. Plot limits reserve space for every successful bracket. Failed comparisons remain visible as group bars but do not receive a misleading significance annotation. Export Contract The data CSV has `Row` followed by one uniquely named column per group. Shorter groups are padded with blank cells. The result CSV has one row per later group and columns for: method, alternative, alpha, reference label, comparison label, sample counts, means and SDs, difference and interval, SE, t, df, p, shorthand, status, message CSV remains the portable boundary. The project MAT file is for recovery, not the only recoverable copy. Persistence And Migration Project schema version 2 replaces: inputs.vectorA inputs.vectorB with: inputs.groups Migration from version 1 preserves vector A as group 1 and vector B as group 2, including labels, values, and source metadata. A not-run scalar result becomes an empty result family; an existing completed A/B result remains the one-row family for those migrated groups. Validation Unit evidence covers: table-cell extraction and source preservation; Welch, pooled, paired, and directional reference values; ordered first-versus-each result generation; result-family identity after group mutation; multi-column group CSV and multi-row result CSV; stable individual failure statuses. Hidden GUI evidence covers: capture of three selections; ordered group state; two comparisons against the first group; equal-height source/editor tables and native Data/Plot workspace pages; stale-plot messaging after a manual data edit; boxed, grid-free rendered axes; group and result exports. Manual validation remains required for: native dialogs; large and irregular workbooks; group-table editing feel; long label wrapping; visual bracket spacing with many groups; publication suitability and scientific interpretation. Private laboratory files may be used only as local evidence. No real paths, filenames, identifiers, timestamps, or recognizable values enter tracked fixtures or documentation."},{"title":"Simple Scientific CSV Exchange","url":"development/data-and-designs/scientific-csv-interchange.html","kind":"guide","section":"development","keywords":"development data and designs scientific csv interchange Simple Scientific CSV Exchange","text":"Simple Scientific CSV Exchange [Development index](../README.md) | [T-Test Wizard](../../apps/statistics/ttest-wizard/README.md) | [T-Test Wizard design](group-comparison-app-design.md) | [App development](../build-apps/app-development.md) | [Architecture](../build-apps/architecture.md) Status And Purpose This page defines the minimum CSV exchange contract for LabKit Apps. It guides new exports and gradual improvement of existing exports; adopting it does not change another released App contract by itself. The contract is intentionally a table format, not a universal scientific data model. It provides a dependable lowest layer when two Apps do not share a richer schema: a person can type or repair it in Excel or a text editor; a script can read it without LabKit; an App can display it without first understanding its scientific meaning; different experiments may use different column names and row meanings; consumers can fall back to explicit column or cell-range selection; an App may add useful columns without asking the common format for permission. There are no required `Schema`, `Dataset`, `SampleID`, `Group`, `PairID`, `Include`, or `Note` columns. There is no required filename pattern or JSON sidecar. Repeating such fields in every row would make the lowest layer harder to write without making an unfamiliar experiment unambiguous. The Boundary Between Source And Exchange Data A laboratory source does not need to comply with this contract before LabKit can open it. Source workbooks may contain repeated blocks, dates between sections, formulas, summary rows, blank separators, inconsistent filenames, and several tables on one sheet. Existing App CSVs may mix text identifiers with numeric result columns. A compatible reader first opens these files as cell grids. It then lets the user select the cells or columns that matter. Exchange data is the simple rectangular table saved after that selection or after an App has produced a well-defined table. This distinction allows a fast, human-friendly experimental worksheet to remain useful without forcing every downstream App to reverse-engineer its layout. Existing file -> open cell grid -> select data -> copy values -> simple CSV Minimum File Contract A conforming exchange CSV has: UTF-8 text; comma delimiters; exactly one nonempty header row; a unique, nonempty name for every column; one rectangular table and no second header or table below it; ordinary CSV quoting for text containing commas, quotes, or line breaks; `.` as the decimal separator and no thousands separators in numeric cells; empty cells for missing values; evaluated values rather than spreadsheet formulas; no merged cells, color-dependent meaning, preamble, or comment lines. Text and numeric columns may coexist. A file is not invalid merely because it contains text, blanks, status messages, or columns that a particular analysis does not use. Headers are user-facing labels. Writers should keep them short and stable. When a unit is useful, the recommended spelling is: Charge [mC] Time [s] Resistance [ohm] The bracketed unit is a readable convention, not a prerequisite for opening the file. A generic reader preserves headers exactly and does not silently infer scientific roles from them. Three Useful Table Shapes The following shapes are examples of the same contract, not separate schemas. Apps may use other rectangular tables when their row meaning is documented. Vector Table A vector table places one numeric vector in each column. It is the preferred exchange shape for t-tests and other small comparisons: Condition A,Condition B 1.2,1.7 1.4,1.8 1.3,2.0 Independent vectors may have different lengths. Shorter columns end with blank cells: Condition A,Condition B 1.2,1.7 1.4,1.8 ,2.0 For a paired analysis, values on the same visible row form a candidate pair only after the user selects a paired test. The CSV does not force one statistical interpretation. An optional first column may give readable row labels: Row,Before,After 1,2.1,2.5 2,2.0,2.4 3,2.2,2.6 The same file can still be analyzed as two independent vectors. `Row` is an ordinary column, not a reserved identity system. Record Table A record table places one result, sample, cycle, image, segment, or other producer-defined record on each row. Text columns describe the row and numeric columns contain measurements: Source,Cycle,Charge [mC],Duration [s],Status sample_a,1,2.4,10,ok sample_a,2,2.5,10,ok sample_b,1,2.9,10,ok This is already a useful cross-App table. A statistics App can display it and the user can select cells from `Charge [mC]`. It does not need a conversion to a repeated `Metric,Value` representation first. Series Table A time series or curve is also a normal table: Time [s],Signal A [mV],Signal B [mV] 0.00,0.12,0.09 0.01,0.18,0.13 0.02,0.14,0.11 The exchange layer preserves it. Whether curve points are valid observations for a t-test is a scientific decision made by the user or the consuming App, not by the CSV reader. Reading Unknown Files A LabKit table reader follows a compatibility ladder: Read the file without requiring known headers. Display the table with spreadsheet row and column coordinates. If an obvious numeric column or vector exists, offer it as a convenience. Always allow the user to select or revise the exact cells. Copy the confirmed values into the App's own state. Offer export as a simple vector or record table. Automatic recognition is optional assistance. It must never be the only route through the App. The reader must not show a file-level error such as “file contains nonnumeric text” merely because some cells are labels. When a selected range contains mixed cells, report the actual selection in plain language, for example: 12 cells selected: 10 numbers will be used; 1 blank and 1 label will be skipped. The numeric preview is authoritative. The user can change the selection before continuing. Cell Selection Rules For a numeric vector: a single row or single column is the clearest selection; disjoint cells may be accepted when their order is shown in the preview; finite numeric cells are copied in visible row-major order; blanks and text are counted and shown, not silently mistaken for zero; `NaN`, positive infinity, and negative infinity are rejected as observations; the source is never overwritten during extraction. A paired test displays the copied A and B vectors side by side and pairs them by their displayed order. Unequal lengths block the paired test but do not block an independent test. Standard Export From A Two-Vector Tool A two-vector App writes the data actually used in a compact table: Row,Condition A,Condition B 1,1.2,1.7 2,1.4,1.8 3,1.3,2.0 It may omit `Row` when no row labels are useful. It pads unequal independent vectors with blanks. It writes labels chosen by the user as headers and units in those headers when known. Statistical results belong in a separate CSV because a completed test and its input observations have different row meanings: Test,Alternative,N A,N B,Mean A,Mean B,Difference A-B,T,DF,P Welch independent,Two-sided,3,3,1.3,1.833333,-0.533333,-5.237,3.812,0.0077 The values above are synthetic. Repository examples and fixtures must not contain real laboratory filenames, paths, identifiers, timestamps, or recognizable measurements. Result writers may add effect sizes, confidence intervals, warnings, or App-specific fields. They document those columns as that App's export contract; the common CSV layer does not standardize every statistical result. Manual Experimental Recording When a simple table fits the experiment, use it directly: put one stable header in each column; put raw numeric observations in ordinary cells; use one vector per column or one record per row; use blank cells for values not recorded; keep mean, SD, SEM, p-values, and notes outside the raw numeric range or in a separate table; start a new file when that is easier than making one very wide table. When repeated visual blocks are faster during acquisition, keep the block layout. The user can later select the relevant raw cells and export a simple table. The exchange contract must not make experimental entry slower merely to simplify one future importer. Several same-shape CSV files are also valid. A consuming App may open them one at a time, combine them, or let the user select one vector from each. Meaning encoded in filenames can be offered as editable text, but a filename pattern is never required and never silently becomes a group or pair assignment. Existing App Exports An existing CSV is already compatible with the lowest layer when it has one header row and one rectangular table. Migration does not require renaming all columns or expanding each numeric cell into a long observation row. Use these migration rules: Document what one row represents. Keep released column names stable unless a normal compatibility change is justified. Split genuinely different tables—raw data, summaries, test results, and logs—into separate CSVs when practical. Use readable headers and include units where they are known. Avoid absolute local paths in portable output. A basename or user label is usually enough. Preserve useful text and status columns; do not delete them merely to make a file “numeric.” Add an optional companion vector table only when it materially simplifies a common downstream workflow. A generic consumer reads the table and exposes manual selection. A producer-specific adapter is justified only when it removes repeated work and its scientific meaning is stable. The adapter still shows its proposed selection before use. No public `+labkit` facade is justified by this design alone. Reading, selection, and writing remain App-owned until several Apps demonstrate a stable, domain-neutral implementation. What This Contract Deliberately Does Not Encode The lowest layer does not claim to determine: which rows are statistically independent; whether equal-length vectors are paired; whether cycles or segments are samples or repeated measurements; whether a numeric column is raw data, a feature, or a summary; which group is control; which hypothesis test is appropriate; whether a filename token is a subject, condition, channel, or date. Those decisions require experimental context. A richer App export may record them, but an unknown CSV remains usable because the fallback is visible cell selection rather than rejection. Validation Expectations Synthetic tests for a compatible reader and writer cover: vector tables with equal and unequal column lengths; record tables containing both text and numeric columns; series tables; quoted headers and text; blank cells and fully blank trailing rows; duplicate or empty headers; mixed selections with a visible accepted/skipped count; nonfinite numeric values; CSV write/read round trips preserving column order and displayed values; extraction from two different source files; paired preview order and unequal-length blocking; source files that do not match any known App profile. Manual checks use representative private laboratory files without copying their names, paths, identifiers, or values into the repository. Related Guidance [T-Test Wizard Design](group-comparison-app-design.md) [T-Test Wizard](../../apps/statistics/ttest-wizard/README.md) [App Development](../build-apps/app-development.md) [Architecture](../build-apps/architecture.md) [Testing](../maintain-and-release/testing.md) [Documentation System](../maintain-and-release/documentation.md)"},{"title":"Documentation Architecture","url":"development/maintain-and-release/documentation.html","kind":"guide","section":"development","keywords":"development maintain and release documentation Documentation Architecture","text":"Documentation Architecture This page defines how LabKit documentation is authored, organized, rendered, and validated. It is a maintainer reference; readers looking for an app or a MATLAB function should start at the [documentation home](../../README.md). Design Goals LabKit documentation must support four distinct reading tasks: learn a workflow from a complete example; complete a specific task without reading architecture material; understand why a framework or scientific operation behaves as it does; look up exact MATLAB syntax, argument rules, fields, units, and errors. The organization follows the successful division visible in MATLAB and Qt documentation: product and module landing pages orient the reader, task pages teach workflows, concept pages explain behavior, and one page per callable entity provides exact reference. A single long narrative must not try to serve all four purposes. Source Tree `docs/` contains only author-maintained, human-readable documentation data: docs/ README.md getting-started/ apps/ /README.md //README.md //.md /history/ framework/ README.md .md history/ libraries/ /README.md /.md /history/ reference/README.md development/ build-apps/ data-and-designs/ maintain-and-release/ tools/ README.md .md history/README.md Executable renderer code and synthetic-asset generators live under `tools/docs/`. Generated HTML, CSS, JavaScript, and search data live under `site/`. No executable `.m` file belongs under `docs/`, and no generated file under `site/` is edited by hand. Launcher documentation follows the user-facing app map under `docs/apps/labkit-core/launcher/`. Direct-call references for maintainer tools follow their development ownership under `docs/development/tools/`; executable implementations remain under the repository `tools/` tree. Page Types Product landing page The documentation home answers what LabKit is, who each major section is for, and where a reader should begin. It is a routing page, not a project history, launcher manual, or API dump. Tutorial A tutorial leads a new reader through one complete, successful result. It states prerequisites, sample/input expectations, ordered actions, the expected result, and the next relevant topic. Tutorials may teach one path and defer alternatives. How-to guide A how-to guide begins with a concrete goal and gives the shortest reliable procedure. It includes prerequisites, commands or actions, verification, and failure recovery. Background explanation is linked, not repeated. App family page A family page explains the shared experimental domain, compares sibling apps, shows how their files or outputs connect, and links the relevant reusable libraries. It must not contain detailed instructions for one app. App manual Every public app owns one manual at `docs/apps///README.md`. Its normal section order is: purpose and applicability; requirements and launch; supported inputs and file interpretation; a shortest successful workflow; controls and interaction modes; parameters, defaults, legal values, and units; calculations, algorithms, and scientific assumptions; outputs and result schemas; project state, autosave, recovery, and overwrite behavior; use without the GUI; errors, limitations, troubleshooting, related topics, and history. Sections that genuinely do not apply may be omitted. Applicable information must not be hidden in an app catalog, family overview, changelog, or source comment. Complex apps add focused topic pages beside the manual rather than turning the manual into an unstructured notebook. Framework module page The framework landing page presents ownership and module relationships. Topic pages then document lifecycle, definition, callbacks, state transactions, busy behavior, file selection, persistence, layout, plotting, interactions, debugging, and extension rules. Each topic identifies: the problem and owning package; the observable behavior guaranteed to apps; the normal call sequence; public types or functions grouped by role; examples, failure behavior, and related topics. Private runtime mechanics are explained only when necessary to understand a public behavior; private functions do not receive reference pages. Library module page Each `labkit.*` facade has a module landing page comparable to a MATLAB toolbox or Qt module page. It states scope, dependencies, data model, grouped public functions, common call sequences, numerical/scientific semantics, compatibility, errors, examples, and related apps. Exact per-function syntax is linked to generated reference pages. Function reference page Every public function has one generated page. The MATLAB help block immediately after its declaration is the only authoring source and documents, as applicable: all supported syntax forms; concise description and observable behavior; each input, name-value option, and struct field; defaults, legal values, units, shape, type, and empty-value behavior; each output and its fields; algorithms, scientific definitions, and numerical assumptions; thrown errors or status-return failure behavior; a copyable example; related functions and the owning app or module. Function reference must not promise an overload, field, or default that the current implementation and tests do not support. History record History is component-owned. A record explains what changed, why it matters, compatibility, affected components, and validation evidence. It is not an instruction manual and must link to the current page that describes the final behavior. The global history page is only an index. The [history record format](../../history/record-format.md) requires a global positive-integer `sequence`; values are unique and contiguous, and a new record normally takes the previous maximum plus one. The renderer orders the timeline by descending sequence rather than inferring same-day order from a title, filename, filesystem timestamp, or Git checkout. Each rendered record ends with adjacent-record navigation: **Previous change** uses `sequence - 1` and **Next change** uses `sequence + 1`. The first and last records show only the direction that exists. Discovered Sources The documentation model is assembled without a page registry or catalog JSON: Source Authority Markdown path and first heading Page identity, output route, title, navigation area, and reader-task branch. `labkit_launcher(\"list\")` plus `docs/apps///README.md` Public Apps, commands, owning folders, and App manuals. Complete public MATLAB help Library APIs and App-owned non-GUI APIs. History record metadata Change identity, order, components, compatibility, and history associations. Every non-history Markdown page under `docs/` is published. `README.md` maps to its directory index; App manuals retain their established sibling HTML route. Missing titles, duplicate ids, duplicate outputs, missing App manuals, or ambiguous App ownership stop the build. Navigation hierarchy The top navigation selects a documentation area. The left sidebar then shows the current area's own hierarchy instead of repeating one flat site-wide list. Where a page has descendants, the sidebar uses labeled branches: app pages are grouped by app family, with the family manual above its apps; function pages are grouped by library, and API pages list sibling functions under their MATLAB package; framework and development guides are grouped by their reader task; history uses its generated timeline and adjacent-record links rather than placing every historical record in the sidebar. The first path segment selects the documentation area. Under `framework/` and `development/`, the next directory names the sidebar branch, so moving a page also changes its ownership and navigation. App branches come from their family manual path, library branches come from facade directories, and history uses record sequence. A documentation area with no descendants remains a single page and does not render an empty artificial branch. Markdown keeps standard relative links so source browsing works on GitHub. After moving pages, run: maintainLabKitDocLinks(repoRoot, \"Update\", true) The maintainer rewrites only uniquely identifiable moved targets. Its default read-only mode fails on unresolved or ambiguous links. Authoring From Current Code Documentation changes begin from the current implementation, not from old prose. For an app manual, inspect at least its entrypoint, runtime definition, workbench layout, state/options owner, input loader, output writer, public app-owned APIs, and focused tests. For a library, inspect its complete public surface and tests. Existing documentation and history may identify questions to investigate, but they are not evidence that a behavior still exists. User-visible labels, defaults, supported file types, state fields, output columns, formulas, units, and error behavior must match their current code owners. If code and documentation disagree, resolve the product contract rather than documenting both as alternatives. App manuals are reader-oriented product pages. Keep App-specific requirements, the shortest successful workflow, scientific semantics, outputs, recovery, limitations, and supported GUI-free APIs. Do not append implementation inventories or repeat framework contracts such as callback ownership, runtime lifecycle, hover-help coverage, source serialization, or layout binding. Document those contracts once under `docs/framework/`; link there only when a reader needs the shared behavior to use the App. Public Function Help A public function help block is part of the tested API. It states public call syntax, behavior, inputs, outputs, option fields, defaults, legal values, and errors or side effects where they matter. The documentation guardrail compares the names in the function signature with the `Inputs` and `Outputs` sections and requires option-bearing APIs to describe named fields. Generated API pages use this same help block, so the command-line `help` view and HTML reference do not have separate prose owners. Code under an `Example:` heading is an executable contract. The test runner extracts that code from the function header and executes it in MATLAB. Use `Typical Call:` when a useful sketch depends on a user's data file, an existing app/session variable, or an interactive dialog. Such a sketch becomes an `Example:` only after it has self-contained synthetic setup and passes the example runner. A self-contained MATLAB block in an app manual may be preceded by ``. The documentation contract extracts and executes every marked block in a clean test setup. Mark only examples that use synthetic data, require no dialogs or user files, and avoid lasting file or UI side effects. Unmarked blocks may demonstrate a typical call with a user's own file, but every variable and placeholder must still be explained by the nearby text. Cross-References Use relative Markdown links between narrative sources. Link the first useful mention of an app, framework topic, library, or related guide. Function names use fully qualified MATLAB symbols so the renderer can link them to generated API pages. Every app manual links its family, related apps, relevant libraries, public app APIs, and component history when records exist. Every library landing links representative apps. Related links should help the reader continue a task; they are not a list of every page sharing a keyword. The renderer rejects unresolved local Markdown targets. External links remain ordinary HTTPS links and are not copied into the site. Rendering And Search `tools/docs/renderLabKitDocs.m` reads the structured model, renders all narrative and API pages, builds navigation and related links, and writes the static site to `site/`. The renderer uses MATLAB only and introduces no third-party runtime dependency. Search is a generated static index containing titles, headings, descriptions, keywords, components, MATLAB symbols, and help text. It works from GitHub Pages and when `site/index.html` is opened directly from disk; it must not depend on a local web server or a fetch request. Generated pages carry a generated-file marker. Regeneration builds a complete temporary tree, then synchronizes its files into `site/`: changed files are updated, obsolete files and empty directories are removed, and the canonical `site/` directory itself is preserved. Keeping that root stable avoids numbered conflict copies when the repository is stored in a synchronized folder. For direct MATLAB syntax, result fields, missing-output reconstruction, and consistency-check behavior, see [Documentation Build Tools](../tools/documentation.md). Build And Validate Use the documentation build and consistency tasks listed in [Testing](testing.md). The build regenerates the tracked site; the consistency check renders independently and compares that result with `site/`. Project documentation tests additionally check page/output uniqueness, source and output existence, API coverage, local links, direct-file search, generated markers, and prohibited retired paths. Before committing documentation work: review the Markdown diff for claims not backed by current code; regenerate `site/`; run the focused documentation guardrail; open representative app, module, API, history, and search pages; commit source and generated output together. Related Topics [Documentation Home](../README.md) [Architecture](../build-apps/architecture.md) [App Development](../build-apps/app-development.md) [Testing](testing.md) [Maintainer Tools](../tools/README.md) [Release Process](release.md)"},{"title":"DIC Postprocess","url":"apps/dic/dic-postprocess.html","kind":"app","section":"apps","keywords":"apps dic dic postprocess DIC Postprocess","text":"DIC Postprocess DIC Postprocess converts Ncorr strain fields into EXX and EYY overlays on an optical reference image and calculates descriptive strain statistics over a validated ROI. It is a rendering and summary tool; it does not rerun DIC. Requirements And Launch Input MAT files must contain the Ncorr result structure recognized by the app loader. labkit_DICPostprocess_app Inputs Three inputs define one analysis: an Ncorr MAT file containing readable EXX and EYY strain fields; the optical reference image used for the displayed specimen domain; a mask image whose nonzero pixels define the requested ROI. All three are required before **Generate overlays + summary** can produce a result. The loader validates the expected Ncorr structure and reports malformed or incomplete MAT files instead of guessing at unrelated arrays. Basic Workflow Choose the DIC MAT file, matching reference image, and mask image. Set the overlay range and opacity. Adjust oversampling, smoothing, and edge trimming when justified. Optionally tune optical-image brightness, contrast, gamma, saturation, and RGB gains. Generate the EXX/EYY overlays and review the summary table. Save clean overlay PNGs and export the summary CSV. Changing an option updates the reproducible project parameters. Generate again before export when inputs or processing parameters change. Overlay Parameters Parameter Default Meaning Alpha 0.60 opacity of strain color over the optical image Color min -0.15 lower strain value mapped into the color scale Color max 0.15 upper strain value mapped into the color scale Oversample 6 output-domain refinement factor used during preparation Smooth sigma 0.8 Gaussian smoothing scale applied to valid strain data Edge trim 1 boundary pixels removed from the valid strain support Color limits affect rendering, not the stored numeric strain values used for the summary. Alpha affects compositing only. Smoothing and edge trimming alter the processed field and therefore can affect the summary; record them with any reported measurements. Optical Image Parameters Brightness defaults to `0`; contrast, gamma, saturation, and red/green/blue gain default to `1`. These controls prepare the visual background. They do not modify EXX/EYY values. Extreme settings can hide features or imply contrast that is not present in the strain field, so exported figures should retain the processing manifest. Processing And Scientific Semantics The app constructs a valid-strain mask from finite Ncorr values, intersects it with the requested ROI, trims invalid edges, and maps the result to the optical image domain. Linear resizing is used for continuous image/strain data and nearest-neighbor resizing for masks. Invalid strain samples remain excluded rather than being converted to zero. Summary statistics are computed independently for EXX and EYY over the final valid ROI. The result table reports the metrics returned by `dic_postprocess.analysisRun.summarizeStrain`; open its API page for the exact table schema and non-finite-value behavior. Outputs a clean EXX overlay PNG; a clean EYY overlay PNG; a CSV table containing the ROI strain summary; a LabKit result manifest describing inputs, parameters, and output roles. The clean PNG exports contain only the composed image, not axes chrome or interactive toolbar controls. Input MAT, reference, and mask files are never modified. Use Without The GUI inputs = struct( ... \"referenceImage\", zeros(16, 16, 3), ... \"maskImage\", true(16), ... \"strain\", struct( ... \"exx\", zeros(8), ... \"eyy\", 0.01 .* ones(8), ... \"roiMask\", true(8))); parameters = struct( ... \"alpha\", 0.60, \"colorMin\", -0.15, \"colorMax\", 0.15, ... \"oversample\", 6, \"smoothSigma\", 0.8, \"edgeTrim\", 1, ... \"brightness\", 0, \"contrast\", 1, \"gamma\", 1, ... \"saturation\", 1, \"redGain\", 1, \"greenGain\", 1, \"blueGain\", 1); [summary, overlayExx, overlayEyy] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters); The source loader is app-owned; the generated API table below lists only operations intentionally supported as public GUI-free calculation APIs. Function Reference Use the generated pages for [`makeStrainOverlay`](../../../reference/api/dic_postprocess/analysisRun/makeStrainOverlay.html), [`prepareOutputs`](../../../reference/api/dic_postprocess/analysisRun/prepareOutputs.html), and [`summarizeStrain`](../../../reference/api/dic_postprocess/analysisRun/summarizeStrain.html) for exact required and optional fields, units, output shapes, empty selections, failure behavior, and related APIs. Errors And Limitations A MAT file without the recognized Ncorr strain structure is rejected. Reference image, mask, and strain arrays must describe the same specimen domain; resizing resolves array dimensions, not semantic misalignment. A mask with no valid finite strain pixels produces no meaningful summary. Smoothing can suppress local extrema. Choose it as an analysis parameter, not merely a visual preference. Related Topics [DIC Preprocess](../dic-preprocess/README.md) [DIC family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) dic_postprocess.analysisRun.makeStrainOverlay Blend a color-mapped strain field over a reference image. dic_postprocess.analysisRun.prepareOutputs Build DIC strain summaries and both display overlays. dic_postprocess.analysisRun.summarizeStrain Summarize finite EXX and EYY values inside a mask."},{"title":"Private Apps","url":"development/maintain-and-release/private-apps.html","kind":"guide","section":"development","keywords":"development maintain and release private apps Private Apps","text":"Private Apps [Development index](../README.md) | [App development](../build-apps/app-development.md) LabKit supports local private app workspaces for apps that should not be published in the public repository. This page documents only the public extension point, folder shape, and development contract. Private app names, workflow notes, data assumptions, validation details, and release notes belong in the private app repository itself. Repository And Mounting Model A private app workspace is its own Git repository. It owns its app source, tests, manuals, histories, and repository rules regardless of where it is stored. The public repository never owns or migrates those files. For convenient local development, the independent repository may be checked out at the public checkout's ignored `private_apps/` mount point: LabKit-MATLAB-Workbench/ apps/ public apps tracked by this repository +labkit/ shared LabKit facades private_apps/ independent private Git repository, ignored here README.md tests/ apps/ / / README.md HISTORY.md labkit__app.m +/ definition.m +workbench/buildLayout.m projectSpec.m optional createSession.m optional +workbench/present.m optional +sourceFiles/... as needed +analysisRun/... as needed +resultFiles/... as needed That physical nesting is only a launcher mount; it does not make the private repository a folder owned by the public repository. The private repository may instead live anywhere and be registered through `LABKIT_PRIVATE_APP_ROOTS`. Each entry points either to a private repository root containing `apps/` or directly to its `apps/` folder. Separate entries with the platform path separator. Launcher Discovery The launcher discovers: public app entry points under `apps/**/labkit_*_app.m` private app entry points under `private_apps/apps/**/labkit_*_app.m` private app entry points under `LABKIT_PRIVATE_APP_ROOTS` Private entries appear in the app catalog with `Visibility` set to `private`. They launch like ordinary LabKit apps after the launcher adds the app folder to the MATLAB path. The launcher can also package a selected private app for offline deployment as either source `.m` files or encoded `.p` files. The generated zip preserves the private app under `private_apps/apps/...` and includes the packaged launcher plus the deployment/profiling tool folders it uses. Private apps discovered through `LABKIT_PRIVATE_APP_ROOTS` are copied into that same `private_apps/apps/...` package shape so the zip does not depend on the source machine's environment variable or private workspace path. Git Ownership Create or clone the private repository independently, then choose either the ignored mount point or the environment-variable registration: cd LabKit-MATLAB-Workbench mkdir private_apps cd private_apps git init git remote add origin git@github.com:/.git The public LabKit repository ignores its `private_apps/` mount point, so a nested checkout remains a normal independent repository and is not added to public history, public release zips, or public CI. Do not use a submodule unless you intentionally want the public repository to record a private remote URL and commit pointer. Structure Rules Private apps should follow the same app-owned package shape as public apps: keep one public entrypoint named `labkit__app.m` keep app workflow code under the owning `+/` package use one `definition.m` for product identity, version, requirements, layout, and references to optional capabilities start with only the entrypoint, definition, and semantic workbench layout bind callbacks and plot renderers directly on their owning layout controls; add `+workbench/present.m` only for dynamic visible state add one `projectSpec.m` only for durable App-owned state; keep create, validate, and version-aware migrate functions local to that file add root `createSession.m` only for App-specific transient reconstruction group workflow code by concrete user capability, such as `+sourceFiles`, `+analysisRun`, `+resultFiles`, or another app-owned domain package use shared LabKit facades such as `labkit.app.*`, `labkit.image.*`, `labkit.thermal.*`, `labkit.dta.*`, `labkit.rhs.*`, and `labkit.biosignal.*` Do not add separate `requirements.m`, `version.m`, generic `+appLifecycle` or `+appState` packages, or per-version `migrateProjectVx.m` files. Supported legacy top-level MAT variables use explicit import functions declared by the project spec. See [Build a Complete App](../build-apps/complete-app.md) for a working file-by-file example and [Runtime and Lifecycle](../../framework/guides/runtime.md) for every definition, project, session, action, presenter, and renderer field. Do not put private app source under public `apps/`. Do not move private workflow formulas, private result schemas, private labels, or private data assumptions into `+labkit`. Private Documentation The public repository should contain only this generic private-app structure guide. Put private app catalogs, SOPs, validation commands, sample notes, deployment notes, and release history in the private app repository. Private app repositories own their own tests and validation runners. They do not need to add app-specific suites, fixtures, build tasks, or sample assets to the public LabKit repository. Private app internals may be looser than public apps when a local workflow needs it, but the launcher-facing surface should remain compatible with LabKit discovery and guardrails: keep launch commands, definition-backed `requirements` and `version` lightweight requests, path setup, and private sample hygiene valid when the private workspace is present next to a public checkout. The public launcher's Code Analyzer tool includes configured private app workspaces in local reports only after the private workspace opts in. Put an empty `.labkit-accept-main-guardrails` file at the private workspace root to accept the public repository's style and code-quality guardrails for that workspace. When `private_apps/apps/` exists or `LABKIT_PRIVATE_APP_ROOTS` points at an accepted private workspace, the generated `artifacts/code-check/matlab_code_issues_*.json` and `.html` files include those private app findings without adding private source to the public repo. For one-off local checks, `LABKIT_GUARD_PRIVATE_APPS=1` temporarily includes configured private roots even without the sentinel file. Because private workspaces are separate Git repositories, public changed-file tasks do not discover private diffs. For an accepted private workspace, run the private repository's own tests and the public `buildtool changedFast` guardrail before push when private source, tests, docs, component history, or version metadata changed; CI remains the complete validation gate."},{"title":"Release Policy","url":"development/maintain-and-release/release.html","kind":"guide","section":"development","keywords":"development maintain and release release Release Policy","text":"Release Policy [Development index](../README.md) | [Project history](../../history/README.md) | [Deployment tool](../tools/deployment.md) | [LabKit Launcher](../../apps/labkit-core/launcher/README.md) Use semantic versioning for public releases. Version Numbers Patch releases, such as `v2.1.1`, are for bug fixes that do not add user-visible capabilities. Minor releases, such as `v2.2.0`, are for user-visible features, workflow changes, or meaningful maintenance improvements. Major releases, such as `v3.0.0`, are for breaking changes or intentionally incompatible workflows. If a commit marked as breaking, such as `feat!`, changes user workflow or app compatibility, choose a major version. If the implementation changes a user workflow without intentionally breaking compatibility, describe it in the release notes so launcher users can choose or roll back versions deliberately. On a development branch, choose each component's final version directly from the merge base with `origin/main`, not from intermediate branch commits. A branch may edit version metadata while work is evolving, but its merge-ready state must be exactly one semantic-version step from the mainline baseline: the next patch, the next minor with patch zero, or the next major with minor and patch zero. The related component history record should describe that direct `main baseline -> branch final` transition. This prevents temporary branch versions from accumulating into artificial public version jumps. Maintain user documentation with the same squash-oriented view: it describes the final branch behavior and one net compatibility transition, not the sequence of intermediate commits used to develop it. Tags Use `vX.Y.Z` for new release tags, for example `v2.2.0`. Do not rename or delete historical published tags only to normalize naming. If an older release used a different tag style, keep it for compatibility with existing links and user checkouts. Future releases should use `vX.Y.Z`. Complete developer-led interactive App validation before starting a release. Then dispatch the `Release` workflow from `main`, provide the new version, and explicitly confirm that manual validation is complete. The exact main commit must already have a successful `Continuous Integration` push run, which proves the complete headless and hidden-GUI suites on Toolbox-free Linux, macOS, and Windows. The release workflow verifies that run instead of executing the same six jobs again, then creates the annotated tag. An invalid version, a non-`main` dispatch, a missing manual-validation confirmation, an existing tag or release, or the absence of a successful same-commit main CI run prevents tag creation. Ordinary push, pull-request, and documentation workflows never create release tags. gh workflow run release.yml --ref main \\ -f version=vX.Y.Z \\ -f manual_validation_confirmed=true gh run watch Use `buildtool coverage` locally when you need a coverage report without preparing a release. GitHub Releases Use the release title format: LabKit MATLAB Workbench vX.Y.Z Use this note structure: ## Highlights - User-facing summary bullets. ## Fixes - Bug fixes, if any. ## Upgrade Note - Compatibility or upgrade guidance, if any. Mention app entrypoint or app requirement changes, launcher or app version bumps, and any LabKit facade contract version or supported-range changes. ## Validation - Local and CI validation evidence. Omit an empty section when it does not apply. Keep validation factual: name the commands or CI workflow that passed and the commit used for the release. After it verifies the required CI run, the workflow exports `labkit_launcher.m` from the annotated tag blob, verifies its SHA-256 against that blob, pushes the tag, creates a draft GitHub Release, uploads the launcher, and verifies the remote asset. The tag points at the same `github.sha` recorded by that Base MATLAB CI run; a later advance of `main` does not move it. Automation deliberately stops at a draft. Before publishing, rewrite or complete the generated notes with the required sections above, confirm the version and tag target, inspect the launcher asset, and record the final manual and CI evidence. Publishing the draft is a developer release decision, not a side effect of ordinary CI. Attach `labkit_launcher.m` to each GitHub release. The root README download link points at the latest release asset so browsers download the launcher instead of opening the raw source text. The launcher version manager lists recent releases, tags, and main-branch commits. Keep release titles and upgrade notes clear enough for users to select an older release when the newest build is unsuitable for their workflow. Component History Release notes summarize one public release. Long-lived change explanations live with the framework, library, app, launcher, or project documentation they affect. Each record is one Markdown file under a relevant `history/` directory. The generated documentation site aggregates those files into the [Project History](../../history/README.md) page and automatically lists matching records on every related component page. When a change bumps `labkit_launcher.m`, a `+labkit/**/version.m` facade, or an App's `AppVersion` metadata in `apps/**/definition.m`: update the owning component documentation; add one history record using the authoritative [history record format](../../history/record-format.md), including the stable Change ID, next global history sequence, affected components, and direct version transition; complete every required narrative section in that format; rebuild the generated site and verify the record appears on each affected component page. Write a cross-component change once and list every affected component in its metadata. Do not copy its narrative into multiple app pages. Current versions come from launcher, facade, and app source metadata, so there is no separate lookup table to synchronize. Git branches, PRs, tags, and commits express delivery state; do not add a second pending/unreleased hierarchy. Before tagging a release that adds, renames, or removes release-blocking guardrail tests, verify that the buildfile CI tasks still discover the intended suite and tag coverage. The workflow should call those build tasks through `matlab-actions/run-build` rather than maintaining long-lived test class selectors. Launcher Asset Reproducibility Generate the launcher asset from the release tag, not from an editor buffer, copied file, or platform-dependent checkout. This keeps the release asset byte for byte identical to the tagged source file, including line endings. Use a tag-specific staging folder: mkdir -p artifacts/release/vX.Y.Z git show vX.Y.Z:labkit_launcher.m > artifacts/release/vX.Y.Z/labkit_launcher.m Before upload, compare the staged launcher against the tag blob: git show vX.Y.Z:labkit_launcher.m | shasum -a 256 shasum -a 256 artifacts/release/vX.Y.Z/labkit_launcher.m wc -c artifacts/release/vX.Y.Z/labkit_launcher.m After creating the GitHub release, verify that the uploaded asset reports the same byte count and SHA-256 digest: gh release view vX.Y.Z --json assets If the asset digest or size differs from the tag-exported file, delete only the incorrect asset and re-upload the tag-exported `labkit_launcher.m`. Do not move or recreate an already-published release tag to fix an asset upload mistake."},{"title":"Testing","url":"development/maintain-and-release/testing.html","kind":"guide","section":"development","keywords":"development maintain and release testing Testing","text":"Testing [Development index](../README.md) | [Architecture](../build-apps/architecture.md) | [Maintainer Tools](../tools/README.md) | [Documentation](documentation.md) LabKit tests specify observable contracts. They are not organized by an implementation-stage label, suite selector, tag query, or hand-maintained test catalog. Start from the production file whose behavior changed; the test catalog determines its required evidence and executes exact test identities. Everyday Workflow For a changed production source, ask the catalog where evidence belongs: addpath(\"tests\") labkittest.explain(\"apps/electrochem/cic/+cic/+analysisRun/computeCIC.m\") `explain` prints the exact specification folders, contracts, environments, selected identities, and the reason for every selection. Add a new behavioral spec without inventing a path, metadata, class shell, or runner command: labkittest.createSpec( ... \"apps/electrochem/cic/+cic/+analysisRun/computeCIC.m\", ... Contract=\"scientific\", Name=\"PulseWindow\", ... Reason=\"Regression: pulse-window bounds must remain stable.\") `Contract` is needed only when one source has more than one author-owned boundary. `Reason` starts with `Regression`, `Invariant`, or `Compatibility`; it records the durable behavior being protected, rather than a temporary implementation detail. The generated file contains the required `Contract:` and `Env:` tags plus an intentional failing placeholder. Replace that placeholder with a small behavioral proof. Remove or revise a specification when that stated behavior is intentionally retired. Never create a test by guessing a folder, a suite range, a test tag, or a wrapper class. During an edit, execute the narrow owner and contract that changed: labkittest.run(Owner=\"apps/electrochem/cic/analysisRun\", ... Contract=\"scientific\") Use `labkittest.run(File=...)` only when the source maps to one complete, known evidence closure. It refuses missing evidence instead of silently running a partial test. A changed analysis source normally requires its scientific behavior, result schema, and presentation consumer together. Test Model Runnable specifications are under `tests/specs/`. Their owner path mirrors a production capability, and every test declares exactly one contract and one execution environment. tests/specs/ ├── framework/ / ├── apps//// ├── apps/conformance/ └── system/ / `tests/+testfixtures/` contains only synthetic inputs reused by multiple specification owners. Keep an owner-specific fixture beside its specification; do not create a generic `shared`, `support`, or `helpers` test directory. Contracts describe evidence, not test cost: Contract Proves `scientific` calculations, numerical policies, and scientifically meaningful branches `source` source decoding, callbacks, and capability-local state transitions `state` durable/transient state transitions and reconstruction `persistence` project schema creation, validation, and migration `result` exports, schemas, manifests, and task identity `presentation` reader-facing snapshot data and declared layout semantics `definition` / `product` parameterized public App conformance `system` build, repository, CI, documentation, packaging, and release guardrails Environments are `headless`, `hidden-gui`, and `path-isolated`. Headless tests do not prove GUI behavior. Hidden-GUI conformance proves that an App can build its declared layout; it does not prove native dialogs, pointer feel, visual quality, real lab data, or scientific review. The path-isolated conformance probes every public App from a reset path boundary in the already running catalog executor. It retains the deployable path boundary and batches all App results without requiring a second concurrent MATLAB license. Build Tasks Use stable Build tasks for branch and CI gates: buildtool changedFast buildtool headless buildtool gui buildtool isolated buildtool coverage buildtool docs buildtool docsCheck Task Purpose `changedFast` Local final pre-commit/pre-push gate. Reads tracked and untracked working-tree paths; on a clean checkpoint it reads `HEAD^..HEAD`. `headless` Every headless catalog identity. `gui` Every hidden-GUI catalog identity. `isolated` Every path-isolated catalog identity. `coverage` Headless catalog with Cobertura XML and HTML coverage artifacts. `docs` / `docsCheck` Render or verify the generated documentation site. `changedFast` prints whether its plan is `focused-local` or `full-profile`, semantic reasons, exact identities, and any explicitly ignored paths. For ordinary App and facade source it runs only the required contract closure. Framework, Build, catalog, and repository-policy paths select explicit bounded system evidence. Documentation and generated-site paths are explicitly ignored because `docsCheck` owns their consistency. An unknown path is a planning error: declare its production role or an explicit no-test reason; do not hide missing ownership by widening the run. Use `labkittest.explainChanged` to inspect that decision without executing tests. It prints each changed path's classification, selected evidence, and any manual boundary. A focused-local result is rapid author feedback, not merge safety evidence; CI runs the full platform profiles. Run focused behavior during iteration. Run `changedFast` once when the branch is ready for review or direct-main integration. CI owns broad platform validation; do not repeatedly run broad tasks after each small edit. Artifacts and Failures Each run writes one folder beneath `artifacts/test-results//`: manifest.json run identity plan.json exact selected identities and reasons events.jsonl progress and heartbeat stream active-test.json latest machine-readable progress state junit.xml CI result exchange summary.json pass/fail summary coverage.xml coverage runs only coverage-html/ coverage runs only After a failure, copy its exact class/method identity from the output and run the smallest owner/contract or exact source that proves the repair. A zero selection or missing-contract error is a test-authoring defect, never passing evidence. When a mapped layout change leaves a non-automatable boundary, its plan can name a manual check. It is printed and recorded in `plan.json`, but it never makes an automated run pass. Manual checks are limited to native dialogs, pointer behavior, visual design, real-data suitability, and scientific interpretation; they cannot replace an automated calculation, state, export, migration, structural-GUI, or workflow proof. CI and Manual Evidence Continuous Integration runs `headless`, `gui`, and `isolated` on Linux, macOS, and Windows from a clean MATLAB runtime without optional Toolboxes. It also runs `docsCheck` once, then reports one aggregate `CI Gate` result that depends on every required profile. Configure repository branch protection to require `CI Gate`; the workflow does not silently replace repository protection policy. It uploads the catalog artifacts even after failure. Coverage is an explicit report, not a duplicate CI gate. CI classifies the exact pushed or pull-request diff before scheduling MATLAB. Source, test, build, workflow, and tool changes run the complete platform matrix. Human documentation-only changes run `docsCheck` without the platform matrix. Agent guidance and GitHub contribution-template-only changes run the lightweight change-policy check without starting MATLAB. Mixed changes run the union of their required profiles, and `CI Gate` verifies every profile selected by the classifier. Changing the classifier or workflow is itself a full-matrix change. Manual App validation remains required for native file dialogs, visual design, pointer interaction, real-data suitability, and scientific interpretation. Use synthetic, minimal fixtures in automated tests. Never add real lab files, local paths, subject/device identifiers, timestamps, or proprietary metadata. Maintainer Rules Keep specs beside the capability that owns their behavior; tests never own a parallel product API. Keep cross-owner synthetic inputs in `tests/+testfixtures/`; keep every other test helper beside the specification that owns its behavior. Prefer direct behavioral calls over full App workflows. Add a structural GUI proof only when layout or wiring itself is the contract. Do not add legacy suite folders, stage tags, selector registries, test wrappers, runner options, or Code Analyzer suppression pragmas. Add a new public framework test API only when it is a stable product boundary. Test infrastructure stays private under `tests/+labkittest/`. Follow `.agents/migration_guide.md` only while retiring compatibility debt; it is not an everyday authoring checklist."},{"title":"Maintainer Tools","url":"development/tools/index.html","kind":"overview","section":"development","keywords":"development tools Maintainer Tools","text":"Maintainer Tools LabKit keeps a small set of MATLAB tools for source-checkout maintenance. They can be launched from the LabKit Launcher or called directly after adding the owning tool directory to the MATLAB path. They are not dependencies of normal app calculations and write generated output under ignored `artifacts/` paths unless an explicit destination is supplied. Choose A Tool Goal Entry point Reference Scan MATLAB source and export reviewable issues `runCodecheckReport` [Code Analyzer Reports](codecheck.md) Package one or more apps for offline use `packageLabKitApp` [App Deployment Packages](deployment.md) Regenerate or verify the static documentation site `renderLabKitDocs`, `checkLabKitDocs` [Documentation Build Tools](documentation.md) Profile startup, callbacks, scripts, or functions `profileLabKitTarget` [Performance Profiling](profiling.md) Direct Invocation Pattern Add the narrow tool folder, not all of `tools/` recursively: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"profiling\")) profileLabKitTarget(\"labkit_launcher\") Each public tool file owns its private implementation folder. Adding the public folder makes those private helpers available only to that tool, without turning them into global commands. Launcher And Command-Line Use The launcher provides the common interactive defaults: it supplies the current checkout root, selected app commands, progress UI, and sensible report locations. Direct calls are preferable for automation, custom output paths, batch MATLAB, reusable profile targets, or packaging several named apps. Tool reports and ZIPs are generated artifacts, not source documentation or app project data. **Clean Artifacts** may remove them. Copy any evidence that must be retained outside `artifacts/` or attach it to the relevant release or issue. Related Documentation [LabKit Launcher](../../apps/labkit-core/launcher/README.md) [Testing](../maintain-and-release/testing.md) [Documentation System](../maintain-and-release/documentation.md) [Release Process](../maintain-and-release/release.md) [Architecture](../build-apps/architecture.md)"},{"title":"Code Analyzer Reports","url":"development/tools/codecheck.html","kind":"guide","section":"development","keywords":"development tools codecheck Code Analyzer Reports","text":"Code Analyzer Reports `runCodecheckReport` runs MATLAB `codeIssues` over the LabKit checkout and writes both the native JSON export and a browsable HTML report. Use it when you need the complete analyzer result; ordinary tests and CI own their own focused quality checks. Syntax report = runCodecheckReport(root) report = runCodecheckReport(root, \"OpenReport\", false) report = runCodecheckReport(root, \"ProgressFcn\", progressFcn) Add the tool folder first: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"codecheck\")) report = runCodecheckReport(repoRoot, \"OpenReport\", false); Inputs And Options Name Meaning `root` LabKit checkout root to scan. It is converted to a text scalar before file discovery. `OpenReport` Logical scalar. Default `true`; opens the generated HTML report in the system browser. `ProgressFcn` Empty or a function handle called as `fcn(message, value)`, where `value` progresses from 0 to 1. The scan includes MATLAB files beneath `root`. It excludes `.git`, `.github`, `.vscode`, `.codes`, `artifacts`, `node_modules`, and `photos`. Additional private app roots configured outside `root` are included when their workspace contains `.labkit-accept-main-guardrails`, or when `LABKIT_GUARD_PRIVATE_APPS` explicitly enables local inspection. Output The returned struct contains: Field Meaning `jsonFile` Native `codeIssues` JSON export path. `htmlFile` LabKit HTML report path. `fileCount` Number of unique MATLAB files scanned. `issueCount` Unsuppressed issue count. `suppressedIssueCount` Suppressed issue count reported by MATLAB. Default reports are timestamped beneath `artifacts/code-check/`. A second run within the same second receives a numeric suffix instead of overwriting the first report. Behavior And Limitations An analyzer finding is report data, not a thrown failure. File-access errors, invalid MATLAB analyzer inputs, or report-write failures still raise their underlying MATLAB error. LabKit source must not add Code Analyzer suppression pragmas merely to make this report empty; fix the source shape or document a real external limitation in project governance. Related Documentation [Maintainer Tools](README.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"App Deployment Packages","url":"development/tools/deployment.html","kind":"guide","section":"development","keywords":"development tools deployment App Deployment Packages","text":"App Deployment Packages `packageLabKitApp` creates one ZIP containing one or more selected LabKit apps, their assets, the shared `+labkit` runtime, standalone entry files, and a machine-readable package manifest. Source and P-code packages serve different deployment needs. Syntax result = packageLabKitApp(appSelector) result = packageLabKitApp(appSelector, zipFile) result = packageLabKitApp(appSelector, zipFile, Name, Value) Typical calls: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"deployment\")) one = packageLabKitApp(\"labkit_CIC_app\"); several = packageLabKitApp( ... [\"labkit_CIC_app\", \"labkit_EIS_app\"], [], ... \"OutputRoot\", fullfile(repoRoot, \"artifacts\", \"deployment\")); runtimeOnly = packageLabKitApp( ... \"labkit_CIC_app\", [], \"CodeFormat\", \"pcode\"); App Selector `appSelector` accepts: one app command such as `\"labkit_CIC_app\"`; a string array or cell collection of app commands; an app entry-file path; an app folder containing exactly one entry point; a launcher app metadata struct or struct array. Commands are resolved through the same public and configured private-app roots used by the launcher. Ambiguous commands, missing entry points, duplicate folder entries, or unavailable source files raise an error before packaging. Options Name Default Meaning `Root` Repository root Source/runtime root containing `+labkit`, apps, launcher, and tools. `OutputRoot` `artifacts/deployment/` Folder used when `zipFile` is empty. `ProgressFcn` Empty Function handle called as `fcn(message, value)` from 0 to 1. `CodeFormat` `\"source\"` `\"source\"`/`\"m\"` for M-files, or `\"pcode\"`/`\"p\"` for runtime-only P-code. Source And P-code Packages Property Source P-code App implementation `.m` files `.p` files Standalone entry `run_.m` `run_.p` `labkit_launcher.m` Included Omitted Deployment/profiling tools Included Omitted `+labkit` and selected app assets Included Included in encoded runtime form Both formats include `README.txt` and `packaged_app_manifest.json`. Neither format includes unrelated sibling apps. P-code is a deployment format, not a source backup; retain the corresponding private or public source repository. Result Structure The result identifies `zipFile`, `packageRootName`, `entryFiles`, `appCommands`, app-relative folders, visibility values, `codeFormat`, and `fileCount`. Legacy scalar fields `entryFile`, `appCommand`, `appRelativeFolder`, and `visibility` describe the first selected app. Packaging stages files in a temporary directory, replaces an existing target ZIP only after validation reaches archive creation, and removes the staging directory afterward. Related Documentation [Maintainer Tools](README.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md) [Private Apps](../maintain-and-release/private-apps.md) [Release Process](../maintain-and-release/release.md)"},{"title":"Documentation Build Tools","url":"development/tools/documentation.html","kind":"guide","section":"development","keywords":"development tools documentation Documentation Build Tools","text":"Documentation Build Tools LabKit uses three public MATLAB entry points for its documentation sources and generated site. `renderLabKitDocs` builds and synchronizes the site; `checkLabKitDocs` performs an independent build and verifies that the tracked site is current; `maintainLabKitDocLinks` checks or repairs standard relative Markdown links. Path-organized Markdown and MATLAB help blocks are the authored sources. Maintain Links result = maintainLabKitDocLinks result = maintainLabKitDocLinks(repoRoot) result = maintainLabKitDocLinks(repoRoot, \"Update\", true) The default is read-only. With `Update=true`, a missing Markdown target is rewritten only when filename and link label identify exactly one current page. Review the rewritten diff, then rerun the default check. Render The Site result = renderLabKitDocs result = renderLabKitDocs(sourceRoot) result = renderLabKitDocs(sourceRoot, outputRoot) repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"docs\")) result = renderLabKitDocs( ... fullfile(repoRoot, \"docs\"), fullfile(repoRoot, \"site\")); Empty or omitted roots default to `docs/` and `site/` beneath the current LabKit repository. The result contains `pageCount`, `apiCount`, `fileCount`, `sourceRoot`, and `outputRoot`. Rendering discovers every Markdown page, public App, history record, and complete public help contract. It first creates a complete temporary tree, then creates `outputRoot` when absent, copies new or changed files, deletes obsolete generated files and empty directories, and preserves the output root itself. Every eligible Markdown page is included automatically, and no duplicated navigation catalog exists in this model. Check The Tracked Site result = checkLabKitDocs result = checkLabKitDocs(sourceRoot, committedSiteRoot) The check renders to a temporary folder and compares generated file paths and bytes with the tracked site. A difference raises `LabKit:Docs:StaleGeneratedSite`; the result otherwise includes `comparedFileCount` in addition to the renderer result fields. Build Tasks The supported command-line wrappers are: buildtool docs buildtool docsCheck Use `docs` when sources changed and `docsCheck` when validation must prove the tracked HTML is already current. Never edit files under `site/` manually. Related Documentation [Maintainer Tools](README.md) [Documentation System](../maintain-and-release/documentation.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"Performance Profiling","url":"development/tools/profiling.html","kind":"guide","section":"development","keywords":"development tools profiling Performance Profiling","text":"Performance Profiling `profileLabKitTarget` runs a MATLAB target under the profiler and exports an interactive HTML report plus a complete JSON sidecar. It supports launcher and app startup, callbacks, scripts, functions, command strings, and already captured `profile(\"info\")` data. Syntax [htmlFile, artifacts] = profileLabKitTarget(target) [htmlFile, artifacts] = profileLabKitTarget(target, htmlFile) [htmlFile, artifacts] = profileLabKitTarget(target, htmlFile, Name, Value) Batch-friendly launcher profile: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"profiling\")) [htmlFile, artifacts] = profileLabKitTarget( ... \"labkit_launcher\", [], ... \"OpenReport\", false, ... \"WaitForGuiClose\", false, ... \"CloseFiguresAfterRun\", true, ... \"PrintSummary\", true); Calling the function with an empty target opens a MATLAB file picker. A target may also be a function handle, function name, command string, `.m` path, or a profile-info struct containing `FunctionTable`. Options Name Default Meaning `OpenReport` `false` Open the HTML report after writing it. `WaitForGuiClose` `true` Wait for newly opened figures before collecting the final profile. `CloseFiguresAfterRun` `false` Close figures opened by the target after capture. `ChangeFolder` `true` Temporarily use a resolved target file's folder as the working directory. `OutputRoot` `artifacts/profile/` Default report folder when `htmlFile` is empty. `InitialRows` `500` Initial number of function-table rows shown. Values below 20 become 20. `ChartTopN` `40` Number of top roots in the flame control. `SortBy` `\"SelfTime\"` Initial report-table sort field. `ProjectRoot` Repository root Root used to classify project-owned functions. `TargetFile` Empty Explicit target path when exporting supplied profile data. `JsonFile` Empty Explicit JSON sidecar path. `PrintSummary` `false` Print the structured summary to the MATLAB command window. `SummaryTopN` `20` Rows retained in each summary ranking. `RethrowError` `false` Rethrow the target error after preserving available profile data. Outputs `htmlFile` is the generated report path. `artifacts` contains `htmlFile`, `jsonFile`, and `functionCount`. The HTML embeds the complete profile and an `AGENT_SUMMARY` block; the JSON contains metadata, summary text and tables, MATLAB profiler metadata, and every captured function row. Reading A Report Start with `topProjectSelfTime` to find time spent directly in editable LabKit code. Use `topProjectTotalTime` to see inclusive project costs and `topCapturedTotalTime` to understand user actions, app launches, callbacks, network operations, or GUI close time captured below a parent. Inspect parent and child edges before optimizing an inclusive row. Rows carry source tags such as `project`, `matlab_internal`, `external`, and `profiler_tool`. Profiler-tool rows remain in the complete data but are omitted from the default captured rankings so report generation does not appear to be application cost. Profile one perceived operation at a time. Separate normal and debug startup, file registration and actual file reads, and startup and close latency. A profile identifies cost; it does not prove correctness or scientific parity. Related Documentation [Maintainer Tools](README.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"LabKit App SDK","url":"framework/index.html","kind":"explanation","section":"framework","keywords":"framework LabKit App SDK","text":"LabKit App SDK `labkit.app` is the MATLAB App SDK above LabKit's GUI-free domain libraries. Apps own scientific workflow, calculations, units, wording, plots, and exports. The SDK owns semantic layout, transactions, project documents, portable sources, dialogs, resources, results, and native component lifecycle. Start Here Goal Documentation Build or refactor an App [Build a Complete App](../development/build-apps/complete-app.md) Understand ownership boundaries [Architecture](../development/build-apps/architecture.md) Declare compatible LabKit modules [Compatibility contracts](compatibility/contracts.md) Look up exact MATLAB syntax [Public API reference](../reference/README.md) Browse the App SDK API by capability [App SDK API](app-sdk-api.md) Validate framework or GUI changes [Testing](../development/maintain-and-release/testing.md) SDK Map The required path has three concepts: API Purpose `labkit.app.Definition` App identity, lifecycle callbacks, layout, and launch `labkit.app.layout.*` Semantic inputs, displays, containers, and workbench structure `labkit.app.view.Snapshot` Derived visible state and App-owned rendering Read an App in this order: `definition.m` shows its complete contract, `+workbench/buildLayout.m` shows the user workflow, and its capability packages show the controls, state transitions, presentation, and rendering owned by each feature. Open `projectSpec.m` or `createSession.m` only when the definition names those optional capabilities. Layout controls bind directly to named App callbacks, and plot areas bind directly to their renderer. Apps do not maintain handler, renderer, or capability registries. Runtime-injected values are `labkit.app.CallbackContext` and typed `labkit.app.event.*` payloads. Callbacks name those boundary values explicitly and delegate domain work through narrow inputs. Optional capabilities are grouped by purpose: Package Purpose `labkit.app.event.*` Typed callback payloads `labkit.app.interaction.*` Managed plot gestures with direct callbacks `labkit.app.plot.*` Domain-neutral axes redraw, message, fit, and annotation mechanics `labkit.app.project.*` Durable project schema and migration `labkit.app.result.*` Exported files and result packages `labkit.app.dialog.*` Explicit dialog outcomes `labkit.app.CallbackContext` Runtime operations available inside callbacks Smallest App function app = definition() workbench = labkit.app.layout.workbench({ ... labkit.app.layout.field(\"gain\", Label=\"Gain\", ... Kind=\"numeric\", Bind=\"project.parameters.gain\"), ... labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the result from the current gain.\")}); app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", AppId=\"example\", ... Title=\"Example\", Family=\"Examples\", ... AppVersion=\"1.0.0\", Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... Workbench=workbench); end The entrypoint calls `definition().launch(...)`. Definition compiles the immutable semantic graph before creating a figure, validates direct callback and renderer signatures, and builds one private native platform plan. Paved Road Bind ordinary state with `Bind=\"project...\"` or `Bind=\"session...\"`. Use `labkit.app.layout.fileList` for portable file records and selection. Set `AllowDuplicatePaths=true` only when separate workflow tasks may share one resolved path, and present row-level workflow state with `Snapshot.fileItemStatuses`. Source changes rebuild the transient session; Apps do not mirror choose, remove, clear, or selection UI events. Give every scientific or workflow action an App-owned `Tooltip`. The framework guarantees a nonempty label-based fallback, while repository guardrails require tracked Apps to explain the action instead of repeating its visible label. File-list choose/folder/remove/clear controls expose dedicated tooltip options and domain-neutral label defaults. Rebuild transient data with `session = createSession(project,context)` and resolve opaque source records with `context.resolveSourcePaths`. Return only derived view state from `labkit.app.view.Snapshot`; runtime supplies layout defaults, bindings, file state, log text, and status text. Give short `statusPanel` summaries an explicit `Lines` hint so the native layout reserves detail height only for genuinely multiline content. Use `labkit.app.layout.dataTable` with `labkit.app.event.TableCellEdit` and `labkit.app.event.TableCellSelection`; Apps never decode native events. Use `labkit.app.layout.plotArea` and a fixed `renderer(axesById,model)` callback. `axesById` is always a named struct, even when the plot area declares only one axis. Pass a transient `ViewRevision` to `Snapshot.renderPlot` when an App exposes an explicit reset-view action. The adapter preserves user zoom while the revision is unchanged and accepts renderer-fitted limits once when it changes. Pass one workspace node or a row cell array of vertically arranged nodes to `workspace.page`; growable tables and plots share the available page height without an App-authored wrapper section. Use `layout.group(..., Title=\"...\")` for a nested reader-facing control boundary inside a section; leave `Title` blank for arrangement-only groups. A control tab containing one growable file list, table, log, status, or plot surface fills the available tab height. Tabs with longer mixed content remain scrollable. Declare editable overlays with `labkit.app.interaction.*` on the plot area; supply their current values with same-named Snapshot methods. For a managed rectangle with `OnBackgroundPressed`, an un-dragged click anywhere on its plot—including inside the rectangle—uses that point callback; dragging the rectangle still uses its change callback. Use `labkit.app.plot.clearAxes`, `showMessage`, and `fitAxesToGraphics` for renderer mechanics; `EqualDataUnits=true` makes a one-time fitted equal-scale view from the settled native axes allocation without dispatching pending UI callbacks, without changing the allocation or locking later zoom. Apps still decide message wording and viewport policy. Use `labkit.app.project.Schema`, `labkit.app.result.File`, and `labkit.app.result.Package` only when those optional capabilities exist. Use `labkit.app.project.sourceRecord` only in pure project creation or migration code that must turn a legacy path into a portable source value; runtime callbacks still resolve paths through `CallbackContext`. Runtime validates candidate state and the complete view snapshot before publishing either. The private MATLAB adapter maps semantic IDs to native components, skips unchanged native property writes, reuses direct associations between controls and their labels, preserves plot viewports, normalizes native event differences, and never exposes component registries to Apps. Normal App launches show the completed native window. Official GUI validation uses the same launch path with a framework-owned visibility policy: `hidden` keeps the final window off screen and `minimized` minimizes it after startup. Tests therefore exercise real controls without individual Apps or test methods having to hide the window after launch. Built-in App Tools The native runtime installs one top-level **Tools** menu so framework-owned utilities do not compete with the App's workflow controls: **Plots** opens, copies, or saves the App's plot surfaces. **Screenshot** copies the complete App surface to the system clipboard or saves it to an image file. **Project State** saves or loads the current project document when the App declares a project schema. These actions are framework-owned native behavior. Apps do not declare menu items, implement clipboard integration, or duplicate project persistence callbacks. Framework concepts and source names are versionless. Compatibility belongs to `labkit.app.version`; saved-data versions belong to `labkit.app.project.Schema`. Related Topics [App catalog](../apps/README.md) [App development](../development/build-apps/app-development.md) [Project history](../history/README.md) labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.dataTable Add a tabular data display with optional editing and selection. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.plot.clearAxes Prepare an axes for an app-owned redraw. labkit.app.project.Schema Declare one durable App project contract. labkit.app.project.sourceRecord Create a portable source value during project migration. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.version Return the LabKit App SDK facade contract version. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"App SDK API","url":"framework/app-sdk-api.html","kind":"guide","section":"framework","keywords":"framework app sdk api App SDK API","text":"App SDK API Use this page to move from the App Framework concepts to exact MATLAB contracts. These links stay within the Framework reading path; each function page provides syntax, inputs, outputs, options, errors, and related APIs. Start With The Core Contract [Definition](../reference/api/labkit/app/Definition.html) declares an App. [layout](../reference/api/labkit/app/layout/workbench.html) assembles the semantic workbench. [Snapshot](../reference/api/labkit/app/view/Snapshot.html) presents derived visible state. [CallbackContext](../reference/api/labkit/app/CallbackContext.html) exposes runtime capabilities to callbacks. Browse By Capability Capability API entry point Layout and controls [App SDK layout API](../reference/api/labkit/app/layout/workbench.html) View snapshots [App SDK view API](../reference/api/labkit/app/view/Snapshot.html) Typed events [App SDK event API](../reference/api/labkit/app/event/ListSelection.html) Projects and sources [App SDK project API](../reference/api/labkit/app/project/Schema.html) Results [App SDK result API](../reference/api/labkit/app/result/File.html) Managed interactions [App SDK interaction API](../reference/api/labkit/app/interaction/anchorPath.html) Plot mechanics [App SDK plot API](../reference/api/labkit/app/plot/clearAxes.html) Runtime diagnostics [Runtime diagnostics and session logging](guides/runtime.md#diagnostics-and-session-logging) Dialog results [App SDK dialog API](../reference/api/labkit/app/dialog/Choice.html) Related Topics [App Framework](README.md) [Runtime and Lifecycle](guides/runtime.md) [Build a Complete App](../development/build-apps/complete-app.md) labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.dataTable Add a tabular data display with optional editing and selection. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.group Arrange related child elements without a titled boundary. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.rangeField Add an input for a numeric lower and upper bound. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.slider Add a bounded numeric slider. labkit.app.layout.statusPanel Add a text display for current App status. labkit.app.layout.tab Add a named tab containing related child elements. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.app.event.IntervalScroll Describe one normalized interval scroll gesture. labkit.app.event.ListSelection Describe selected file or list item identities. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.interaction.anchorPath Declare an editable open or closed path on one plot axis. labkit.app.interaction.interpolateAnchorPath Build a visible path through image anchor points. labkit.app.interaction.interval Declare an editable one-dimensional plot interval. labkit.app.interaction.pairedAnchors Declare matching editable points across plot axes. labkit.app.interaction.pointSlots Declare a fixed set of editable labeled point positions. labkit.app.interaction.rectangle Declare an editable rectangular plot region. labkit.app.interaction.regionSelection Declare a transient click-or-drag region gesture. labkit.app.interaction.scaleBarGeometry Compute serializable image scale-bar overlay geometry. labkit.app.interaction.scaleCalibration Convert a known image distance into pixels per unit. labkit.app.interaction.scaleReference Declare an editable two-point scale reference. labkit.app.plot.clampPointToAxes Keep data coordinates inside the visible axes box. labkit.app.plot.clearAxes Prepare an axes for an app-owned redraw. labkit.app.plot.enablePopout Add a standalone-figure action to an axes context menu. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data. labkit.app.plot.fitCanvasToSource Fit a preview axes into a fixed-aspect pixel frame. labkit.app.plot.offsetPointByAxesFraction Move data coordinates by normalized axes fractions. labkit.app.plot.showMessage Show a centered empty-state message in an axes. labkit.app.project.Schema Declare one durable App project contract. labkit.app.project.emptySourceRecords Create an empty portable source collection. labkit.app.project.sourceRecord Create a portable source value during project migration. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.dialog.Choice Represent a typed dialog choice or cancellation."},{"title":"DIC Preprocess","url":"apps/dic/dic-preprocess.html","kind":"app","section":"apps","keywords":"apps dic dic preprocess DIC Preprocess","text":"DIC Preprocess DIC Preprocess registers a moving optical image to a reference image, applies repeatable crop operations to the pair, and creates a binary analysis mask. Use it when camera motion or framing differences must be removed before an external DIC solver is run. Requirements And Launch Image IO and registration use MATLAB and repository-owned code; no external registration package is installed. labkit_DICPreprocess_app Inputs Choose one reference image and one moving image. Supported extensions are the formats returned by `labkit.image.supportedExtensions`, including common PNG, JPEG, TIFF, and BMP files. The reference and moving images may initially have different dimensions; alignment produces a moving image on the reference domain. Choosing a new reference or moving file clears operations derived from the old pair. Source files are read-only and are not overwritten. Basic Workflow Choose the reference and moving images. Select **Current pair** or **False-color overlay** to assess the initial displacement. Run manual point matching or **Auto align current pair**. Inspect the false-color result and undo or reset if necessary. Start the crop ROI, drag or resize the square, and apply it to both images. Start ROI editing, place a boundary, and add or subtract it from the mask. Save the prepared images and ROI mask. Alignment and crop actions form an ordered edit history. **Undo align/crop** removes the most recent applied operation. **Reset to originals** discards the derived working pair and replays no edits. Preview Modes Mode Display Current pair current reference above the current moving image Current moving image moving image in the main comparison view False-color overlay red/green registration comparison of the current pair Original pair source images before applied edits ROI mask current binary mask over the image domain Changing preview mode does not change project data. Point placement, point dragging, crop editing, mask editing, and applying an operation preserve the current axes zoom. Use the plot **Fit** action when a full-image view is wanted. Manual Point Matching Press **Start point matching**. Click a feature in the reference image, then click the same feature in the moving image. Repeat this reference/moving order for at least two complete pairs. Numbered markers show correspondence and the preview subtitle states which image expects the next point. Drag an existing marker to refine it. **Undo point pair** removes the newest complete pair. **Cancel point matching** discards the pending point set without changing the current images. **Apply point alignment** estimates and applies a rigid two-dimensional transform. Manual alignment uses all pairs in a least-squares rotation-and-translation fit. It does not estimate scale or shear and prevents a reflected solution. Choose points that are visually unambiguous and spread across the specimen; clustered points provide weak rotational leverage. Automatic Alignment **Auto align current pair** runs the app-owned base-MATLAB registration path. It returns the same aligned image and transform fields as manual alignment. Automatic alignment is a starting estimate, not a guarantee of DIC-quality correspondence. Always inspect the false-color overlay and prefer manual points when the image has repeated texture, large occlusion, or weak contrast. Crop ROI **Start/reset crop ROI** creates a square rectangle constrained to the current reference image. Drag the rectangle to move it and use its resize handles to change its size. **Apply ROI crop** uses exactly the same integer image-domain rectangle on the reference and aligned moving image. **Cancel ROI** exits the editor without adding a crop step. Applied crops change the coordinate domain for later operations. Undo the crop before reusing point coordinates defined on the larger image. Mask ROI Start ROI editing and choose **Curve** or **Straight lines**. Place boundary anchors as instructed by the canvas subtitle; anchors remain draggable while editing. **Undo point** removes the most recent boundary anchor and **Clear boundary** removes the pending boundary only. **Preview ROI mask** shows the rasterized boundary. **Add to mask** unions the boundary interior with the current mask; **Subtract from mask** removes it. **Undo mask edit** restores the previous mask snapshot. **Clear mask** removes all included pixels. The mask is stored as a logical image in the current reference domain. Outputs **Save current images** writes two images to the selected folder using stable reference and moving result names. **Save ROI mask** writes a binary image to the selected file. LabKit result manifests record source references, parameters, output roles, and generated filenames; the manifest JSON is provenance metadata, not an additional scientific result. The saved project stores portable source references, preview choices, edit steps, crop and mask annotations, and result references. Decoded source images and intermediate working images are loaded or recomputed when the project is opened; they are not duplicated inside the project file. Use Without The GUI reference = imread(\"reference.png\"); moving = imread(\"moving.png\"); referencePoints = [120 80; 360 95; 230 310]; movingPoints = [128 88; 369 101; 239 318]; [aligned, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... reference, moving, referencePoints, movingPoints); overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... reference, aligned); cropped = dic_preprocess.analysisRun.cropImage(aligned, [80 60 300 300]); Use the linked function pages below for exact coordinate, rectangle, return shape, interpolation, and error contracts. Function Reference The generated pages for [`alignMovingToReference`](../../../reference/api/dic_preprocess/analysisRun/alignMovingToReference.html), [`autoAlignMovingToReference`](../../../reference/api/dic_preprocess/analysisRun/autoAlignMovingToReference.html), [`applyRigidTransform`](../../../reference/api/dic_preprocess/analysisRun/applyRigidTransform.html), and the other cataloged DIC functions document coordinate conventions, assumptions, output shape, limitations, failure behavior, and related APIs. Errors And Troubleshooting Point alignment remains unavailable until at least two complete pairs exist. A strong colored double edge in the overlay indicates residual geometric mismatch, not a palette defect. Automatic alignment can fail on nearly uniform images; use separated manual points or improve the source capture. Crop and mask geometry is constrained to the active image domain; an old project source that changed size cannot safely reuse its prior annotations. Related Topics [DIC Postprocess](../dic-postprocess/README.md) [DIC family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) dic_preprocess.analysisRun.alignMovingToReference Fit and apply a rigid transform from matched points. dic_preprocess.analysisRun.applyRigidTransform Resample a moving image onto a reference-sized canvas. dic_preprocess.analysisRun.autoAlignMovingToReference Estimate and apply an integer translation. dic_preprocess.analysisRun.cropImage Crop an image using base-MATLAB indexing. dic_preprocess.analysisRun.makeFalseColorOverlay Build a red/green registration preview."},{"title":"Framework Compatibility Contracts","url":"framework/compatibility/contracts.html","kind":"guide","section":"framework","keywords":"framework compatibility contracts Framework Compatibility Contracts","text":"Framework Compatibility Contracts [App Framework](../README.md) | [API reference](../../reference/README.md) | [App development](../../development/build-apps/app-development.md) `labkit.contract` lets an app state which LabKit API versions it supports and check those requirements before startup. It compares versions already present in this repository; it does not download, install, or update code. This is part of the documented App Framework surface. The MATLAB namespace remains `labkit.contract` because it checks UI and domain facades alike; it is not an App metadata registry or a UI-only helper. Typical Use An app normally defines its requirements in a small function: function req = requirements() req = labkit.contract.requirements( ... \"ui\", \">=7 <8\", ... \"image\", \">=4 <5\"); end The app entry point checks them before creating the interface: labkit.contract.assertRequirements( ... \"labkit_Example_app\", requirements()) If all requirements are compatible, `assertRequirements` returns without a value. Otherwise it throws `labkit_Example_app:IncompatibleLabKit` with one message for each incompatible module. Choose a Function Task Function Build a requirement structure [`labkit.contract.requirements`](../../reference/api/labkit/contract/requirements.html) Inspect compatibility without throwing [`labkit.contract.checkRequirements`](../../reference/api/labkit/contract/checkRequirements.html) Stop startup when requirements fail [`labkit.contract.assertRequirements`](../../reference/api/labkit/contract/assertRequirements.html) Create the structure returned by a module's `version` function [`labkit.contract.versionInfo`](../../reference/api/labkit/contract/versionInfo.html) Version Ranges A range contains one or more whitespace-separated constraints. Supported operators are `<`, `<=`, `>`, `>=`, `=`, and `==`: \">=7 <8\" % 7.x releases \">=2.1 <=2.4\" % from 2.1 through 2.4, inclusive \"=1.3.0\" % exactly 1.3.0 Versions may contain one, two, or three numeric components. Missing components are treated as zero during comparison, so `6`, `6.0`, and `6.0.0` compare as the same version. Facade names may be short, such as `\"thermal\"`, or include the prefix, such as `\"labkit.thermal\"`. Names are normalized to lowercase without the prefix. Listing the same facade twice in one requirement structure is an error. How Compatibility Is Decided Each module's `version` function returns a structure like this: info = labkit.thermal.version() Field Meaning `name` Full module name, such as `\"labkit.thermal\"` `facade` Short normalized name, such as `\"thermal\"` `current` Current API version `compatible` One or more requirement ranges implemented by this version `status` `\"stable\"`, `\"experimental\"`, or `\"deprecated\"` `notes` Short description of the module contract A requirement passes only when both conditions are true: The module's `current` version falls inside the app's requested range. The requested range overlaps at least one range in the module's `compatible` list. The second check prevents a current version number from appearing acceptable when the installed implementation does not advertise the requested contract. Inspecting a Compatibility Report Use `checkRequirements` when a caller should decide how to display or handle a failure: req = labkit.contract.requirements(\"thermal\", \">=1 <2\"); report = labkit.contract.checkRequirements(req); if ~report.ok disp(report.message) end The returned structure contains: Field Meaning `ok` True when every requirement passes `failures` One structure per failed requirement `message` Success text or newline-separated failure explanations Each failure records the normalized `facade`, the `required` range, the module's advertised `available` ranges, and a readable `message`. An unknown facade has an empty `available` array. Supplying Version Information Explicitly `checkRequirements` normally queries the installed UI, DTA, RHS, biosignal, image, and thermal modules. Diagnostic code can pass an explicit version structure instead: available = labkit.contract.versionInfo( ... \"image\", \"4.1.0\", \">=4 <5\", \"stable\", ... \"Image file IO and processing functions.\"); req = labkit.contract.requirements(\"image\", \">=4.0 <5\"); report = labkit.contract.checkRequirements(req, available); This form is useful for checking a proposed version declaration without changing the installed module. Related Topics [App development](../../development/build-apps/app-development.md) explains how one App definition owns facade requirements and product version metadata. [Architecture](../../development/build-apps/architecture.md) describes the public LabKit modules that can appear in a requirement list. [Project history](../../history/README.md) records API version and compatibility changes. labkit.contract.assertRequirements Throw an error when LabKit API requirements are not met. labkit.contract.checkRequirements Compare required API ranges with available LabKit versions. labkit.contract.requirements Describe the LabKit API versions required by a caller. labkit.contract.versionInfo Describe one LabKit API version and its compatibility range. labkit.thermal.version Return version information for the thermal API."},{"title":"Runtime And Lifecycle","url":"framework/guides/runtime.html","kind":"guide","section":"framework","keywords":"framework guides runtime Runtime And Lifecycle","text":"Runtime And Lifecycle `labkit.app` is the App-facing runtime contract. Apps declare product meaning; the private runtime owns native MATLAB components, event serialization, transactions, project documents, portable sources, resources, and cleanup. Definition And Launch Each App-owned `definition.m` is the single immutable product contract, and each entrypoint delegates to it: function varargout = labkit_Example_app(varargin) [varargout{1:nargout}] = example.definition().launch(varargin{:}); end function app = definition() app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", ... AppId=\"examples.example\", ... Title=\"Example\", ... Family=\"Examples\", ... AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... Workbench=example.workbench.buildLayout(), ... ProjectSchema=example.projectSpec(), ... CreateSession=@example.createSession, ... PresentWorkbench=@example.workbench.present); end Required Definition arguments are product metadata, requirements, and one `labkit.app.layout.workbench` value. Optional callbacks are: Argument Signature Purpose `CreateSession` `session = callback(project,callbackContext)` Rebuild transient App data from durable project state. `PresentWorkbench` `view = callback(applicationState)` Return the App-owned fragment of the complete visible snapshot. `OnStart` `applicationState = callback(applicationState,callbackContext)` Perform a real post-first-commit request or resource initialization. `BuildSyntheticSample` `sample = callback(callbackContext)` Build clean-room debug input when the App supports it. Ordinary default state needs no startup callback. Exact syntax and errors are in the generated [public API reference](../../reference/README.md). `launch(\"requirements\")` and `launch(\"version\")` answer metadata without creating a figure. `launch()` constructs the private native adapter and shows the App. Static Workbench Contract `+workbench/buildLayout.m` returns a tree composed from `labkit.app.layout.*` values. Layout IDs are stable semantic identifiers and must be globally unique. function layout = buildLayout() controls = { ... labkit.app.layout.section(\"parameters\", \"Parameters\", { ... labkit.app.layout.field(\"gain\", ... Label=\"Gain\", Kind=\"numeric\", ... Bind=\"project.parameters.gain\"), ... labkit.app.layout.button(\"exportResult\", ... \"Export\", @example.resultFiles.exportResult, ... Tooltip=\"Export the current analyzed result.\")})}; workspace = labkit.app.layout.workspace( ... labkit.app.layout.plotArea( ... \"previewPlot\", @example.previewPlot.draw)); layout = labkit.app.layout.workbench( ... controls, Workspace=workspace); end Layout controls own direct callbacks. Plot areas own direct renderers. There is no handler, renderer, command, or capability registry for Apps to maintain. Definition compiles the immutable graph once and validates callback roles, renderer roles, IDs, bindings, and view capabilities before UI mutation. Complex Apps keep the top-level workbench readable by composing capability-owned `layoutSection`, `workspaceTable`, or `workspacePlot` functions in user order. State And Transactions Runtime state always has two App-owned buckets: applicationState.project applicationState.session `project` is durable, validated meaning. `session` is transient and reconstructible. A callback receives the previous complete state and returns a candidate complete state. The runtime: serializes the event; invokes the direct callback; validates project and session shape; builds and validates the complete view snapshot; reconciles native components; publishes state and view together. Failure rolls back both state and presentation and clears event-scoped resources. Apps do not implement busy flags, callback queues, readiness timers, or figure close guards. Use direct `Bind=\"project....\"` or `Bind=\"session....\"` paths for ordinary fields, ranges, sliders, file sources, and selection. Bound controls need no callback or presenter operation unless the App has additional derived meaning. Portable Sources And Session Reconstruction `labkit.app.layout.fileList` owns file/folder selection, portable source records, removal, clearing, and optional selection binding: labkit.app.layout.fileList(\"sources\", ... Filters=[\"*.csv\", \"CSV files\"], ... Bind=\"project.inputs.sources\", ... SelectionBind=\"session.selection.sources\", ... SourceRole=\"measurement\", ... SourceIdPrefix=\"source\") The App does not mirror those UI lifecycle actions with callbacks. Runtime updates the bound source records and invokes `CreateSession` after source changes: function session = createSession(project, callbackContext) paths = callbackContext.resolveSourcePaths(project.inputs.sources); session = struct(\"measurements\", example.sourceFiles.read(paths)); end Portable source records are opaque. Resolve their paths only at IO boundaries. Saved projects store portable references and use runtime relinking. Typed Events Callbacks are attached only where an App owns real behavior: button: `state = callback(state,callbackContext)` field, range, slider, workspace page, or interaction change: `state = callback(state,value,callbackContext)` table edit: `state = callback(state,labkit.app.event.TableCellEdit,callbackContext)` table selection: `state = callback(state,labkit.app.event.TableCellSelection,callbackContext)` Name the boundary values explicitly and delegate domain work through narrow inputs: function applicationState = replaceGroupValue( ... applicationState, cellEdit, callbackContext) arguments applicationState (1,1) struct cellEdit (1,1) labkit.app.event.TableCellEdit callbackContext (1,1) labkit.app.CallbackContext end groups = applicationState.project.groups; applicationState.project.groups = ... example.groupData.replaceValue(groups, ... cellEdit.Row, cellEdit.Column, cellEdit.NewValue); end Do not pass the complete state or callback context into calculation code that only needs groups and one edited value. Complete View Snapshots Runtime starts from layout defaults, bindings, file state, log text, and status text. `PresentWorkbench` returns only derived App-owned operations: function view = present(applicationState) view = labkit.app.view.Snapshot(); view = view.include(example.previewPlot.present( ... applicationState.session.measurements, ... applicationState.project.parameters)); view = view.enabled(\"exportResult\", ... ~isempty(applicationState.session.measurements)); end The combined snapshot must cover every semantic target exactly as its layout capabilities require. `Snapshot.include` composes feature-owned fragments without opening a generic property-patch schema. Plot presentation passes a prepared model to the renderer declared by its plot area: view = view.renderPlot(\"previewPlot\", model); function draw(axesById, model) ax = axesById.main; cla(ax); plot(ax, model.x, model.y); end Renderers own drawing and viewport policy, not workflow decisions or project mutation. Display-only graphics disable hit testing. Managed interaction specs own editable gestures and event-scoped resources. Declare managed gestures statically on their plot area and provide their current value in the snapshot: crop = labkit.app.interaction.rectangle( ... \"cropRegion\", @example.cropGeometry.moveCrop); plot = labkit.app.layout.plotArea( ... \"previewPlot\", @example.previewPlot.draw, Interactions={crop}); view = view.rectangle( ... \"cropRegion\", project.annotations.crop, ImageSize=size(image)); Named contracts also cover anchor paths, paired anchors, fixed point slots, transient region selection, intervals, and scale references. Apps never author `Kind`, `Targets`, `Event`, or `Options` transport structs. Renderer mechanics such as complete clears, empty-state messages, fitting, fixed-aspect canvases, and axes-relative annotation placement live under `labkit.app.plot.*`. Apps own when those operations occur, user wording, and whether a semantic change should preserve or fit the viewport. CallbackContext `labkit.app.CallbackContext` is sealed and exposes specifically named runtime operations for dialogs, status and diagnostics, portable sources, project documents, result packages, render surfaces, and managed resources. It does not expose figures, component registries, queues, lifecycle handles, or a nested service bag. Use context methods only at a callback or reconstruction boundary. Pure readers, calculations, result builders, and render-model builders accept ordinary explicit values. `callbackContext.chooseOption(prompt, choices, ...)` owns ordinary native confirmation choices. `Title` controls the dialog title, `DefaultChoice` selects the Enter-key action, and `CancelChoice` is returned when the user dismisses the dialog. All three named choices must be members of the declared nonempty unique choice row. File and folder methods remain separate because they return paths and use platform file choosers. An App-specific project button may choose a MAT file and return `callbackContext.restoreProjectDocument(filepath)`. The context prepares the same migrated, relinked project/session candidate used by the framework Load State menu; the active callback transaction still owns validation, native presentation, rollback, document metadata, and title publication. `callbackContext.newProjectDocument()` similarly returns the schema's fresh project/session state and publishes a new unsaved document identity only when that callback transaction commits. Diagnostics And Session Logging Every ordinary App launch starts one sanitized session event stream and durable journal. Launch arguments do not select a debug mode, change startup behavior, or generate sample data. Runtime automatically records lifecycle, callback, transaction, dialog, project, source, result, and failure boundaries with correlated operation IDs. App callbacks add domain events through `callbackContext.log(severity,eventName,message,Name=Value)`. Use `info` for useful progress and completed user actions, `warning` for recoverable conditions, and `error` or `critical` for failures. Stable event names and structured allowlisted attributes support diagnosis; messages remain concise and safe for display. Pass caught exceptions through the dedicated `Exception` option instead of copying stack, path, identifier, or scientific data into free text. The App's **Tools > Diagnostics** menu opens the live session viewer, enables more detailed trace capture for future activity, and exports a diagnostic bundle from the same session history. Enabling trace does not restart the App or reconstruct earlier detail. Journal degradation is itself exposed in the surviving in-memory stream; logging failures never alter callback transaction semantics or scientific results. The viewer's **View** filter describes intended readers, not access control. **Useful** shows user-workflow events plus developer warnings and failures; **User workflow**, **Developer details**, and **Everything** select the two event audiences explicitly. The **Action** filter groups a top-level user or lifecycle action with its nested callback, presentation, dialog, resource, and transaction records. Its readable label includes time and a semantic message while retaining the stable `op-*` correlation identifier for exported diagnostics. TRACE capture is independent of ordinary failure capture. With TRACE off, Runtime still retains DEBUG lifecycle/callback start and completion boundaries and all INFO, WARNING, ERROR, and CRITICAL events. The default viewer hides normal developer DEBUG/INFO records; selecting **DEBUG+** and **Developer details** reveals earlier retained callback boundaries. A callback exception is recorded as an ERROR with `failed` operation result, rollback disposition, safe exception identifier, and sanitized function stack. Runtime close is also an instrumented lifecycle operation. Resource and native adapter cleanup continue independently; a cleanup exception is retained and persisted before the journal closes, then returned to the caller. Diagnostics cannot manufacture evidence for a native event that never entered Runtime, an exception swallowed by App code without logging, or a MATLAB process that hangs or terminates before a terminal event. In those cases the last retained DEBUG boundary and durable journal state are the available evidence. Persistence, Results, And Cleanup Synthetic Inputs An App that declares `BuildSyntheticSample` exposes **Tools > Developer Tools > Generate Synthetic Inputs...**. The action writes an anonymous, validated `labkit.app.synthetic.Pack` and `synthetic-input-pack.json` into a new folder beneath the selected destination. Generation does not load the pack, mutate the open project, or suppress `OnStart`; every App launch follows the same clean startup path. Users deliberately import the generated files through the App's ordinary controls. `labkit.app.project.Schema` owns current project creation, validation, and ordered version migration. Runtime owns the project envelope, atomic save, restore, recovery, and relinking loop. Saved-project compatibility boundary Every save writes exactly one `labkitProject` variable using the current App payload version. A restore accepts an older payload only when the App's current schema declares one `Migrate(project, fromVersion)` callback; Runtime invokes it once for each missing version in order and then validates the current payload. A payload newer than the running App is rejected rather than guessed at. An App may declare an exact legacy MAT variable name in `LegacyImports` when real user files still require a one-way import. That callback converts the legacy value directly to the current project and optional resume state. It is read-only: current saves never write the legacy variable, and Runtime contains no App ID, field-shape, or filename heuristics. These readers are supported data contracts while their Apps declare and test them. They are not an excuse for duplicate live state fields or old runtime APIs. Removing a supported payload migration or importer is an explicit breaking saved-data decision; adding one requires App-owned persistence evidence plus a runtime restore test for the framework mechanism. `labkit.app.result.File` and `labkit.app.result.Package` describe App-owned outputs. `CallbackContext.writeResultPackage` writes through the runtime so source and project provenance remain consistent. Resources have event, interaction, document, or application scope. Replacing the same scope and ID is idempotent; the runtime cleans every surviving resource on scope end or close. Validation Use focused contract tests for Definition, layout, callbacks, snapshots, project schema, and runtime transactions. Add downstream App tests for changed behavior and a bounded hidden-GUI test for native wiring. Automated hidden GUI tests do not prove dialog quality, pointer feel, scientific validity, or a complete interactive workflow. See [Testing](../../development/maintain-and-release/testing.md) and [Build A Complete App](../../development/build-apps/complete-app.md). labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.interaction.rectangle Declare an editable rectangular plot region. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.project.Schema Declare one durable App project contract. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.synthetic.Pack Describe one typed anonymous App reproduction scenario. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"Getting Started","url":"getting-started/index.html","kind":"tutorial","section":"getting-started","keywords":"getting started Getting Started","text":"Getting Started This guide covers the shortest path from an empty folder to a running LabKit app. For source development, continue with the [development guide](../development/README.md). Requirements A supported MATLAB installation. Internet access for the first launcher download or update. Installed source and P-code packages run offline unless an app's own input lives on a network drive. Any MathWorks products declared by the selected app. The launcher shows app requirements before launch. Production apps do not create Python or Conda environments, download model weights, or install third-party packages on first use. Install And Launch Download the latest [`labkit_launcher.m`](https://github.com/Pluze/LabKit-MATLAB-Workbench/releases/latest/download/labkit_launcher.m). Put it in a standalone folder such as `LabKit/`. Open MATLAB in that folder. Run: labkit_launcher The launcher can install or update the runtime, discover available apps, and start the selected app. Keep experimental data and exported results outside the LabKit runtime folder. See the [LabKit Launcher manual](../apps/labkit-core/launcher/README.md) for every button, programmatic mode, discovery rule, and maintenance action. Choose A Version **Release** installs the latest stable GitHub release. **Latest** installs the current `main` branch. **Versions** selects a recent release, tag, or commit for a deliberate upgrade or rollback. Before replacing an installed runtime, the launcher moves its current contents to a dated `LabKit-previous-*` folder. This provides a local recovery point but is not a substitute for backing up lab data. Open An App Select one row in the launcher and choose **Open**. The launcher prepares the MATLAB path, checks the app's LabKit version requirements, and displays startup progress until the app is ready. Use the [app catalog](../apps/README.md) to choose a workflow and confirm its input and output formats. Common App Commands Every current LabKit app exposes one top-level **Tools** menu: **Tools > Plots** contains plot-specific actions such as opening larger editable views, copying plots, and saving plots. **Tools > Screenshot > Copy to Clipboard** copies the complete active App surface as an image. **Tools > Screenshot > Save to File...** saves the complete App surface. **Tools > Project State > Save State...** writes the current project document. **Tools > Project State > Load State...** opens a compatible project document. State files preserve app projects. They are different from exported result files and from ignored diagnostic manifests under `artifacts/diagnostics/`. Source Checkout Clone the repository only when you need source development, tests, profiling, or review: git clone https://github.com/Pluze/LabKit-MATLAB-Workbench.git cd LabKit-MATLAB-Workbench buildtool headless See [Testing](../development/maintain-and-release/testing.md) before choosing a broader build task. Next Steps [App guide](../apps/README.md) [LabKit Launcher](../apps/labkit-core/launcher/README.md) [Public API reference](../reference/README.md) [Development guide](../development/README.md)"},{"title":"Project History","url":"history/index.html","kind":"history index","section":"history","keywords":"history Project History","text":"Project History This page lists the recorded changes to LabKit in reverse chronological order. Each change has its own page, so a reader can open the reason, effect, compatibility notes, and validation without searching through one very long changelog. Reading The Timeline Every recorded change, including work before v1.0, appears once in the same reverse-chronological timeline below. Early records describe how imported laboratory scripts became an app workbench; later records use the same page format to explain framework, library, App, validation, and release decisions. Version boundaries are milestones in this one timeline, not separate kinds of history. History source files have one predictable home: docs/history/records/// The folder is chronological, while the metadata inside each record names the apps, framework, or libraries affected by the change. Every record also has a global positive-integer `sequence`. Sequence values are unique and contiguous: the first record is `1`, and each later change takes the next value even when several changes share a date. The generated timeline sorts by descending sequence, so titles and filenames cannot reorder same-day versions. Dates must remain nondecreasing as sequence increases. The authoritative [history record format](record-format.md) defines every metadata field and body section. The documentation site uses that metadata to show a relevant **Change history** section on each component page. A change that affects several components is stored once and linked from all of them. Find A Change Use the site search for an app command, public function, Change ID, or feature name. You can also browse the complete timeline below by date. Current version numbers come from the launcher, facade `version.m` files, and App `definition.m` metadata rather than from a separate table in this history."},{"title":"History record format","url":"history/record-format.html","kind":"guide","section":"history","keywords":"history record format History record format","text":"History record format Each file under `docs/history/records///` records one durable project decision or user-visible evolution. This page is the sole authority for its authored structure. The documentation renderer validates every record, builds the timeline from its sequence, adds component links, and indexes its scopes for search. Canonical metadata The title is the first line. It is followed by one blank line and exactly one `labkit-change` block. Metadata fields use the shown order; no other metadata fields are legal. [Level-one Markdown title: concise change title] id: LK-YYYYMMDD-lowercase-change-slug date: YYYY-MM-DD sequence: N type: feat compatibility: compatible component: `component-id` | `old-version -> new-version` scope: Search phrase Field Required Value Used for `id` Yes, once `LK-` + ISO date without hyphens + lowercase hyphenated slug Stable change identity and search `date` Yes, once ISO date: `YYYY-MM-DD` Timeline display and ordering check `sequence` Yes, once Next positive integer; all records are contiguous from `1` Definitive timeline order `type` Yes, once `feat`, `fix`, `perf`, `refactor`, `test`, `docs`, `ci`, or `chore` Timeline classification and search `compatibility` Yes, once `compatible` or `breaking` Upgrade expectation and search `component` Optional, repeated `` `component-id` `` or `` `component-id` \\ `old-version -> new-version` ``; use ``new -> X.Y.Z`` for an introduced component Component-page history aggregation `scope` Yes, one or more A trimmed, human-readable search phrase of at most 96 characters Search keywords Use a component ID only when the record should appear on that component's documentation page. A record may have multiple components and scopes; list all components before all scopes. Do not add a `schema` field or encode release state in a record. Canonical narrative After the metadata block, every record contains these headings exactly once and in this order. Each heading has substantive content. ## Context ## Decision and rationale ## Changes ## User and data impact ## Compatibility and migration ## Validation ## Evidence ## Known limitations and follow-up State what changed and why in the first three sections; state affected users, data, compatibility, and validation in the next four; reserve the last section for remaining limits or explicitly state that none are known. Write one cross-component record rather than duplicating a narrative across components. When to write a record Write a record for a versioned App, launcher, or facade change, or for a meaningful project evolution decision. Do not create one solely for generated site output, a typo-only edit, or mechanical formatting churn."},{"title":"Biosignal API","url":"libraries/biosignal/index.html","kind":"reference","section":"reference","keywords":"libraries biosignal Biosignal API","text":"Biosignal API [Public API index](../../reference/README.md) | [App guide](../../apps/README.md) `labkit.biosignal.*` provides GUI-free import and processing functions for physiological and wearable time-series data. Use it to build a MATLAB script from the same recording, channel, event, segment, template, and measurement structures used by LabKit apps. `labkit.biosignal.version()` returns biosignal-library version and compatibility information used by `labkit.contract` requirement checks. ECG peak detection is the only modality-specific detector in the current module. Import, filtering, segmentation, template construction, measurement, and group comparison use generic biosignal structures. Public Surface Current app-facing functions: [recording, status] = labkit.biosignal.readRecording(filepath); names = labkit.biosignal.listChannels(recording); signal = labkit.biosignal.getChannel(recording, channel); cropped = labkit.biosignal.cropSignal(signal, [startSec endSec]); filtered = labkit.biosignal.filterSignal(signal, filterSpec); ecgPeakOptions = labkit.biosignal.defaultEcgPeakOptions(\"qrs-streaming\"); events = labkit.biosignal.detectEcgPeaks(signal, ecgPeakOptions); segments = labkit.biosignal.segmentByEvents(signal, events, windowSec); template = labkit.biosignal.buildTemplate(segments, templateOptions); measurements = labkit.biosignal.measureSegments(segments, template, measureOptions); comparison = labkit.biosignal.compareGroups(values, groups); `readRecording` accepts MAT files containing timetable variables and delimited text tables such as CSV and TSV. For delimited text tables, time handling is deliberately conservative. The reader does not treat an arbitrary monotonic numeric column as seconds. It uses a table column as time only when the column is `datetime`/`duration`, when the column name is time-like such as `time_s` or `time_ms`, or when the caller explicitly passes `timeColumn` and optionally `timeUnit`. Otherwise the recording uses a synthetic sample-index time axis and keeps numeric columns as signal channels. CSV and TXT files from wearable workflows can contain preamble rows, headerless numeric data, `I0/I1`-style Arduino columns, `sec`/channel tables with per-column count rows, epoch timestamps, footer metadata rows, gaps, duplicated rows, or time axes that jump backward. The table reader tries to handle the common cases automatically and records import choices in `recording.metadata`. Backward or duplicate time steps are stitched forward with a nominal sample interval; large positive gaps are preserved and counted in `metadata.timeRepair`. If auto-detection is ambiguous, the app or caller should pass explicit import options rather than relying on inference. Example explicit text-table time options: opts = struct('timeColumn', 'timestamp', 'timeUnit', 'milliseconds'); [recording, status] = labkit.biosignal.readRecording(filepath, opts); Useful delimited-table options include `headerLine`, `hasHeader`, `timeColumn`, `timeUnit`, `signalColumns`, `fallbackFs`, and `timeRepair`. `readRecording` Options Option Type Default Valid values / meaning `headerLine` positive integer auto Header line for text tables, or first data line when `hasHeader=false`. `hasHeader` logical auto Whether the detected/explicit line contains column names. `timeColumn` name or 1-based index auto Explicit time column. Use this when auto-detection is ambiguous. `timeUnit` string infer `seconds`, `milliseconds`, `microseconds`, `nanoseconds`, or sample/index aliases. `signalColumns` names or 1-based indices all numeric non-time columns Restricts imported signal channels. `fallbackFs` positive scalar Hz none Used for synthetic sample-index time or timestamp repair fallback. `timeRepair` string `auto` `auto` stitches duplicate/backward time steps; `none`/`off` disables repair. `gapFactor` positive scalar `20` Positive gaps larger than this multiple of nominal `dt` are counted as large gaps. `useFirstNumericColumnAsTime` logical `false` Opt-in fallback for ambiguous text tables. Data Structures Functions exchange ordinary MATLAB structures with these principal fields: recording.sourcePath, recording.name, recording.signals, recording.metadata signal.time, signal.values, signal.fs, signal.name, signal.displayName, signal.metadata events.index, events.time, events.amplitude, events.score, events.label segments.values, segments.timeOffset, segments.eventIndex, segments.eventTime, segments.fs template.values, template.timeOffset, template.keptSegmentIndex, template.score measurements.perSegment, measurements.summary Open an individual function page for required fields, row/column orientation, units, empty-result behavior, and errors. Processing Provided By This Module The public functions cover: file loading and timetable/table normalization channel listing and extraction time ROI cropping generic filtering ECG/QRS peak detection event-centered segmentation template building and template-residual SNR-style segment measurements generic group summaries and pairwise Welch-style comparisons `detectEcgPeaks` supports `qrs-streaming`, `pan-tompkins`, and `local` methods. Select the method through its options; no detector-specific helper call is required. `detectEcgPeaks` Options Start from `labkit.biosignal.defaultEcgPeakOptions(method)` and override only the fields the app exposes: opts = labkit.biosignal.defaultEcgPeakOptions(\"pan-tompkins\"); opts.polarity = \"positive\"; opts.minDistanceSec = 0.35; events = labkit.biosignal.detectEcgPeaks(signal, opts); Option Type Default Valid values / meaning `method` string `qrs-streaming` `qrs-streaming`, `pan-tompkins`, or `local`. `polarity` string `auto` `auto`, `positive`, `negative`, or `absolute`. `minDistanceSec` positive scalar `0.25` for ECG methods, `0.05` for local Minimum accepted peak spacing. `thresholdStd` scalar `3` Local method robust-threshold multiplier. `smoothSec` positive scalar `0.01` Local method score smoothing window. `integrationWindowSec` positive scalar `0.150` Pan-Tompkins moving-integration window. `refineSearchSec` positive scalar `0.120` Pan-Tompkins, `0.090` streaming Detector-trace peak snap search half-window. `rawRefineSearchSec` positive scalar `0.020` Final raw-signal peak snap half-window for Pan-Tompkins and streaming. `baselineWindowSec` positive scalar `0.600` Streaming causal baseline window. `envelopeWindowSec` positive scalar `0.080` Streaming slope-envelope window. `lookaheadSec` positive scalar `0.080` Streaming local-maximum lookahead. `minTemplateScore` scalar `0.45` Streaming rolling-template QC threshold. `medianPolarityCorrection` logical `true` Streaming post-pass for `auto`/`positive` polarity: reviews recent anchors against the signal median and re-snaps inverted anchors. `medianReviewPeakCount` positive integer `3` Number of latest streaming peaks considered by the median polarity review. Other Processing Options Function Options / parameters `filterSignal(signal, spec)` `spec.type`: `bandpass`, `lowpass`, `highpass`, `none`, `off`; `spec.cutoffHz`: scalar or `[low high]`; `spec.edgeMode`: `reflect` default or `none`; `spec.edgePadSec`: padding seconds, auto by default; `spec.edgeTaperSec`: padded-edge taper seconds, default `1`. `segmentByEvents(signal, events, windowSec)` `windowSec`: `[start end]` seconds relative to event, default `[-0.35 0.35]`. `buildTemplate(segments, opts)` `opts.topN`: positive integer, default `min(30, segmentCount)`. `measureSegments(segments, template, opts)` `opts.signalWindowSec`: `[start end]`, default `[-0.06 0.06]`; `opts.noiseWindowsSec`: N-by-2 matrix, default `[-0.30 -0.20; 0.40 0.50]`. Applications decide: ECG-specific labels and workflow wording which channel is treated as ECG GUI controls, previews, plot arrangement, and printing or export layout which measurements are displayed or exported class labels and interpretation of group comparisons These application choices are intentionally not parameters of the generic biosignal functions. Reference Contract The generated page for every `labkit.biosignal` function documents its exact input structure, implemented options, defaults, legal values, empty-result and error behavior, and related functions. The repository documentation contract discovers the whole public package rather than relying on a hand-maintained function list, and executes every block labeled `Example:`. labkit.biosignal.buildTemplate Build a representative segment template. labkit.biosignal.compareGroups Summarize groups and compute pairwise Welch comparisons. labkit.biosignal.cropSignal Return a signal clipped to a time range in seconds. labkit.biosignal.defaultEcgPeakOptions Return documented defaults for ECG/QRS peak detection. labkit.biosignal.detectEcgPeaks Detect ECG/QRS peaks as event anchors. labkit.biosignal.filterSignal Apply a zero-phase FFT-domain filter to a biosignal. labkit.biosignal.getChannel Return one signal from a recording by display name or index. labkit.biosignal.listChannels Return display names for all channels in a recording. labkit.biosignal.measureSegments Measure generic template-residual segment quality. labkit.biosignal.readRecording Read a biosignal file into a standard recording struct. labkit.biosignal.segmentByEvents Extract fixed windows around event anchors. labkit.biosignal.version Return the LabKit biosignal facade contract version."},{"title":"Gamry DTA Files","url":"libraries/dta/index.html","kind":"reference","section":"reference","keywords":"libraries dta Gamry DTA Files","text":"Gamry DTA Files [API reference](../../reference/README.md) | [Electrochemistry apps](../../apps/electrochemistry/README.md) The `labkit.dta` functions find and read Gamry DTA files without opening an app. They recognize chrono, electrochemical impedance spectroscopy (EIS), and cyclic-voltammetry/charge-time (CV/CT) data, and return ordinary MATLAB structures and numeric arrays for further analysis. Start Here Use `loadFile` when you know the path to one file: [item, status] = labkit.dta.loadFile(\"measurement.DTA\", \"auto\"); if ~status.ok error(\"Could not read DTA file: %s\", status.message) end switch item.type case \"chrono\" plot(item.t_s, item.Vf_V) xlabel(\"Time (s)\") ylabel(\"Voltage (V)\") case \"eis\" loglog(item.freq_Hz, item.Zmod_ohm) xlabel(\"Frequency (Hz)\") ylabel(\"|Z| (ohm)\") case \"cvct\" curve = item.curves(1); [x, y] = labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); plot(x, y) end The second argument restricts the accepted data family. Use `\"auto\"` to detect it, or use `\"chrono\"`, `\"eis\"`, or `\"cvct\"` when loading the wrong family should be reported as a failure. Values are case-insensitive and may contain surrounding whitespace. A blank value also means `\"auto\"`. Choose a Function Task Function Identify the data family in one file [`labkit.dta.detectType`](../../reference/api/labkit/dta/detectType.html) Read one file [`labkit.dta.loadFile`](../../reference/api/labkit/dta/loadFile.html) Read a known list of files [`labkit.dta.loadFiles`](../../reference/api/labkit/dta/loadFiles.html) Find and read every DTA file below a folder [`labkit.dta.loadFolder`](../../reference/api/labkit/dta/loadFolder.html) Find paths without reading their contents [`labkit.dta.findFiles`](../../reference/api/labkit/dta/findFiles.html) Select a chrono or EIS table [`getMainCurve`](../../reference/api/labkit/dta/getMainCurve.html), [`getZCurve`](../../reference/api/labkit/dta/getZCurve.html) Extract numeric columns from parsed tables [`getColumn`](../../reference/api/labkit/dta/getColumn.html), [`getCurveXY`](../../reference/api/labkit/dta/getCurveXY.html) Locate chrono pulse windows [`labkit.dta.detectPulses`](../../reference/api/labkit/dta/detectPulses.html) Check library version and compatibility [`labkit.dta.version`](../../reference/api/labkit/dta/version.html) Loading Several Files `loadFiles` keeps every successful item and records failures instead of stopping at the first unreadable file: files = [\"run-01.DTA\", \"run-02.DTA\", \"run-03.DTA\"]; [items, report] = labkit.dta.loadFiles(files, \"chrono\"); fprintf(\"Loaded %d of %d files.\\n\", ... report.nLoaded, report.nRequested) for k = 1:numel(report.failed) warning(\"%s: %s\", ... report.failed(k).filepath, report.failed(k).message) end `report.statuses` contains one status in the same order as the input paths. `items` contains successful items only, so use the status array when you need to match a result to every original position. Use `loadFolder` for recursive folder import. It adds `folder`, `filepaths`, and `nDiscovered` to the same report: [items, report] = labkit.dta.loadFolder(\"experiment\", \"eis\"); Chrono Data A chrono item contains the parsed metadata and tables, the selected transient curve, pulse information, and these unit-explicit vectors: Field Meaning `t_s` Time in seconds `Vf_V` Measured voltage in volts `Im_A` Measured current in amperes `pt` Point number from the file, or a generated zero-based sequence `controlMode` `\"current\"`, `\"voltage\"`, or `\"unknown\"`, inferred from step metadata `pulse` Cathodic, gap, and anodic windows returned by `detectPulses` `meta`, `tables`, `curve` Parsed metadata, all parsed tables, and the selected T/Vf/Im table Rows with nonfinite T, Vf, or Im values are removed. Duplicate time values keep their first occurrence. Loading fails when fewer than two valid samples remain. Pulse Detection The default mode, `\"Metadata first, then auto\"`, first looks for negative and positive ISTEP/TSTEP metadata. If current-step metadata is absent, it can use VSTEP/TSTEP timing. If metadata detection fails, it locates dominant negative and positive segments in the measured current. [pulse, message] = labkit.dta.detectPulses( ... item.t_s, item.Im_A, item.meta, \"Metadata first, then auto\"); Other modes are `\"Metadata only\"` and `\"Auto from Im only\"`. Programmatic aliases `\"metadata_first\"`, `\"metadata_only\"`, and `\"current_only\"` are also accepted. Current-based detection uses 25 percent of `max(abs(Im))` as its threshold, with a `1e-12 A` floor, then selects the longest cathodic segment and the longest later anodic segment. An unknown mode returns `pulse.ok=false` with an explanatory message; it is never silently treated as the default. On success, `pulse.pre`, `pulse.cath`, `pulse.gap`, `pulse.anod`, and `pulse.post` provide unit-explicit `start_s` and `end_s` windows. `pulse.cath.current_A` and `pulse.anod.current_A` contain nominal or measured phase currents, and `pulse.gap.center_s` contains the alignment center. `pulse.ok` is false and numeric fields are `NaN` when a valid cathodic/anodic pair cannot be found. Always check `pulse.ok` before using the windows in a calculation. Version 3 returns only this unit-explicit item and pulse model. Chrono items use `t_s`, `Vf_V`, and `Im_A`; EIS items use the fields listed below. Earlier unit-ambiguous aliases are not emitted. EIS Data An EIS item selects the `ZCURVE` table, or the first compatible table with Freq, Zreal, and Zimag columns. It exposes these unit-explicit vectors: Field Meaning `freq_Hz` Frequency in hertz `time_s` Elapsed time in seconds `point` Point number `Zreal_ohm` Real impedance in ohms `Zimag_ohm` Imaginary impedance in ohms `negZimag_ohm` Negative imaginary impedance in ohms `Zmod_ohm` Impedance magnitude in ohms `Zphz_deg` Phase angle in degrees `Idc_A` DC current in amperes `Vdc_V` DC voltage in volts Rows are retained when at least one primary impedance value is finite. Missing optional columns are returned as same-length `NaN` vectors. Loading fails when fewer than two usable ZCURVE rows remain. CV/CT Data A CV/CT item contains `curves` in file order and exposes the parsed scan rate as `scanRate_V_per_s`. The parser converts a Gamry `SCANRATE` value from mV/s to V/s. Each curve retains its headers, units, numeric data, and information about which values were parsed successfully. Use exact header names with `getCurveXY`: curve = item.curves(1); [voltageV, currentA, xname, yname] = ... labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); Rows where either selected column is `NaN` are removed as a pair. `getCurveXY` returns empty arrays and empty names when either header is absent. Parsed Table Structure Chrono and EIS parsers return `tables`, a structure array containing the table name, headers, units when available, and numeric data. Use `getMainCurve` or `getZCurve` to select a compatible table, then use `getColumn` for case-insensitive column lookup: [curve, ok, message] = labkit.dta.getMainCurve(item.tables); if ok timeSec = labkit.dta.getColumn(curve, \"T\"); currentA = labkit.dta.getColumn(curve, \"Im\"); end `getColumn` returns `[]` when the requested header is absent. It does not change units or remove `NaN` values. File Format and Limitations The reader treats DTA files as tab-delimited text. It scans metadata and named TABLE or CURVE sections, preserves headers and units when available, and converts numeric rows conservatively. Gamry software versions and experiment methods can produce additional sections or columns. Successful parsing means that a supported core curve was found; it does not imply that every vendor-specific field was interpreted. `detectType` and `loadFile` return readable status messages for missing files, unsupported content, parse failures, and expected-kind mismatches. Invalid argument types and invalid expected-kind strings throw documented LabKit errors. Reference Contract Open an individual DTA function page for exact accepted kinds, path forms, status fields, thrown errors, and related loaders or accessors. Public help is checked against implemented options and its executable examples are run in a clean MATLAB test process. Related Topics [CIC](../../apps/electrochemistry/cic/README.md), [CSC](../../apps/electrochemistry/csc/README.md), and [EIS](../../apps/electrochemistry/eis/README.md) use DTA records in interactive workflows. [Contract functions](../../framework/compatibility/contracts.md) explain how apps declare a compatible `labkit.dta` API version. [Project history](../../history/README.md) lists parser, schema, and compatibility changes. labkit.dta.detectPulses Locate cathodic and anodic pulse windows in chrono data. labkit.dta.detectType Identify the supported Gamry DTA data family in a file. labkit.dta.findFiles Find DTA files in a folder and its subfolders. labkit.dta.getColumn Extract a named column from a parsed DTA table. labkit.dta.getCurveXY Extract paired X and Y data from a parsed CV/CT curve. labkit.dta.getMainCurve Select the transient table containing T, Vf, and Im data. labkit.dta.loadFile Read one supported Gamry DTA file. labkit.dta.loadFiles Read a list of Gamry DTA files. labkit.dta.loadFolder Read every DTA file below a folder. labkit.dta.version Return version information for the DTA API."},{"title":"Image API","url":"libraries/image/index.html","kind":"reference","section":"reference","keywords":"libraries image Image API","text":"Image API [Public API index](../../reference/README.md) | [App guide](../../apps/README.md) `labkit.image.*` provides GUI-free image file IO, conversion, preview sizing, and common enhancement operations. App-specific registration, measurement, ROI, matching, and export workflows build on these functions. `labkit.image.version()` returns image-library version and compatibility information used by App `definition.m` requirement declarations. Common Calls filter = labkit.image.fileDialogFilter(\"IncludeAll\", true); paths = labkit.image.normalizePaths(rawPaths); records = labkit.image.readFiles(paths); source = labkit.image.im2double(records(1).image); preview = labkit.image.ensureRgb(source); preview = min(max(preview, 0), 1); luma = labkit.image.rgb2gray(preview); [preview, scale] = labkit.image.resizeToFit(preview, \"MaxHeight\", 1500); [preview, budget] = labkit.image.previewBudget(preview, \"MaxPixels\", appPreviewBudget); blurred = labkit.image.meanFilter2(preview(:, :, 1), 7); enhanced = labkit.image.adjustBrightnessContrast(preview, 10, 20); enhanced = labkit.image.localContrast(enhanced, 50, 3); enhanced = labkit.image.sharpen(enhanced, 30, 1.5); enhanced = labkit.image.grayWorldWhiteBalance(enhanced, 80, 0); labkit.image.writeFile(enhanced, outputPath); Normalization Helpers `labkit.image.im2double` follows the MATLAB `im2double` call contract for supported numeric image classes, including the optional `\"indexed\"` mode. `labkit.image.rgb2gray` follows the MATLAB `rgb2gray` call contract for RGB images and colormaps while using the documented Rec.601 luma transform. `labkit.image.ensureRgb` changes channel shape only. It expands grayscale data to three channels or drops channels after RGB without changing class or sample values. Callers that need display-ready RGB data explicitly compose `im2double`, `ensureRgb`, and `[0, 1]` clamping as shown above. `labkit.image.previewBudget` preserves native pixels by default. An App passes an explicit finite `\"MaxPixels\"` value only when its own workflow permits sampling; the resulting integer coordinate scale is returned with the preview. Provided Operations The module provides: supported source-image extension lists and file-dialog filters path normalization and display names `imread`/`imwrite` wrappers that normalize app-facing edge behavior MATLAB-compatible image conversion, explicit RGB shaping, preview-size fitting, and edge-normalized mean filtering caller-owned display-pixel budget helpers for responsive previews while preserving a documented integer coordinate scale generic image enhancement primitives such as brightness/contrast, HSV hue/saturation, gray-world white balance, local contrast, and sharpening Applications add: which filters or processing steps appear in the app UI parameter labels, defaults, validation ranges, and step history semantics reference matching, protected background/ROI behavior, scientific formulas, crop geometry, focus stacking, curvature, and DIC processing export manifests, result columns, filenames, failed-row policy, alerts, and log text Use `labkit.image.readFiles` when an app needs generic source-image records. Apps may copy the returned `path`, `name`, and `image` fields into their own item structures. Specialized formats and result structures remain documented by the app that uses them. Reference Contract The generated Image API pages linked from the [public API index](../../reference/README.md) document exact syntax, inputs, outputs, implemented options and defaults, legal values, failure behavior, examples, and related functions. In particular, [`resizeToFit`](../../reference/api/labkit/image/resizeToFit.html) accepts only the documented `\"bilinear\"` and `\"nearest\"` methods and rejects any other method instead of silently selecting an interpolation policy. labkit.image.adjustBrightnessContrast Apply simple brightness and contrast adjustment. labkit.image.ensureRgb Return image data with exactly three color channels. labkit.image.fileDialogFilter Return a file-chooser-compatible image filter. labkit.image.grayWorldWhiteBalance Apply generic gray-world white balance. labkit.image.im2double Convert image data to double using MATLAB's im2double contract. labkit.image.localContrast Enhance local value-channel contrast with a mean-filter mask. labkit.image.meanFilter2 Apply normalized 2-D mean filtering with edge correction. labkit.image.normalizePaths Normalize image file path inputs to a string column. labkit.image.previewBudget Downsample image data to fit a display pixel budget. labkit.image.readFiles Read image files into path/name/image records. labkit.image.resizeToFit Resize an image to fit within maximum row/column limits. labkit.image.rgb2gray Convert RGB data using MATLAB's rgb2gray call contract. labkit.image.sharpen Apply generic unsharp-mask sharpening. labkit.image.version Return the LabKit image facade contract version. labkit.image.writeFile Write one image file, creating the parent folder when needed."},{"title":"Intan RHS Recordings","url":"libraries/rhs/index.html","kind":"reference","section":"reference","keywords":"libraries rhs Intan RHS Recordings","text":"Intan RHS Recordings [API reference](../../reference/README.md) | [Neurophysiology apps](../../apps/neurophysiology/README.md) The `labkit.rhs` functions inspect Intan RHS recordings and read selected waveform windows without loading an entire file into memory. They expose file metadata, channel names, data-block layout, timestamps, and samples in physical units. The functions can be called directly from MATLAB without opening a LabKit app. Start Here Inspect a file first when you need to see its channels and duration: [info, status] = labkit.rhs.inspectFile(\"recording.rhs\"); if ~status.ok error(\"Could not inspect RHS file: %s\", status.message) end fprintf(\"%.2f s at %.0f Hz\\n\", info.durationSec, info.sampleRateHz) disp(info.channelTable(:, [\"family\", \"nativeName\", \"customName\"])) Then read only the channels and times required by the analysis: opts = struct( ... \"family\", \"amplifier\", ... \"channels\", [\"C-001\", \"C-002\"], ... \"timeRangeSec\", [0.100 0.150]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", opts); if status.ok plot(window.timeSec, window.values) xlabel(\"Time (s)\") ylabel(\"Amplitude (microvolts)\") legend(window.channels) end `window.values` is always samples-by-channels. `window.timeSec` contains the timestamps stored in the recording, converted to seconds. Choose a Function Task Function Find RHS paths recursively [`labkit.rhs.findFiles`](../../reference/api/labkit/rhs/findFiles.html) Read header, channel, and duration information [`labkit.rhs.inspectFile`](../../reference/api/labkit/rhs/inspectFile.html) Build the byte/sample layout for later reads [`labkit.rhs.indexFile`](../../reference/api/labkit/rhs/indexFile.html) Read one channel family and time interval [`labkit.rhs.readWindow`](../../reference/api/labkit/rhs/readWindow.html) Check library version and compatibility [`labkit.rhs.version`](../../reference/api/labkit/rhs/version.html) Header Information `inspectFile` reads the header and calculates how much complete waveform data is available. Important fields include: Field Meaning `fileVersion` Intan file-format major and minor version `sampleRateHz` Amplifier sample rate in hertz `durationSec` Duration represented by complete data blocks `channelFamilies` Channel structures grouped as amplifier, board ADC, board DAC, digital input, and digital output `channelTable` One table combining enabled stored channels and their native/custom names `frequencyParameters` Requested and actual filter and bandwidth settings `stimParameters` Stimulation step size and charge-recovery settings `dcAmplifierSaved` Whether DC-amplifier samples are stored `blockCount`, `sampleCount` Number of complete blocks and samples `exactBlocks` Whether the data section ends on a complete block boundary Header-only files can be inspected successfully and have a sample count of zero. A file with trailing partial bytes remains readable through its last complete block; `exactBlocks` is false and the status message reports the condition. Channel Families and Units `readWindow` reads one family at a time: Family Accepted examples Returned unit `\"amplifier\"` `\"amplifier\"`, `\"amp\"` microvolts `\"stim\"` `\"stim\"`, `\"stimulus\"`, `\"stimcurrent\"` microamps `\"dcAmplifier\"` `\"dc\"`, `\"dcamplifier\"`, `\"dcamp\"` volts `\"boardAdc\"` `\"boardadc\"`, `\"adc\"` volts `\"boardDac\"` `\"boarddac\"`, `\"dac\"` volts `\"boardDigIn\"` `\"boarddigin\"`, `\"digitalin\"`, `\"digin\"` logical 0 or 1 `\"boardDigOut\"` `\"boarddigout\"`, `\"digitalout\"`, `\"digout\"` logical 0 or 1 Family matching ignores case and punctuation. Selecting a family that has no stored channels returns `status.ok=false` with an empty window. Selecting Channels Set `channels=[]` to read every channel in the selected family. Otherwise use one-based numeric positions, native names, or custom names: opts.channels = [1 3]; opts.channels = [\"C-001\", \"C-003\"]; opts.channels = [\"Reference\", \"Recording\"]; Names are matched exactly first. If no exact match is found, comparison ignores case and punctuation, so `\"C001\"` can match the native name `\"C-001\"`. Returned `window.channels` always contains native channel names. Selecting a Time Window `timeRangeSec=[start end]` selects an inclusive sample interval. The start is clamped to zero, `Inf` reads through the last complete sample, and an end past the recording duration is clipped. For example: opts.timeRangeSec = [0 0.050]; % first 50 ms opts.timeRangeSec = [1.5 Inf]; % from 1.5 s to the end A valid request that begins after the available data returns `status.ok=true` with empty `timeSec` and `values`, allowing a caller to distinguish an empty interval from a file-read failure. Block Indexing and Lazy Reads RHS waveform data is stored in 128-sample blocks. `indexFile` calculates the header offset, bytes per block, complete block count, sample count, and duration without reading the waveform arrays. `readWindow` uses this index to seek directly to the blocks that overlap the requested interval. [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); if status.ok fprintf(\"%d samples in %d blocks\\n\", ... index.sampleCount, index.blockCount) end This makes short previews and event-centered analyses practical for recordings that are too large to load all at once. Physical-Unit Conversion The reader follows the Intan RHS block layout and applies the documented conversion gains and code offsets for amplifier, DC-amplifier, stimulation, board ADC, and board DAC data. Digital inputs and outputs are decoded from their stored bit positions. No filtering, baseline correction, event detection, channel-role interpretation, or response measurement is performed by these file-reading functions. Errors and Unsupported Files Missing files, malformed headers, unsupported magic numbers, absent channel families, and waveform read failures are returned through `status.ok=false` and `status.message`. Invalid function arguments, such as an unknown family, channel, or reversed time range, throw the documented `labkit:rhs:*` error. Successful parsing confirms that the supported RHS header and complete block layout were readable. It does not validate an experiment protocol, decide which channels form a differential signal, or assess recording quality. Reference Contract The generated pages for `findFiles`, `inspectFile`, `indexFile`, `readWindow`, and `version` state complete inputs, outputs, options, defaults, legal values, status/error behavior, and related APIs. Contract tests discover the complete public package and execute each documented `Example:` block. Related Topics [RHS Preview](../../apps/neurophysiology/rhs-preview/README.md) provides interactive waveform review and channel-role drafting. [Nerve Response Analysis](../../apps/neurophysiology/nerve-response-analysis/README.md) reads selected RHS channels for event and response calculations. [Biosignal functions](../biosignal/README.md) operate on imported recordings and generic waveforms after file IO. [Project history](../../history/README.md) lists RHS parser and app changes. labkit.rhs.findFiles Find Intan RHS files in a folder and its subfolders. labkit.rhs.indexFile Build a block index for reading selected RHS time windows. labkit.rhs.inspectFile Read metadata and data-layout information from an RHS file. labkit.rhs.readWindow Read selected channels and times from an Intan RHS file. labkit.rhs.version Return version information for the RHS API."},{"title":"Thermal Images","url":"libraries/thermal/index.html","kind":"reference","section":"reference","keywords":"libraries thermal Thermal Images","text":"Thermal Images [API reference](../../reference/README.md) | [FLIR Thermal app](../../apps/image-measurement/flir-thermal/README.md) The `labkit.thermal` functions read FLIR radiometric JPEG files, expose their raw sensor data and calibration metadata, convert sensor values to degrees Celsius, and create display-ready RGB images. They can be called from MATLAB without opening a LabKit app. Start Here Use this sequence when you want to analyze thermal files in a script: files = [\"capture-01.rjpg\", \"capture-02.rjpg\"]; [records, report] = labkit.thermal.readFiles(files, ... struct(\"SkipInvalid\", true)); for k = 1:numel(records) temperatureC = records(k).temperatureC; fprintf(\"%s: %.1f to %.1f C\\n\", records(k).name, ... min(temperatureC, [], \"all\"), max(temperatureC, [], \"all\")); end fprintf(\"Loaded %d files; skipped %d.\\n\", ... report.loaded, report.skipped) `readFiles` preserves the input order. With `SkipInvalid` set to `true`, it returns every readable record and describes unreadable files in `report.failures`. Without that option, the first read error stops the call. Choose a Function Task Function Read one radiometric image [`labkit.thermal.readFile`](../../reference/api/labkit/thermal/readFile.html) Read a list of images [`labkit.thermal.readFiles`](../../reference/api/labkit/thermal/readFiles.html) Check a file without throwing a read error [`labkit.thermal.inspectFile`](../../reference/api/labkit/thermal/inspectFile.html) Convert raw counts with supplied calibration [`labkit.thermal.rawToTemperatureC`](../../reference/api/labkit/thermal/rawToTemperatureC.html) Convert a numeric matrix to RGB colours [`labkit.thermal.renderImage`](../../reference/api/labkit/thermal/renderImage.html) Build a file-dialog filter [`labkit.thermal.fileDialogFilter`](../../reference/api/labkit/thermal/fileDialogFilter.html) Test or list recognized extensions [`isSupportedPath`](../../reference/api/labkit/thermal/isSupportedPath.html), [`supportedExtensions`](../../reference/api/labkit/thermal/supportedExtensions.html) Check library version and compatibility [`labkit.thermal.version`](../../reference/api/labkit/thermal/version.html) Thermal Records `readFile` returns one structure with these fields: Field Meaning `path`, `name` Source location and filename `format` Detected radiometric container format `raw` Decoded sensor-count matrix `temperatureC` Same-size double matrix in degrees Celsius; invalid conversion pixels are `NaN` `units` `\"C\"` when temperature conversion succeeded, otherwise `\"raw\"` `metadata` Parsed FLIR tags, calibration values, record information, and conversion diagnostics `message` Short description of the conversion result An ordinary JPEG preview is not a temperature matrix. The reader looks for an embedded FLIR FFF `RawThermalImage` record and uses that sensor data for the conversion. A `.jpg` or `.rjpg` extension therefore indicates only a candidate file. Call `inspectFile` when you need to know whether the contents are actually readable as thermal data. Temperature Correction The default `\"environment\"` correction uses the camera's Planck constants and, when available, emissivity, object distance, reflected apparent temperature, atmospheric temperature, humidity, infrared-window temperature, and infrared-window transmission. Missing environmental values receive documented defaults so that conversion can continue. The five Planck constants never receive fallback values. Use two outputs from `rawToTemperatureC` to see exactly what happened: [temperatureC, diagnostics] = labkit.thermal.rawToTemperatureC( ... raw, calibration, struct(\"Correction\", \"environment\")); if diagnostics.usedDefaults fprintf(\"Defaults used for: %s\\n\", ... strjoin(diagnostics.defaultedFields, \", \")) end `diagnostics.parameterSources` records `\"calibration\"` or `\"default\"` for each environmental input. A conversion that used defaults may be useful for previewing data, but the result is not evidence that the assumed emissivity, distance, humidity, or temperatures match the experiment. Check those values before using the temperatures in quantitative analysis. Choose `\"planck-basic\"` only when you intentionally want the conversion based on the five Planck constants alone: temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration, ... struct(\"Correction\", \"planck-basic\")); Rendering a Temperature Matrix `renderImage` maps a numeric matrix onto a colour palette. It does not change the temperatures and does not perform calibration. rgb = labkit.thermal.renderImage(temperatureC, ... struct(\"Limits\", [20 40], \"Palette\", \"iron\", \"Levels\", 256)); image(rgb) axis image off Values outside `Limits` are clipped to the end colours. Supported palettes are `\"turbo\"`, `\"parula\"`, `\"hot\"`, `\"gray\"`, and `\"iron\"`. The FLIR Thermal app also offers display-only log and gamma controls; those controls are app features and do not alter exported temperature values. Supported Files and Limitations The current reader supports FLIR radiometric JPEG and RJPEG files with an embedded FFF raw-thermal record. Camera models and firmware revisions can encode metadata differently. Use `inspectFile` or `readFiles` with `SkipInvalid=true` for mixed or uncertain inputs, and retain the returned failure messages when reporting an unsupported file. Do not infer measurement accuracy from successful parsing alone. Quantitative temperature accuracy still depends on the camera calibration and on correct environmental inputs for the photographed material and scene. Reference Contract Each generated Thermal API page distinguishes returned status from thrown errors and documents every implemented option, default, legal value, output field, and related function. Executable examples and package-wide public-help discovery prevent the manual and callable surface from silently diverging. Related Topics [Image functions](../image/README.md) cover ordinary image IO and image processing that does not use radiometric calibration. [FLIR Thermal app](../../apps/image-measurement/flir-thermal/README.md) provides interactive file review, measurements, display controls, and exports. [Project history](../../history/README.md) lists changes to thermal file support and calibration behavior. labkit.thermal.fileDialogFilter Create a file-selection filter for thermal images. labkit.thermal.inspectFile Check whether a file contains supported radiometric data. labkit.thermal.rawToTemperatureC Convert FLIR raw sensor values to degrees Celsius. labkit.thermal.readFile Read radiometric image data and convert it to degrees Celsius. labkit.thermal.readFiles Read several radiometric image files. labkit.thermal.renderImage Map a thermal matrix to an RGB image. labkit.thermal.version Return version information for the thermal API."},{"title":"Electrochemistry Apps","url":"apps/electrochemistry/index.html","kind":"app family","section":"apps","keywords":"apps electrochemistry Electrochemistry Apps","text":"Electrochemistry Apps The Electrochemistry family reads Gamry DTA files through `labkit.dta` and keeps experiment-specific analysis, plotting, defaults, and export schemas in the owning app. Source DTA files are never modified. Choose An App Measurement or task App Required DTA content Main result Compare voltage and current transients across files [Chrono Overlay](chrono-overlay/README.md) chrono curve with `T`, `Vf`, `Im` aligned overlay and wide CSV Charge-injection capacity and polarization voltage [CIC](cic/README.md) biphasic chrono transient per-file CIC, charge, and voltage metrics Charge-storage capacity by time and CV integration [CSC](csc/README.md) CV/CT cycles with `T`, `Vf`, `Im` per-cycle CT/CV CSC comparison Impedance inspection and export [EIS](eis/README.md) `ZCURVE` configurable Nyquist/Bode-style overlay Steady pulse resistance [VT Resistance](vt-resistance/README.md) biphasic chrono transient cathodic, anodic, and mean resistance Shared File Behavior File controls accept one or more `.DTA` files from one folder in a single selection. A canceled chooser leaves the current project unchanged. CIC, CSC, and VT Resistance use the selected row as the active preview while retaining the loaded source list for batch export. Invalid items are reported per file; one failed item does not silently replace another result. The DTA library returns structured items, curve tables, headers, units, metadata, parser messages, and status. Apps use exact required columns for scientific calculations and do not infer missing physical quantities from a plot label. The Apps require the version 3 unit-explicit DTA contract rather than branching between duplicate legacy and canonical item fields. Units And Traceability Time is seconds, voltage is volts, current is amperes, impedance is ohms, charge is coulombs, electrode area is square centimetres, and normalized CIC or CSC is reported in the unit shown by the app. UI display conversions do not change stored base-unit calculations. Export tables include the source identity and analysis settings needed to interpret the result. Runtime-generated `.labkit.json` files are provenance manifests describing inputs, parameters, and output roles; they are not a second numerical result and can be kept with the corresponding CSV. Use Without The GUI Reusable parsing and curve access live in `labkit.dta`. App-specific numeric operations such as `cic.analysisRun.computeCIC`, `csc.analysisRun.computeCSC`, and `vt_resistance.analysisRun.computeResistance` are documented on the owning app page and in the generated API reference. Related Modules [DTA Library](../../libraries/dta/README.md) [API Reference](../../reference/README.md) [All Apps](../README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics. csc.analysisRun.computeCSC Compare time-integrated and scan-rate-derived electrode charge. vt_resistance.analysisRun.computeResistance Estimate cathodic and anodic resistance from a VT pulse."},{"title":"API Reference","url":"reference/index.html","kind":"reference","section":"reference","keywords":"reference API Reference","text":"API Reference This index covers domain libraries and GUI-free App APIs. The App SDK itself is documented under [Framework: App SDK API](../framework/app-sdk-api.md), so its contracts stay with the lifecycle and architecture guidance they support. Use this reference to look up supported MATLAB functions. Every function name in the generated module tables is a direct link to an individual page with syntax, arguments, outputs, behavior, source, and related APIs. Find The Right Layer Need Module guide App lifecycle, layout, plotting, interaction, state, debugging, and compatibility requirements [App Framework](../framework/README.md) Image IO and generic image operations [Image](../libraries/image/README.md) Radiometric image decoding and temperature conversion [Thermal](../libraries/thermal/README.md) Gamry DTA discovery, parsing, curves, and pulses [DTA](../libraries/dta/README.md) Intan RHS inspection and waveform windows [RHS](../libraries/rhs/README.md) Generic biosignal import and processing [Biosignal](../libraries/biosignal/README.md) Scientific operations owned by one app [Apps](../apps/README.md) Module guides explain scope, data models, normal call sequences, algorithms, and representative examples. Function pages are the source for exact callable contracts. Files in `private/` directories and App helpers without a complete public help contract are not supported entry points. Compatibility Each reusable library publishes a `version()` result. Apps declare compatible library ranges in the `Requirements` field of their single `definition.m` contract. A function documented here is public within that library version; compatibility across a breaking version is not implied. The tables below are generated from current MATLAB source. Each entry links to the current function help. labkit.biosignal Recording import, channel access, filtering, events, templates, and measurements. Framework Compatibility Version and MathWorks-product requirement contracts. labkit.dta Gamry DTA discovery, parsing, curve access, and pulse detection. labkit.image Generic image file IO, normalization, resizing, filtering, and enhancement primitives. labkit.rhs Intan RHS discovery, indexing, inspection, and bounded waveform reads. labkit.thermal Radiometric image inspection, conversion, reading, and rendering. Batch Image Crop Supported GUI-free operations owned by the Batch Crop app in the Image Measurement family. Charge-Injection Capacity Supported GUI-free operations owned by the Cic app in the Electrochemistry family. Charge-Storage Capacity Supported GUI-free operations owned by the Csc app in the Electrochemistry family. Curvature Measurement Supported GUI-free operations owned by the Curvature app in the Image Measurement family. DIC Postprocess Supported GUI-free operations owned by the Dic Postprocess app in the Digital Image Correlation family. DIC Preprocess Supported GUI-free operations owned by the Dic Preprocess app in the Digital Image Correlation family. ECG Print Supported GUI-free operations owned by the Ecg Print app in the Wearable family. FLIR Thermal Supported GUI-free operations owned by the Flir Thermal app in the Image Measurement family. Focus Stack Supported GUI-free operations owned by the Focus Stack app in the Image Measurement family. Gait Analysis Supported GUI-free operations owned by the Gait Analysis app in the Gait family. Image Enhance Supported GUI-free operations owned by the Image Enhance app in the Image Measurement family. Image Match Supported GUI-free operations owned by the Image Match app in the Image Measurement family. Nerve Response Analysis Supported GUI-free operations owned by the Nerve Response Analysis app in the Neurophysiology family. Response Review And Stats Supported GUI-free operations owned by the Response Review Stats app in the Neurophysiology family. RHS Preview Supported GUI-free operations owned by the Rhs Preview app in the Neurophysiology family. T-Test Wizard Supported GUI-free operations owned by the Ttest Wizard app in the Statistics family. Video Marker Supported GUI-free operations owned by the Video Marker app in the Image Measurement family. VT Resistance Supported GUI-free operations owned by the Vt Resistance app in the Electrochemistry family."},{"title":"Chrono Overlay","url":"apps/electrochemistry/chrono-overlay.html","kind":"app","section":"apps","keywords":"apps electrochemistry chrono overlay Chrono Overlay","text":"Chrono Overlay Chrono Overlay compares voltage and current transients from multiple Gamry DTA files on a common pulse-centered time axis and exports the aligned curves. Requirements And Launch Each source must contain a readable chrono curve with time, voltage, and current data. labkit_ChronoOverlay_app Inputs Use **Add DTA files** to select one or more `.DTA` files from one directory. The app parses each file as chrono data and reports unreadable items. The file list controls curve order, legend labels, and removal; selection does not discard other loaded curves. Saved projects preserve the ordered file list and reopen it through portable source references. Basic Workflow Add the DTA files to compare. Inspect the overlaid voltage and current plots. Choose seconds, milliseconds, or sample number for the X axis. Set line width, legend visibility, and grid visibility. Export the aligned curves CSV. Plot styling does not alter exported values. Removing a file removes its curves from both plots and the next export. Alignment Algorithm For each valid transient, the app uses the detected cathodic/anodic blank interval and defines time zero at its center. If pulse detection or the gap center is unavailable, it explicitly falls back to the first recorded sample and reports that fallback in the app log. If files use different sample times, export constructs a merged aligned-time axis and interpolates each voltage/current series onto that axis. Missing coverage remains missing; the app does not extrapolate a transient beyond its recorded time range. Parameters Parameter Default Effect X axis Time (s) plot coordinate only; alternatives are Time (ms) and Sample # Line width 1.3 rendered curve width Show file-name legend on legend visibility Show grid on axes grid visibility Output **Export curves CSV** writes `TimeGapCenterAligned_s` followed by paired voltage and current columns for each source. Column names retain a sanitized source identity. The app also writes a result manifest containing source references, current parameters, and the CSV output role. Use Without The GUI [items, status] = labkit.dta.loadFiles([\"run01.DTA\", \"run02.DTA\"], ... \"chrono\"); assert(all([status.ok]), \"One or more DTA files could not be read.\"); aligned = arrayfun(@(item) ... chrono_overlay.sourceFiles.alignByPulseGap(item), items); tableOut = chrono_overlay.resultFiles.buildOverlayExportTable(aligned); These app-owned helpers expose the workflow to scripts but are not currently listed as stable public APIs; reusable DTA reading is supported through `labkit.dta`. Errors And Limitations A source without a detectable biphasic gap is aligned to its first sample; review the log before comparing it with gap-centered files. Interpolation enables a common export table but does not make differently sampled instruments scientifically identical. Compare only experiments whose control mode, scale, and units are compatible. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [CIC](../cic/README.md) [VT Resistance](../vt-resistance/README.md)"},{"title":"Charge-Injection Capacity","url":"apps/electrochemistry/cic.html","kind":"app","section":"apps","keywords":"apps electrochemistry cic Charge-Injection Capacity","text":"Charge-Injection Capacity The CIC app measures charge delivered by a biphasic current pulse, normalizes charge by electrode area, and reports voltage-transient polarization metrics at a controlled delay after each pulse phase. Requirements And Launch The app requires a chrono DTA curve with valid `T`, `Vf`, and `Im` columns. labkit_CIC_app Inputs And Batch Behavior Add one or more chrono `.DTA` files. The selected row is decoded for immediate preview; batch calculation is performed with the same analysis settings when results are exported. This avoids repeatedly decoding every large file while the user is only switching previews. Saved projects preserve file order and portable source identity through removal, later additions, and reopen. Electrode area comes from a positive UI override when supplied, otherwise from the parsed DTA metadata. Without a valid positive area, charge in coulombs can still be calculated but area-normalized CIC fields are `NaN`. Basic Workflow Add chrono DTA files and select a representative file. Choose a water-window preset or set custom cathodic/anodic limits. Choose pulse detection, post-pulse delay, electrode area, phase mode, and output unit. Inspect detected phases, sampling markers, and voltage/current plots. Review the current-file summary. Export the batch result CSV. Analysis Parameters Parameter Default Legal choices or unit Preset Pt (-0.6 to 0.8 V) Pt, PEDOT:PSS, or Custom Cathodic / anodic limits -0.6 / 0.8 volts Delay 10 microseconds after each phase end Pulse detection Metadata first, then auto metadata fallback, metadata only, or current-only auto CIC phase Total biphasic Cathodic phase, Anodic phase, Total biphasic CIC unit mC/cm^2 mC/cm^2 or uC/cm^2 Use measured current on integrate measured `Im`; otherwise use detected pulse estimates Plot markers, limit lines, shading, axes, and grid choices affect presentation only. Analysis controls apply to the entire exported batch. Calculation Semantics The app removes samples where time, voltage, or current is not finite and requires at least five valid points. `labkit.dta.detectPulses` identifies the cathodic phase, interpulse gap, anodic phase, and surrounding baseline windows. Measured-current mode integrates each detected current phase over time. The phase charges `Qc_C` and `Qa_C` are positive magnitudes; total charge is their sum. With area `A` in cm^2: CIC_mC_cm2 = 1000 * Q_C / A_cm2 Maximum cathodic and anodic polarization potentials are interpolated at `phase end + delay`. The app does not extrapolate: if either requested time is outside the recorded range, that file fails with an explicit delay message. Baseline candidates are selected from pre-pulse, interpulse, and post-pulse windows, with documented fallbacks recorded in the result struct. Water-window status compares the calculated polarization potentials with the selected cathodic and anodic limits. The limits are an app policy and do not change the integrated charge. Output Schema The batch CSV contains source identity, area, delay, pulse-detection method, phase timing, measured/estimated current information, charge fields, area-normalized CIC fields, polarization and baseline values, water-window status, and a per-file result message. `Area_cm2` and `Delay_us` are exported so normalization and sample timing remain auditable. A `.labkit.json` result manifest accompanies the CSV and records source references, parameters, and the output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message); options = struct( ... \"delay_s\", 10e-6, ... \"cathLimit\", -0.6, ... \"anodLimit\", 0.8, ... \"area_cm2\", 0.05, ... \"pulseMode\", \"Metadata first, then auto\", ... \"usedMeasuredCurrent\", true); result = cic.analysisRun.computeCIC(item, options); assert(result.ok, result.message); Errors And Limitations Missing `T`, `Vf`, or `Im`, fewer than five valid samples, or failed pulse detection returns `ok=false` with a message. A delay outside the recorded time range fails instead of extrapolating. Area-normalized values require a positive area; invalid area is not replaced by an arbitrary default. CIC results depend on pulse detection, baseline selection, sampling rate, electrode area, and whether measured or estimated current is used. Related Topics [Electrochemistry family](../README.md) [DTA pulse detection](../../../libraries/dta/README.md) [VT Resistance](../vt-resistance/README.md) [API Reference](../../../reference/README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics."},{"title":"Charge-Storage Capacity","url":"apps/electrochemistry/csc.html","kind":"app","section":"apps","keywords":"apps electrochemistry csc Charge-Storage Capacity","text":"Charge-Storage Capacity The CSC app compares charge obtained from time-domain current integration with charge obtained from cyclic-voltammetry integration for every readable CV/CT cycle in one or more Gamry DTA files. Requirements And Launch Each analyzed cycle must expose exact `T`, `Vf`, and `Im` columns and a positive scan rate. labkit_CSC_app Inputs And Selection Add one or more CV/CT `.DTA` files. The selected file determines the current curve list, readout, and plots. Selecting another file resets the curve selection and default plot quantities to that file; it does not silently keep a cycle from the previous source. Saved projects preserve the successfully decoded file order through removal, later additions, and reopen. The default curve selection is **All cycles**. Individual cycle selection updates the comparison readout for that cycle. Basic Workflow Add CV/CT files and select the active source. Inspect all cycles for incomplete or anomalous edge cycles. Choose Full, Cathodic, or Anodic comparison mode. Enter electrode area when area-normalized CSC is required. Optionally ignore the first and last cycle. Configure the two plot panes and review CT/CV agreement. Export all-cycle results and, when needed, column-oriented CV data. Plot Behavior All-cycle plots use a distinct color for each cycle; cathodic and anodic current branches use related dark/light variants. Time is shifted so each cycle begins at zero. Selecting new X/Y quantities refits the axes. Toggling trim overlays preserves the user's current view because it changes an overlay, not the underlying coordinate selection. **Ignore first/last cycle** applies consistently to the all-cycle plot, result table, CSC export, and CV-data export. It is intended for known incomplete edge cycles and must not be used to hide arbitrary outliers. Calculation Semantics Time-domain charge integrates current against time. CV-domain charge integrates current against potential and divides by positive scan rate. Each line segment is split at a current zero crossing so cathodic and anodic contributions are assigned without smearing a sign change across one trapezoid. Q_CT,cath = integral |I(t)| dt over cathodic current Q_CT,anod = integral I(t) dt over anodic current Q_CV = integral I(V) dV / scanRate, split by current sign CSC = 1000 * Q_C / area_cm2 Full mode sums positive cathodic and anodic charge magnitudes. Relative difference is `100 * abs(Q_CT - Q_CV) / max(abs(Q_CT), abs(Q_CV))`, with zero reported when both charges are zero. `dtErr` reports the maximum discrepancy between measured time increments and `abs(dV)/scanRate` over valid segments. Without a valid positive area, charge comparison remains available but CSC fields in mC/cm^2 are `NaN`. Outputs **Export all cycles CSV** writes one row per included file cycle with source, cycle, mode, scan rate, area, CT and CV cathodic/anodic/full charges, selected charge and CSC values, absolute/relative difference, time-step consistency, and status fields. **Export CV data CSV** writes potential plus paired current and scan-rate columns for replotting and independent calculation. When all exported cycles share the same potential vector, they occupy one file. When vectors differ, the app writes one CSV per source item using the selected filename as a stem. Each export action has its own `.labkit.json` provenance manifest. Use Without The GUI [item, status] = labkit.dta.loadFile(\"cv.DTA\", \"cvct\"); assert(status.ok, status.message); curve = item.curves(1); options = struct(\"scanRate\", 0.05, ... \"area_cm2\", 0.05, \"mode\", \"Full\"); result = csc.analysisRun.computeCSC(curve, options); assert(result.ok, result.message); Function Reference The generated pages for [`computeCSC`](../../../reference/api/csc/analysisRun/computeCSC.html) and [`chargeDensity`](../../../reference/api/csc/analysisRun/chargeDensity.html) state the exact numeric inputs, units, sign conventions, outputs, invalid-area behavior, executable examples, and related APIs used outside the GUI. Errors And Limitations Scan rate must be finite and positive. Required column names are exact; a visually similar arbitrary table is not accepted as a CV/CT cycle. Area normalization is omitted rather than guessed when area is invalid. Agreement between CT and CV integration is a consistency measurement, not proof that the electrochemical experiment is otherwise valid. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md) csc.analysisRun.chargeDensity Convert charge in coulombs to density in mC/cm^2. csc.analysisRun.computeCSC Compare time-integrated and scan-rate-derived electrode charge."},{"title":"batch_crop.cropGeometry.cropImage","url":"reference/api/batch_crop/cropGeometry/cropImage.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.cropImage","text":"CROPIMAGE Rotate and crop an image at a fixed pixel size. CROPIMAGE Rotate and crop an image at a fixed pixel size. Usage: result = batch_crop.cropGeometry.cropImage(imageData, opts) Description: Adds optional padding, rotates the resulting canvas, and extracts a crop centered at a point expressed in the original image coordinate system. The crop always has the requested pixel dimensions. Areas outside the transformed canvas use the selected fill value. This function does not resize the crop, display graphics, or write files. Inputs: imageData - Nonempty numeric or logical image. Accepted sizes are M-by-N and M-by-N-by-C. The output preserves its class and channel count. opts - Scalar structure containing the crop options below. Options: cropWidth - Required positive pixel count. Noninteger values are rounded. cropHeight - Required positive pixel count. Noninteger values are rounded. angleDeg - Counterclockwise canvas rotation in degrees. Default: 0. centerXY - Crop center [x y] in original-image pixel coordinates, where x is the column and y is the row. Invalid or empty input selects the geometric image center. The center is limited so the requested crop remains representable on the prepared canvas. Default: []. paddingPercent - Padding added around each source edge as a percentage of the corresponding source dimension. Negative values are treated as zero by the canvas preparation code. Default: 0. fillValue - Scalar or channel-compatible value used outside the rotated image. Empty input selects the app's white value for imageData. Default: []. Outputs: result - Scalar crop result structure. result.image is exactly cropHeight-by-cropWidth-by-C. result.centerX and centerY retain the effective center in original-image coordinates. rotationDeg, paddingPercent, sourceWidth, sourceHeight, cropWidth, cropHeight, nativeCropWidth, nativeCropHeight, outputWidth, and outputHeight describe the operation. scaleMode is \"Pixels\", ok is true, status is \"cropped\", and message is \"OK\". Errors: labkit_BatchImageCrop_app:InvalidImage - imageData is empty, nonnumeric, nonlogical, or has more than three dimensions. labkit_BatchImageCrop_app:InvalidCropSize - cropWidth or cropHeight is absent, nonfinite, nonscalar, or smaller than one. Example: imageData = reshape(uint8(1:100), 10, 10); opts = struct(\"cropWidth\", 4, \"cropHeight\", 3, \"centerXY\", [5 6]); result = batch_crop.cropGeometry.cropImage(imageData, opts); assert(result.ok && isequal(size(result.image), [3 4])) See also batch_crop.cropGeometry.cropScaledImage, batch_crop.cropGeometry.scalePlan"},{"title":"batch_crop.cropGeometry.cropScaledImage","url":"reference/api/batch_crop/cropGeometry/cropScaledImage.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.cropScaledImage","text":"CROPSCALEDIMAGE Crop one calibrated image to a common physical output scale. CROPSCALEDIMAGE Crop one calibrated image to a common physical output scale. Usage: result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan) result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan, itemIndex) Description: Uses the selected item's native crop dimensions from scalePlan, delegates rotation and cropping to batch_crop.cropGeometry.cropImage, then resamples the crop to the plan's common output dimensions. Resampling uses bilinear interpolation independently for every channel and preserves image class. The function has no graphics or file side effects. Inputs: imageData - Nonempty numeric or logical M-by-N or M-by-N-by-C image. opts - Crop option structure accepted by cropImage. cropWidth and cropHeight are replaced by the native dimensions selected by plan. plan - Scalar structure returned by batch_crop.cropGeometry.scalePlan. itemIndex - Positive index selecting nativeCropWidth, nativeCropHeight, sourcePixelsPerUnit, resampleFactor, and warnings for imageData. Default: 1. Options: angleDeg - Counterclockwise canvas rotation in degrees. Default: 0. centerXY - Crop center [x y] in original-image pixel coordinates. Empty or invalid input selects the image center. Default: []. paddingPercent - Padding around the source before rotation, as a percentage of each source dimension. Default: 0. fillValue - Value used outside the rotated image. Empty selects the app's white value for imageData. Default: []. Outputs: result - cropImage result updated for physical mode. result.image has plan.outputHeight rows and plan.outputWidth columns. cropWidth and cropHeight describe that output; nativeCropWidth and nativeCropHeight describe the pre-resampling crop. scaleUnit, sourcePixelsPerUnit, targetPixelsPerUnit, resampleFactor, and scaleWarning come from plan. Failure Behavior: itemIndex must select an element from every per-image plan field, and plan output dimensions must be positive. Missing fields, out-of-range indices, invalid image/crop options, or unsupported interpolation classes propagate the originating cropGeometry or MATLAB error. Example: imageData = reshape(uint8(1:100), 10, 10); opts = struct(\"centerXY\", [5 5]); plan = struct(\"nativeCropWidth\", 4, \"nativeCropHeight\", 4, ... \"outputWidth\", 8, \"outputHeight\", 6, \"unit\", \"um\", ... \"sourcePixelsPerUnit\", 2, \"targetPixelsPerUnit\", 4, ... \"resampleFactor\", 2, \"warnings\", \"\"); result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan); assert(isequal(size(result.image), [6 8])) See also batch_crop.cropGeometry.cropImage, batch_crop.cropGeometry.scalePlan"},{"title":"batch_crop.cropGeometry.scalePlan","url":"reference/api/batch_crop/cropGeometry/scalePlan.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.scalePlan","text":"SCALEPLAN Plan equal-size physical crops across calibrated images. SCALEPLAN Plan equal-size physical crops across calibrated images. Usage: plan = batch_crop.cropGeometry.scalePlan(items, opts) Description: Converts every image calibration to a requested physical unit, determines a common output pixel density, and calculates native crop and common output dimensions. Automatic density is the median converted source density. Large scale differences produce warnings but do not reject the plan. Inputs: items - Nonempty structure array. Each element must have a valid scaleCalibration accepted by batch_crop.scaleCalibration.isSet, including a positive pixelsPerUnit value and its source unit. opts - Scalar structure containing the physical crop options below. Options: physicalWidth - Required positive crop width in scaleUnit. physicalHeight - Required positive crop height in scaleUnit. scaleUnit - Unit used for physical dimensions and pixel densities. Legal values are \"m\", \"cm\", \"mm\", \"um\", and \"nm\". Default: \"um\". targetPixelsPerUnit - Positive common output density in pixels per scaleUnit. Zero, negative, or nonfinite input selects the median source density automatically. Default: 0. maxUpsamplePercent - Largest accepted upsampling above native density before a warning is recorded. Negative input is treated as zero. Default: 15. Outputs: plan - Scalar structure with these fields: Plan Fields: mode - \"Physical\". unit - Effective scaleUnit. targetSource - \"Manual\" for a positive targetPixelsPerUnit, otherwise \"Auto\". physicalWidth, physicalHeight - Requested dimensions in unit. sourcePixelsPerUnit - Converted density for each input item. targetPixelsPerUnit - Common output density. resampleFactor - targetPixelsPerUnit ./ sourcePixelsPerUnit. nativeCropWidth, nativeCropHeight - Per-item native crop sizes in pixels, rounded to integers with a minimum of one. outputWidth, outputHeight - Common output size in pixels, rounded to integers with a minimum of one. warnings - Per-item text. Upsampling beyond maxUpsamplePercent reports \"upsample ...x\"; resample factors below 0.5 report \"downsample ...x\". Errors: labkit_BatchImageCrop_app:ScaleCalibrationMissing - Any item lacks a valid calibration convertible to scaleUnit. labkit_BatchImageCrop_app:InvalidPhysicalSize - physicalWidth or physicalHeight is missing, nonscalar, nonfinite, or nonpositive. labkit_BatchImageCrop_app:InvalidScaleUnit - scaleUnit is unsupported. Example: cal = labkit.app.interaction.scaleCalibration(20, 10, \"um\"); items = struct(\"scaleCalibration\", cal); opts = struct(\"physicalWidth\", 5, \"physicalHeight\", 3, ... \"scaleUnit\", \"um\", \"targetPixelsPerUnit\", 4); plan = batch_crop.cropGeometry.scalePlan(items, opts); assert(plan.outputWidth == 20 && plan.outputHeight == 12) See also batch_crop.cropGeometry.cropScaledImage, labkit.app.interaction.scaleCalibration"},{"title":"cic.analysisRun.computeCIC","url":"reference/api/cic/analysisRun/computeCIC.html","kind":"reference","section":"reference","keywords":"cic.analysisRun.computeCIC","text":"COMPUTECIC Calculate charge-injection and voltage-transient metrics. COMPUTECIC Calculate charge-injection and voltage-transient metrics. Usage: A = cic.analysisRun.computeCIC(item) A = cic.analysisRun.computeCIC(item, opts) Description: Analyzes one biphasic chronopotentiometry transient. The function detects the cathodic and anodic pulse windows, samples the polarization voltage after each phase, integrates injected charge, normalizes charge by electrode area, and checks the measured voltages against the selected cathodic and anodic limits. It performs no plotting or file export. Inputs: item - Scalar DTA item structure. item.curve, or the main curve selected from item.tables, must provide T in seconds, Vf in volts, and Im in amperes. Pt is optional; sample numbers are generated when it is absent. item.meta may contain pulse metadata and area_cm2. opts - Optional scalar structure. See Options. Options: delay_s - Time in seconds from the end of each pulse phase to the Emc or Ema sample. The voltage is linearly interpolated at cath_end+delay_s and anod_end+delay_s. Default: 10e-6 seconds. cathLimit - Cathodic water-window limit in volts. Emc passes when Emc >= cathLimit. Default: -0.6 V. anodLimit - Anodic water-window limit in volts. Ema passes when Ema <= anodLimit. Default: 0.8 V. areaOverride - Positive electrode area in square centimetres, supplied as a numeric scalar or numeric text. A valid value takes precedence over area_cm2 and item.meta.area_cm2. Default: \"\". area_cm2 - Positive numeric electrode area in square centimetres used when areaOverride is empty or invalid. If neither option supplies an area, item.meta.area_cm2 is used. Without a valid area, charge is still calculated but CIC density fields are NaN. Default: NaN. pulseMode - Pulse-detection policy. Allowed display values are \"Metadata first, then auto\", \"Metadata only\", and \"Auto from Im only\". The equivalent values \"metadata_first\", \"metadata_only\", and \"current_only\" are also accepted by labkit.dta.detectPulses. Default: \"Metadata first, then auto\". usedMeasuredCurrent - Logical scalar. When true, Qc and Qa are the absolute trapezoidal integrals of measured Im over the detected phase windows. When false, charge is the absolute nominal phase current multiplied by phase duration. Default: true. Calculations: Samples with NaN T, Vf, or Im are removed together. At least five remaining samples are required. Emc is Vf at cath_end+delay_s and Ema is Vf at anod_end+delay_s. The cathodic baseline Eipp uses the first finite value in this order: pre-pulse median, interpulse median, post-pulse median, zero. The anodic baseline Eipp_gap prefers the interpulse median, followed by the pre-pulse median, post-pulse median, and Eipp. Qc_C and Qa_C are stored as positive charge magnitudes in coulombs; Qt_C = Qc_C + Qa_C. For a valid area, CIC density is 1000*Q/area_cm2 in mC/cm^2. CICc_mCcm2, CICa_mCcm2, and CICt_mCcm2 refer to the cathodic, anodic, and summed biphasic charge respectively. Outputs: A - Scalar result structure. A.ok is true only when curve selection, pulse detection, delayed voltage sampling, and both charge integrations succeed. A.message is \"OK\" on success and explains the first failed stage otherwise. Output Fields: ok - Logical success flag. message - \"OK\" or a description of the first failed stage. delay_s - Effective delayed-voltage sampling interval in seconds. cathLimit - Effective cathodic voltage limit in volts. anodLimit - Effective anodic voltage limit in volts. area_cm2 - Selected positive electrode area in cm^2, or NaN when missing. usedMeasuredCurrent - Effective charge-integration policy. logOnFailure - True for failures that the app should copy into its visible diagnostic log; false for ordinary incomplete-input results. Emc - Delayed cathodic polarization voltage in volts. Ema - Delayed anodic polarization voltage in volts. t_emc - Requested Emc sample time in seconds. t_ema - Requested Ema sample time in seconds. emc_idx - Index of the recorded sample nearest t_emc. ema_idx - Index of the recorded sample nearest t_ema. Epre - Median pre-pulse voltage in volts. Ebetween - Median interpulse voltage in volts. Epost - Median post-pulse voltage in volts. Eipp - Baseline selected for the cathodic phase, in volts. Eipp_gap - Baseline selected for the anodic phase, in volts. baselineCathSource - Text naming the cathodic baseline source. baselineAnodSource - Text naming the anodic baseline source. baselineCathWindow - Two-element time window used for Eipp, in seconds. baselineAnodWindow - Two-element time window used for Eipp_gap, in seconds. Vc_on - Voltage sampled delay_s after cathodic phase onset, in volts. Va_on - Voltage sampled delay_s after anodic phase onset, in volts. t_conset - Requested cathodic onset sample time in seconds. t_aonset - Requested anodic onset sample time in seconds. Va_cath_mag - Absolute Eipp-to-Vc_on voltage change, in volts. Va_anod_mag - Absolute Eipp_gap-to-Va_on voltage change, in volts. tc_s - Cathodic phase duration in seconds. ta_s - Anodic phase duration in seconds. tip_s - Interpulse gap duration in seconds. Ic_est_A - Median measured cathodic current in amperes. Ia_est_A - Median measured anodic current in amperes. cathMask - Logical vector selecting cathodic phase samples. anodMask - Logical vector selecting anodic phase samples. Qc_C - Cathodic charge magnitude in coulombs. Qa_C - Anodic charge magnitude in coulombs. Qt_C - Qc_C + Qa_C in coulombs. CICc_mCcm2 - Cathodic charge density in mC/cm^2. CICa_mCcm2 - Anodic charge density in mC/cm^2. CICt_mCcm2 - Total biphasic charge density in mC/cm^2. cathOK - True when Emc is not below cathLimit. anodOK - True when Ema is not above anodLimit. safe - True when both cathOK and anodOK are true. limitSide - \"safe\", \"cathodic exceeded\", \"anodic exceeded\", or \"both exceeded\". pulse - Pulse geometry returned by labkit.dta.detectPulses. detectMode - Pulse-detection method that produced pulse. detectMsg - Pulse-detection status message. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. pt - Filtered source point numbers or generated zero-based sample numbers. sample_dt - Median difference between consecutive filtered times, in seconds. sample_dt_report contains the same value for exports. ampEstimate_A - Largest absolute filtered current in amperes. Failure Behavior: Missing curves, too few valid samples, failed pulse detection, an out-of-range delay, or a pulse window with fewer than two samples returns A.ok=false. These data failures normally return a message instead of throwing. Invalid MATLAB types or malformed structures can still raise an error from the DTA access or numeric functions. Typical Call: [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message) opts = struct( ... \"delay_s\", 10e-6, ... \"cathLimit\", -0.6, ... \"anodLimit\", 0.8, ... \"area_cm2\", 0.03, ... \"pulseMode\", \"Metadata first, then auto\", ... \"usedMeasuredCurrent\", true); A = cic.analysisRun.computeCIC(item, opts); assert(A.ok, A.message) fprintf(\"Cathodic CIC: %.4g mC/cm^2\\n\", A.CICc_mCcm2) See also labkit.dta.detectPulses, vt_resistance.analysisRun.computeResistance"},{"title":"csc.analysisRun.chargeDensity","url":"reference/api/csc/analysisRun/chargeDensity.html","kind":"reference","section":"reference","keywords":"csc.analysisRun.chargeDensity","text":"CHARGEDENSITY Convert charge in coulombs to density in mC/cm^2. CHARGEDENSITY Convert charge in coulombs to density in mC/cm^2. Usage: value = csc.analysisRun.chargeDensity(chargeC, areaCm2) Inputs: chargeC - Numeric scalar or array of charge values in coulombs. Signed values remain signed. areaCm2 - Positive finite scalar electrode area in square centimetres. Outputs: value - Charge density with the same size as chargeC, in mC/cm^2. When areaCm2 is nonfinite or nonpositive, value is scalar NaN. Description: The conversion is 1000*chargeC/areaCm2 because one coulomb equals 1000 millicoulombs. This unit helper performs no integration and does not take the absolute value of cathodic charge. Failure Behavior: A nonfinite or nonpositive scalar area returns scalar NaN. areaCm2 must be scalar and chargeC must support numeric division; incompatible MATLAB values or nonscalar area conditions raise the originating logical or arithmetic error. Example: density = csc.analysisRun.chargeDensity([0.002 -0.001], 0.5); assert(isequal(density, [4 -2])) See also csc.analysisRun.computeCSC"},{"title":"csc.analysisRun.computeCSC","url":"reference/api/csc/analysisRun/computeCSC.html","kind":"reference","section":"reference","keywords":"csc.analysisRun.computeCSC","text":"COMPUTECSC Compare time-integrated and scan-rate-derived electrode charge. COMPUTECSC Compare time-integrated and scan-rate-derived electrode charge. Usage: A = csc.analysisRun.computeCSC(curve) A = csc.analysisRun.computeCSC(curve, opts) Inputs: curve - Scalar DTA curve struct. curve.headers must contain the exact names T, Vf, and Im, and curve.data must hold the corresponding numeric columns. T is in seconds, Vf in volts, and Im in amperes. opts - Optional scalar struct described below. Default: struct(). Options: scanRate - Positive finite voltage scan rate in V/s. This value is required for the CV-derived charge. Default: NaN, which returns ok=false. mode - Charge branch reported in Qct and Qcv: \"Full\", \"Cathodic\", or \"Anodic\". Unrecognized values use \"Full\". Default: \"Full\". area_cm2 - Positive electrode area in cm^2 as a numeric scalar or numeric text. Invalid or missing values leave density fields NaN while charge calculations still succeed. Default: NaN. Outputs: A - Scalar result struct. On success, it contains every field described below. Validation failures return ok=false, a stable message and logMessage, plus the option fields established before the failure. Result Fields: ok - Logical success flag. message - \"OK\", \"scan rate missing\", \"Need T, Vf, Im\", or \"Not enough points\". logMessage - User-facing explanation for a failed comparison; blank on success. mode - Effective branch used for Qct and Qcv. scanRate - Effective scan rate in V/s. area_cm2 - Parsed positive electrode area, or NaN. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. IcathDisp - Im with zero and anodic samples replaced by NaN. IanodDisp - Im with zero and cathodic samples replaced by NaN. QctCath - Absolute cathodic integral of I dt in coulombs. QctAnod - Anodic integral of I dt in coulombs. QctFull - QctCath + QctAnod in coulombs. QcvCath - Absolute cathodic scan-rate-derived charge in coulombs. QcvAnod - Anodic scan-rate-derived charge in coulombs. QcvFull - QcvCath + QcvAnod in coulombs. Qct - QctCath, QctAnod, or QctFull selected by mode. Qcv - QcvCath, QcvAnod, or QcvFull selected by mode. diff_C - Signed difference Qct-Qcv in coulombs. rel_pct - 100*abs(diff_C)/max(abs(Qct),abs(Qcv)); zero when both selected charges are zero. dtErr - Largest absolute difference, in seconds, between each measured time interval and abs(dV)/scanRate after zero-crossing subdivision. Qct_mC_cm2 - Selected CT charge density in mC/cm^2, or NaN without area. Qcv_mC_cm2 - Selected CV charge density in mC/cm^2, or NaN without area. diff_mC_cm2 - Signed density difference in mC/cm^2, or NaN without area. Description: computeCSC evaluates one current-voltage cycle two ways. The CT result uses recorded time, while the CV result reconstructs interval duration from the commanded scan rate. Reporting both values makes timing inconsistencies and charge-density differences visible without requiring the CSC GUI. Calculations: Rows containing NaN in T, Vf, or Im are removed together. Each adjacent sample interval is split where linearly interpolated current crosses zero. CT charge integrates current over measured time. CV charge uses dt=abs(dV)/scanRate for the same current endpoints. Cathodic contributions are stored as positive magnitudes, so each full charge is the sum of two nonnegative phase charges. Failure Behavior: A missing scan rate, missing exact column name, or fewer than two remaining samples returns ok=false instead of throwing. Malformed MATLAB types or inconsistent curve dimensions may still raise an indexing or numeric error. Example: curve = struct(\"headers\", {{'T','Vf','Im'}}, ... \"data\", [0 0 -1; 1 1 1; 2 2 1]); A = csc.analysisRun.computeCSC(curve, ... struct(\"scanRate\", 2, \"mode\", \"Full\")); assert(A.ok && abs(A.QctFull - 1.5) < 1e-12) See also csc.analysisRun.chargeDensity"},{"title":"curvature.analysisRun.computeCurvatureFit","url":"reference/api/curvature/analysisRun/computeCurvatureFit.html","kind":"reference","section":"reference","keywords":"curvature.analysisRun.computeCurvatureFit","text":"COMPUTECURVATUREFIT Fit a circle and curvature to traced image points. COMPUTECURVATUREFIT Fit a circle and curvature to traced image points. Usage: fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix) fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix, calibration) fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix, ... calibration, doDensify, denseN, fitPathX, fitPathY) Description: Removes consecutive duplicate points, optionally resamples a traced path at equal arc-length intervals, obtains an algebraic Kasa circle estimate, and minimizes radial squared error with base-MATLAB fminsearch. The result includes pixel-space values and, when calibration is valid, physical radius, curvature, residuals, RMSE, and traced length. No GUI or file is required. Inputs: xPix - Numeric x-coordinate vector in image pixels (image columns). At least three unique consecutive point pairs must remain. yPix - Numeric y-coordinate vector in image pixels (image rows), with the same number of elements as xPix. calibration - Scale structure accepted by curvature.analysisRun.normalizeScaleCalibration. Important fields are referencePixels, referenceLength, unit, pixelsPerUnit, isCalibrated, and referenceLine. Empty or omitted input selects uncalibrated pixels. doDensify - Logical-like scalar controlling equal-arc-length resampling. Default: true. denseN - Number of resampled fitting points, rounded to an integer with a minimum of three. Default: 300. fitPathX - Optional displayed-path x-coordinate vector. fitPathY - Optional displayed-path y-coordinate vector. When fitPathX and fitPathY have equal length and at least three unique points, this path supplies curve length and, when doDensify is true, the circle fit. Otherwise the anchor vectors supply both calculations. Calculations: R_px is the fitted radius and kappa_per_px = 1/R_px. rmse_px is the root mean square radial residual of the points used by the optimizer. When calibration.isCalibrated is true, displayed lengths are divided by pixelsPerUnit and displayed curvature is the reciprocal physical radius. residuals_show always corresponds to the original unique anchor points. Outputs: fit - Scalar structure describing the successful fit. Fit Fields: ok, message - true and empty text after a successful calculation. xc_px, yc_px, R_px - Circle center and radius in pixels. kappa_per_px, rmse_px - Pixel curvature and radial RMSE. R_show, kappa_show, rmse_show - Values in unitLen/unitK when calibrated, otherwise the corresponding pixel values. unitLen, unitK - Length and curvature units, for example \"um\" and \"1/um\", or \"px\" and \"1/px\" without calibration. residuals_show - Anchor-point radial residual column vector in unitLen. xPix, yPix - Unique anchor coordinates used for reported residuals. xFit, yFit - Points passed to the circle optimizer. curveLength_px, curveLength_show, curveLengthUnit, curvePointCount - Polyline length result for the accepted fit path. referencePx, referenceLength, scaleUnit, px_per_unit, usePhysicalScale - Effective scale metadata. Errors: labkit_CurvatureMeasurement_app:NotEnoughPoints - Fewer than three unique anchor points remain. labkit_CurvatureMeasurement_app:InvalidFit - Optimization returns a nonfinite or nonpositive radius. Example: theta = (0:11).' * (2*pi/12); x = 30 + 10*cos(theta); y = 40 + 10*sin(theta); fit = curvature.analysisRun.computeCurvatureFit(x, y, [], false); assert(fit.ok && abs(fit.R_px - 10) < 1e-3) See also curvature.analysisRun.computeCurveLength, curvature.analysisRun.normalizeScaleCalibration"},{"title":"curvature.analysisRun.computeCurveLength","url":"reference/api/curvature/analysisRun/computeCurveLength.html","kind":"reference","section":"reference","keywords":"curvature.analysisRun.computeCurveLength","text":"COMPUTECURVELENGTH Measure the polyline length of traced image points. COMPUTECURVELENGTH Measure the polyline length of traced image points. Usage: lengthResult = curvature.analysisRun.computeCurveLength(xPix, yPix) lengthResult = curvature.analysisRun.computeCurveLength( ... xPix, yPix, calibration) Description: Removes consecutive points closer than the app's duplicate tolerance and sums the Euclidean distance between the remaining adjacent points. A valid calibration converts the pixel length to the requested physical unit. The function does not smooth, interpolate, close the path, plot, or write files. Inputs: xPix - Numeric x-coordinate vector in image pixels (image columns). At least two unique consecutive point pairs are required. yPix - Numeric y-coordinate vector in image pixels (image rows), with the same number of elements as xPix. calibration - Scale structure accepted by curvature.analysisRun.normalizeScaleCalibration. Empty or omitted input reports length in pixels. Outputs: lengthResult - Scalar structure with the following fields. Result Fields: ok, message - true and empty text after a successful calculation. length_px - Sum of adjacent segment lengths in pixels. length_show - length_px divided by pixelsPerUnit when calibrated; otherwise length_px. unitLen - Calibration unit when calibrated, otherwise \"px\". referencePx, referenceLength, scaleUnit, px_per_unit, usePhysicalScale - Effective scale metadata. pointCount - Number of points remaining after duplicate removal. Errors: labkit_CurvatureMeasurement_app:NotEnoughLengthPoints - Fewer than two unique points remain. Example: x = [0 3 3]; y = [0 0 4]; result = curvature.analysisRun.computeCurveLength(x, y); assert(result.length_px == 7 && result.unitLen == \"px\") See also curvature.analysisRun.computeCurvatureFit, curvature.analysisRun.normalizeScaleCalibration"},{"title":"dic_postprocess.analysisRun.makeStrainOverlay","url":"reference/api/dic_postprocess/analysisRun/makeStrainOverlay.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.makeStrainOverlay","text":"MAKESTRAINOVERLAY Blend a color-mapped strain field over a reference image. MAKESTRAINOVERLAY Blend a color-mapped strain field over a reference image. Usage: overlay = dic_postprocess.analysisRun.makeStrainOverlay( ... referenceImage, strainMap, mask, roiMask, opts) Inputs: referenceImage - Numeric grayscale or RGB image used as the background. strainMap - Two-dimensional numeric strain field on the DIC grid. mask - Logical display mask. It is resized to referenceImage by nearest neighbor before blending. roiMask - Optional logical mask on the strain grid. When nonempty, it determines valid strain samples together with finite strainMap values; otherwise a resized form of mask is used. opts - Scalar overlay option struct described below. Required Options: alpha - Strain-color blend fraction; 0 shows the reference and 1 shows the color map inside the overlay mask. colorRange - Two-element [minimum maximum] strain range mapped to the first and last colormap rows. Values outside the range are clipped. colormap - N-by-3 RGB colormap. oversample - Positive strain-grid enlargement factor, rounded to an integer before linear resize. Values below 1 use 1. sigmaSmooth - Gaussian smoothing sigma on the extended strain grid. Values at or below 0 disable smoothing. brightness - Additive reference-image brightness adjustment. contrast - Multiplicative contrast around normalized intensity 0.5. gamma - Exponent applied after brightness and contrast adjustment. saturation - Multiplier for the HSV saturation channel. rgbGain - Three-element red, green, and blue gain vector. Optional Options: edgeTrim - Nonnegative number of valid-mask edge pixels removed before interpolation. Default: 1 when the field is absent. Outputs: overlay - Double RGB image with the reference height and width. Reference pixels remain unchanged outside the intersection of mask and valid resized strain data. Description: The reference is normalized and enhanced first. Valid strain values are extended into their ROI, optionally smoothed, linearly resized, clipped to colorRange, and blended only where both masks permit. The function creates image data only and performs no plotting or file output. Failure Behavior: referenceImage, strainMap, masks, and every required option must have compatible numeric shapes. Missing fields, an invalid color range or colormap, and incompatible mask/image sizes propagate the originating App helper or MATLAB indexing/conversion error. Typical Call: opts = struct(\"alpha\", 0.55, \"colorRange\", [-0.02 0.02], ... \"colormap\", jet(256), \"oversample\", 2, \"sigmaSmooth\", 1, ... \"edgeTrim\", 1, \"brightness\", 0, \"contrast\", 1, ... \"gamma\", 1, \"saturation\", 1, \"rgbGain\", [1 1 1]); overlay = dic_postprocess.analysisRun.makeStrainOverlay( ... referenceImage, strainMap, displayMask, roiMask, opts); See also dic_postprocess.analysisRun.prepareOutputs, dic_postprocess.analysisRun.summarizeStrain"},{"title":"dic_postprocess.analysisRun.prepareOutputs","url":"reference/api/dic_postprocess/analysisRun/prepareOutputs.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.prepareOutputs","text":"PREPAREOUTPUTS Build DIC strain summaries and both display overlays. PREPAREOUTPUTS Build DIC strain summaries and both display overlays. Usage: [summaryTable, overlayExx, overlayEyy] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters) Inputs: inputs - Scalar source struct with referenceImage, maskImage, and strain. strain must contain exx, eyy, and roiMask arrays on a common DIC grid. parameters - Scalar display struct described below. Parameters: alpha - Overlay blend fraction. colorMin - Lower shared EXX/EYY strain color limit. colorMax - Upper shared EXX/EYY strain color limit. oversample - Strain-grid interpolation enlargement factor. smoothSigma - Gaussian smoothing sigma. edgeTrim - Valid strain edge pixels removed before interpolation. brightness - Additive normalized reference brightness adjustment. contrast - Reference contrast multiplier around 0.5. gamma - Reference-image intensity exponent. saturation - Reference HSV saturation multiplier. redGain - Reference red-channel multiplier. greenGain - Reference green-channel multiplier. blueGain - Reference blue-channel multiplier. Outputs: summaryTable - Five-row table of Mean, Std, Median, Min, and Max with EXX and EYY columns. overlayExx - Double RGB EXX overlay at referenceImage size. overlayEyy - Double RGB EYY overlay at referenceImage size. Description: prepareOutputs is the GUI-free composition used by the app. It converts the mask image to a logical display mask, uses a fixed 256-color jet map for both components, renders EXX and EYY with identical display parameters, and summarizes finite strain samples inside roiMask when supplied. Failure Behavior: Missing input/parameter fields, incompatible EXX/EYY/mask geometry, or invalid display values propagate the originating field-access, overlay, or summary error. The function does not catch partial-output failures. Typical Call: [summary, exxImage, eyyImage] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters); See also dic_postprocess.analysisRun.makeStrainOverlay, dic_postprocess.analysisRun.summarizeStrain"},{"title":"dic_postprocess.analysisRun.summarizeStrain","url":"reference/api/dic_postprocess/analysisRun/summarizeStrain.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.summarizeStrain","text":"SUMMARIZESTRAIN Summarize finite EXX and EYY values inside a mask. SUMMARIZESTRAIN Summarize finite EXX and EYY values inside a mask. Usage: T = dic_postprocess.analysisRun.summarizeStrain(strain, mask) Inputs: strain - Scalar struct with numeric exx and eyy arrays of the same size. mask - Logical array matching exx and eyy. true entries select samples for both components. Outputs: T - Five-row table. Metric contains \"Mean\", \"Std\", \"Median\", \"Min\", and \"Max\"; EXX and EYY contain the corresponding statistics. Description: Nonfinite values are removed independently from each selected component. Std uses MATLAB's default sample standard deviation. When a component has no finite selected values, all five values for that component are NaN. Failure Behavior: An empty finite selection returns NaN statistics as documented. Missing exx or eyy fields, incompatible mask geometry, or values unsupported by MATLAB numeric statistics propagate the originating field/indexing error. Example: strain = struct(\"exx\", [1 2; NaN 4], \"eyy\", [2 4; 6 Inf]); T = dic_postprocess.analysisRun.summarizeStrain( ... strain, true(2)); assert(T.EXX(T.Metric == \"Mean\") == 7/3) assert(T.EYY(T.Metric == \"Max\") == 6) See also dic_postprocess.analysisRun.prepareOutputs"},{"title":"dic_preprocess.analysisRun.alignMovingToReference","url":"reference/api/dic_preprocess/analysisRun/alignMovingToReference.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.alignMovingToReference","text":"ALIGNMOVINGTOREFERENCE Fit and apply a rigid transform from matched points. ALIGNMOVINGTOREFERENCE Fit and apply a rigid transform from matched points. Usage: [alignedImage, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... referenceImage, movingImage, fixedPoints, movingPoints) Inputs: referenceImage - Reference image whose height and width define the output canvas. movingImage - Numeric grayscale or multichannel image to transform. fixedPoints - N-by-2 [x y] coordinates in referenceImage. movingPoints - N-by-2 corresponding [x y] coordinates in movingImage. Use the same number and order of points as fixedPoints. At least two distinct pairs are needed to constrain a 2-D rigid transform. Outputs: alignedImage - movingImage resampled onto the reference canvas. It has the moving image class and channel count; pixels outside the source are 0. tformRigid - Three-by-three row-vector homogeneous transform, shown as transform in the usage syntax. A moving coordinate maps to reference coordinates as [x y 1]*tformRigid. Description: The function uses the least-squares orthogonal Procrustes solution for one rotation and translation; reflection is explicitly rejected. It does not estimate scale or shear. Resampling is delegated to applyRigidTransform. Failure Behavior: The caller is responsible for supplying corresponding finite N-by-2 point arrays with enough nondegenerate geometry to constrain the fit. The function does not validate pair count or fit quality; insufficient or degenerate data can therefore return an arbitrary transform. Shapes that cannot participate in the matrix operations, unsupported image classes, and SVD/interpolation failures propagate the originating MATLAB error. Example: reference = zeros(12, 12); moving = zeros(12, 12); moving(4:6, 3:5) = 1; movingPoints = [3 4; 5 4; 3 6]; fixedPoints = movingPoints + [2 1]; [aligned, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... reference, moving, fixedPoints, movingPoints); assert(isequal(size(aligned), size(reference))) assert(norm([movingPoints ones(3,1)]*transform - ... [fixedPoints ones(3,1)], \"fro\") < 1e-10) See also dic_preprocess.analysisRun.applyRigidTransform, dic_preprocess.analysisRun.autoAlignMovingToReference"},{"title":"dic_preprocess.analysisRun.applyRigidTransform","url":"reference/api/dic_preprocess/analysisRun/applyRigidTransform.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.applyRigidTransform","text":"APPLYRIGIDTRANSFORM Resample a moving image onto a reference-sized canvas. APPLYRIGIDTRANSFORM Resample a moving image onto a reference-sized canvas. Usage: alignedImage = dic_preprocess.analysisRun.applyRigidTransform( ... referenceImage, movingImage, transform) Inputs: referenceImage - Image whose height and width define the output canvas. Its pixel values and channel count are not used. movingImage - Numeric two-dimensional or multichannel source image. transform - Three-by-three row-vector homogeneous rigid transform. Source points map forward as [x y 1]*transform. The upper-left two-by-two block is rotation and row 3, columns 1:2 are [x y] translation. Outputs: alignedImage - Linearly interpolated moving image on the reference height and width, preserving movingImage class and channels. Samples outside movingImage are 0; integer outputs are rounded and clipped to range. Description: The function performs inverse mapping for every output pixel using base MATLAB interp2. It assumes the supplied two-by-two block is an orthogonal rotation; scale or shear matrices are not validated. Failure Behavior: transform is expected to be a finite 3-by-3 numeric matrix and images must be compatible with interp2 and output casting. The function does not validate finiteness, rigidity, or invertibility; nonfinite or nonrigid values can therefore produce an unusable image without a dedicated error. Invalid dimensions and unsupported classes propagate the originating indexing, interpolation, or cast error. Example: moving = uint8(reshape(1:25, 5, 5)); transform = [1 0 0; 0 1 0; 1 0 1]; aligned = dic_preprocess.analysisRun.applyRigidTransform( ... zeros(5), moving, transform); assert(isa(aligned, \"uint8\") && aligned(1,1) == 0) assert(aligned(1,2) == moving(1,1)) See also dic_preprocess.analysisRun.alignMovingToReference"},{"title":"dic_preprocess.analysisRun.autoAlignMovingToReference","url":"reference/api/dic_preprocess/analysisRun/autoAlignMovingToReference.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.autoAlignMovingToReference","text":"AUTOALIGNMOVINGTOREFERENCE Estimate and apply an integer translation. AUTOALIGNMOVINGTOREFERENCE Estimate and apply an integer translation. Usage: [alignedImage, transform, method] = ... dic_preprocess.analysisRun.autoAlignMovingToReference( ... referenceImage, movingImage) Inputs: referenceImage - Numeric grayscale or RGB reference image. Its first two dimensions define the output canvas and correlation size. movingImage - Numeric grayscale or RGB image to translate. Outputs: alignedImage - Original movingImage translated onto the reference canvas, with linear interpolation and zero fill. tformRigid - Three-by-three row-vector homogeneous translation transform, shown as transform in the usage syntax. method - Character vector identifying the fixed phase-correlation method. Description: Each image is converted to normalized grayscale independently. For shift estimation only, moving grayscale data is resized to the reference size by nearest-neighbor sampling. Phase correlation returns a whole-pixel circular shift. Rotation, scale, deformation, repeated texture, and large nonoverlap can produce a poor fit. Failure Behavior: The function does not assign a confidence score or reject an ambiguous phase-correlation peak; low-texture or repeated-pattern inputs can return a numerically valid but poor translation. Empty arrays, unsupported image classes, or invalid channel shapes propagate image conversion/interpolation errors. Example: reference = zeros(16); reference(5:8, 6:9) = 1; moving = circshift(reference, [2 -3]); [aligned, transform, method] = ... dic_preprocess.analysisRun.autoAlignMovingToReference( ... reference, moving); assert(isequal(size(aligned), size(reference))) assert(isequal(size(transform), [3 3]) && contains(method, \"phase-correlation\")) See also dic_preprocess.analysisRun.alignMovingToReference, dic_preprocess.analysisRun.applyRigidTransform"},{"title":"dic_preprocess.analysisRun.cropImage","url":"reference/api/dic_preprocess/analysisRun/cropImage.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.cropImage","text":"CROPIMAGE Crop an image using base-MATLAB indexing. CROPIMAGE Crop an image using base-MATLAB indexing. Usage: cropped = dic_preprocess.analysisRun.cropImage(imageData, rect) Inputs: imageData - Numeric two-dimensional or multichannel image. rect - Four finite values [x y width height] in image-pixel coordinates. Values are rounded to pixel indices and limited to image bounds. Outputs: cropped - Inclusive pixel crop preserving input class and channels. Description: Width and height follow MATLAB imcrop's inclusive-endpoint convention: [x y 10 10] selects 11-by-11 pixels when fully inside the image. A partially overlapping rectangle is clipped; a rectangle with no overlap errors. Errors: labkit_DICPreprocess_app:InvalidCropRectangle - rect does not contain four finite numeric values. labkit_DICPreprocess_app:EmptyCropRectangle - The rounded rectangle does not overlap imageData. Example: imageData = reshape(1:100, 10, 10); cropped = dic_preprocess.analysisRun.cropImage(imageData, [2 3 4 2]); assert(isequal(size(cropped), [3 5])) See also dic_preprocess.analysisRun.applyRigidTransform"},{"title":"dic_preprocess.analysisRun.makeFalseColorOverlay","url":"reference/api/dic_preprocess/analysisRun/makeFalseColorOverlay.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.makeFalseColorOverlay","text":"MAKEFALSECOLOROVERLAY Build a red/green registration preview. MAKEFALSECOLOROVERLAY Build a red/green registration preview. Usage: overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... referenceImage, alignedImage) Inputs: referenceImage - Numeric grayscale or RGB reference image. alignedImage - Numeric grayscale or RGB moving image. A different height or width is resized to the reference dimensions by nearest neighbor. Outputs: overlay - Double RGB image the size of referenceImage. Red contains the independently normalized moving image, green contains the independently normalized reference, and blue is zero. Description: Coincident structure appears yellow, moving-only structure red, and reference-only structure green. Independent min/max grayscale normalization emphasizes geometry rather than preserving absolute intensity calibration. Failure Behavior: A constant image retains its normalized intensity instead of being stretched. An all-NaN image remains NaN. Empty arrays, unsupported image classes, or invalid channel shapes propagate errors from LabKit image conversion or nearest-neighbor resizing. Example: reference = eye(5); moving = circshift(reference, [0 1]); overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... reference, moving); assert(isequal(size(overlay), [5 5 3])) assert(all(overlay(:,:,3) == 0, \"all\")) See also dic_preprocess.analysisRun.autoAlignMovingToReference"},{"title":"ecg_print.analysisRun.analyzeSignal","url":"reference/api/ecg_print/analysisRun/analyzeSignal.html","kind":"reference","section":"reference","keywords":"ecg_print.analysisRun.analyzeSignal","text":"ANALYZESIGNAL Rebuild ECG analysis products from one decoded signal cache. ANALYZESIGNAL Rebuild ECG analysis products from one decoded signal cache. Usage: cache = ecg_print.analysisRun.analyzeSignal(cache, parameters) Description: Runs the numerical pipeline used by ECG Print without opening the app. It filters the complete decoded channel, optionally crops the raw and filtered signals to a time interval, detects ECG peaks, extracts event-centered segments, builds a representative template, and measures each segment against that template. Filtering before cropping reduces FFT boundary artifacts at the requested interval edges. The input structure is returned with six rebuildable analysis fields replaced. Other cache fields are preserved, so callers can keep source and import metadata beside the derived results. The function creates no graphics and reads or writes no files. Inputs: cache - Scalar structure containing cache.signal. The signal must have corresponding time and values vectors, a positive sample rate fs in hertz, a displayName, and a metadata structure. These are the fields used by the biosignal functions in this pipeline. parameters - Scalar structure containing every field listed under Parameter Fields. Unlike the ECG Print controls, this direct API does not fill missing fields or sanitize invalid values. Parameter Fields: lowCut - Lower band-pass cutoff in hertz. Supply a finite nonnegative scalar below highCut and below 45% of the sample rate. highCut - Requested upper band-pass cutoff in hertz. For a conventional lowCut, the effective value is min(highCut, 0.45*fs). If lowCut is at or above that limit, the cap becomes lowCut+eps so the two cutoffs can remain ordered. Callers should avoid that case because it leaves no useful safety margin below Nyquist. roiStart - Start time in seconds on cache.signal.time. roiEnd - End time in seconds on cache.signal.time. When roiEnd is greater than roiStart, samples at both endpoints are retained and the cropped time vectors restart at zero. Otherwise the full signal is analyzed. peakMethod - ECG Print method label: \"QRS streaming\", \"Pan-Tompkins\", or \"Local peaks\". Any other value currently falls back to QRS streaming. peakDistance - Minimum accepted peak spacing in seconds. Use a positive finite scalar. segmentWindow - Positive half-width in seconds for the symmetric interval [-segmentWindow, segmentWindow] around every peak. Peaks too close to either signal boundary are omitted. Values below 0.5 seconds may not cover all default noise-measurement windows and can yield NaN metrics. templateTopN - Number of highest-correlation segments requested for the template. buildTemplate rounds the value and limits it to the available segment count, with a minimum of one for nonempty input. Fixed Analysis Settings: The detector uses automatic polarity and a 2.8 robust-standard-deviation threshold setting. Segment measurements use signal window [-0.06 0.06] seconds and noise windows [-0.30 -0.20] and [0.40 0.50] seconds. Call the individual labkit.biosignal functions when those settings must be changed. Outputs: cache - Updated analysis cache with the fields below. Cache Fields: signal - Original decoded signal, unchanged. workingSignal - Raw full signal or selected time crop. filteredSignal - Corresponding band-pass filtered signal. events - Peak anchors from labkit.biosignal.detectEcgPeaks. segments - Retained event-centered columns from segmentByEvents. template - Representative waveform and segment ranking from buildTemplate. measurements - Per-segment and summary signal-quality tables from measureSegments. Empty detections lead to empty downstream results instead of invented measurements. Errors: Missing cache or parameter fields raise normal MATLAB field-reference errors. Invalid signal shapes, time ranges, filter settings, or detector choices raise the documented errors from the corresponding labkit.biosignal function; this function does not catch them. Example: fs = 100; time = (0:1/fs:6)'; values = 0.02*sin(2*pi*1.5*time); values(101:100:501) = values(101:100:501) + 1; signal = struct('time', time, 'values', values, 'fs', fs, ... 'displayName', \"Synthetic ECG\", 'metadata', struct()); cache = struct('signal', signal); parameters = struct('lowCut', 0.5, 'highCut', 40, ... 'roiStart', 0, 'roiEnd', 0, 'peakMethod', \"Local peaks\", ... 'peakDistance', 0.5, 'segmentWindow', 0.7, 'templateTopN', 5); cache = ecg_print.analysisRun.analyzeSignal(cache, parameters); assert(isfield(cache, 'measurements')) See also labkit.biosignal.filterSignal, labkit.biosignal.cropSignal, labkit.biosignal.detectEcgPeaks, labkit.biosignal.segmentByEvents, labkit.biosignal.buildTemplate, labkit.biosignal.measureSegments"},{"title":"flir_thermal.analysisRun.extremeTemperatureReadings","url":"reference/api/flir_thermal/analysisRun/extremeTemperatureReadings.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.extremeTemperatureReadings","text":"EXTREMETEMPERATUREREADINGS Locate finite global thermal extrema. EXTREMETEMPERATUREREADINGS Locate finite global thermal extrema. Usage: [hotSpot, coldSpot] = ... flir_thermal.analysisRun.extremeTemperatureReadings(temperatureC) Description: Finds the highest and lowest finite values in a calibrated temperature image. NaN, Inf, and -Inf pixels are ignored. Coordinates are one-based image coordinates: x is the column and y is the row. If an extreme occurs more than once, MATLAB column-major order selects the first occurrence. Display palettes and color limits do not affect this calculation. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before calculation. Outputs: hotSpot - Scalar structure containing the finite maximum's x, y, and temperatureC fields. coldSpot - Scalar structure containing the finite minimum's x, y, and temperatureC fields. Empty, nonmatrix, or all-nonfinite input returns NaN in every field of both outputs. Failure Behavior: Empty, nonmatrix, or all-nonfinite input returns NaN readings. Values must otherwise be convertible to double; unsupported MATLAB types propagate the originating conversion error. Example: T = [NaN 24; 18 31]; [hot, cold] = flir_thermal.analysisRun.extremeTemperatureReadings(T); assert(hot.x == 2 && hot.y == 2 && hot.temperatureC == 31) assert(cold.x == 1 && cold.y == 2 && cold.temperatureC == 18) See also labkit.thermal.readFile, flir_thermal.analysisRun.roiTemperatureMeanReading"},{"title":"flir_thermal.analysisRun.pointTemperatureReading","url":"reference/api/flir_thermal/analysisRun/pointTemperatureReading.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.pointTemperatureReading","text":"POINTTEMPERATUREREADING Read the nearest calibrated thermal pixel. POINTTEMPERATUREREADING Read the nearest calibrated thermal pixel. Usage: reading = flir_thermal.analysisRun.pointTemperatureReading( ... temperatureC, pointXY) Description: Samples the nearest pixel in a calibrated temperature image. Coordinates are rounded to integers and limited to the image bounds, so a point outside the image samples the nearest edge pixel. The function does not change the image, draw a marker, or apply display color limits. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before sampling. pointXY - Numeric image coordinate [x y], where x is the column and y is the row. Additional values are ignored. Both selected values must be finite. Outputs: reading - Scalar structure with x, y, and temperatureC fields. x and y are the effective one-based integer pixel coordinates. An empty or nonmatrix image, or a coordinate without two finite values, returns NaN in all fields. A nonfinite sampled pixel remains nonfinite in the result. Failure Behavior: Invalid image geometry or fewer than two finite coordinates returns a NaN reading. Inputs must otherwise be convertible to double; unsupported MATLAB types propagate the originating conversion error. Example: T = [10 20 30; 40 50 60]; reading = flir_thermal.analysisRun.pointTemperatureReading(T, [2.2 1.7]); assert(reading.x == 2 && reading.y == 2 && reading.temperatureC == 50) See also flir_thermal.analysisRun.roiTemperatureMeanReading, flir_thermal.analysisRun.extremeTemperatureReadings"},{"title":"flir_thermal.analysisRun.roiTemperatureMeanReading","url":"reference/api/flir_thermal/analysisRun/roiTemperatureMeanReading.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.roiTemperatureMeanReading","text":"ROITEMPERATUREMEANREADING Measure an inclusive rectangular thermal ROI. ROITEMPERATUREMEANREADING Measure an inclusive rectangular thermal ROI. Usage: [hotSpot, coldSpot, meanReading] = ... flir_thermal.analysisRun.roiTemperatureMeanReading( ... temperatureC, startXY, endXY) Description: Interprets two points as opposite corners of an inclusive rectangular ROI. Bounds are rounded, ordered, and limited to the image. NaN, Inf, and -Inf pixels are excluded from the extrema and arithmetic mean. Coordinates are one-based: x is the column and y is the row. No graphics are created. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before calculation. startXY - Numeric [x y] image coordinate at one ROI corner. endXY - Numeric [x y] image coordinate at the opposite corner. Corner order is irrelevant. Additional values are ignored; the first two values of each corner must be finite. Outputs: hotSpot - Scalar structure with x, y, and temperatureC for the finite ROI maximum. coldSpot - Scalar structure with x, y, and temperatureC for the finite ROI minimum. Ties select the first value in column-major order. meanReading - Scalar structure with x and y at the clamped upper-left ROI corner, inclusive width and height, mean temperatureC, and pixelCount. pixelCount counts only finite pixels, whereas width and height describe the entire clamped rectangle. Failure Behavior: Empty or nonmatrix temperature data, invalid corners, or an ROI with no finite pixels returns NaN point fields. meanReading contains NaN geometry and temperature and a pixelCount of zero. Example: T = [10 NaN 30; 40 50 60; 70 80 90]; [hot, cold, avg] = ... flir_thermal.analysisRun.roiTemperatureMeanReading(T, [1 1], [2 2]); assert(hot.temperatureC == 50 && cold.temperatureC == 10) assert(avg.temperatureC == 100/3 && avg.pixelCount == 3) See also flir_thermal.analysisRun.pointTemperatureReading, flir_thermal.analysisRun.extremeTemperatureReadings"},{"title":"focus_stack.analysisRun.computeFocusStack","url":"reference/api/focus_stack/analysisRun/computeFocusStack.html","kind":"reference","section":"reference","keywords":"focus_stack.analysisRun.computeFocusStack","text":"COMPUTEFOCUSSTACK Fuse a stack of images with different focus planes. COMPUTEFOCUSSTACK Fuse a stack of images with different focus planes. Usage: result = focus_stack.analysisRun.computeFocusStack(images) result = focus_stack.analysisRun.computeFocusStack(images, opts) Description: Builds a Laplacian pyramid for each input image, estimates local detail energy at every pyramid level, and blends pixels using the resulting focus weights. Low-confidence regions use normalized soft weights instead of a hard source selection. The first image defines the output height and width; other images are resized to that geometry. Grayscale and RGB inputs may be mixed, in which case the working stack and output are RGB. This function fuses focus only. It does not align images, compensate for parallax, or correct moving subjects. Use the app's registration step before calling it when the camera or specimen moved between frames. Inputs: images - At least two numeric images supplied as a cell array or numeric stack. A four-dimensional array uses its fourth dimension as the image index. A three-dimensional numeric array is interpreted as a stack of two-dimensional grayscale images, not as one RGB image; use a cell array for multiple RGB images. Values are converted with labkit.image.im2double. opts - Optional scalar structure containing the fusion options below. Options: focusWindow - Local detail-energy window in pixels. The value must be a positive finite scalar; it is rounded up to an odd integer of at least three. Default: 31. smoothRadius - Radius in pixels used to smooth source weights at the finest pyramid level. It is rounded to a nonnegative integer and reduced at coarser levels. Default: 4. minConfidence - Threshold in [0,1] below which fusion uses relative detail scores rather than only the best source image. Default: 0.05. pyramidLevels - Requested number of Laplacian levels. It is rounded and limited to the range supported by the image geometry, from one through five. Default: 4. Outputs: result - Scalar structure containing the fused image and diagnostics. Result Fields: ok, message - true and empty text after successful fusion. fused - Grayscale or RGB double image in [0,1]. focusIndex - M-by-N uint16 map naming the source with the largest finest- level detail score at each pixel. Values run from 1 to inputCount. confidence - M-by-N double map in [0,1] measuring separation between the best and second-best finest-level detail scores. focusCoverage - One fraction per source image, calculated from focusIndex; the fractions sum to one. inputCount, imageHeight, imageWidth, channelCount - Working stack geometry. focusWindow, smoothRadius, minConfidence, pyramidLevels - Effective options. meanConfidence - Mean of the confidence map. method - \"Laplacian pyramid focus fusion\". resizedCount - Number of input images resized to the first image's geometry. Errors: labkit_FocusStack_app:NotEnoughImages - Fewer than two images are supplied. labkit_FocusStack_app:InvalidImages - The input container or one of its elements is not a supported numeric image. MATLAB validation errors - focusWindow or minConfidence is outside its documented numeric domain. Example: [x, y] = meshgrid(1:24, 1:20); first = double(mod(x, 4) < 2); second = double(mod(y, 4) < 2); result = focus_stack.analysisRun.computeFocusStack( ... {first, second}, struct(\"focusWindow\", 5, \"smoothRadius\", 1)); assert(result.ok && isequal(size(result.fused), [20 24])) assert(abs(sum(result.focusCoverage) - 1) < 1e-12) See also labkit.image.im2double, labkit.image.meanFilter2"},{"title":"gait_analysis.analysisRun.computeGait","url":"reference/api/gait_analysis/analysisRun/computeGait.html","kind":"reference","section":"reference","keywords":"gait_analysis.analysisRun.computeGait","text":"COMPUTEGAIT Calculate joint, segment, swing, and step-quality measurements. COMPUTEGAIT Calculate joint, segment, swing, and step-quality measurements. Usage: result = gait_analysis.analysisRun.computeGait(pose, opts) Description: Analyzes named two-dimensional pose coordinates without opening the app. The function maps five anatomical roles to source points, smooths their coordinates, calculates joint angles and segment lengths, detects repeated active swings, evaluates each swing against quality thresholds, and returns the same four tables used by app preview and CSV export. Inputs: pose - Scalar normalized pose structure, normally returned by gait_analysis.sourceFiles.readPoseFile. Required fields are coords, an F-by-P-by-2 numeric array of [x y] image coordinates, and pointNames, a P-element text vector. Optional frameIndex and time vectors must have F elements. unitName labels coordinates that are already physical. sourceFormat is copied into the summary table. opts - Scalar option structure. Start with gait_analysis.analysisRun.defaultOptions; missing fields use those defaults. Options: iliacPoint - Case-insensitive point name assigned to the iliac role. Default: \"iliac\". hipPoint - Point name assigned to the hip role. Default: \"hip\". kneePoint - Point name assigned to the knee role. Default: \"knee\". anklePoint - Point name assigned to the ankle role. Default: \"ankle\". footPoint - Point name used for event detection and step length. Default: \"foot\". frameRate - Frames per second used only when pose.time has no finite values. Current Video Marker projects supply this value from embedded video metadata. Zero leaves time_s as NaN. Default: 0. pixelsPerUnit - Positive pixel density for physical output. Distances and scaled coordinates are divided by this value. When invalid, pose.unitName is retained at scale 1 when present; otherwise output uses pixels. Default: 1. unitName - Label used with a valid pixelsPerUnit. Default: \"px\". originAtFirstFrameFirstPoint - When true, coordinateTable subtracts the first point's first-frame coordinate from every scaled point. Raw pixel columns are unchanged. Default: false. smoothWindow - Requested centered smoothing span in frames, rounded to an integer of at least one. The effective odd span is 2*floor((smoothWindow-1)/2)+1. Finite values are averaged separately for each point and axis. Default: 5. detectionProminence - Minimum foot-X peak prominence in source coordinate units. Default: 20, preserving the legacy treadmill workflow threshold. detectionMinHeightSigma - A lift-off peak must be at least mean(foot X)-value*std(foot X). Default: 2, preserving the legacy treadmill peak-height floor. minLiftOffIntervalSeconds - Minimum separation between lift-off peaks. It is converted to frames from source time/frame rate. Default: 0.2 seconds. minSwingFrames - Minimum accepted lift-off-to-landing span. It is also the event separation fallback when source timing is unavailable. Default: 3. maxSwingFrames - Maximum accepted inclusive lift-off-to-landing span. Default: 300. minStepLength - Minimum accepted two-dimensional foot endpoint displacement. Default: 1. maxHipTranslation - Maximum accepted hip endpoint displacement in output units. Default: 1000000, which normally leaves this check inactive. Event Detection: The detection trace is smoothed foot X. A lift-off candidate is a local maximum meeting detectionProminence; nearby candidates retain the higher peak. Each lift-off is independently paired with the following foot-X minimum before the next lift-off, or before the recording ends. Therefore a completed final swing is retained even without a later lift-off. These are kinematic treadmill events, not force-plate contact measurements. Measurements: Hip, knee, and ankle angles are the unsigned angles from 0 to 180 degrees between the two adjacent segment vectors. A zero-length or nonfinite segment produces NaN. Segment lengths are Euclidean distances. For each cycle, Step length and each point translation are Euclidean endpoint displacements from lift-off to landing. Range of motion is the finite maximum-minus-minimum joint angle within that swing. When a next lift-off exists, cycle time, stance time, cadence, and duty factor are also reported; the final complete swing may legitimately lack them. Outputs: result - Scalar structure containing status, normalized options, events, and four tables. Result Fields: ok, message - true and \"Analysis complete\" after successful calculation. options - Effective options after defaults, scalar cleanup, and rounding. events - Structure with paired liftOffFrames and landingFrames, peak prominence, the foot-X detectionSignal, and footRelativeX diagnostic. frameTable - One row per frame. It contains frame/time/step membership, landing and lift-off flags, coordinate unit, three joint angles, four segment lengths, and scaled _x/_y columns. coordinateTable - One row per frame with frame/time and origin metadata, raw __x_px/__y_px columns, and scaled, optionally shifted __x/__y columns. The unshifted origin is [0 0] in pixel coordinates rather than the center of pixel [1 1]. stepTable - One row per paired lift-off-to-landing swing. It contains validity and reason, event frames, swing/cycle/stance timing, cadence, duty factor, two-dimensional step length and point translations, three joint extrema/ranges of motion, and recording-wide joint extrema. invalid_reason is \"ok\", \"duration_out_of_range\", \"step_length_too_small\", or \"hip_translation_too_large\". summaryTable - Metric/value text table reporting source geometry, detected and valid step counts, mean valid-swing time and step length, and global finite joint-angle extrema. Errors: labkit_GaitAnalysis_app:NoPoseData - pose has no nonempty coords field. labkit_GaitAnalysis_app:MissingRolePoint - A configured role name is absent from pose.pointNames. Example: frame = (1:12).'; footX = [-2; -3; -1; 2; 4; -3; -1; 2; 4; -3; -1; 1]; pose = struct(\"coords\", NaN(12, 5, 2), ... \"pointNames\", [\"iliac\"; \"hip\"; \"knee\"; \"ankle\"; \"foot\"], ... \"frameIndex\", frame, \"time\", (frame-1)/30, ... \"unitName\", \"px\", \"sourceFormat\", \"synthetic\"); pose.coords(:, :, 1) = [-2*ones(12,1), zeros(12,1), ... ones(12,1), 2*ones(12,1), footX]; pose.coords(:, :, 2) = repmat([8 6 4 2 0], 12, 1); opts = gait_analysis.analysisRun.defaultOptions(); opts.smoothWindow = 1; opts.detectionProminence = 2; opts.minLiftOffIntervalSeconds = 0.1; result = gait_analysis.analysisRun.computeGait(pose, opts); assert(result.ok && height(result.stepTable) == 2) See also gait_analysis.sourceFiles.readPoseFile, gait_analysis.analysisRun.defaultOptions, video_marker.coordinateExport.buildTable"},{"title":"gait_analysis.sourceFiles.readPoseFile","url":"reference/api/gait_analysis/sourceFiles/readPoseFile.html","kind":"reference","section":"reference","keywords":"gait_analysis.sourceFiles.readPoseFile","text":"READPOSEFILE Read one current Video Marker MAT project for gait analysis. READPOSEFILE Read one current Video Marker MAT project for gait analysis. Usage: pose = gait_analysis.sourceFiles.readPoseFile(filepath) Inputs: filepath - Scalar path to a MAT file containing one current `labkitProject` envelope produced by Video Marker payload version 2. The reader loads only that named variable and never reopens the video. Outputs: pose - Normalized scalar structure. coords is F-by-P-by-2 [x y] image coordinates; pointNames and skeleton preserve the Video Marker order; frameIndex is 1:F; time is derived from embedded frameRate; annotation status/source, calibration, video metadata, and units are preserved. Errors: labkit_GaitAnalysis_app:PoseFileNotFound - filepath does not exist. labkit_GaitAnalysis_app:UnsupportedPoseFile - filepath is not MAT. labkit_GaitAnalysis_app:InvalidMarkerProject - The file is not a current Video Marker project or its coordinate/skeleton shapes disagree. labkit_GaitAnalysis_app:MissingVideoMetadata - Embedded timing metadata is absent, invalid, or has a nonpositive frame rate. Description: Video Marker MAT is the sole file-input contract for Gait Analysis. CSV, arbitrary workspace MAT variables, and legacy marker payloads are rejected because they do not jointly own timing, skeleton, calibration, and frame provenance. `computeGait` remains callable with an in-memory normalized pose for deterministic tests and GUI-free calculations. Typical Call: pose = gait_analysis.sourceFiles.readPoseFile(\"walk.video_marker.autosave.mat\"); assert(pose.ok && pose.frameRate > 0) See also gait_analysis.analysisRun.computeGait, video_marker.videoSource.metadataFromInfo"},{"title":"image_enhance.analysisRun.applyPipeline","url":"reference/api/image_enhance/analysisRun/applyPipeline.html","kind":"reference","section":"reference","keywords":"image_enhance.analysisRun.applyPipeline","text":"APPLYPIPELINE Replay an ordered non-destructive image enhancement history. APPLYPIPELINE Replay an ordered non-destructive image enhancement history. Usage: processed = image_enhance.analysisRun.applyPipeline(images, steps) processed = image_enhance.analysisRun.applyPipeline(images, steps, contexts) Description: Converts every input image to RGB double data, then applies each history step in order. The same step sequence is applied independently to every image. The input arrays and step records are not modified, so the function can be used outside the GUI for reproducible preview or export processing. Inputs: images - One numeric image or a cell array of numeric images. Grayscale and multichannel inputs are converted to M-by-N-by-3 RGB double images and limited to [0,1]. The returned cell order matches the input order. steps - Structure array created by image_enhance.analysisRun.makeStep. Steps are reshaped to a column and executed in array order. An empty array performs normalization only. contexts - Optional cell array with one entry per image. Context is used by \"White ROI calibration\" to find whiteRoi, either directly as context.whiteRoi or as context.item.whiteRoi. Other step kinds ignore it. Default: one empty context per image. Step Fields: kind - One label returned by image_enhance.imagePreview.presentationData.toolKinds: \"Brightness/contrast\", \"Local contrast\", \"Sharpen\", \"Hue/saturation\", \"White balance\", \"White ROI calibration\", or \"Subject-preserving enhance\". amount - Primary numeric control. It is brightness percentage, local- contrast strength, sharpening strength, hue rotation in degrees, white-balance strength, or protected-enhancement blend strength, depending on kind. secondary - Secondary numeric control. It is contrast percentage, radius in pixels, saturation percentage, warm/cool percentage, or target background white level in percent, depending on kind. referenceIndex - Preserved history metadata; applyPipeline does not use it. label - Human-readable history text; applyPipeline does not branch on it. Outputs: processed - Column cell array of M-by-N-by-3 double images in [0,1]. Each output preserves its corresponding source height and width. Errors: labkit_ImageEnhance_app:UnknownEnhancementStep - A step kind is not one of the supported labels. labkit_ImageEnhance_app:MissingWhiteRoi - \"White ROI calibration\" is used without a valid [x y width height] ROI in that image's context. MATLAB indexing errors - A nonempty contexts array has fewer entries than images. Example: imageData = repmat(linspace(0.1, 0.8, 20), 12, 1); steps = [ ... image_enhance.analysisRun.makeStep(\"Brightness/contrast\", 10, 20, 0); ... image_enhance.analysisRun.makeStep(\"Sharpen\", 30, 1, 0)]; processed = image_enhance.analysisRun.applyPipeline(imageData, steps); assert(iscell(processed) && isequal(size(processed{1}), [12 20 3])) See also image_enhance.analysisRun.applyStep, image_enhance.analysisRun.makeStep, labkit.image.adjustBrightnessContrast"},{"title":"image_match.analysisRun.applyMatch","url":"reference/api/image_match/analysisRun/applyMatch.html","kind":"reference","section":"reference","keywords":"image_match.analysisRun.applyMatch","text":"APPLYMATCH Transfer reference appearance statistics to one image. APPLYMATCH Transfer reference appearance statistics to one image. Usage: outputImage = image_match.analysisRun.applyMatch( ... inputImage, referenceImage, step) Description: Converts the source and reference to RGB double images, transfers the selected tone and color statistics from the reference, and blends the matched result with the source. Matching never registers, crops, or resizes either image; only appearance statistics are transferred. The source geometry therefore determines the output geometry. Inputs: inputImage - Numeric or logical grayscale, RGB, or multichannel source image. It is converted to M-by-N-by-3 double data in [0,1]. referenceImage - Numeric or logical reference image. Its dimensions may differ from inputImage because matching uses image-wide statistics. Empty input returns the normalized source unchanged. step - Scalar structure created by image_match.analysisRun.makeStep. Step Fields: matchMethod - One label returned by image_match.imagePreview.presentationData.matchMethods. Punctuation and spaces are ignored when matching labels. An unknown or empty value uses \"Balanced\". amount - Overall blend percentage. Zero returns the source; 100 returns the selected matched result. Values are limited to [0,100]. secondary - Tone-match percentage, limited to [0,100]. colorStrength - Color-match percentage, limited to [0,100]. Match Methods: Balanced - Applies robust white-balance gains, then Lab tone quantile and a/b covariance matching. White balance - Matches robust bright neutral RGB channel ratios only. Tone only - Quantile-matches Lab lightness; colorStrength is unused. Protected tone - Moves background lightness and color toward the reference while limiting changes in saturated subject regions. Lab style - Quantile-matches Lab lightness and covariance-matches the Lab a/b color channels. Histogram - Quantile-matches all three Lab channels independently. Outputs: outputImage - M-by-N-by-3 double image in [0,1], with the same height and width as inputImage. Failure Behavior: Empty referenceImage returns normalized inputImage unchanged. Missing step fields use the App's match defaults; unsupported image classes/channel shapes or malformed numeric step values propagate the originating LabKit image or MATLAB calculation error. Example: source = cat(3, 0.3*ones(8), 0.5*ones(8), 0.8*ones(8)); reference = cat(3, 0.8*ones(6), 0.5*ones(6), 0.3*ones(6)); step = image_match.analysisRun.makeStep(\"White balance\", 100, 100, 100); outputImage = image_match.analysisRun.applyMatch(source, reference, step); assert(isequal(size(outputImage), [8 8 3])) See also image_match.analysisRun.applyPipeline, image_match.analysisRun.makeStep, image_match.imagePreview.presentationData.matchMethods"},{"title":"image_match.analysisRun.applyPipeline","url":"reference/api/image_match/analysisRun/applyPipeline.html","kind":"reference","section":"reference","keywords":"image_match.analysisRun.applyPipeline","text":"APPLYPIPELINE Replay ordered reference-match steps over source images. APPLYPIPELINE Replay ordered reference-match steps over source images. Usage: processed = image_match.analysisRun.applyPipeline(images, steps) processed = image_match.analysisRun.applyPipeline( ... images, steps, referenceImage) Description: Normalizes each source image and applies the ordered reference-match history independently to every source. A step sees the output of the preceding step. The reference image is normalized once, remains separate from the source batch, and is never included in the returned cell array. Inputs: images - One numeric image or a cell array of source images. Each source is converted to RGB double data in [0,1]. Cell order is preserved. steps - Structure array created by image_match.analysisRun.makeStep. Steps are reshaped to a column and executed in array order. An empty array performs source normalization only. referenceImage - Optional numeric reference image. Its dimensions may differ from the sources. Empty or omitted input makes every match step a no-op after source normalization. Default: []. Step Fields: kind - History category; makeStep sets it to \"Reference match\". matchMethod - \"Balanced\", \"White balance\", \"Tone only\", \"Protected tone\", \"Lab style\", or \"Histogram\". amount - Overall match blend percentage. secondary - Tone-match percentage. colorStrength - Color-match percentage. label - Display text retained for history and export; it does not control the calculation. Outputs: processed - Column cell array containing only processed source images. Each entry is an M-by-N-by-3 double image in [0,1] and keeps its own source height and width. Failure Behavior: Empty images returns an empty cell column; empty steps or referenceImage performs normalization only. An invalid image container, unsupported image value, or malformed step propagates the originating normalization or applyMatch error and no partial output is returned. Example: sourceA = cat(3, 0.2*ones(6), 0.4*ones(6), 0.7*ones(6)); sourceB = 0.6*ones(4, 5); reference = cat(3, 0.7*ones(8), 0.5*ones(8), 0.3*ones(8)); step = image_match.analysisRun.makeStep(\"Balanced\", 75, 100, 80); processed = image_match.analysisRun.applyPipeline( ... {sourceA, sourceB}, step, reference); assert(numel(processed) == 2 && isequal(size(processed{2}), [4 5 3])) See also image_match.analysisRun.applyMatch, image_match.analysisRun.makeStep, image_match.imagePreview.presentationData.matchMethods"},{"title":"labkit.app.CallbackContext","url":"reference/api/labkit/app/CallbackContext.html","kind":"reference","section":"reference","keywords":"labkit.app.CallbackContext","text":"CALLBACKCONTEXT Provide declared App-neutral runtime capabilities. CALLBACKCONTEXT Provide declared App-neutral runtime capabilities. Usage: context.log(severity, eventName, message, Name=Value) context.alert(message, title) result = context.chooseOption(prompt, choices, Name=Value) result = context.chooseInputFile(filters, startPath) result = context.chooseInputFolder(startPath) result = context.chooseOutputFile(filters, startPath) result = context.chooseOutputFolder(startPath) result = context.saveProjectDocument(state, filepath) state = context.restoreProjectDocument(filepath) state = context.newProjectDocument() context.saveRecoveryDocument(state, filepath) paths = context.resolveSourcePaths(sources) paths = context.resolveSourcePaths(sources, ids) context.setResource(scope, id, value, cleanup) value = context.getResource(scope, id) context.removeResource(scope, id) context.clearResourceScope(scope) result = context.writeResultPackage(folder, result) Description: CallbackContext is the sealed callback capability boundary. Each specifically named method invokes one private runtime operation. Apps receive this value only as a callback argument and never construct it. It exposes no figure, registry, component, launch request, debug object, or nested service bag. Inputs: state - Complete App-owned state value. message - Scalar reader-facing text. severity - Log severity from \"trace\" through \"critical\". eventName - Stable semantic event identifier. Category - Semantic App capability category. Default: \"workflow\". Audience - \"user\" or \"developer\"; default: \"user\". Attributes - Scalar privacy-safe structured details. Default: struct(). Exception - Scalar MException associated with the event. Default: []. id - Stable semantic diagnostic or resource identifier. count - Nonnegative integer diagnostic count. title - Scalar reader-facing dialog title. prompt - Scalar reader-facing choice prompt. choices - Row string or cellstr array. Title - Reader-facing choice-dialog title. Default: \"Choose an option\". DefaultChoice - Choice selected by pressing Enter. Default: the first choice. CancelChoice - Choice returned when the dialog is dismissed. Default: the first choice. filters - Runtime-supported file-dialog filter value. startPath - Scalar starting file or folder path. filepath - Scalar project or recovery source or destination path. sources - Runtime-owned portable source collection. ids - Optional source identifiers to resolve. scope - \"event\", \"interaction\", \"document\", or \"application\". value - App-neutral resource value. cleanup - Empty or fixed callback cleanup(value). folder - Scalar result-package folder. result - labkit.app.result.Package value for writeResultPackage. Outputs: state - Complete restored App state prepared for the current runtime transaction. result - labkit.app.dialog.Choice for dialogs, project saves, and result writing. paths - Column string array of resolved source paths. value - Stored resource value. Errors: labkit:app:contract:InvalidValue - A public argument is malformed. labkit:app:runtime:InvariantFailure - A private backend operation is unavailable. Typical Call: function state = runAnalysis(state,event,callbackContext) arguments state (1,1) struct event callbackContext (1,1) labkit.app.CallbackContext end callbackContext.log(\"info\", \"analysis.started\", ... \"Analysis started.\"); end See also labkit.app.Definition, labkit.app.dialog.Choice, labkit.app.result.Package"},{"title":"labkit.app.Definition","url":"reference/api/labkit/app/Definition.html","kind":"reference","section":"reference","keywords":"labkit.app.Definition","text":"DEFINITION Compile and launch one immutable App SDK contract. DEFINITION Compile and launch one immutable App SDK contract. Usage: app = labkit.app.Definition(Entrypoint=entrypoint, AppId=appId, ... Title=title, ... Family=family, AppVersion=version, Updated=date, ... Requirements=requirements, Workbench=workbench, Name=Value) fig = app.launch() fig = app.launch(InitialProject=project) requirements = app.launch(\"requirements\") version = app.launch(\"version\") Description: Definition validates product metadata, layout-owned callbacks and renderers, global IDs, callback roles, and references in one atomic constructor. The static target graph is cached once. validateViewSnapshot checks a complete view snapshot against that graph without rebuilding the layout. Required Name-Value Arguments: Entrypoint - Public MATLAB launch function name as a scalar identifier. AppId - Stable App identifier beginning with an ASCII letter and containing letters, digits, underscore, hyphen, or period. Title - Nonempty reader-facing scalar text. Family - Nonempty reader-facing scalar text. AppVersion - Semantic version in X.Y.Z form. Updated - Product date in YYYY-MM-DD form. Requirements - Empty value or labkit.contract.requirements result. Workbench - Root value returned by labkit.app.layout.workbench. Optional Name-Value Arguments: DisplayName - Nonempty scalar text. Default: Title. ProjectSchema - labkit.app.project.Schema or empty for a static App. Default: empty. CreateSession - Fixed callback session = callback(project,context). Portable project sources remain opaque; use context.resolveSourcePaths while rebuilding transient session data. Default: empty. PresentWorkbench - Fixed callback view = callback(state). Default: empty. OnStart - Fixed callback state = callback(state,context), invoked after the first view commit. Default: empty. BuildSyntheticSample - Fixed callback pack = callback(context). Default: empty. Outputs: app - Immutable compiled labkit.app.Definition value. Definition Methods: launch() - Build and show the native MATLAB App figure. launch(\"requirements\") - Return declared facade requirements without creating a figure. launch(\"version\") - Return product version metadata without creating a figure. validateViewSnapshot(view) - Validate target references, target capabilities and complete target coverage. Returns true or throws before any runtime UI mutation. Errors: labkit:app:contract:UnknownArgument - A required argument is missing or an argument is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Metadata, requirements, workbench, callbacks, or renderers are malformed. labkit:app:contract:DuplicateId - A layout ID is duplicated. labkit:app:contract:UnknownReference - A view target is undeclared. labkit:app:contract:UnsupportedOperation - A view operation is not legal for its target. labkit:app:contract:InvalidValue - A launch request or output count is unsupported. Typical Call: workbench = labkit.app.layout.workbench({ ... labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the current analysis.\")}); app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", AppId=\"example.app\", ... Title=\"Example\", Family=\"Examples\", AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", Requirements=[], Workbench=workbench); See also labkit.app.layout.workbench, labkit.app.view.Snapshot, labkit.contract.requirements"},{"title":"labkit.app.dialog.Choice","url":"reference/api/labkit/app/dialog/Choice.html","kind":"reference","section":"reference","keywords":"labkit.app.dialog.Choice","text":"DIALOGRESULT Represent a typed dialog choice or cancellation. DIALOGRESULT Represent a typed dialog choice or cancellation. Usage: result = labkit.app.dialog.Choice(value) result = labkit.app.dialog.Choice(value, Cancelled=cancelled) Description: Choice separates cancellation from the chosen value so an empty string, zero, false, or empty App value is not interpreted as cancel. Inputs: value - App-facing value returned by the dialog. Name-Value Arguments: Cancelled - Logical scalar. Default: false. Outputs: result - Immutable labkit.app.dialog.Choice value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Cancelled is not logical scalar. Example: result = labkit.app.dialog.Choice(\"\", Cancelled=false); assert(~result.Cancelled) See also labkit.app.CallbackContext"},{"title":"labkit.app.event.IntervalScroll","url":"reference/api/labkit/app/event/IntervalScroll.html","kind":"reference","section":"reference","keywords":"labkit.app.event.IntervalScroll","text":"INTERVALSCROLL Describe one normalized interval scroll gesture. INTERVALSCROLL Describe one normalized interval scroll gesture. Usage: event = labkit.app.event.IntervalScroll(Anchor=anchor, Count=count) Description: IntervalScroll replaces native MATLAB scroll events and untyped structs with the horizontal data coordinate under the pointer and the signed vertical scroll count used by interval interactions. Required Name-Value Arguments: Anchor - Finite scalar horizontal data coordinate. Count - Finite nonzero scalar vertical scroll count. Outputs: event - Immutable labkit.app.event.IntervalScroll value. Errors: labkit:app:contract:UnknownArgument - An argument is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Anchor or Count is malformed. Example: event = labkit.app.event.IntervalScroll(Anchor=0.25, Count=-1); assert(event.Anchor == 0.25) See also labkit.app.interaction.interval"},{"title":"labkit.app.event.ListSelection","url":"reference/api/labkit/app/event/ListSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.event.ListSelection","text":"LISTSELECTION Describe selected file or list item identities. LISTSELECTION Describe selected file or list item identities. Usage: selection = labkit.app.event.ListSelection(Name=Value) Description: ListSelection carries stable item IDs, positive display indices, or both for file panels and list controls. When both are supplied they have equal lengths and matching order. Optional Name-Value Arguments: Ids - Unique row string or cellstr array. Default: empty. Indices - Unique positive integer numeric row. Default: empty. Outputs: selection - Immutable labkit.app.event.ListSelection value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - IDs or indices are malformed or have inconsistent lengths. Example: selection = labkit.app.event.ListSelection( ... Ids=[\"sample-a\", \"sample-b\"], Indices=[1 3]); assert(isequal(selection.Indices, [1 3])) See also labkit.app.event.TableCellSelection, labkit.app.layout.fileList"},{"title":"labkit.app.event.TableCellEdit","url":"reference/api/labkit/app/event/TableCellEdit.html","kind":"reference","section":"reference","keywords":"labkit.app.event.TableCellEdit","text":"TABLEEDIT Describe one validated table-cell edit signal. TABLEEDIT Describe one validated table-cell edit signal. Usage: edit = labkit.app.event.TableCellEdit(Name=Value) Description: TableEdit replaces raw MATLAB CellEditData and event metadata with stable row/column identity, the previous and proposed values, and the complete proposed table data when the callback must interpret pasted or related rows atomically. Required Name-Value Arguments: RowIndex - Positive integer row index. ColumnIndex - Positive integer column index. PreviousValue - App-owned value before the edit. NewValue - App-owned proposed value. Optional Name-Value Arguments: RowId - Empty or nonempty scalar text stable across sorting. Default: empty. ColumnId - Empty or nonempty scalar text stable across display-name changes. Default: empty. Data - Complete proposed table data after the edit. Default: empty. Outputs: edit - Immutable labkit.app.event.TableCellEdit value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - An index or ID is malformed. Example: edit = labkit.app.event.TableCellEdit(RowIndex=2, ColumnIndex=3, ... ColumnId=\"group\", PreviousValue=\"A\", NewValue=\"B\"); assert(edit.ColumnId == \"group\") See also labkit.app.layout.dataTable, labkit.app.event.ListSelection"},{"title":"labkit.app.event.TableCellSelection","url":"reference/api/labkit/app/event/TableCellSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.event.TableCellSelection","text":"TABLECELLSELECTION Describe selected cells in a semantic data table. TABLECELLSELECTION Describe selected cells in a semantic data table. Usage: selection = labkit.app.event.TableCellSelection(cellIndices) Description: TableCellSelection carries unique N-by-2 row/column index pairs. It replaces native MATLAB table-selection event shapes at the App callback boundary. Inputs: cellIndices - Unique positive integer N-by-2 matrix. An empty selection is zeros(0,2). Outputs: selection - Immutable TableCellSelection value. Errors: labkit:app:contract:InvalidValue - cellIndices is not a unique positive integer N-by-2 matrix. Example: selection = labkit.app.event.TableCellSelection([1 2; 3 1]); assert(isequal(selection.CellIndices, [1 2; 3 1])) See also labkit.app.event.TableCellEdit, labkit.app.layout.dataTable"},{"title":"labkit.app.interaction.anchorPath","url":"reference/api/labkit/app/interaction/anchorPath.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.anchorPath","text":"ANCHORPATH Declare an editable open or closed path on one plot axis. ANCHORPATH Declare an editable open or closed path on one plot axis. Usage: spec = labkit.app.interaction.anchorPath(id, onChanged, Name=Value) Description: Creates the semantic declaration for a managed multi-anchor path editor; the runtime owns native graphics, viewport preservation, and dispatch. Inputs: id - Unique MATLAB identifier for this interaction. onChanged - Callback state = callback(state,points,context). Options: Axis - Axis ID within the owning plotArea. Default: \"main\". Style - Scalar struct of anchor editor visual options. Default: struct(). Instruction - Scalar user guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration accepted by layout.plotArea. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: spec = labkit.app.interaction.anchorPath( ... \"curve\", @changeCurve, Style=struct(\"closed\", false)); See also labkit.app.layout.plotArea, labkit.app.view.Snapshot"},{"title":"labkit.app.interaction.interpolateAnchorPath","url":"reference/api/labkit/app/interaction/interpolateAnchorPath.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.interpolateAnchorPath","text":"INTERPOLATEANCHORPATH Build a visible path through image anchor points. INTERPOLATEANCHORPATH Build a visible path through image anchor points. Usage: curve = labkit.app.interaction.interpolateAnchorPath(points, imageSize) curve = labkit.app.interaction.interpolateAnchorPath(points, imageSize, Name=Value) [curve, owners] = labkit.app.interaction.interpolateAnchorPath(...) Inputs: points - N-by-2 numeric matrix of [x y] anchor coordinates in image-pixel coordinates. imageSize - Numeric vector whose first two elements are the positive finite image height and width. options - Name-value argument container for Style and Closed. Supply these values by name; do not pass an options struct. Name-Value Arguments: Style - \"Curve\" for a Catmull-Rom path or \"Straight lines\" for line segments joining the anchors. Default: \"Curve\". Closed - Logical scalar. true joins the last anchor to the first and requires at least three anchors. false requires at least two. Default: false. Outputs: curve - M-by-2 path samples. Coordinates are limited to pixel-edge bounds [0.5, width+0.5] and [0.5, height+0.5]. The result is empty until the selected open or closed path has enough anchors. owners - (M-1)-by-1 anchor-segment indices used to associate each visible curve segment with its starting anchor. It is empty with curve. Description: anchorPath contains the deterministic geometry shared by managed anchor editors and app previews. Curved paths pass through the supplied anchors; two-point open curves reduce to a straight segment. The function creates no graphics and can be used independently of a LabKit app. Errors: MATLAB argument-validation or assertion errors are raised when points is not N-by-2, imageSize lacks positive finite height and width, Style is unsupported, or a named argument has an incompatible type or shape. Example: points = [10 30; 30 10; 50 30]; curve = labkit.app.interaction.interpolateAnchorPath( ... points, [40 60], \"Style\", \"Straight lines\"); assert(isequal(curve, points)) See also labkit.app.interaction.anchorPath"},{"title":"labkit.app.interaction.interval","url":"reference/api/labkit/app/interaction/interval.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.interval","text":"INTERVAL Declare an editable one-dimensional plot interval. INTERVAL Declare an editable one-dimensional plot interval. Usage: spec = labkit.app.interaction.interval(id,onChanged,Name=Value) Description: Creates the semantic declaration for a managed interval editor and its optional typed scroll callback on one plot axis. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,range,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnScrolled - Optional callback state = callback(state,event,context), where event is labkit.app.event.IntervalScroll. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.interval(\"window\",@changeWindow); See also labkit.app.layout.plotArea, labkit.app.event.IntervalScroll"},{"title":"labkit.app.interaction.pairedAnchors","url":"reference/api/labkit/app/interaction/pairedAnchors.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.pairedAnchors","text":"PAIREDANCHORS Declare matching editable points across plot axes. PAIREDANCHORS Declare matching editable points across plot axes. Usage: spec = labkit.app.interaction.pairedAnchors(id,onChanged,Name=Value) Description: Creates the semantic declaration for corresponding editable points across two or more axes while the runtime owns their native editors. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,pointSets,context). Options: Axes - Two or more axis IDs within the owning plotArea. Required. Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.pairedAnchors(\"matches\",@changeMatches); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.pointSlots","url":"reference/api/labkit/app/interaction/pointSlots.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.pointSlots","text":"POINTSLOTS Declare a fixed set of editable labeled point positions. POINTSLOTS Declare a fixed set of editable labeled point positions. Usage: spec = labkit.app.interaction.pointSlots(id,onChanged,Name=Value) Description: Creates the semantic declaration for a fixed, labeled set of editable points whose structured value is committed by the runtime. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,value,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.pointSlots(\"markers\",@changeMarkers); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.rectangle","url":"reference/api/labkit/app/interaction/rectangle.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.rectangle","text":"RECTANGLE Declare an editable rectangular plot region. RECTANGLE Declare an editable rectangular plot region. Usage: spec = labkit.app.interaction.rectangle(id,onChanged,Name=Value) Description: Creates the semantic declaration for a persistent editable rectangle, including an optional background-point callback on the same axis. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,position,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnBackgroundPressed - Optional point callback state = callback(state,point,context). It receives clicks on blank plot space and clicks on the rectangle that do not move it. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.rectangle(\"crop\",@moveCrop); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.regionSelection","url":"reference/api/labkit/app/interaction/regionSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.regionSelection","text":"REGIONSELECTION Declare a transient click-or-drag region gesture. REGIONSELECTION Declare a transient click-or-drag region gesture. Usage: spec = labkit.app.interaction.regionSelection(id,onSelected,Name=Value) Description: Creates the semantic declaration for a transient click-or-drag selection whose result is delivered without exposing native interaction objects. Inputs: id - Unique MATLAB identifier. onSelected - Callback state = callback(state,position,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnBackgroundPressed - Optional point callback. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.regionSelection( ... \"temperatureRegion\",@measureRegion, ... OnBackgroundPressed=@measurePoint); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.scaleBarGeometry","url":"reference/api/labkit/app/interaction/scaleBarGeometry.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleBarGeometry","text":"SCALEBARGEOMETRY Compute serializable image scale-bar overlay geometry. SCALEBARGEOMETRY Compute serializable image scale-bar overlay geometry. Usage: geometry = labkit.app.interaction.scaleBarGeometry(imageSize, ... calibration, barLength, position, colorName) Inputs: imageSize - Numeric vector whose first two elements are positive finite image height and width in pixels. calibration - Struct with a positive finite pixelsPerUnit field and a unit field, normally returned by scaleBarCalibration. barLength - Positive finite physical length expressed in calibration.unit. position - Text containing \"top\" or \"bottom\" and optionally \"left\", \"center\", or \"right\". Unrecognized vertical text uses bottom; unrecognized horizontal text uses center. colorName - \"White\" selects [1 1 1]. Every other value selects black. Outputs: geometry - Scalar data struct with the fields described below. Geometry Fields: line - Two-by-two [x y] endpoints in image-pixel coordinates. label - Display text containing barLength and calibration.unit. color - RGB triplet selected by colorName. labelPosition - One-by-two centered label position in image pixels. verticalAlignment - \"top\" for a top bar or \"bottom\" for a bottom bar. pixelsPerUnit - Calibration value copied into the geometry. unit - Unit label copied into the geometry as a string scalar. barLength - Requested physical length. position - Supplied position text as a string scalar. colorName - Supplied color text as a string scalar. Description: The helper places the line with an eight-percent image-edge margin and a five-pixel minimum inset. It errors when the requested bar cannot fit in the available horizontal span. Apps and renderers own drawing; this function creates no graphics and is suitable for saved project state. Errors: labkit:app:interaction:InvalidImageSize - imageSize lacks positive finite height and width. labkit:app:interaction:InvalidScaleBar - calibration or barLength is not a positive finite scalar. labkit:app:interaction:ScaleBarTooLong - The requested bar does not fit inside the horizontal margins. Example: cal = labkit.app.interaction.scaleCalibration(80, 20, \"mm\"); geometry = labkit.app.interaction.scaleBarGeometry( ... [600 800], cal, 10, \"Bottom right\", \"White\"); assert(abs(diff(geometry.line(:,1))) == 40) See also labkit.app.interaction.scaleCalibration"},{"title":"labkit.app.interaction.scaleCalibration","url":"reference/api/labkit/app/interaction/scaleCalibration.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleCalibration","text":"SCALECALIBRATION Convert a known image distance into pixels per unit. SCALECALIBRATION Convert a known image distance into pixels per unit. Usage: cal = labkit.app.interaction.scaleCalibration(referencePixels, ... referenceLength, unitName) cal = labkit.app.interaction.scaleCalibration(..., opts) Inputs: referencePixels - Measured reference distance in image pixels. Empty, nonnumeric, nonfinite, or nonpositive values are treated as missing. referenceLength - Physical reference distance expressed in unitName. Missing, nonfinite, or negative values become 0. unitName - Unit label. With default options, legal values are \"m\", \"cm\", \"mm\", \"um\", and \"nm\". An unsupported value uses defaultUnit. opts - Optional scalar struct described below. Default: struct(). Options: units - Allowed unit labels. Default: {'m','cm','mm','um','nm'}. defaultUnit - Fallback unit. Default: the first entry in units. referenceLine - N-by-2 numeric reference points stored with the result. When it contains exactly two rows and referencePixels is missing, their Euclidean distance supplies referencePixels. Default: zeros(0,2). Outputs: cal - Scalar struct with the fields described below. Calibration Fields: referencePixels - Positive measured pixel distance, or NaN when missing. referenceLength - Nonnegative physical reference distance. unit - Normalized unit label as a character vector. pixelsPerUnit - referencePixels/referenceLength, or 0 when calibration is incomplete. isCalibrated - true when pixelsPerUnit is positive. referenceLine - Normalized N-by-2 numeric reference coordinates. Description: This function builds a serializable calibration value; it does not read an image or draw a scale bar. Repeating the call with identical inputs returns identical numeric fields. Divide a pixel distance by pixelsPerUnit to obtain a distance in cal.unit. Failure Behavior: Missing or invalid measurement values are normalized into an uncalibrated result instead of throwing. opts must be a scalar structure whose units can be converted to text and whose referenceLine can be converted to an N-by-2 numeric array; incompatible MATLAB values propagate conversion errors. Example: cal = labkit.app.interaction.scaleCalibration(80, 20, \"mm\"); physicalLength = 40 / cal.pixelsPerUnit; assert(cal.isCalibrated && physicalLength == 10) See also labkit.app.interaction.scaleBarGeometry"},{"title":"labkit.app.interaction.scaleReference","url":"reference/api/labkit/app/interaction/scaleReference.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleReference","text":"SCALEREFERENCE Declare an editable two-point scale reference. SCALEREFERENCE Declare an editable two-point scale reference. Usage: spec = labkit.app.interaction.scaleReference(id,onChanged,Name=Value) Description: Creates the semantic declaration for an editable two-endpoint physical scale reference while the runtime owns its native editor lifecycle. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,endpoints,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.scaleReference(\"scale\",@changeScale); See also labkit.app.layout.plotArea"},{"title":"labkit.app.layout.button","url":"reference/api/labkit/app/layout/button.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.button","text":"BUTTON Add a push button with one explicit pressed callback. BUTTON Add a push button with one explicit pressed callback. Usage: node = labkit.app.layout.button(id, label, onPressed, Name=Value) Description: Declares a semantic push button without creating a native component. Inputs: id - Unique MATLAB identifier for the layout target. label - Nonempty text displayed on the button. onPressed - Scalar function handle with the fixed callback state = onPressed(state,context). Options: BusyMessage - Status text while the action runs. Default: \"\". Enabled - Initial logical enabled state. Default: true. Tooltip - Nonempty hover text explaining the action's scientific or workflow effect. Default: label. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the current analysis from the selected inputs.\"); See also labkit.app.layout.workbench, labkit.app.CallbackContext"},{"title":"labkit.app.layout.dataTable","url":"reference/api/labkit/app/layout/dataTable.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.dataTable","text":"DATATABLE Add a tabular data display with optional editing and selection. DATATABLE Add a tabular data display with optional editing and selection. Usage: node = labkit.app.layout.dataTable(id, Name=Value) Description: Declares a semantic data table and typed cell callbacks. Inputs: id - Unique MATLAB identifier for the layout target. Options: Title - Reader-facing panel title or blank. A single-table section supplies its title when this is blank. Default: blank. Columns - Column-label text row. Default: strings(1,0). RowNames - Row-label text row. Default: strings(1,0). ColumnEditable - Logical scalar or row matching Columns. Default: false. OnCellEdited - Scalar callback state = callback(state,edit,context), where edit is a labkit.app.event.TableCellEdit. Default: []. OnCellSelectionChanged - Scalar callback state = callback(state,selection,context), where selection is a labkit.app.event.TableCellSelection. Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid options or callback signatures. Typical Call: node = labkit.app.layout.dataTable(\"results\", Columns=[\"Name\" \"Value\"]); See also labkit.app.event.TableCellEdit, labkit.app.event.TableCellSelection"},{"title":"labkit.app.layout.field","url":"reference/api/labkit/app/layout/field.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.field","text":"FIELD Add a text, numeric, choice, or logical input field. FIELD Add a text, numeric, choice, or logical input field. Usage: node = labkit.app.layout.field(id, Name=Value) Description: Declares one bound or event-driven scalar input field. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Kind - \"text\", \"numeric\", \"choice\", \"logical\", or \"readonly\". Readonly fields display one labeled result value. Default: \"text\". Value - Initial value. Default: []. Choices - Text row for choice fields. Default: strings(1,0). Limits - Increasing finite numeric 1-by-2 row. Default: []. Step - Positive numeric scalar. Default: []. Bind - Project or session field path. Default: \"\". ValueDisplayFormat - MATLAB numeric display format. Default: \"\". ShowTicks - Logical slider-tick preference. Default: false. Enabled - Initial logical enabled state. Default: true. OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.field(\"gain\", Kind=\"numeric\", ... Bind=\"project.parameters.gain\"); See also labkit.app.layout.rangeField, labkit.app.layout.slider"},{"title":"labkit.app.layout.fileList","url":"reference/api/labkit/app/layout/fileList.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.fileList","text":"FILELIST Add file selection and portable-source controls. FILELIST Add file selection and portable-source controls. Usage: node = labkit.app.layout.fileList(id, Name=Value) Description: Declares framework-owned file choosing, removal, selection, and portable source binding. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Reader-facing collection label. Default: id. Mode - \"files\" or \"folder\". Default: \"files\". Filters - File-dialog filter text row. Default: strings(1,0). SelectionMode - \"single\" or \"multiple\". Default: \"multiple\". MaxFiles - Positive scalar or Inf. Default: Inf. FolderWarningThreshold - Positive scalar or Inf. Default: 500. ShowStatus - Logical status visibility. Default: true. StartPath - Initial folder text. Default: \"\". ChooseLabel - File button text. Default: \"Choose\". FolderLabel - Folder button text. Default: \"Choose Folder\". RecursiveFolderLabel - Recursive button text. Default: \"Choose Folder Recursively\". RemoveLabel - Remove button text. Default: \"Remove\". ClearLabel - Clear button text. Default: \"Clear\". ChooseTooltip - File button hover text. Default: ChooseLabel. FolderTooltip - Folder button hover text. Default: FolderLabel. RecursiveFolderTooltip - Recursive-folder button hover text. Default: RecursiveFolderLabel. RemoveTooltip - Remove button hover text. Default: RemoveLabel. ClearTooltip - Clear button hover text. Default: ClearLabel. EmptyText - Empty-list text. Default: \"No files selected\". AllowDuplicatePaths - Preserve separate portable source records that resolve to the same path. Use this when each list row is a distinct workflow task. Default: false. Bind - Project source-record field path. Default: \"\". SelectionBind - ListSelection field path. Default: \"\". OnSelectionChanged - Optional callback applicationState = callback(applicationState,selection,callbackContext) for business effects such as lazily decoding the selected source. selection is labkit.app.event.ListSelection. Ordinary selection state needs only SelectionBind. Default: empty. SourceRole - Portable source role. Default: id. SourceIdPrefix - Portable source ID prefix. Default: id. Required - Logical relinking requirement. Default: true. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid options, paths, or callbacks. Typical Call: node = labkit.app.layout.fileList(\"files\", ... Bind=\"project.inputs.sources\", ... ChooseTooltip=\"Choose calibrated source images for this analysis.\"); See also labkit.app.event.ListSelection, labkit.app.CallbackContext, labkit.app.view.Snapshot"},{"title":"labkit.app.layout.group","url":"reference/api/labkit/app/layout/group.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.group","text":"GROUP Arrange related child elements without a titled boundary. GROUP Arrange related child elements without a titled boundary. Usage: node = labkit.app.layout.group(id, children, Name=Value) Description: Groups compatible control nodes into one semantic arrangement. A titled group draws a nested reader-facing boundary inside its owning section. Inputs: id - Unique MATLAB identifier for the layout target. children - Row cell array of layout nodes. Options: Layout - \"auto\", \"vertical\", or \"horizontal\". Default: \"auto\". Title - Reader-facing nested-group title or blank. Default: blank. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:* for invalid IDs, children, or options. Typical Call: node = labkit.app.layout.group(\"inputs\", {gainField}); See also labkit.app.layout.section, labkit.app.layout.workbench"},{"title":"labkit.app.layout.plotArea","url":"reference/api/labkit/app/layout/plotArea.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.plotArea","text":"PLOTAREA Add one or more axes rendered by an App-owned renderer. PLOTAREA Add one or more axes rendered by an App-owned renderer. Usage: node = labkit.app.layout.plotArea(id, renderer, Name=Value) Description: Declares axes and their one App-owned renderer. The renderer is declared here once; a view snapshot supplies only its current model. Inputs: id - Unique MATLAB identifier for the layout target. renderer - Scalar function handle renderer(axesById,model) with no output. axesById is always a scalar struct with one graphics axes field per declared AxisIds value, including single-axis plots. Options: Title - Reader-facing panel title or blank. Default: blank. Layout - Axes arrangement: \"single\", \"pair\", or \"stack\". Default: \"single\" for one axis and \"stack\" for multiple axes. AxisIds - Unique MATLAB identifier row. Default: \"main\". AxisTitles - One title per axis. Default: axis IDs. XLabels - One x-axis label per axis. Default: blank. YLabels - One y-axis label per axis. Default: blank. ColumnWidths - One positive pixel, \"fit\", or \"1x\" value per axis for pair layout. Default: equal flexible widths. RowHeights - One positive pixel, \"fit\", or \"1x\" value per axis for stack layout. Default: equal flexible heights. ScrollZoomAxes - One \"xy\", \"x\", or \"y\" value per axis. Default: \"xy\". ViewModes - App-owned mode labels. Default: strings(1,0). OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Interactions - Row cell array returned by named labkit.app.interaction.* declarations. Default: {}. Outputs: node - Immutable internal layout node accepted by workspace. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.plotArea(\"preview\", @drawTrace, ... AxisIds=\"trace\"); See also labkit.app.view.Snapshot, labkit.app.layout.workspace, labkit.app.interaction.anchorPath"},{"title":"labkit.app.layout.rangeField","url":"reference/api/labkit/app/layout/rangeField.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.rangeField","text":"RANGEFIELD Add an input for a numeric lower and upper bound. RANGEFIELD Add an input for a numeric lower and upper bound. Usage: node = labkit.app.layout.rangeField(id, Name=Value) Description: Declares one two-value numeric range input. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Value - Finite numeric 1-by-2 row. Default: []. Limits - Increasing finite numeric 1-by-2 row. Default: []. Enabled - Initial logical enabled state. Default: true. Bind - Project or session field path. Default: \"\". OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: node = labkit.app.layout.rangeField(\"window\", Limits=[0 10]); See also labkit.app.layout.field, labkit.app.layout.slider"},{"title":"labkit.app.layout.section","url":"reference/api/labkit/app/layout/section.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.section","text":"SECTION Arrange related child elements under a visible title. SECTION Arrange related child elements under a visible title. Usage: node = labkit.app.layout.section(id, title, children, Name=Value) Description: Groups compatible controls under a reader-facing title. Inputs: id - Unique MATLAB identifier for the layout target. title - Nonempty section title. children - Row cell array of layout nodes. Options: Collapsible - Logical collapse support. Default: false. Expanded - Initial logical expansion state. Default: true. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:* for invalid IDs, children, or options. Typical Call: node = labkit.app.layout.section(\"inputs\", \"Inputs\", {gainField}); See also labkit.app.layout.group, labkit.app.layout.tab"},{"title":"labkit.app.layout.slider","url":"reference/api/labkit/app/layout/slider.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.slider","text":"SLIDER Add a bounded numeric slider. SLIDER Add a bounded numeric slider. Usage: node = labkit.app.layout.slider(id, Name=Value) Description: Declares one bounded scalar slider. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Value - Finite numeric scalar. Default: lower Limits value. Limits - Increasing finite numeric 1-by-2 row. Default: [0 1]. Step - Positive numeric scalar. Default: []. ValueDisplayFormat - Optional spinner numeric format such as \"%.6g\". Default: \"\". ShowTicks - Logical tick visibility. Default: false. Bind - Project or session field path. Default: \"\". Enabled - Initial logical enabled state. Default: true. OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: node = labkit.app.layout.slider(\"frame\", Limits=[1 100], Step=1); See also labkit.app.layout.field, labkit.app.layout.rangeField"},{"title":"labkit.app.layout.statusPanel","url":"reference/api/labkit/app/layout/statusPanel.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.statusPanel","text":"STATUSPANEL Add a text display for current App status. STATUSPANEL Add a text display for current App status. Usage: node = labkit.app.layout.statusPanel(id, Name=Value) Description: Declares a current-status or static instruction display. Inputs: id - Unique MATLAB identifier for the layout target. Options: Title - Visible panel title. Default: \"Status\". Text - Static text lines. When omitted, the runtime's latest status is displayed. Default: strings(1,0). Lines - Approximate visible text lines. The native layout uses this hint to size short summaries without consuming detail-panel height. Integer from 1 through 12. Default: 5. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:InvalidValue for an invalid ID. Typical Call: node = labkit.app.layout.statusPanel(\"status\"); See also labkit.app.CallbackContext"},{"title":"labkit.app.layout.tab","url":"reference/api/labkit/app/layout/tab.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.tab","text":"TAB Add a named tab containing related child elements. TAB Add a named tab containing related child elements. Usage: node = labkit.app.layout.tab(id, title, children) Description: Declares one named control-side tab. Inputs: id - Unique MATLAB identifier for the layout target. title - Nonempty tab title. children - Row cell array of compatible layout nodes. Outputs: node - Immutable internal layout node accepted by workbench. Errors: Throws labkit:app:contract:* for invalid IDs, titles, or children. Typical Call: node = labkit.app.layout.tab(\"settings\", \"Settings\", {gainField}); See also labkit.app.layout.section, labkit.app.layout.workbench"},{"title":"labkit.app.layout.workbench","url":"reference/api/labkit/app/layout/workbench.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.workbench","text":"WORKBENCH Assemble controls and a central workspace into the root layout. WORKBENCH Assemble controls and a central workspace into the root layout. Usage: node = labkit.app.layout.workbench(children, Name=Value) Description: Produces the root semantic layout required by labkit.app.Definition. Inputs: children - Row cell array of controls, groups, sections, or tabs. Options: Workspace - Value returned by layout.workspace. Default: []. Usage - Static workflow instruction lines appended consistently to the first control tab. Default: strings(1,0). UsageTitle - Title for generated workflow instructions. Default: \"Usage\". Outputs: node - Immutable root layout node. Errors: Throws labkit:app:contract:* for invalid children or workspace values. Typical Call: node = labkit.app.layout.workbench({runButton}); See also labkit.app.Definition, labkit.app.layout.workspace"},{"title":"labkit.app.layout.workspace","url":"reference/api/labkit/app/layout/workspace.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.workspace","text":"WORKSPACE Define the App's central working area and optional pages. WORKSPACE Define the App's central working area and optional pages. Usage: node = labkit.app.layout.workspace() node = labkit.app.layout.workspace(content, Name=Value) Description: Declares a single-content workspace or a workspace extended with node.page(id,title,content) and node.initialPage(id). A named page accepts one workspace node or a nonempty row cell array of nodes arranged vertically; growable tables and plots share the available page height. Inputs: content - Optional plotArea, dataTable, group, or section node. For node.page, content may also be a nonempty row cell array of workspace nodes. Options: Title - Reader-facing workspace title. Default: \"Workspace\". OnPageChanged - Callback state = callback(state,pageId,context). Default: []. Outputs: node - Immutable workspace node accepted by layout.workbench. Errors: Throws labkit:app:contract:* for invalid content, pages, or callbacks. Typical Call: node = labkit.app.layout.workspace(plotArea); See also labkit.app.layout.workbench, labkit.app.layout.plotArea"},{"title":"labkit.app.plot.clampPointToAxes","url":"reference/api/labkit/app/plot/clampPointToAxes.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.clampPointToAxes","text":"CLAMPPOINTTOAXES Keep data coordinates inside the visible axes box. CLAMPPOINTTOAXES Keep data coordinates inside the visible axes box. Usage: xyOut = labkit.app.plot.clampPointToAxes(ax, xy) xyOut = labkit.app.plot.clampPointToAxes(ax, xy, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. Its XLim, YLim, XScale, YScale, XDir, and YDir properties define the visible box. xy - N-by-2 numeric matrix of [x y] data coordinates. Name-Value Arguments: Padding - Minimum distance from each axes edge, expressed as a fraction of the visible width or height. Values are limited to [0, 0.49]. Default: 0.04. Outputs: xy - N-by-2 data coordinates, shown as xyOut in the usage syntax. Each point is moved only as far as needed to satisfy Padding. Description: clampData is useful for labels and annotations that must remain readable near an axes boundary. Conversion through normalized axes coordinates keeps the result visually consistent on logarithmic or reversed axes. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidPointPairs - xy is not an N-by-2 numeric array. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig, \"XLim\", [0 10], \"YLim\", [0 20]); xy = labkit.app.plot.clampPointToAxes(ax, [-2 25], \"Padding\", 0.1); assert(isequal(xy, [1 18])) See also labkit.app.plot.offsetPointByAxesFraction"},{"title":"labkit.app.plot.clearAxes","url":"reference/api/labkit/app/plot/clearAxes.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.clearAxes","text":"CLEARAXES Prepare an axes for an app-owned redraw. CLEARAXES Prepare an axes for an app-owned redraw. Usage: labkit.app.plot.clearAxes(ax) labkit.app.plot.clearAxes(ax, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle to clear. Name-Value Arguments: ResetScale - Logical value. true restores linear X/Y scales and automatic X/Y tick modes. false preserves those four properties. Default: false. ClearLegend - Logical value. true turns the legend off. Default: true. Outputs: None. Description: clear deletes plotted children, clears LabKit's cached image home view, releases hold, and returns XLim, YLim, ZLim, and CLim to automatic mode. Labels and other axes decorations follow MATLAB cla behavior. Call it once at the start of a complete redraw, not when adding an overlay that should preserve the current zoom. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. MATLAB graphics errors raised while clearing a valid axes propagate to the caller. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); plot(ax, 1:3, [2 1 3]); labkit.app.plot.clearAxes(ax, \"ResetScale\", true); assert(isempty(ax.Children)) See also labkit.app.plot.fitAxesToGraphics, labkit.app.plot.showMessage"},{"title":"labkit.app.plot.enablePopout","url":"reference/api/labkit/app/plot/enablePopout.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.enablePopout","text":"ENABLEPOPOUT Add a standalone-figure action to an axes context menu. ENABLEPOPOUT Add a standalone-figure action to an axes context menu. Usage: labkit.app.plot.enablePopout(ax) Inputs: ax - MATLAB axes or uiaxes handle that receives an \"Open axes in new figure\" context-menu item. Empty or invalid handles are ignored. Outputs: None. Description: The menu copies the current axes content, limits, labels, colors, and visible legend entries into a normal MATLAB figure with publication and export tools. Existing axes context-menu items are preserved. Children without their own context menu inherit the axes menu, including children added after this call. Repeated calls do not add duplicate menu items. Failure Behavior: Empty or invalid handles are ignored. Errors raised while MATLAB creates the context menu, copies graphics, or opens the standalone figure are not caught and propagate from the originating graphics operation. Typical Call: plot(ax, time, signal); labkit.app.plot.enablePopout(ax); See also labkit.app.layout.plotArea"},{"title":"labkit.app.plot.fitAxesToGraphics","url":"reference/api/labkit/app/plot/fitAxesToGraphics.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.fitAxesToGraphics","text":"FITAXESTOGRAPHICS Fit axes limits to finite plotted X/Y data. FITAXESTOGRAPHICS Fit axes limits to finite plotted X/Y data. Usage: limits = labkit.app.plot.fitAxesToGraphics(ax) limits = labkit.app.plot.fitAxesToGraphics(ax, graphicsHandles) limits = labkit.app.plot.fitAxesToGraphics(..., Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle whose limits are changed. graphicsHandles - Optional graphics handle array. Objects with numeric XData and YData contribute to the fitted range. When omitted, all current axes children are examined. Name-Value Arguments: Padding - Nonnegative fractional padding added on each side of the data range. Default: 0.02. For logarithmic axes, padding is computed in base-10 logarithmic space. EqualDataUnits - Logical value. true expands one fitted dimension so one data unit occupies the same screen distance on X and Y without changing the axes position. For logarithmic dimensions, equality is evaluated in base-10 logarithmic space. Default: false. Outputs: limits - Scalar struct with x and y fields. Each field contains the applied two-element limit, or [] when that dimension had no usable data and was returned to automatic limit mode. Description: fit ignores nonfinite XData and YData. Nonpositive values do not contribute to a logarithmic dimension. Supplying graphicsHandles lets an app exclude annotations such as reference lines from the fitted range. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Invalid supplied graphics handles are ignored when they do not expose numeric XData/YData. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); h = plot(ax, [1 2 3], [10 20 15]); xline(ax, 100); limits = labkit.app.plot.fitAxesToGraphics(ax, h, \"Padding\", 0); assert(isequal(limits.x, [1 3])) See also labkit.app.plot.clearAxes"},{"title":"labkit.app.plot.fitCanvasToSource","url":"reference/api/labkit/app/plot/fitCanvasToSource.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.fitCanvasToSource","text":"FITCANVASTOSOURCE Fit a preview axes into a fixed-aspect pixel frame. FITCANVASTOSOURCE Fit a preview axes into a fixed-aspect pixel frame. Usage: [applied, frame] = labkit.app.plot.fitCanvasToSource(ax, width, height) [applied, frame] = labkit.app.plot.fitCanvasToSource(..., Name=Value) Inputs: ax - UI axes whose parent is a uigridlayout created for a LabKit preview. width - Positive finite source-canvas width in pixels. height - Positive finite source-canvas height in pixels. Name-Value Arguments: margin - Preferred empty margin around the frame in pixels. Default: 24. maxScale - Largest allowed ratio between displayed and source dimensions. Default: 1, so the helper does not enlarge the source canvas. Outputs: applied - true when the grid and axes positions were updated; false when the axes, parent grid, dimensions, or available space were unsuitable. frame - Scalar struct with width, height, ratio, position, scale, and pixelPosition fields. It is an empty struct when applied is false. Description: fitCanvas centers the axes in the middle row and column of a three-by-three flexible grid and preserves the requested aspect ratio. The helper keeps one managed parent-position listener with the axes and reapplies the most recent canvas request after host resizing. It returns false instead of throwing when the host has not been laid out yet or is smaller than the minimum usable preview area. Failure Behavior: Invalid axes, host grids, source dimensions, or insufficient available space return applied=false and frame=struct(). Malformed or unsupported name-value arguments throw labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption. Typical Call: [ok, frame] = labkit.app.plot.fitCanvasToSource(ax, 720, 540); See also labkit.app.layout.plotArea"},{"title":"labkit.app.plot.offsetPointByAxesFraction","url":"reference/api/labkit/app/plot/offsetPointByAxesFraction.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.offsetPointByAxesFraction","text":"OFFSETPOINTBYAXESFRACTION Move data coordinates by normalized axes fractions. OFFSETPOINTBYAXESFRACTION Move data coordinates by normalized axes fractions. Usage: xyOut = labkit.app.plot.offsetPointByAxesFraction(ax, xy, offsetFraction) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. Its current limits, scales, and directions define the coordinate conversion. xy - N-by-2 numeric matrix of [x y] data coordinates. offsetFraction - 1-by-2 or N-by-2 axes-fraction offsets. One row is reused for every point; otherwise the row count must match xy. For example, [0.03 -0.04] moves a label slightly right and down in visual axes space, including on log or reversed axes. Outputs: xy - N-by-2 offset coordinates in data units, shown as xyOut in the usage syntax. Values are not clamped to the visible axes box. Description: offsetData expresses annotation spacing in visual axes fractions instead of data units. This keeps the apparent offset stable as data ranges change and correctly handles logarithmic and reversed axes. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidPointPairs - xy is not an N-by-2 numeric array. labkit:app:plot:InvalidPointOffsets - offsetFraction is neither one row nor one row per point. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig, \"XLim\", [0 10], \"YLim\", [0 20]); xy = labkit.app.plot.offsetPointByAxesFraction(ax, [5 10], [0.1 -0.1]); assert(isequal(xy, [6 8])) See also labkit.app.plot.clampPointToAxes"},{"title":"labkit.app.plot.showMessage","url":"reference/api/labkit/app/plot/showMessage.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.showMessage","text":"SHOWMESSAGE Show a centered empty-state message in an axes. SHOWMESSAGE Show a centered empty-state message in an axes. Usage: hText = labkit.app.plot.showMessage(ax, message) hText = labkit.app.plot.showMessage(ax, message, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. message - User-visible text scalar displayed at the axes center. Name-Value Arguments: Title - Axes title text. Default: \"\". Color - MATLAB color value for the message text. Default: [0.30 0.30 0.30]. Outputs: hText - MATLAB text object containing the message. Description: message clears the axes, resets it to linear unit limits [0 1], removes ticks, and draws non-pickable text. Use it for an empty preview, loading prompt, or unavailable result. Because it performs a complete clear, it is not an overlay operation and does not preserve the previous zoom. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Invalid MATLAB text or color values propagate their originating graphics error. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); h = labkit.app.plot.showMessage(ax, \"No data\", \"Title\", \"Preview\"); assert(string(h.String) == \"No data\") See also labkit.app.plot.clearAxes"},{"title":"labkit.app.project.Schema","url":"reference/api/labkit/app/project/Schema.html","kind":"reference","section":"reference","keywords":"labkit.app.project.Schema","text":"PROJECTCONTRACT Declare one durable App project contract. PROJECTCONTRACT Declare one durable App project contract. Usage: contract = labkit.app.project.Schema() contract = labkit.app.project.Schema(Name=Value) Description: Project Schema owns payload creation, validation, sequential migration, optional legacy import, resume, and source-relink callback signatures. App-specific fields and scientific meaning remain inside the payload and are not interpreted by this value. Default Contract: With no arguments, Version is 1, Create returns struct(), and Validate accepts any scalar struct. This is the standard path for a simple App with no migration or specialized project invariants. Required Name-Value Arguments (custom contract): Version - Positive integer payload version. Create - Fixed callback project = create(). Validate - Fixed callback accepted = validate(project). Optional Name-Value Arguments: Migrate - Fixed callback project = migrate(project,fromVersion). Required when Version is greater than 1. Default: empty. LegacyImports - Scalar struct mapping legacy MAT variable names to fixed callbacks accepting one value and returning project or [project,resume]. Default: struct(). CreateResume - Fixed callback resume = createResume(session,project). Default: empty. ApplyResume - Fixed callback session = applyResume(session,resume,project). Default: empty. RelinkSources - Fixed callback project = relink(project,unresolved,projectFile). Returning empty cancels the load. Default: empty. Outputs: contract - Immutable labkit.app.project.Schema value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Version or LegacyImports is invalid. labkit:app:contract:CallbackRoleMismatch - A callback does not have its documented fixed input and output count. Typical Call: contract = labkit.app.project.Schema(Version=1, ... Create=@createProject, Validate=@validateProject); See also labkit.app.Definition, labkit.app.result.Package"},{"title":"labkit.app.project.emptySourceRecords","url":"reference/api/labkit/app/project/emptySourceRecords.html","kind":"reference","section":"reference","keywords":"labkit.app.project.emptySourceRecords","text":"EMPTYSOURCERECORDS Create an empty portable source collection. EMPTYSOURCERECORDS Create an empty portable source collection. Usage: records = labkit.app.project.emptySourceRecords() Description: Returns a zero-row struct collection with the same durable field shape as labkit.app.project.sourceRecord. App project factories use this value so the first file selection can assign a portable source without exposing or duplicating its field schema. Outputs: records - Empty column collection of portable source records. Failure Behavior: None - The function accepts no inputs and returns the canonical empty collection deterministically. Example: project.inputs.sources = labkit.app.project.emptySourceRecords(); assert(isempty(project.inputs.sources)) See also labkit.app.project.sourceRecord, labkit.app.project.Schema"},{"title":"labkit.app.project.sourceRecord","url":"reference/api/labkit/app/project/sourceRecord.html","kind":"reference","section":"reference","keywords":"labkit.app.project.sourceRecord","text":"SOURCERECORD Create a portable source value during project migration. SOURCERECORD Create a portable source value during project migration. Usage: record = labkit.app.project.sourceRecord(id,role,filepath) record = labkit.app.project.sourceRecord(id,role,filepath,required) Description: Creates the opaque durable source value accepted by App project schemas. Use this pure constructor only while creating or migrating payloads. Runtime callbacks resolve paths through CallbackContext. Inputs: id - Nonempty scalar text stable within the project. role - Nonempty scalar semantic source role. filepath - Nonempty scalar source path, or an existing scalar opaque reference struct during legacy migration. required - Optional logical scalar relinking requirement. Default: true. Outputs: record - Scalar portable source struct owned by the App framework. Errors: labkit:app:contract:InvalidValue - Text or required is malformed. Example: source = labkit.app.project.sourceRecord( ... \"image1\",\"cropSource\",\"image.png\"); assert(source.id == \"image1\") See also labkit.app.CallbackContext, labkit.app.project.Schema"},{"title":"labkit.app.result.File","url":"reference/api/labkit/app/result/File.html","kind":"reference","section":"reference","keywords":"labkit.app.result.File","text":"RESULTOUTPUT Declare one validated result-package output. RESULTOUTPUT Declare one validated result-package output. Usage: output = labkit.app.result.File(id, role, relativePath, Name=Value) Description: File records App-owned output identity and media semantics. Runtime result writing later resolves the relative path, file size, checksum, and aggregate manifest status. Inputs: id - Nonempty scalar text unique within one Result. role - Nonempty scalar text describing output purpose. relativePath - Nonempty package-relative path without traversal. Optional Name-Value Arguments: MediaType - Nonempty media-type scalar text. Default: \"application/octet-stream\". Status - \"success\", \"failed\", or \"skipped\". Default: \"success\". Message - Scalar reader-facing status text. Default: empty. Warnings - Row string or cellstr array. Default: empty. Outputs: output - Immutable labkit.app.result.File value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Text, path, status, or warnings are malformed. Example: output = labkit.app.result.File( ... \"summary\", \"primary\", \"summary.csv\", MediaType=\"text/csv\"); assert(output.Status == \"success\") See also labkit.app.result.Package, labkit.app.CallbackContext"},{"title":"labkit.app.result.Package","url":"reference/api/labkit/app/result/Package.html","kind":"reference","section":"reference","keywords":"labkit.app.result.Package","text":"RESULT Declare one App-owned result manifest request. RESULT Declare one App-owned result manifest request. Usage: result = labkit.app.result.Package(Name=Value) Description: Result contains validated output declarations and App-owned input, parameter, and summary data. Runtime result writing owns provenance, App and facade versions, document identity, file bytes, checksums, creation identity/time, aggregate status, and atomic replacement. Required Name-Value Arguments: Outputs - Nonempty row cell array of labkit.app.result.File values with unique IDs and relative paths and at least one success. Inputs - Scalar App-owned struct describing result inputs. Parameters - Scalar App-owned struct describing result parameters. Summary - Scalar App-owned struct describing result summary. Optional Name-Value Arguments: Warnings - Row string or cellstr array. Default: empty. ManifestName - Nonempty filename without folders. Default: \"labkit_result.json\". Outputs: result - Immutable labkit.app.result.Package value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - A value or manifest name is invalid. labkit:app:contract:DuplicateId - Output IDs or paths repeat. Example: output = labkit.app.result.File( ... \"summary\", \"primary\", \"summary.csv\", MediaType=\"text/csv\"); result = labkit.app.result.Package(Outputs={output}, Inputs=struct(), ... Parameters=struct(), Summary=struct(\"rows\", 1)); assert(result.ManifestName == \"labkit_result.json\") See also labkit.app.result.File, labkit.app.CallbackContext"},{"title":"labkit.app.synthetic.Artifact","url":"reference/api/labkit/app/synthetic/Artifact.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Artifact","text":"ARTIFACT Describe one anonymous synthetic-input artifact. ARTIFACT Describe one anonymous synthetic-input artifact. Usage: artifact = labkit.app.synthetic.Artifact( ... id,role,relativePath,Name=Value) Description: Identifies one synthetic input, expected export, or support file beneath a synthetic-input root without exposing a user file path. Inputs: id - Nonempty semantic identifier unique within a Pack. role - Nonempty App-owned artifact purpose. relativePath - Nonempty synthetic-root-relative path without traversal. Optional Name-Value Arguments: Expectation - \"loads\", \"rejects\", \"exports\", or \"support\". Default: \"loads\". Outputs: artifact - Immutable synthetic-input artifact value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Text, path, or Expectation is malformed. Example: artifact = labkit.app.synthetic.Artifact( ... \"input\",\"source\",\"samples/input.csv\"); assert(artifact.Expectation == \"loads\") See also labkit.app.synthetic.Context, labkit.app.synthetic.Pack"},{"title":"labkit.app.synthetic.Context","url":"reference/api/labkit/app/synthetic/Context.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Context","text":"CONTEXT Provide bounded folders for anonymous synthetic inputs. CONTEXT Provide bounded folders for anonymous synthetic inputs. Usage: context = labkit.app.synthetic.Context(rootFolder) filepath = context.samplePath(relativePath) filepath = context.outputPath(relativePath) record = context.sourceRecord(id,role,filepath,required) artifact = context.artifact(id,role,filepath,Name=Value) Description: Context creates one synthetic-input root with samples and outputs children. App-owned BuildSyntheticSample callbacks may write only anonymous synthetic files beneath these folders. The value does not expose a runtime, recorder, project store, or UI object. Inputs: rootFolder - Nonempty scalar folder dedicated to one generated pack. relativePath - Nonempty child path without an absolute root, empty segment, current segment, or parent traversal. id - Stable portable-source identifier. role - Stable portable-source role. filepath - Path returned by samplePath. required - Logical scalar source requirement. Outputs: context - Immutable synthetic-input context. filepath - Absolute path bounded by SampleFolder or OutputFolder. record - Portable source value from labkit.app.project.sourceRecord. artifact - Typed synthetic artifact whose relative path is derived from a filepath beneath RootFolder. Errors: labkit:app:contract:InvalidValue - A folder, relative path, or source argument is malformed. MATLAB filesystem errors propagate when the synthetic folders cannot be created. Typical Call: context = labkit.app.synthetic.Context(tempname); filepath = context.samplePath(\"input.csv\"); See also labkit.app.synthetic.Artifact, labkit.app.synthetic.Pack, labkit.app.project.sourceRecord"},{"title":"labkit.app.synthetic.Pack","url":"reference/api/labkit/app/synthetic/Pack.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Pack","text":"PACK Describe one typed anonymous App reproduction scenario. PACK Describe one typed anonymous App reproduction scenario. Usage: pack = labkit.app.synthetic.Pack( ... Scenario=scenario,InitialProject=project,Artifacts=artifacts) Description: Couples one App-authored synthetic project with its anonymous artifact declarations so users and tests can reproduce a named scenario through the App's ordinary import workflow. Required Name-Value Arguments: Scenario - Nonempty stable scenario identifier. InitialProject - Scalar current App project struct. Artifacts - Row cell array of labkit.app.synthetic.Artifact values. Empty is legal for an App whose scenario needs no files. Outputs: pack - Immutable synthetic-input pack. Errors: labkit:app:contract:UnknownArgument - An argument is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Scenario, project, or Artifacts is malformed. labkit:app:contract:DuplicateId - Artifact IDs or relative paths are duplicated. Example: artifact = labkit.app.synthetic.Artifact( ... \"input\",\"source\",\"samples/input.csv\"); pack = labkit.app.synthetic.Pack( ... Scenario=\"representative\",InitialProject=struct(), ... Artifacts={artifact}); assert(pack.Scenario == \"representative\") See also labkit.app.synthetic.Context, labkit.app.synthetic.Artifact, labkit.app.Definition"},{"title":"labkit.app.version","url":"reference/api/labkit/app/version.html","kind":"reference","section":"reference","keywords":"labkit.app.version","text":"VERSION Return the LabKit App SDK facade contract version. VERSION Return the LabKit App SDK facade contract version. Usage: info = labkit.app.version() Description: Reports the semantic version and compatibility range of the public LabKit App SDK. The SDK owns App definitions, semantic layout, transactions, projects, portable sources, resources, results, dialogs, and native MATLAB presentation. It is independent of App product and saved-project schema versions. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo with name, current, compatible, status, and notes fields. Errors: labkit:contract:InvalidVersionInfo - Embedded facade metadata is invalid. Example: info = labkit.app.version(); assert(startsWith(info.current, \"2.\")) See also labkit.contract.versionInfo, labkit.contract.requirements, labkit.app.Definition"},{"title":"labkit.app.view.Snapshot","url":"reference/api/labkit/app/view/Snapshot.html","kind":"reference","section":"reference","keywords":"labkit.app.view.Snapshot","text":"SNAPSHOT Build one immutable complete visible-state snapshot. SNAPSHOT Build one immutable complete visible-state snapshot. Usage: view = labkit.app.view.Snapshot() view = view.value(target, value) view = view.choices(target, choices) view = view.limits(target, limits) view = view.enabled(target, enabled) view = view.visible(target, visible) view = view.text(target, text) view = view.filePaths(target, paths) view = view.fileItemStatuses(target, statuses) view = view.listSelection(target, selection) view = view.tableCellSelection(target, selection) view = view.tableData(target, data, Name=Value) view = view.renderPlot(target, model, Name=Value) view = view.workspacePage(target, Name=Value) view = view.anchorPath(interaction, points, Name=Value) view = view.pairedAnchors(interaction, pointSets, Name=Value) view = view.pointSlots(interaction, value, Name=Value) view = view.rectangle(interaction, position, Name=Value) view = view.regionSelection(interaction, Name=Value) view = view.interval(interaction, range, Name=Value) view = view.scaleReference(interaction, endpoints, Name=Value) view = view.include(fragment) Description: View snapshot is a closed immutable operation vocabulary. One value describes the complete current visible state; Definition validates targets, capabilities, renderer references, and completeness before a runtime may reconcile it. Apps do not author patches or generic target/property setters. Inputs: target - Nonempty Layout target ID. value - Target value owned by the App state. choices - Text array or cellstr of legal choices. limits - Increasing finite two-element numeric row. enabled - Logical scalar availability. visible - Logical scalar visibility. text - Scalar text. paths - String or cell array of file paths. statuses - Empty or one reader-facing status per file-list row. selection - Selection value accepted by the target. data - App-owned table, numeric array, or cell array. model - App-owned model passed to the renderer declared by plotArea. Options: ViewRevision - Nonnegative integer identifying the requested initial viewport. The adapter preserves user zoom while the value is unchanged and accepts renderer limits once when it changes. Default: 0. Outputs: view - New immutable labkit.app.view.Snapshot snapshot. fragment - Another Snapshot whose non-duplicate operations should be included in this snapshot. Errors: labkit:app:contract:InvalidValue - An operation argument is malformed. labkit:app:contract:DuplicateId - The same operation is supplied twice for one target. Example: view = labkit.app.view.Snapshot(); view = view.choices(\"group\", [\"A\", \"B\"]); view = view.value(\"group\", \"A\"); assert(isa(view, \"labkit.app.view.Snapshot\")) See also labkit.app.Definition, labkit.app.layout.workbench"},{"title":"labkit.biosignal.buildTemplate","url":"reference/api/labkit/biosignal/buildTemplate.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.buildTemplate","text":"BUILDTEMPLATE Build a representative segment template. BUILDTEMPLATE Build a representative segment template. Usage: template = labkit.biosignal.buildTemplate(segments) template = labkit.biosignal.buildTemplate(segments, opts) Description: Forms a first-pass template by averaging every segment, then ranks each segment by its correlation with that average. The returned waveform is the sample-by-sample mean of the highest-ranked segments. This two-pass procedure reduces the influence of beats or trials whose shape differs from the majority. Correlations are computed after subtracting each waveform's mean. Missing samples are omitted from means and correlations. A constant waveform has a NaN score and sorts behind finite scores. If segments contains no waveforms, the function returns an empty template with the same timeOffset vector. Inputs: segments - Segment struct returned by labkit.biosignal.segmentByEvents. segments.values is an M-by-N matrix, with one segment per column; segments.timeOffset contains the M relative times. opts - Optional scalar struct containing the fields listed below. Options: topN - Number of ranked segments to average. The default is the smaller of 30 and the number of available segments. The value is rounded to an integer and limited to the range 1:N. Outputs: template - Structure containing the representative waveform and the ranking information used to construct it. Output Fields: type - String scalar \"biosignalTemplate\". values - M-by-1 template waveform, or an M-by-0 array when no segments are available. timeOffset - M-by-1 relative time vector copied from segments. keptSegmentIndex - Indices of the segment columns included in values, ordered from highest to lowest correlation. score - N-by-1 correlation score for every input segment. metadata.topN - Number of segments included in a nonempty template. metadata.sourceName - Source signal name copied from segments for a nonempty template. metadata is empty when no segment waveforms are available. Errors: labkit:biosignal:InvalidSegments - segments is not a structure with values and timeOffset fields. Invalid numeric values or an unusable topN value may also raise the originating MATLAB conversion error. Example: segments = struct('values', [0 0; 1 0.9; 0 0], ... 'timeOffset', [-1; 0; 1], 'sourceName', \"ECG\"); template = labkit.biosignal.buildTemplate(segments, struct('topN', 1)); See also labkit.biosignal.segmentByEvents, labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.compareGroups","url":"reference/api/labkit/biosignal/compareGroups.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.compareGroups","text":"COMPAREGROUPS Summarize groups and compute pairwise Welch comparisons. COMPAREGROUPS Summarize groups and compute pairwise Welch comparisons. Usage: result = labkit.biosignal.compareGroups(values, groups) Description: Calculates descriptive statistics for each group and a two-sided Welch t-test for every pair of groups. Welch's test does not assume equal variances. Group order follows the first occurrence of each label in groups. Rows with a nonfinite measurement or a blank group label are discarded before calculation. A pairwise row is still returned when either group has fewer than two observations or the estimated standard error is zero; its test statistic, degrees of freedom, and p-value are NaN. Inputs: values - Numeric vector of measurements. groups - Vector of labels accepted by string, such as a string array, cell array of character vectors, or categorical array. It must contain one label for each element of values. Outputs: result - Structure with summary and pairwise tables. Output Fields: summary - One row per group. Columns are Group, N, Mean, Std, Median, Min, and Max. pairwise - One row per unique group pair. Columns are GroupA, GroupB, MeanDifference, T, DF, and P. MeanDifference is mean(GroupA) minus mean(GroupB); P is the two-sided Welch-test p-value. Failure Behavior: Groups with fewer than two finite observations, or with zero estimated standard error, retain a pairwise row with NaN test statistics. values and groups must contain the same number of elements; incompatible input shapes or values that cannot be converted to numeric/text raise the originating MATLAB error. Example: values = [4.8 5.1 5.0 6.2 6.0 6.4]; groups = [\"control\" \"control\" \"control\" \"treated\" \"treated\" \"treated\"]; result = labkit.biosignal.compareGroups(values, groups); See also labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.cropSignal","url":"reference/api/labkit/biosignal/cropSignal.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.cropSignal","text":"CROPSIGNAL Return a signal clipped to a time range in seconds. CROPSIGNAL Return a signal clipped to a time range in seconds. Usage: cropped = labkit.biosignal.cropSignal(signal, timeRangeSec) cropped = labkit.biosignal.cropSignal(signal) Description: Keeps samples whose timestamps lie between the requested start and end times, including both endpoints. For a nonempty result, time is shifted so that the first retained sample occurs at zero. The sample rate is recalculated from the median positive time step when at least two usable timestamps remain; otherwise the input sample rate is preserved. Omitting timeRangeSec or passing [] returns signal unchanged. A valid range that contains no samples returns empty time and values vectors. Inputs: signal - Biosignal structure with time, values, fs, and metadata fields. time and values must describe corresponding samples. timeRangeSec - Two-element numeric vector [startSec endSec], expressed on signal.time, with startSec less than endSec. The value may be omitted or empty to skip cropping. Outputs: cropped - Copy of signal containing only the selected samples. The original range is recorded in metadata.cropTimeRangeSec. Errors: labkit:biosignal:InvalidSignal - signal does not contain time, values, and fs fields. labkit:biosignal:InvalidTimeRange - timeRangeSec is not an increasing two-element vector. Example: signal = struct('time', (0:0.5:3)', 'values', (10:16)', ... 'fs', 2, 'metadata', struct()); cropped = labkit.biosignal.cropSignal(signal, [0.75 2.25]); See also labkit.biosignal.filterSignal, labkit.biosignal.getChannel"},{"title":"labkit.biosignal.defaultEcgPeakOptions","url":"reference/api/labkit/biosignal/defaultEcgPeakOptions.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.defaultEcgPeakOptions","text":"DEFAULTECGPEAKOPTIONS Return documented defaults for ECG/QRS peak detection. DEFAULTECGPEAKOPTIONS Return documented defaults for ECG/QRS peak detection. Usage: opts = labkit.biosignal.defaultEcgPeakOptions() opts = labkit.biosignal.defaultEcgPeakOptions(method) Description: Returns a complete starting options structure for one ECG peak detector. Modify only the fields needed for a recording, then pass the structure to labkit.biosignal.detectEcgPeaks. Fields that do not apply to the selected method are omitted, which makes saved settings easier to interpret. Inputs: method - Optional character vector or scalar string. Canonical values are \"qrs-streaming\" (default), \"pan-tompkins\", and \"local\". Common spellings such as \"streaming\", \"pantompkin\", and \"simple\" are accepted and normalized to a canonical value. Outputs: opts - Scalar structure containing the selected method and its defaults. Common Options: method - Canonical detector name. polarity - Peak direction: \"auto\", \"positive\", \"negative\", or \"absolute\". The default is \"auto\". minDistanceSec - Minimum interval between accepted peaks. The default is 0.25 seconds for qrs-streaming and pan-tompkins, and 0.05 seconds for local. Local Options: thresholdStd - Number of robust standard deviations above the median score used as the automatic threshold. Default 3. smoothSec - Moving-average duration applied to the peak score. Default 0.01 seconds. Pan-Tompkins Options: integrationWindowSec - Moving integration duration for squared QRS-band slope energy. Default 0.150 seconds. refineSearchSec - Half-width used to move an energy candidate to the nearest peak in the detector trace. Default 0.120 seconds. rawRefineSearchSec - Half-width for the final move to a peak in the raw signal. Default 0.020 seconds. Streaming QRS Options: baselineWindowSec - Causal moving-baseline duration. Default 0.600 seconds. envelopeWindowSec - Causal slope-envelope duration. Default 0.080 seconds. lookaheadSec - Forward interval used to confirm a local envelope maximum. Default 0.080 seconds. refineSearchSec - Half-width used to move a candidate from the envelope to the baseline-corrected signal. Default 0.090 seconds. rawRefineSearchSec - Half-width for the final move to a raw-signal peak. Default 0.020 seconds. minTemplateScore - Minimum correlation with the rolling QRS template once at least four templates are available. Default 0.45. medianPolarityCorrection - Logical value that enables a final correction of isolated opposite-polarity anchors. Default true. medianReviewPeakCount - Number of accepted peaks used by the polarity review. Default 3. Errors: labkit:biosignal:UnsupportedPeakMethod - method cannot be normalized to a supported detector. Example: opts = labkit.biosignal.defaultEcgPeakOptions(\"pan-tompkins\"); opts.minDistanceSec = 0.35; See also labkit.biosignal.detectEcgPeaks"},{"title":"labkit.biosignal.detectEcgPeaks","url":"reference/api/labkit/biosignal/detectEcgPeaks.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.detectEcgPeaks","text":"DETECTECGPEAKS Detect ECG/QRS peaks as event anchors. DETECTECGPEAKS Detect ECG/QRS peaks as event anchors. Usage: events = labkit.biosignal.detectEcgPeaks(signal) events = labkit.biosignal.detectEcgPeaks(signal, opts) Description: Detects beat anchors from one ECG-like signal. The function first obtains the documented defaults for opts.method, then replaces them with fields supplied in opts. Input time, sample indices, and raw amplitudes are preserved in the returned events so the anchors can be used directly by labkit.biosignal.segmentByEvents. qrs-streaming uses a causal moving baseline and slope envelope, adaptive signal and noise levels, a rolling morphology template, and optional median-polarity review. pan-tompkins uses a QRS-band signal, squared derivative energy, moving integration, adaptive thresholding, and search back. local applies a robust amplitude threshold followed by nonmaximum suppression. Pan-Tompkins and streaming candidates are finally moved to nearby peaks in the raw signal. Empty signals, invalid sample rates, and signals too short for the chosen detector return an empty events structure rather than an error. Inputs: signal - Biosignal structure with time, values, and fs fields. time and values must have corresponding samples; fs is in hertz. opts - Optional scalar struct. Start with labkit.biosignal.defaultEcgPeakOptions and change the fields that need tuning for the recording. Options: method - \"qrs-streaming\" (default), \"pan-tompkins\", or \"local\". polarity - \"auto\" (default), \"positive\", \"negative\", or \"absolute\". auto chooses the stronger direction; absolute scores either direction by magnitude. minDistanceSec - Minimum accepted peak spacing in seconds. Defaults are method-specific; see defaultEcgPeakOptions. threshold - Local method only. Explicit score threshold. The default [] derives a threshold from thresholdStd. thresholdStd - Local method only. Robust standard-deviation multiplier; default 3. smoothSec - Local method only. Score smoothing duration; default 0.01 seconds. integrationWindowSec - Pan-Tompkins integration duration; default 0.150 seconds. refineSearchSec - Detector-trace search half-width. Defaults are 0.120 seconds for pan-tompkins and 0.090 seconds for qrs-streaming. rawRefineSearchSec - Final raw-signal search half-width for pan-tompkins and qrs-streaming. Default 0.020 seconds. baselineWindowSec - qrs-streaming baseline duration; default 0.600 seconds. envelopeWindowSec - qrs-streaming slope-envelope duration; default 0.080 seconds. lookaheadSec - qrs-streaming local-maximum lookahead; default 0.080 seconds. minTemplateScore - qrs-streaming rolling-template correlation threshold; default 0.45. medianPolarityCorrection - qrs-streaming logical switch for final anchor polarity correction; default true. medianReviewPeakCount - qrs-streaming review length; default 3 peaks. Outputs: events - Structure containing one row-aligned entry per detected peak. Output Fields: type - String scalar \"biosignalEvents\". index - 1-based sample indices in signal.values. time - Event times copied from signal.time at index. amplitude - Raw signal amplitudes at the accepted indices. score - Method-specific detector score at each event. label - \"qrs\" for pan-tompkins and qrs-streaming, or \"peak\" for local. threshold - Final score threshold used by the detector. metadata - Selected method, polarity, minimum spacing, and other method-specific settings used during detection. Errors: labkit:biosignal:InvalidSignal - signal lacks time, values, or fs. labkit:biosignal:UnsupportedPeakMethod - opts.method is unsupported. labkit:biosignal:UnsupportedPolarity - opts.polarity is unsupported. Example: fs = 100; time = (0:1/fs:4)'; values = zeros(size(time)); values(101:100:401) = 1; signal = struct('time', time, 'values', values, 'fs', fs); opts = struct('method', 'local', 'polarity', 'positive', ... 'minDistanceSec', 0.5, 'thresholdStd', 1); events = labkit.biosignal.detectEcgPeaks(signal, opts); See also labkit.biosignal.defaultEcgPeakOptions, labkit.biosignal.segmentByEvents"},{"title":"labkit.biosignal.filterSignal","url":"reference/api/labkit/biosignal/filterSignal.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.filterSignal","text":"FILTERSIGNAL Apply a zero-phase FFT-domain filter to a biosignal. FILTERSIGNAL Apply a zero-phase FFT-domain filter to a biosignal. Usage: filtered = labkit.biosignal.filterSignal(signal) filtered = labkit.biosignal.filterSignal(signal, spec) Description: Applies an ideal frequency mask in the discrete Fourier domain and uses an inverse transform to return a zero-phase filtered waveform. Low-pass and band-pass filtering restore the input mean after filtering; high-pass filtering does not. Missing samples are filled before the transform, so filtered.values is finite when the input contains enough finite data for interpolation. Reflect edge mode extrapolates both ends of the waveform before applying the FFT and removes the padding afterward. A cosine taper is applied only to the outer padded samples. This reduces wraparound artifacts without changing the length or time axis of the signal. Use edgeMode \"none\" when the unpadded periodic FFT behavior is required. Inputs: signal - Biosignal structure with values, fs, and metadata fields. fs is the sample rate in hertz. spec - Optional scalar struct containing the fields listed below. Options: type - \"bandpass\" (default), \"lowpass\", \"highpass\", \"none\", or \"off\". The latter two skip frequency masking but still fill missing data. cutoffHz - Scalar cutoff for low-pass or high-pass filtering, or a two-element [low high] band for band-pass filtering. The default is [0.5 40] Hz. Empty or nonfinite values skip filtering and return the filled waveform. edgeMode - \"reflect\" (default) or \"none\". The aliases \"reflection\", \"symmetric\", \"pad\", and \"padded\" select reflection; \"off\" and \"raw\" select no padding. edgePadSec - Nonnegative padding duration in seconds. By default, band-pass and high-pass filters use three periods of the lowest cutoff, limited to 0.5 through 5 seconds. Other filters use 1 second. Padding cannot exceed half the available signal minus one sample. edgeTaperSec - Nonnegative duration of the outer padding taper. The default is 1 second. Outputs: filtered - Copy of signal with filtered values. All other signal fields are preserved, and metadata.filter records spec exactly as supplied by the caller. Errors: labkit:biosignal:InvalidSignal - signal lacks values or fs. labkit:biosignal:UnsupportedFilter - type is not supported. labkit:biosignal:UnsupportedFilterEdgeMode - edgeMode is not supported. Example: fs = 100; time = (0:1/fs:2)'; values = sin(2*pi*2*time) + 0.2*sin(2*pi*30*time); signal = struct('time', time, 'values', values, 'fs', fs, ... 'metadata', struct()); filtered = labkit.biosignal.filterSignal(signal, ... struct('type', 'lowpass', 'cutoffHz', 10)); See also labkit.biosignal.cropSignal, labkit.biosignal.detectEcgPeaks"},{"title":"labkit.biosignal.getChannel","url":"reference/api/labkit/biosignal/getChannel.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.getChannel","text":"GETCHANNEL Return one signal from a recording by display name or index. GETCHANNEL Return one signal from a recording by display name or index. Usage: signal = labkit.biosignal.getChannel(recording, channel) Description: Selects one channel from a recording created by labkit.biosignal.readRecording. A numeric channel is interpreted as a 1-based index. Text is matched exactly against displayName first and then against the source name. Matching is case-sensitive. Use labkit.biosignal.listChannels to obtain the display names presented by the recording. Display names are unique even when source channel names are repeated. Inputs: recording - Recording structure returned by labkit.biosignal.readRecording. channel - Positive integer index, character vector, or scalar string containing a channel name or display name. Outputs: signal - Selected signal structure. Its principal fields are time in seconds, values, fs in hertz, name, displayName, unit, and metadata. Errors: labkit:biosignal:InvalidRecording - recording has no signals field. labkit:biosignal:NoChannels - recording contains no signals. labkit:biosignal:InvalidChannelIndex - numeric index is not an integer within the available range. labkit:biosignal:InvalidChannel - channel has an unsupported type. labkit:biosignal:UnknownChannel - no exact name matches channel. Example: channel = struct('name', \"ECG\", 'displayName', \"ECG\", ... 'time', [0; 0.01], 'values', [0; 1], 'fs', 100, ... 'unit', \"mV\", 'metadata', struct()); recording = struct('signals', channel); signal = labkit.biosignal.getChannel(recording, \"ECG\"); See also labkit.biosignal.listChannels, labkit.biosignal.readRecording"},{"title":"labkit.biosignal.listChannels","url":"reference/api/labkit/biosignal/listChannels.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.listChannels","text":"LISTCHANNELS Return display names for all channels in a recording. LISTCHANNELS Return display names for all channels in a recording. Usage: names = labkit.biosignal.listChannels(recording) Description: Returns channel display names in recording order. These are the same names accepted by labkit.biosignal.getChannel and are suitable for a list box or drop-down menu. A recording with no signals returns a 1-by-0 cell array. Inputs: recording - Recording structure returned by labkit.biosignal.readRecording. Outputs: names - 1-by-N cell array of display-name character vectors. Errors: labkit:biosignal:InvalidRecording - recording is not a structure with a signals field. Example: signals(1).displayName = \"ECG\"; signals(2).displayName = \"Respiration\"; recording = struct('signals', signals); names = labkit.biosignal.listChannels(recording); See also labkit.biosignal.getChannel, labkit.biosignal.readRecording"},{"title":"labkit.biosignal.measureSegments","url":"reference/api/labkit/biosignal/measureSegments.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.measureSegments","text":"MEASURESEGMENTS Measure generic template-residual segment quality. MEASURESEGMENTS Measure generic template-residual segment quality. Usage: result = labkit.biosignal.measureSegments(segments, template) result = labkit.biosignal.measureSegments(segments, template, opts) Description: Measures each segment against a representative template. SignalP2P is the peak-to-peak amplitude inside the signal window. NoiseRMS is the root mean square of segment minus template inside the selected noise windows. SNRdB is 20*log10(SignalP2P/NoiseRMS), and TemplateCorrelation is the mean-centered correlation between the complete segment and template. The function also summarizes the per-segment measurements with means and sample standard deviations. If either the segment matrix or template is empty, both returned tables and metadata are empty. Inputs: segments - Segment structure returned by labkit.biosignal.segmentByEvents. Its values matrix must have the same number of rows as template.values. template - Template structure returned by labkit.biosignal.buildTemplate. opts - Optional scalar struct containing the fields listed below. Options: signalWindowSec - Two-element interval used for SignalP2P. The default is [-0.06 0.06] seconds relative to the event. The interval must include at least one timeOffset sample. noiseWindowsSec - N-by-2 matrix of intervals used for NoiseRMS. The default is [-0.30 -0.20; 0.40 0.50] seconds. Their union must include at least one timeOffset sample. Outputs: result - Structure containing per-segment measurements, aggregate statistics, and the windows used in the calculation. Output Fields: type - String scalar \"biosignalSegmentMeasurements\". perSegment - Table with Segment, EventIndex, EventTime, SignalP2P, NoiseRMS, SNRdB, and TemplateCorrelation columns. summary - One-row table with SegmentCount and the mean and standard deviation of SignalP2P, NoiseRMS, and SNRdB, plus the mean TemplateCorrelation. metadata.signalWindowSec - Signal interval used for the calculation. metadata.noiseWindowsSec - Noise intervals used for the calculation. Errors: labkit:biosignal:InvalidSegments - segments lacks values or timeOffset. labkit:biosignal:InvalidTemplate - template lacks values. Example: segments = struct('values', [0 0; 2 1.8; 0 0], ... 'timeOffset', [-0.1; 0; 0.1], 'eventIndex', [10; 20], ... 'eventTime', [1; 2]); template = struct('values', [0; 1.9; 0]); opts = struct('signalWindowSec', [-0.05 0.05], ... 'noiseWindowsSec', [-0.1 -0.05; 0.05 0.1]); result = labkit.biosignal.measureSegments(segments, template, opts); See also labkit.biosignal.buildTemplate, labkit.biosignal.segmentByEvents"},{"title":"labkit.biosignal.readRecording","url":"reference/api/labkit/biosignal/readRecording.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.readRecording","text":"READRECORDING Read a biosignal file into a standard recording struct. READRECORDING Read a biosignal file into a standard recording struct. Usage: [recording, status] = labkit.biosignal.readRecording(filepath) [recording, status] = labkit.biosignal.readRecording(filepath, opts) Description: Imports supported biosignal files into one recording format used by the rest of labkit.biosignal. MAT files contribute each numeric or logical vector stored in a timetable. CSV, TSV, and TXT files contribute each selected numeric column other than the time column. For delimited text, the importer detects a header after an optional preamble and recognizes numeric, datetime, or duration time columns. An explicit timeColumn takes precedence. Otherwise, names that look like time are preferred; a headerless first numeric column and the common I0 device column are also recognized. If no time column can be identified, the importer creates a sample-index time axis. All returned signal times are measured in seconds from zero. Import and format errors are reported in status instead of being thrown. The returned recording is then an empty recording carrying the requested source path. An invalid filepath value is a programming error and is thrown before import begins. Inputs: filepath - Character vector or scalar string naming a MAT, CSV, TSV, or TXT file. opts - Optional scalar struct containing the fields listed below. Options: headerLine - Positive integer giving the header line, or the first data line when hasHeader is false. By default the importer scans the first 120 lines. hasHeader - Logical scalar indicating whether headerLine contains column names. The default [] selects automatic detection. timeColumn - Column name or 1-based column index to use as time. The default [] enables the inference described above. timeUnit - Unit for a numeric time column: \"seconds\", \"milliseconds\", \"microseconds\", \"nanoseconds\", or the abbreviations s, sec, ms, us, and ns. \"sample\", \"samples\", \"index\", and \"sample_index\" leave the numeric scale unchanged. The default infers SI-prefixed units from the column name and otherwise uses seconds. signalColumns - Column names or 1-based indices to import from a delimited file. The default [] imports every numeric non-time vector. fallbackFs - Positive sample rate in hertz. It sets the spacing of a synthetic time axis and supplies a nominal interval when timestamps cannot provide one. Without it, synthetic sample-index time advances by one second per row. timeRepair - \"auto\" (default) replaces duplicate or backward timestamp steps with the nominal positive step. \"none\" and \"off\" preserve the converted intervals after shifting to zero. gapFactor - Positive scalar used only to report unusually large positive gaps. A gap larger than gapFactor times the median positive interval is counted. The default is 20. useFirstNumericColumnAsTime - Logical scalar. true permits the first numeric column to be used when no other time rule matches. The default is false. Outputs: recording - Scalar recording structure. signals is a structure array with one element per imported channel. status - Scalar structure describing whether import succeeded. Recording Fields: type - String scalar \"biosignalRecording\". version - Recording schema version, currently 1. sourcePath - Source path supplied by filepath. name - Source file name including its extension. signals - Channel structures with time, values, fs, name, displayName, sourceName, unit, and metadata fields. metadata - Import information. Text imports report sourceKind, timeColumn, timeUnit, timeSource, timeRepair, importHeaderLine, and importHasHeader. Status Fields: ok - Logical true when import completed. message - Empty string on success; otherwise the import error message. kind - \"mat\" or \"table\" on success. Failed requests report \"unknown\" or the attempted file extension. filepath - Normalized source path. Failure Behavior: A missing, unsupported, unreadable, or malformed source returns status.ok=false, preserves the normalized source path, and returns an empty recording. Only an invalid filepath MATLAB value is thrown as labkit:biosignal:InvalidFilepath before import begins. Example: filepath = [tempname '.csv']; sampleTime = (0:0.01:0.03)'; ECG = [0; 1; 0; -1]; writetable(table(sampleTime, ECG), filepath); cleanup = onCleanup(@() delete(filepath)); [recording, status] = labkit.biosignal.readRecording(filepath, ... struct('timeColumn', 'sampleTime', 'timeUnit', 'seconds')); signal = labkit.biosignal.getChannel(recording, 'ECG'); See also labkit.biosignal.listChannels, labkit.biosignal.getChannel"},{"title":"labkit.biosignal.segmentByEvents","url":"reference/api/labkit/biosignal/segmentByEvents.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.segmentByEvents","text":"SEGMENTBYEVENTS Extract fixed windows around event anchors. SEGMENTBYEVENTS Extract fixed windows around event anchors. Usage: segments = labkit.biosignal.segmentByEvents(signal, events) segments = labkit.biosignal.segmentByEvents(signal, events, windowSec) Description: Extracts one fixed-length signal segment around each event index. Events whose requested window would extend beyond the signal are omitted rather than padded. The returned columns therefore correspond only to interior events, and eventIndex and eventTime identify which events were retained. Window boundaries are converted to sample counts by rounding after multiplication by signal.fs. The default window is 0.35 seconds before through 0.35 seconds after each event. Inputs: signal - Biosignal structure with values, fs, and displayName fields. events - Event structure returned by labkit.biosignal.detectEcgPeaks. index contains 1-based sample positions and time contains the corresponding event times. windowSec - Optional two-element vector [startSec endSec] relative to an event. Use a negative start and nonnegative end to span the event. The default is [-0.35 0.35] seconds. Outputs: segments - Structure containing the retained waveform segments. Output Fields: type - String scalar \"biosignalSegments\". values - M-by-N matrix with one retained event segment per column. timeOffset - M-by-1 vector of sample times relative to the event. eventIndex - N-by-1 source sample indices of retained events. eventTime - N-by-1 source times of retained events. fs - Sample rate in hertz copied from signal. sourceName - signal.displayName. metadata.windowSec - Requested window in seconds. Errors: labkit:biosignal:InvalidSignal - signal lacks values or fs. labkit:biosignal:InvalidEvents - events lacks index or time. labkit:biosignal:InvalidWindow - windowSec is not an increasing two-element vector. Example: signal = struct('values', (1:21)', 'fs', 10, 'displayName', \"ECG\"); events = struct('index', [6; 16], 'time', [0.5; 1.5]); segments = labkit.biosignal.segmentByEvents(signal, events, [-0.2 0.2]); See also labkit.biosignal.detectEcgPeaks, labkit.biosignal.buildTemplate, labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.version","url":"reference/api/labkit/biosignal/version.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.version","text":"VERSION Return the LabKit biosignal facade contract version. VERSION Return the LabKit biosignal facade contract version. Usage: info = labkit.biosignal.version() Description: Reports the semantic version and compatibility range of the public labkit.biosignal API. Use this information when an app or saved project needs to check whether the installed biosignal facade satisfies a known contract range. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. It identifies the biosignal component, current version, compatible contract range, stability status, and a short description. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.biosignal.version(); currentVersion = info.current; See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.contract.assertRequirements","url":"reference/api/labkit/contract/assertRequirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.assertRequirements","text":"ASSERTREQUIREMENTS Throw an error when LabKit API requirements are not met. ASSERTREQUIREMENTS Throw an error when LabKit API requirements are not met. Usage: labkit.contract.assertRequirements(appName, req) labkit.contract.assertRequirements(appName, req, versions) Description: Calls checkRequirements and returns normally when every requirement is compatible. On failure, combines the individual report messages into one error. Use checkRequirements instead when incompatibility should be shown without throwing. Inputs: appName - Text scalar used as the error-identifier prefix. It should be a valid MATLAB identifier, for example \"labkit_Example_app\". req - Scalar structure returned by labkit.contract.requirements. versions - Optional version structure array accepted by checkRequirements. Outputs: None - Returns no value. Errors: Throws :IncompatibleLabKit when compatibility checks fail. Input and range validation errors from checkRequirements are passed through. Example: req = labkit.contract.requirements(\"app\", \">=1 <2\"); labkit.contract.assertRequirements(\"labkit_Example_app\", req) See also labkit.contract.checkRequirements, labkit.contract.requirements"},{"title":"labkit.contract.checkRequirements","url":"reference/api/labkit/contract/checkRequirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.checkRequirements","text":"CHECKREQUIREMENTS Compare required API ranges with available LabKit versions. CHECKREQUIREMENTS Compare required API ranges with available LabKit versions. Usage: report = labkit.contract.checkRequirements(req) report = labkit.contract.checkRequirements(req, versions) Description: Checks each requested facade in two ways: its current version must satisfy the caller's range, and that range must overlap at least one compatibility range advertised by the facade. When versions is omitted, the current app, dta, rhs, biosignal, image, and thermal version functions are queried. Inputs: req - Scalar structure returned by labkit.contract.requirements. versions - Optional structure array returned by facade version functions or versionInfo. Every element must contain name, current, compatible, status, and notes. An optional facade field overrides the name used for matching. This argument is mainly useful for diagnostics and tests. Outputs: report - Scalar structure with ok, failures, and message fields. ok is true when every requirement passes. message is a success sentence or newline-separated failure text. Output Fields: failures - Structure array with one element per failed requirement. failures.facade - Normalized facade name. failures.required - Range requested by the caller. failures.available - Compatibility ranges advertised by the facade, or an empty string array when the facade is unknown. failures.message - Readable incompatibility explanation. Errors: Throws labkit:contract:InvalidRequirements for a malformed req, labkit:contract:InvalidVersionInfo for a malformed versions array, labkit:contract:InvalidFacadeName for invalid names, and labkit:contract:InvalidVersionRange when a version or constraint cannot be parsed. Versions contain one to three numeric components, such as 6, 6.1, or 6.1.2. Example: req = labkit.contract.requirements(\"thermal\", \">=1.0 <2\"); report = labkit.contract.checkRequirements(req); if ~report.ok warning(\"%s\", report.message) end See also labkit.contract.requirements, labkit.contract.assertRequirements, labkit.contract.versionInfo"},{"title":"labkit.contract.requirements","url":"reference/api/labkit/contract/requirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.requirements","text":"REQUIREMENTS Describe the LabKit API versions required by a caller. REQUIREMENTS Describe the LabKit API versions required by a caller. Usage: req = labkit.contract.requirements(\"app\", \">=1.0 <2\", ...) Description: Builds a normalized requirement structure from facade/range pairs. A facade name may be written as \"app\" or \"labkit.app\". Names are converted to lowercase and the optional \"labkit.\" prefix is removed. No compatibility check is performed until checkRequirements or assertRequirements is called. Inputs: varargin - Alternating facade names and version ranges. Facade names are text scalars containing letters, digits, or underscores. A range is a nonempty text scalar containing whitespace-separated constraints such as \">=2.0 <3\". Supported operators are >, >=, <, <=, =, and ==. Outputs: req - Scalar structure with type=\"labkit.requirements\" and a facades structure array. Each facades element has normalized facade and range fields. Calling requirements() with no pairs returns an empty list. Errors: Throws labkit:contract:InvalidRequirements for an odd number of arguments, labkit:contract:InvalidFacadeName for an invalid name, labkit:contract:InvalidVersionRange for an empty or nontext range, and labkit:contract:DuplicateRequirement when a facade appears more than once. Example: req = labkit.contract.requirements( ... \"app\", \">=1 <2\", ... \"image\", \">=4.0 <5\"); report = labkit.contract.checkRequirements(req); See also labkit.contract.checkRequirements, labkit.contract.assertRequirements, labkit.contract.versionInfo"},{"title":"labkit.contract.versionInfo","url":"reference/api/labkit/contract/versionInfo.html","kind":"reference","section":"reference","keywords":"labkit.contract.versionInfo","text":"VERSIONINFO Describe one LabKit API version and its compatibility range. VERSIONINFO Describe one LabKit API version and its compatibility range. Usage: info = labkit.contract.versionInfo(facade, current, compatible, status, notes) Description: Creates the standard structure returned by module version functions. The function normalizes text and validates the status value, but it does not parse current or compatible as semantic versions. checkRequirements parses those values when it performs a comparison. Inputs: facade - Character vector or string scalar, such as \"ui\", \"dta\", or \"labkit.image\". The optional \"labkit.\" prefix is removed. current - Character vector or string scalar containing the current API version, normally a semantic version such as \"2.0.0\". compatible - Character vector, string scalar, or string array containing one or more supported requirement ranges, such as \">=2.0 <3\". status - \"stable\", \"deprecated\", or \"experimental\". Matching is case-insensitive. notes - Character vector or string scalar summarizing the module contract. Outputs: info - Scalar structure with name, facade, current, compatible, status, and notes. name includes the \"labkit.\" prefix; compatible is a string column vector; other text values are string scalars. Errors: Throws labkit:contract:InvalidVersionInfo for nontext scalar inputs, an empty facade, no nonempty compatible ranges, or an unsupported status. Example: info = labkit.contract.versionInfo( ... \"image\", \"4.1.0\", \">=4 <5\", \"stable\", ... \"Image file IO and processing functions.\"); See also labkit.contract.requirements, labkit.contract.checkRequirements"},{"title":"labkit.dta.detectPulses","url":"reference/api/labkit/dta/detectPulses.html","kind":"reference","section":"reference","keywords":"labkit.dta.detectPulses","text":"DETECTPULSES Locate cathodic and anodic pulse windows in chrono data. DETECTPULSES Locate cathodic and anodic pulse windows in chrono data. Usage: [pulse, message] = labkit.dta.detectPulses(t, Im, meta) [pulse, message] = labkit.dta.detectPulses(t, Im, meta, mode) Description: Finds a negative pulse followed by a positive pulse. Metadata modes use ISTEP/TSTEP values when available, or VSTEP/TSTEP values when current-step metadata is absent. Current-based detection identifies dominant negative and positive segments directly from Im. Inputs: t - Numeric time vector in seconds. Values are used in their supplied order and must correspond element-for-element with Im. Im - Numeric current vector in amperes with the same number of elements as t. meta - Chrono metadata structure, usually item.meta from loadFile. A steps field may contain I, V, and T values parsed from ISTEP, VSTEP, and TSTEP metadata. Use struct() when metadata is unavailable. mode - Character vector, string scalar, or structure with a mode field. See Mode Values. Default: \"Metadata first, then auto\". Mode Values: metadata_first - Accepts \"metadata_first\" or \"Metadata first, then auto\". Uses metadata first and falls back to Im when metadata detection fails. This is the default. metadata_only - Accepts \"metadata_only\" or \"Metadata only\". Does not use measured-current fallback. current_only - Accepts \"current_only\" or \"Auto from Im only\". Ignores metadata and detects segments from Im. The threshold is 25 percent of max(abs(Im)), with a 1e-12 A numerical floor. Outputs: pulse - Scalar structure describing the detected windows. pulse.ok is false and numeric window fields are NaN when detection fails. message - Character vector explaining which method succeeded or why detection failed. The same text is stored in pulse.message. Output Fields: ok - Logical success flag. method - \"metadata-current\", \"metadata-voltage\", \"auto-from-Im\", or \"-\". pre - Structure with start_s and end_s. cath - Structure with start_s, end_s, and current_A. gap - Structure with start_s, end_s, and center_s. anod - Structure with start_s, end_s, and current_A. post - Structure with start_s and end_s. Failure Behavior: Missing usable metadata, absent opposite-polarity current phases, or unsupported mode text returns pulse.ok=false with NaN window fields and an explanatory message. t and Im must be corresponding numeric vectors; incompatible MATLAB values or lengths may raise the originating indexing or conversion error. Example: t = (0:0.001:0.020).'; Im = zeros(size(t)); Im(4:7) = -1e-3; Im(13:16) = 1e-3; [pulse, message] = labkit.dta.detectPulses( ... t, Im, struct(), \"Auto from Im only\"); See also labkit.dta.loadFile, labkit.dta.getMainCurve"},{"title":"labkit.dta.detectType","url":"reference/api/labkit/dta/detectType.html","kind":"reference","section":"reference","keywords":"labkit.dta.detectType","text":"DETECTTYPE Identify the supported Gamry DTA data family in a file. DETECTTYPE Identify the supported Gamry DTA data family in a file. Usage: [kind, status] = labkit.dta.detectType(filepath) Description: Reads enough of one DTA file to determine whether it contains EIS, cyclic-voltammetry/charge-time, or chrono data. Detection verifies usable curve columns rather than relying on the filename. A missing or unsupported file is reported in status instead of throwing a read error. Inputs: filepath - Character vector or string scalar naming one DTA file. Outputs: kind - String scalar: \"chrono\", \"eis\", \"cvct\", or \"unknown\". status - Scalar structure describing detection. ok is true only for a recognized file; message is empty on success and explains failure otherwise; kind repeats the detected value; and filepath contains the normalized character-vector path. Errors: Throws labkit:dta:InvalidFilepath when filepath is not a character vector or string scalar. Example: [kind, status] = labkit.dta.detectType(\"experiment.DTA\"); if status.ok fprintf(\"Detected %s data.\\n\", kind) else warning(\"%s\", status.message) end See also labkit.dta.loadFile, labkit.dta.findFiles"},{"title":"labkit.dta.findFiles","url":"reference/api/labkit/dta/findFiles.html","kind":"reference","section":"reference","keywords":"labkit.dta.findFiles","text":"FINDFILES Find DTA files in a folder and its subfolders. FINDFILES Find DTA files in a folder and its subfolders. Usage: filepaths = labkit.dta.findFiles(rootDir) Description: Recursively walks rootDir and collects files whose extension is .dta, using a case-insensitive comparison. The function does not inspect file contents; use detectType or loadFile to validate each result. Inputs: rootDir - Character vector or string scalar naming an existing folder. Outputs: filepaths - Cell row vector of full file paths in directory traversal order. The value is an empty cell array when no DTA files are found. Errors: Throws labkit:dta:InvalidFolder when rootDir is not a text scalar or does not name an existing folder. Typical Call: files = labkit.dta.findFiles(\"data\"); [items, report] = labkit.dta.loadFiles(files, \"auto\"); See also labkit.dta.detectType, labkit.dta.loadFiles, labkit.dta.loadFolder"},{"title":"labkit.dta.getColumn","url":"reference/api/labkit/dta/getColumn.html","kind":"reference","section":"reference","keywords":"labkit.dta.getColumn","text":"GETCOLUMN Extract a named column from a parsed DTA table. GETCOLUMN Extract a named column from a parsed DTA table. Usage: col = labkit.dta.getColumn(tbl, name) Description: Finds the first header that matches name without regard to letter case and returns the corresponding numeric data column. The function does not convert units or remove NaN values. Inputs: tbl - Scalar parsed table or curve structure with headers and data fields. data has one column for each element of headers. name - Character vector or string scalar naming the requested header. Outputs: col - Numeric column vector from tbl.data, or [] when no header matches. Failure Behavior: A missing header returns []. tbl must expose corresponding headers and data fields; malformed structures or an out-of-range data layout raise the originating MATLAB field or indexing error. Typical Call: timeSec = labkit.dta.getColumn(item.curve, \"T\"); currentA = labkit.dta.getColumn(item.curve, \"Im\"); See also labkit.dta.getCurveXY, labkit.dta.getMainCurve, labkit.dta.getZCurve"},{"title":"labkit.dta.getCurveXY","url":"reference/api/labkit/dta/getCurveXY.html","kind":"reference","section":"reference","keywords":"labkit.dta.getCurveXY","text":"GETCURVEXY Extract paired X and Y data from a parsed CV/CT curve. GETCURVEXY Extract paired X and Y data from a parsed CV/CT curve. Usage: [x, y, xname, yname] = labkit.dta.getCurveXY(curve, xsel, ysel) Description: Looks up two headers using case-sensitive, exact-name matching. Rows where either selected column is NaN are removed from both outputs so x and y remain paired. Use getColumn when case-insensitive lookup or unfiltered data is required. Inputs: curve - Scalar parsed curve structure with headers and data fields. xsel - Character vector or string scalar matching one header exactly. ysel - Character vector or string scalar matching one header exactly. Outputs: x - Numeric column vector for xsel, with paired NaN rows removed. y - Numeric column vector for ysel, with paired NaN rows removed. xname - Matched header character vector, or '' when lookup fails. yname - Matched header character vector, or '' when lookup fails. Failure Behavior: If either exact header is absent, all numeric outputs remain empty and unmatched names are ''. curve must expose corresponding headers and data fields; malformed structures raise the originating MATLAB error. Typical Call: [voltageV, currentA, xname, yname] = ... labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); See also labkit.dta.getColumn, labkit.dta.loadFile"},{"title":"labkit.dta.getMainCurve","url":"reference/api/labkit/dta/getMainCurve.html","kind":"reference","section":"reference","keywords":"labkit.dta.getMainCurve","text":"GETMAINCURVE Select the transient table containing T, Vf, and Im data. GETMAINCURVE Select the transient table containing T, Vf, and Im data. Usage: [curve, ok, msg] = labkit.dta.getMainCurve(tables) Description: First selects a table named CURVE or CURVE1. If neither name is present, selects the first table whose headers include T, Vf, and Im, using case-insensitive comparisons. No table is selected when the input is empty or no compatible columns are found. Inputs: tables - Structure array of parsed DTA tables. Each element is expected to contain name, headers, and data fields. Outputs: curve - Selected table structure, or an empty structure on failure. ok - Logical scalar indicating whether a table was selected. msg - Character vector naming the selected table or stating that the main transient table was not found. Failure Behavior: Empty input or no table with the required headers returns curve=struct(), ok=false, and a diagnostic message. Nonempty elements must expose name and headers fields; malformed table structures raise the originating MATLAB field-access error. Typical Call: [curve, ok, msg] = labkit.dta.getMainCurve(item.tables); if ok timeSec = labkit.dta.getColumn(curve, \"T\"); end See also labkit.dta.getColumn, labkit.dta.detectPulses, labkit.dta.loadFile"},{"title":"labkit.dta.getZCurve","url":"reference/api/labkit/dta/getZCurve.html","kind":"reference","section":"reference","keywords":"labkit.dta.getZCurve","text":"GETZCURVE Select the impedance table from parsed EIS data. GETZCURVE Select the impedance table from parsed EIS data. Usage: [curve, ok, msg] = labkit.dta.getZCurve(tables) Description: First selects a table named ZCURVE. If that name is absent, selects the first table whose headers include Freq, Zreal, and Zimag, using case-insensitive comparisons. Inputs: tables - Structure array of parsed DTA tables. Each element is expected to contain name, headers, and data fields. Outputs: curve - Selected impedance table structure, or an empty structure on failure. ok - Logical scalar indicating whether a table was selected. msg - Character vector naming the selected table or stating that a ZCURVE table was not found. Failure Behavior: Empty input or no impedance table returns curve=struct(), ok=false, and a diagnostic message. Nonempty elements must expose name and headers fields; malformed table structures raise the originating MATLAB field-access error. Typical Call: [curve, ok, msg] = labkit.dta.getZCurve(item.tables); if ok frequencyHz = labkit.dta.getColumn(curve, \"Freq\"); end See also labkit.dta.getColumn, labkit.dta.loadFile"},{"title":"labkit.dta.loadFile","url":"reference/api/labkit/dta/loadFile.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFile","text":"LOADFILE Read one supported Gamry DTA file. LOADFILE Read one supported Gamry DTA file. Usage: [item, status] = labkit.dta.loadFile(filepath) [item, status] = labkit.dta.loadFile(filepath, expectedKind) [item, status] = labkit.dta.loadFile(filepath, expectedKind, opts) Description: Detects and parses one chrono, EIS, or CV/CT DTA file. Set expectedKind to a specific family when a workflow must reject other DTA types. Parse and format failures are returned in status; invalid arguments throw errors. Inputs: filepath - Character vector or string scalar naming one DTA file. expectedKind - Character vector or string scalar. Allowed values are \"auto\", \"chrono\", \"eis\", and \"cvct\". Matching is case-insensitive, surrounding whitespace is ignored, and a blank value means \"auto\". Default: \"auto\". opts - Optional scalar structure. Options apply to chrono loading only. Options: pulseMode - Pulse-detection mode accepted by detectPulses, such as \"Metadata first, then auto\", \"Metadata only\", or \"Auto from Im only\". pulseOptions - Structure with a mode field accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: item - Scalar parsed item structure when status.ok is true, or an empty structure array when loading fails. See Output Fields. status - Scalar structure with ok, message, kind, expectedKind, and filepath fields. A kind mismatch reports both the expected and detected family in message. Output Fields: type - \"chrono\", \"eis\", or \"cvct\". filepath - Source path as a character vector. name - Source filename including extension. meta - Parsed metadata for chrono and EIS items. tables - Parsed table structures for chrono and EIS items. t_s - Chrono time in seconds. Vf_V - Chrono measured voltage in volts. Im_A - Chrono measured current in amperes. pulse - Chrono pulse structure returned by detectPulses. freq_Hz - EIS frequency in hertz. Zreal_ohm - EIS real impedance in ohms. Zimag_ohm - EIS imaginary impedance in ohms. Zmod_ohm - EIS impedance magnitude in ohms. Zphz_deg - EIS phase angle in degrees. scanRate_V_per_s - CV/CT scan rate in volts per second. curves - Parsed CV/CT curve structures in file order. analysis - Empty structure reserved for caller-owned results. Errors: Throws labkit:dta:InvalidFilepath for a nonscalar path and labkit:dta:InvalidKind for an unsupported expectedKind. Missing files, unsupported content, parse failures, and type mismatches are returned as status.ok=false rather than thrown. Example: [item, status] = labkit.dta.loadFile(\"measurement.DTA\", \"eis\"); if status.ok semilogx(item.freq_Hz, item.Zmod_ohm) else warning(\"%s\", status.message) end See also labkit.dta.detectType, labkit.dta.loadFiles, labkit.dta.detectPulses"},{"title":"labkit.dta.loadFiles","url":"reference/api/labkit/dta/loadFiles.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFiles","text":"LOADFILES Read a list of Gamry DTA files. LOADFILES Read a list of Gamry DTA files. Usage: [items, report] = labkit.dta.loadFiles(filepaths) [items, report] = labkit.dta.loadFiles(filepaths, expectedKind) [items, report] = labkit.dta.loadFiles(filepaths, expectedKind, opts) Description: Calls loadFile for each path in input order. Successful items are returned in items; failures do not stop the batch and are recorded in report. The positions in items therefore need not match the positions in filepaths; use report.statuses when per-input correspondence matters. Inputs: filepaths - One character-vector or string-scalar path, a string array, or a cell array of character vectors and string scalars. An empty input is allowed and returns an empty report. expectedKind - \"auto\", \"chrono\", \"eis\", or \"cvct\", with the same normalization as loadFile. Default: \"auto\". opts - Optional scalar structure forwarded unchanged to loadFile. Options: pulseMode - Pulse-detection mode used for chrono files. See detectPulses for the legal labels and normalization behavior. pulseOptions - Scalar structure accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: items - Cell row vector containing successfully loaded item structures in input order, with failed inputs omitted. report - Scalar structure summarizing every requested path. Output Fields: loaded - Cell array of paths that loaded successfully. failed - Structure array with filepath, kind, and message for failures. statuses - One loadFile status structure per requested input, in input order. nRequested - Number of input paths. nLoaded - Number of successful items. nFailed - Number of failed inputs. Errors: Throws labkit:dta:InvalidFilepaths or labkit:dta:InvalidFilepath when the path collection contains unsupported values, and labkit:dta:InvalidKind for an unsupported expectedKind. Individual file read failures are stored in report instead of thrown. Example: files = [\"run-01.DTA\", \"run-02.DTA\"]; [items, report] = labkit.dta.loadFiles(files, \"chrono\"); fprintf(\"Loaded %d of %d files.\\n\", report.nLoaded, report.nRequested) See also labkit.dta.loadFile, labkit.dta.findFiles, labkit.dta.loadFolder"},{"title":"labkit.dta.loadFolder","url":"reference/api/labkit/dta/loadFolder.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFolder","text":"LOADFOLDER Read every DTA file below a folder. LOADFOLDER Read every DTA file below a folder. Usage: [items, report] = labkit.dta.loadFolder(rootDir) [items, report] = labkit.dta.loadFolder(rootDir, expectedKind) [items, report] = labkit.dta.loadFolder(rootDir, expectedKind, opts) Description: Recursively discovers .dta files with findFiles and passes the resulting list to loadFiles. Unsupported or unreadable files are recorded in report while readable items are retained. Inputs: rootDir - Character vector or string scalar naming an existing folder. expectedKind - \"auto\", \"chrono\", \"eis\", or \"cvct\", with the same normalization as loadFile. Default: \"auto\". opts - Optional scalar structure forwarded to loadFiles and loadFile. Options: pulseMode - Pulse-detection mode used for discovered chrono files. See detectPulses for the legal labels and normalization behavior. pulseOptions - Scalar structure accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: items - Cell row vector of successfully loaded item structures. report - loadFiles report with three additional discovery fields. Output Fields: folder - Requested root folder as a character vector. filepaths - Cell array of discovered DTA paths in traversal order. nDiscovered - Number of discovered paths. This equals nRequested. loaded - Cell array of paths that loaded successfully. failed - Structure array describing failed inputs. statuses - One loadFile status per discovered input. nRequested - Number of discovered paths sent to loadFiles. nLoaded - Number of successful items. nFailed - Number of failed inputs. Errors: Throws labkit:dta:InvalidFolder for an invalid rootDir and labkit:dta:InvalidKind for an unsupported expectedKind. Typical Call: [items, report] = labkit.dta.loadFolder(\"experiment\", \"auto\"); fprintf(\"Found %d DTA files; loaded %d.\\n\", ... report.nDiscovered, report.nLoaded) See also labkit.dta.findFiles, labkit.dta.loadFiles, labkit.dta.loadFile"},{"title":"labkit.dta.version","url":"reference/api/labkit/dta/version.html","kind":"reference","section":"reference","keywords":"labkit.dta.version","text":"VERSION Return version information for the DTA API. VERSION Return version information for the DTA API. Usage: info = labkit.dta.version() Description: Reports the API version and compatibility range used when an app declares a dependency on labkit.dta. This is not the Gamry software version or a version identifier read from a DTA file. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.dta\"; facade is \"dta\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.dta.version(); fprintf(\"DTA API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.image.adjustBrightnessContrast","url":"reference/api/labkit/image/adjustBrightnessContrast.html","kind":"reference","section":"reference","keywords":"labkit.image.adjustBrightnessContrast","text":"ADJUSTBRIGHTNESSCONTRAST Apply simple brightness and contrast adjustment. ADJUSTBRIGHTNESSCONTRAST Apply simple brightness and contrast adjustment. Usage: imageOut = labkit.image.adjustBrightnessContrast(imageIn, brightnessPct, contrastPct) Description: Converts imageIn to clamped RGB double, then applies a linear adjustment around mid-gray. For normalized input x, the calculation is (x - 0.5)*(1 + contrastPct/100) + 0.5 + brightnessPct/100. A contrast scale below zero is limited to zero, and final values are clamped to [0,1]. Positive brightness shifts every channel upward. Positive contrast moves values away from 0.5; negative contrast moves them toward 0.5. A contrastPct of -100 produces a flat mid-gray image before the brightness offset is applied. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. brightnessPct - Numeric scalar offset as percent of normalized full scale. contrastPct - Numeric scalar percent change in contrast around 0.5. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. brightnessPct and contrastPct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = reshape(linspace(0, 1, 12), 3, 4); imageOut = labkit.image.adjustBrightnessContrast(imageIn, 10, 20); See also labkit.image.adjustHueSaturation, labkit.image.grayWorldWhiteBalance, labkit.image.im2double"},{"title":"labkit.image.adjustHueSaturation","url":"reference/api/labkit/image/adjustHueSaturation.html","kind":"reference","section":"reference","keywords":"labkit.image.adjustHueSaturation","text":"ADJUSTHUESATURATION Apply HSV hue and saturation adjustment. ADJUSTHUESATURATION Apply HSV hue and saturation adjustment. Usage: imageOut = labkit.image.adjustHueSaturation(imageIn, hueDeg, saturationPct) Description: Converts imageIn to clamped RGB double and then to HSV. hueDeg rotates the hue circle and wraps at 360 degrees. Saturation is multiplied by 1 + saturationPct/100 and limited to [0,1]. The HSV value channel is not changed directly. A saturationPct of -100 or less produces grayscale. Positive saturation increases colorfulness until individual saturation values reach 1. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. hueDeg - Numeric scalar hue rotation in degrees. Values outside one turn are wrapped. saturationPct - Numeric scalar percent change in HSV saturation. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. hueDeg and saturationPct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = cat(3, ones(2), 0.4*ones(2), zeros(2)); imageOut = labkit.image.adjustHueSaturation(imageIn, 30, 25); See also labkit.image.adjustBrightnessContrast, labkit.image.grayWorldWhiteBalance"},{"title":"labkit.image.assertSupportedPaths","url":"reference/api/labkit/image/assertSupportedPaths.html","kind":"reference","section":"reference","keywords":"labkit.image.assertSupportedPaths","text":"ASSERTSUPPORTEDPATHS Throw when any path has an unsupported image extension. ASSERTSUPPORTEDPATHS Throw when any path has an unsupported image extension. Usage: labkit.image.assertSupportedPaths(paths) Description: Validates a collection of source-image paths by filename extension. The function accepts the same path forms as labkit.image.normalizePaths and stops at the first unsupported extension. It does not test whether files exist or contain valid image data. Empty input succeeds. Inputs: paths - Character vector, string array, cell array, or empty value accepted by labkit.image.normalizePaths. Outputs: None. Errors: labkit:image:UnsupportedImageFile - A normalized path has an extension outside supportedExtensions. labkit:image:InvalidPaths - paths has an unsupported container type. Example: labkit.image.assertSupportedPaths([\"frame01.png\"; \"frame02.TIF\"]); See also labkit.image.normalizePaths, labkit.image.isSupportedPath, labkit.image.supportedExtensions"},{"title":"labkit.image.displayName","url":"reference/api/labkit/image/displayName.html","kind":"reference","section":"reference","keywords":"labkit.image.displayName","text":"DISPLAYNAME Return a short image-file display name. DISPLAYNAME Return a short image-file display name. Usage: name = labkit.image.displayName(pathValue) Description: Removes the folder portion of one path and returns the final filename with its extension. The function performs text processing only; the path does not need to exist. If fileparts cannot derive a filename, the trimmed input text is returned. Inputs: pathValue - Character vector, string scalar, or another value convertible to one string scalar. Outputs: name - String scalar containing the filename and extension. Errors: labkit:image:InvalidPath - pathValue converts to more than one string. Example: name = labkit.image.displayName(fullfile(\"images\", \"frame01.tif\")); See also labkit.image.normalizePaths, labkit.image.readFiles"},{"title":"labkit.image.ensureRgb","url":"reference/api/labkit/image/ensureRgb.html","kind":"reference","section":"reference","keywords":"labkit.image.ensureRgb","text":"ENSURERGB Return image data with exactly three color channels. ENSURERGB Return image data with exactly three color channels. Usage: rgb = labkit.image.ensureRgb(imageData) Description: Converts grayscale image arrays to RGB by copying the same values into the red, green, and blue channels. RGB input passes through unchanged, and input with more than three channels keeps only the first three. This is useful for removing an alpha or auxiliary channel after image import. The function does not change numeric values, normalize intensity, or convert the input class. Empty input is returned with its original empty shape. Inputs: imageData - Nonsparse numeric or logical M-by-N grayscale, M-by-N-by-1 grayscale, M-by-N-by-3 RGB, or M-by-N-by-C array with C greater than three. Outputs: rgb - Array with the same class and first two dimensions as imageData. Nonempty output has exactly three channels. Errors: labkit:image:ensureRgb:InvalidChannelCount - Nonempty 3-D input has two color channels. Example: gray = uint8([0 64; 128 255]); rgb = labkit.image.ensureRgb(gray); See also labkit.image.im2double, labkit.image.rgb2gray"},{"title":"labkit.image.fileDialogFilter","url":"reference/api/labkit/image/fileDialogFilter.html","kind":"reference","section":"reference","keywords":"labkit.image.fileDialogFilter","text":"FILEDIALOGFILTER Return a file-chooser-compatible image filter. FILEDIALOGFILTER Return a file-chooser-compatible image filter. Usage: filterSpec = labkit.image.fileDialogFilter() filterSpec = labkit.image.fileDialogFilter(\"IncludeAll\", true) Description: Builds the two-column filter specification used by MATLAB file-selection dialogs and LabKit file panels. The first row contains every extension returned by labkit.image.supportedExtensions. By default, files outside that set are hidden from the chooser. Inputs: None. Name-Value Arguments: IncludeAll - Logical scalar. true appends an \"All files (*.*)\" row after the image filter. The default is false. Numeric scalars are converted to logical values. Outputs: filterSpec - One- or two-row cell array. Column 1 contains wildcard patterns and column 2 contains user-visible descriptions. Errors: MATLAB inputParser errors are raised for an unknown name, a missing name-value partner, or an IncludeAll value that is not a logical or numeric scalar. Example: filterSpec = labkit.image.fileDialogFilter(\"IncludeAll\", true); See also labkit.image.supportedExtensions, labkit.image.isSupportedPath"},{"title":"labkit.image.grayWorldWhiteBalance","url":"reference/api/labkit/image/grayWorldWhiteBalance.html","kind":"reference","section":"reference","keywords":"labkit.image.grayWorldWhiteBalance","text":"GRAYWORLDWHITEBALANCE Apply generic gray-world white balance. GRAYWORLDWHITEBALANCE Apply generic gray-world white balance. Usage: imageOut = labkit.image.grayWorldWhiteBalance(imageIn, strengthPct, temperaturePct) Description: Estimates one gain per RGB channel so that the three channel means move toward their common mean. strengthPct blends between the original image and the fully balanced result. This is the gray-world assumption: over a representative scene, the average reflected color is expected to be neutral. temperaturePct adds a small red offset and subtracts the same blue offset after gain correction. Positive values produce a warmer result; negative values produce a cooler result. The temperature offset is 0.08 times the normalized percentage. Final values are clamped to [0,1]. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. strengthPct - Numeric scalar blend percentage. Values below 0 become 0; values above 100 become 100. temperaturePct - Numeric scalar warm/cool adjustment in percent. Use 0 for neutral gray-world balancing. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. strengthPct and temperaturePct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = cat(3, 0.3*ones(4), 0.5*ones(4), 0.7*ones(4)); imageOut = labkit.image.grayWorldWhiteBalance(imageIn, 100, 0); See also labkit.image.adjustBrightnessContrast, labkit.image.adjustHueSaturation"},{"title":"labkit.image.im2double","url":"reference/api/labkit/image/im2double.html","kind":"reference","section":"reference","keywords":"labkit.image.im2double","text":"IM2DOUBLE Convert image data to double using MATLAB's im2double contract. IM2DOUBLE Convert image data to double using MATLAB's im2double contract. Usage: imageOut = labkit.image.im2double(imageIn) imageOut = labkit.image.im2double(indexedImage, \"indexed\") Description: Converts image data to double without requiring Image Processing Toolbox. Logical values become 0 or 1. uint8 and uint16 values are divided by the maximum value of their class. int16 values are mapped linearly from the complete signed range to [0,1]. single and double values are converted to double without rescaling or clipping. For uint8 or uint16 indexed images, the \"indexed\" form converts the zero-based integer storage to one-based double colormap indices. For example, uint8 values [0 1] become [1 2]. Empty input returns an empty double array. Inputs: imageIn - Logical, uint8, uint16, int16, single, or double image array. imageType - Optional character vector or scalar string \"indexed\". No other image-type value is accepted. Outputs: imageOut - Double array with the same size as imageIn. Errors: labkit:image:im2double:InvalidImageType - imageType is not \"indexed\". labkit:image:im2double:UnsupportedClass - imageIn has an unsupported numeric class. Example: intensity = labkit.image.im2double(uint8([0 128 255])); indices = labkit.image.im2double(uint8([0 1 2]), \"indexed\"); See also labkit.image.ensureRgb, labkit.image.rgb2gray"},{"title":"labkit.image.isSupportedPath","url":"reference/api/labkit/image/isSupportedPath.html","kind":"reference","section":"reference","keywords":"labkit.image.isSupportedPath","text":"ISSUPPORTEDPATH Return true when a path has a supported image extension. ISSUPPORTEDPATH Return true when a path has a supported image extension. Usage: tf = labkit.image.isSupportedPath(pathValue) Description: Compares the final filename extension with labkit.image.supportedExtensions. The comparison is case-insensitive and does not open the file, inspect its contents, or require it to exist. A path with no extension returns false. Inputs: pathValue - One value convertible to text and accepted by fileparts. Outputs: tf - Logical scalar indicating whether the extension is supported. Failure Behavior: Missing or unknown extensions return false without accessing the file system. pathValue must be convertible to scalar text; unsupported MATLAB values or shapes may raise the originating string or fileparts error. Example: tf = labkit.image.isSupportedPath(\"experiment.TIFF\"); See also labkit.image.supportedExtensions, labkit.image.assertSupportedPaths, labkit.image.normalizePaths"},{"title":"labkit.image.localContrast","url":"reference/api/labkit/image/localContrast.html","kind":"reference","section":"reference","keywords":"labkit.image.localContrast","text":"LOCALCONTRAST Enhance local value-channel contrast with a mean-filter mask. LOCALCONTRAST Enhance local value-channel contrast with a mean-filter mask. Usage: imageOut = labkit.image.localContrast(imageIn, amountPct, radiusPx) Description: Converts imageIn to clamped RGB double and operates on the HSV value channel. A square local mean with width 2*radius+1 forms the low-frequency reference. The difference between the value channel and that mean is multiplied by 1.5*amountPct/100 and added back before clipping. Hue and saturation are preserved by the adjustment. At image boundaries, the local mean uses only available pixels. This is a lightweight local contrast operation, not adaptive histogram equalization. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. amountPct - Numeric scalar effect strength in percent. Negative values are treated as 0. radiusPx - Numeric scalar neighborhood radius in pixels. The value is rounded and limited to at least 1. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. amountPct and radiusPx must be numeric scalars; incompatible values raise the originating conversion or indexing error. Example: imageIn = repmat(linspace(0.2, 0.8, 20), 10, 1); imageOut = labkit.image.localContrast(imageIn, 40, 3); See also labkit.image.meanFilter2, labkit.image.sharpen"},{"title":"labkit.image.meanFilter2","url":"reference/api/labkit/image/meanFilter2.html","kind":"reference","section":"reference","keywords":"labkit.image.meanFilter2","text":"MEANFILTER2 Apply normalized 2-D mean filtering with edge correction. MEANFILTER2 Apply normalized 2-D mean filtering with edge correction. Usage: imageOut = labkit.image.meanFilter2(imageIn, windowSize) Description: Replaces each sample with the arithmetic mean of the rectangular neighborhood that overlaps the image. At an edge or corner, the divisor is reduced to the number of available samples instead of assuming zero padding. Each channel of a 3-D image is filtered independently. windowSize is rounded to an integer and limited to at least 1. Both odd and even widths are accepted; conv2 determines the alignment of an even window. Input is converted to double for calculation. NaN values are not omitted and therefore propagate to neighborhoods that include them. Inputs: imageIn - 2-D image matrix or 3-D image with channels in dimension 3. windowSize - Numeric scalar neighborhood width in pixels. Outputs: imageOut - Double array with the same size as imageIn. Failure Behavior: imageIn must be compatible with double and conv2, and windowSize must be convertible to one numeric scalar. Unsupported types, sparse arrays, or nonscalar window values raise the originating MATLAB error. Example: impulse = [0 0 0; 0 1 0; 0 0 0]; smoothed = labkit.image.meanFilter2(impulse, 3); See also labkit.image.localContrast, labkit.image.sharpen"},{"title":"labkit.image.normalizePaths","url":"reference/api/labkit/image/normalizePaths.html","kind":"reference","section":"reference","keywords":"labkit.image.normalizePaths","text":"NORMALIZEPATHS Normalize image file path inputs to a string column. NORMALIZEPATHS Normalize image file path inputs to a string column. Usage: paths = labkit.image.normalizePaths(paths) paths = labkit.image.normalizePaths(paths, \"AllowEmpty\", false) Description: Converts the path forms commonly returned by MATLAB file selection into one string column. Leading and trailing whitespace is removed and blank entries are discarded. Order and duplicate paths are preserved. A single character vector is treated as one path rather than one path per row. This function normalizes text only. It does not expand relative paths, validate extensions, remove duplicates, or test file existence. Inputs: paths - Character vector, string array, cell array, or empty value. Outputs: paths - String column containing the nonblank normalized paths. Name-Value Arguments: AllowEmpty - Logical scalar controlling whether zero paths are accepted. The default is true. false throws labkit:image:NoPaths when normalization produces an empty result. Errors: labkit:image:InvalidPaths - paths is not char, string, cell, or empty. labkit:image:NoPaths - No path remains and AllowEmpty is false. Example: paths = labkit.image.normalizePaths([\" frame01.png \"; \"\"; \"frame02.JPG\"]); See also labkit.image.assertSupportedPaths, labkit.image.isSupportedPath, labkit.image.readFiles"},{"title":"labkit.image.previewBudget","url":"reference/api/labkit/image/previewBudget.html","kind":"reference","section":"reference","keywords":"labkit.image.previewBudget","text":"PREVIEWBUDGET Downsample image data to fit a display pixel budget. PREVIEWBUDGET Downsample image data to fit a display pixel budget. Usage: [preview, info] = labkit.image.previewBudget(imageData) [preview, info] = labkit.image.previewBudget(imageData, Name, Value) Description: When MaxPixels is finite, produces a lightweight preview by taking every Nth row and column. N is the smallest integer stride whose estimated processing area does not exceed MaxPixels. The estimate is source rows times source columns times Expansion, so callers can reserve memory for workflows that pad, tile, or otherwise enlarge an image before display. The default is Inf and preserves native pixels; each caller owns whether a display budget is appropriate for its workflow. This is sampling rather than interpolating resize. The first source pixel is always retained, and image class and channel count are preserved. A preview may use fewer pixels than the budget because the stride is an integer. Inputs: imageData - Nonempty numeric or logical 2-D or 3-D image array. Name-Value Arguments: MaxPixels - Positive display-area budget or Inf. Default: Inf, retaining native pixels. Invalid, empty, or nonpositive values fall back to the default rather than throwing an error. Expansion - Positive multiplier applied only to estimatedPixels. The default is 1. Invalid values fall back to 1. Outputs: preview - imageData sampled with the selected integer stride. info - Scalar structure describing the preview calculation. Info Fields: scaleFactor - Integer row and column stride N. coordinateScale - Reciprocal 1/N for mapping source distances to the sampled preview scale. maxPixels - Validated display-area budget. estimatedPixels - Source row-column area multiplied by Expansion. sourceSize - Original size vector. previewSize - Returned preview size vector. Errors: labkit:image:InvalidImageData - imageData is empty, nonnumeric/nonlogical, or has more than three dimensions. labkit:image:InvalidPreviewBudgetOptions - Name-value arguments are not paired. labkit:image:InvalidPreviewBudgetOption - An option name is unsupported. Example: rgb = zeros(100, 200, 3, 'uint8'); [preview, info] = labkit.image.previewBudget(rgb, ... \"MaxPixels\", 5000, \"Expansion\", 2); See also labkit.image.resizeToFit"},{"title":"labkit.image.readFiles","url":"reference/api/labkit/image/readFiles.html","kind":"reference","section":"reference","keywords":"labkit.image.readFiles","text":"READFILES Read image files into path/name/image records. READFILES Read image files into path/name/image records. Usage: records = labkit.image.readFiles(paths) records = labkit.image.readFiles(paths, opts) Description: Reads one or more image files in the supplied order and returns a uniform record for each file. The default path validates source extensions and file existence, converts data with labkit.image.im2double, expands grayscale to RGB, discards channels after the first three, and clamps values to [0,1]. Set Normalize to false when the exact class and channel layout returned by imread must be preserved. progressFcn is called immediately before and after every imread. If a read, conversion, or callback fails, the error propagates and no partial records array is returned. Inputs: paths - Character vector, string array, cell array, or empty value accepted by labkit.image.normalizePaths. opts - Optional scalar struct containing the fields listed below. Options: Normalize - Logical scalar controlling conversion to clamped RGB double. The default is true. AllowEmpty - Logical scalar controlling whether an empty path collection returns an empty record column. The default is true. ValidateExtensions - Logical scalar controlling validation with assertSupportedPaths. The default is true. RequireExisting - Logical scalar controlling the explicit file-existence check before imread. The default is true. imread can still fail when this option is false. progressFcn - Function handle or []. The callback receives a scalar structure before and after each read. The default is []. Outputs: records - N-by-1 structure array with path, name, and image fields. path and name are strings; image contains the normalized or raw image matrix. Progress Fields: stage - \"beforeRead\" or \"afterRead\". index - 1-based index of the current path. count - Total number of paths. path - Current path as a string scalar. name - Filename and extension from labkit.image.displayName. Errors: labkit:image:InvalidOptions - opts or one of its fields is invalid. labkit:image:NoPaths - paths is empty while AllowEmpty is false. labkit:image:UnsupportedImageFile - An extension is unsupported while ValidateExtensions is true. labkit:image:ImageFileNotFound - A file is missing while RequireExisting is true. Typical Call: opts = struct('Normalize', true, 'progressFcn', @updateProgress); records = labkit.image.readFiles(selectedPaths, opts); See also labkit.image.normalizePaths, labkit.image.assertSupportedPaths, labkit.image.writeFile"},{"title":"labkit.image.resizeToFit","url":"reference/api/labkit/image/resizeToFit.html","kind":"reference","section":"reference","keywords":"labkit.image.resizeToFit","text":"RESIZETOFIT Resize an image to fit within maximum row/column limits. RESIZETOFIT Resize an image to fit within maximum row/column limits. Usage: imageOut = labkit.image.resizeToFit(imageIn) imageOut = labkit.image.resizeToFit(imageIn, Name, Value) [imageOut, scale] = labkit.image.resizeToFit(___) Description: Computes one uniform scale that satisfies both the maximum row and column limits, preserving aspect ratio. With the default AllowUpscale value, images that already fit are returned unchanged. Target dimensions are rounded to the nearest integer and limited to at least one pixel. \"nearest\" samples endpoint-aligned nearest input pixels. \"bilinear\" performs endpoint-aligned linear interpolation independently in each channel. Output keeps the input class; integer linear results are rounded and limited to the class range. Inputs: imageIn - Numeric or logical 2-D image or 3-D multichannel image. Empty input is returned unchanged with scale 1. Name-Value Arguments: MaxHeight - Positive maximum output row count. The default is Inf. MaxWidth - Positive maximum output column count. The default is Inf. AllowUpscale - Logical scalar controlling enlargement when both limits permit it. The default is false. Method - \"bilinear\" (default) or \"nearest\". Outputs: imageOut - Resized image, or imageIn unchanged when scale is 1. scale - Uniform requested scale before target dimensions are rounded. Errors: MATLAB inputParser errors are raised for invalid limits, AllowUpscale, malformed name-value arguments, or unsupported option names. labkit:image:InvalidResizeMethod - Method is not \"bilinear\" or \"nearest\". Unsupported image classes or dimensions may raise the originating MATLAB interpolation or cast error. Example: imageIn = reshape(uint8(1:200), 10, 20); [imageOut, scale] = labkit.image.resizeToFit(imageIn, ... \"MaxHeight\", 5, \"Method\", \"nearest\"); See also labkit.image.previewBudget"},{"title":"labkit.image.rgb2gray","url":"reference/api/labkit/image/rgb2gray.html","kind":"reference","section":"reference","keywords":"labkit.image.rgb2gray","text":"RGB2GRAY Convert RGB data using MATLAB's rgb2gray call contract. RGB2GRAY Convert RGB data using MATLAB's rgb2gray call contract. Usage: gray = labkit.image.rgb2gray(rgb) Description: Converts an RGB image with the ITU-R BT.601 luma coefficients 0.2989, 0.5870, and 0.1140 for red, green, and blue. The output retains the input numeric class. Integer results therefore use MATLAB's normal cast and rounding behavior. For an M-by-3 colormap, each row is converted to one luma value and that value is repeated across three columns. This preserves the colormap shape while removing color. The function does not clamp floating-point input. Inputs: rgb - Real, nonsparse M-by-N-by-3 RGB image or M-by-3 colormap of class uint8, uint16, int16, single, or double. Outputs: gray - M-by-N grayscale image or M-by-3 grayscale colormap with the same numeric class as rgb. Errors: labkit:image:rgb2gray:InvalidShape - rgb is neither an RGB image nor an M-by-3 colormap. Example: rgb = cat(3, ones(2), zeros(2), zeros(2)); gray = labkit.image.rgb2gray(rgb); See also labkit.image.ensureRgb, labkit.image.im2double"},{"title":"labkit.image.sharpen","url":"reference/api/labkit/image/sharpen.html","kind":"reference","section":"reference","keywords":"labkit.image.sharpen","text":"SHARPEN Apply generic unsharp-mask sharpening. SHARPEN Apply generic unsharp-mask sharpening. Usage: imageOut = labkit.image.sharpen(imageIn, amountPct, radiusPx) Description: Converts imageIn to clamped RGB double and constructs an unsharp mask by subtracting a square local mean from the image. The detail mask is scaled by 2*amountPct/100 and added to the original. Each RGB channel is treated independently, and the result is clamped to [0,1]. radiusPx selects an odd mean-filter width of 2*round(radius)+1, with a minimum width of 3 pixels. This function uses a box blur rather than a Gaussian blur, so radius describes neighborhood size rather than a Gaussian standard deviation. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. amountPct - Numeric scalar effect strength in percent. Negative values are treated as 0. radiusPx - Numeric scalar blur radius in pixels. Values below 0.5 are treated as 0.5 before rounding. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. amountPct and radiusPx must be numeric scalars; incompatible values raise the originating conversion or indexing error. Example: imageIn = zeros(9); imageIn(3:7, 3:7) = 0.6; imageOut = labkit.image.sharpen(imageIn, 50, 1); See also labkit.image.meanFilter2, labkit.image.localContrast"},{"title":"labkit.image.supportedExtensions","url":"reference/api/labkit/image/supportedExtensions.html","kind":"reference","section":"reference","keywords":"labkit.image.supportedExtensions","text":"SUPPORTEDEXTENSIONS Return extensions supported by LabKit image file inputs. SUPPORTEDEXTENSIONS Return extensions supported by LabKit image file inputs. Usage: extensions = labkit.image.supportedExtensions() Description: Lists the filename extensions accepted by the generic LabKit image input functions. Matching in labkit.image.isSupportedPath is case-insensitive. This list describes source-image support; an individual app may offer a smaller set of export formats. Inputs: None. Outputs: extensions - String column of lowercase extensions, including the leading dot. The current values are .png, .jpg, .jpeg, .tif, .tiff, and .bmp. Failure Behavior: The function accepts no caller input and performs no file-system access. It returns the fixed facade extension list without a recoverable failure state. Example: extensions = labkit.image.supportedExtensions(); acceptsTiff = any(extensions == \".tiff\"); See also labkit.image.isSupportedPath, labkit.image.fileDialogFilter"},{"title":"labkit.image.version","url":"reference/api/labkit/image/version.html","kind":"reference","section":"reference","keywords":"labkit.image.version","text":"VERSION Return the LabKit image facade contract version. VERSION Return the LabKit image facade contract version. Usage: info = labkit.image.version() Description: Reports the semantic version and compatibility range of the public labkit.image API. Apps can use this structure to check whether the installed image facade satisfies a saved or declared requirement. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo with name, facade, current, compatible, status, and notes fields. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.image.version(); currentVersion = info.current; See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.image.writeFile","url":"reference/api/labkit/image/writeFile.html","kind":"reference","section":"reference","keywords":"labkit.image.writeFile","text":"WRITEFILE Write one image file, creating the parent folder when needed. WRITEFILE Write one image file, creating the parent folder when needed. Usage: labkit.image.writeFile(imageData, filepath) Description: Writes imageData with MATLAB imwrite. The extension in filepath selects the output format. If the parent folder does not exist, the function creates it, including intermediate folders supported by mkdir. Existing files are replaced according to imwrite behavior. The function does not rescale, clamp, or otherwise normalize imageData. Errors from folder creation and imwrite propagate to the caller. Inputs: imageData - Image matrix accepted by MATLAB imwrite. filepath - Nonempty character vector or scalar string. The filename extension controls the output format. Outputs: None. Errors: labkit:image:InvalidOutputPath - filepath is empty or nonscalar. Other folder and image-format errors are raised by mkdir or imwrite. Typical Call: labkit.image.writeFile(processedImage, fullfile(outputFolder, \"result.png\")); See also labkit.image.readFiles, labkit.image.displayName"},{"title":"labkit.rhs.findFiles","url":"reference/api/labkit/rhs/findFiles.html","kind":"reference","section":"reference","keywords":"labkit.rhs.findFiles","text":"FINDFILES Find Intan RHS files in a folder and its subfolders. FINDFILES Find Intan RHS files in a folder and its subfolders. Usage: filepaths = labkit.rhs.findFiles(rootDir) Description: Recursively collects files whose extension is .rhs and sorts their full paths for reproducible batch order. The function does not inspect file headers; use inspectFile when the contents must be validated. Inputs: rootDir - Character vector or string scalar naming an existing folder. Outputs: filepaths - Cell column vector of sorted full paths. The value is an empty cell array when no RHS files are found. Errors: Throws labkit:rhs:InvalidFolder when rootDir is not a text scalar or does not name an existing folder. Typical Call: files = labkit.rhs.findFiles(\"recordings\"); [info, status] = labkit.rhs.inspectFile(files{1}); See also labkit.rhs.inspectFile, labkit.rhs.indexFile, labkit.rhs.readWindow"},{"title":"labkit.rhs.indexFile","url":"reference/api/labkit/rhs/indexFile.html","kind":"reference","section":"reference","keywords":"labkit.rhs.indexFile","text":"INDEXFILE Build a block index for reading selected RHS time windows. INDEXFILE Build a block index for reading selected RHS time windows. Usage: [index, status] = labkit.rhs.indexFile(filepath) Description: Inspects the file header and records the byte and sample layout used by readWindow. No waveform arrays are loaded. Header-only files produce a valid index with hasData=false. When trailing partial bytes are present, complete blocks remain usable and status.message describes the condition. Inputs: filepath - Character vector or string scalar naming one RHS file. Outputs: index - Scalar structure containing header information and block layout. On inspection failure, hasData is false and numeric layout fields use empty defaults. status - Scalar structure with ok, message, and filepath. The index can be usable when ok is true and message reports trailing partial bytes. Output Fields: info - Full structure returned by inspectFile. sampleRateHz - Sample rate in hertz. samplesPerBlock - Number of samples in each complete block; currently 128. dataOffsetBytes - Byte offset of the first waveform block. bytesPerBlock - Calculated bytes in one complete block. dataBytes - Number of bytes following the header. blockCount - Number of complete waveform blocks. sampleCount - Number of samples in complete blocks. durationSec - Complete-block duration in seconds. exactBlocks - True when no trailing partial bytes remain. hasData - True when at least one complete sample is available. Errors: Throws labkit:rhs:InvalidFilepath for an invalid filepath argument. File and header failures are returned in status. Example: [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); if status.ok && index.hasData fprintf(\"%d samples in %d blocks\\n\", ... index.sampleCount, index.blockCount) end See also labkit.rhs.inspectFile, labkit.rhs.readWindow"},{"title":"labkit.rhs.inspectFile","url":"reference/api/labkit/rhs/inspectFile.html","kind":"reference","section":"reference","keywords":"labkit.rhs.inspectFile","text":"INSPECTFILE Read metadata and data-layout information from an RHS file. INSPECTFILE Read metadata and data-layout information from an RHS file. Usage: [info, status] = labkit.rhs.inspectFile(filepath) Description: Reads the Intan RHS header and calculates the complete-block count, available sample count, and recording duration without loading waveform arrays. A missing, malformed, or unsupported file is returned as status.ok=false after filepath validation. Inputs: filepath - Character vector or string scalar naming one RHS file. Outputs: info - Scalar structure containing the parsed header and calculated file layout. On failure, identity fields remain available and other fields contain empty or NaN values. status - Scalar structure with ok, message, and filepath. message is empty on success and explains the read failure otherwise. Output Fields: fileVersion - Two-element numeric vector [major minor]. sampleRateHz - Amplifier sample rate in hertz. durationSec - Duration represented by complete data blocks, in seconds. channelFamilies - Structures for amplifier, boardAdc, boardDac, boardDigIn, and boardDigOut channels. channelTable - Table containing family and channel-name metadata for all enabled stored channels. frequencyParameters - Requested and actual filter/sample frequencies. stimParameters - Stimulation step size and charge-recovery settings. dcAmplifierSaved - Logical flag indicating whether DC amplifier samples are present. dataOffsetBytes - Byte offset of the first waveform block. bytesPerBlock - Calculated bytes in one complete 128-sample block. blockCount - Number of complete waveform blocks. sampleCount - Number of samples in complete blocks. exactBlocks - True when the data section has no trailing partial bytes. Errors: Throws labkit:rhs:InvalidFilepath when filepath is not a character vector or string scalar. File-open and header-format errors are returned in status instead of thrown. Example: [info, status] = labkit.rhs.inspectFile(\"recording.rhs\"); if status.ok fprintf(\"%.1f s at %.0f Hz\\n\", info.durationSec, info.sampleRateHz) disp(info.channelTable(:, [\"family\", \"nativeName\", \"customName\"])) end See also labkit.rhs.indexFile, labkit.rhs.readWindow, labkit.rhs.findFiles"},{"title":"labkit.rhs.readWindow","url":"reference/api/labkit/rhs/readWindow.html","kind":"reference","section":"reference","keywords":"labkit.rhs.readWindow","text":"READWINDOW Read selected channels and times from an Intan RHS file. READWINDOW Read selected channels and times from an Intan RHS file. Usage: [window, status] = labkit.rhs.readWindow(filepath) [window, status] = labkit.rhs.readWindow(filepath, opts) Description: Reads only the complete data blocks needed for one time interval and one channel family. Returned values are converted to physical units using the Intan RHS scaling constants. File/header/read failures are returned in status; invalid option values throw errors. Inputs: filepath - Character vector or string scalar naming one RHS file. opts - Optional scalar structure. See Options. Options: family - String scalar selecting a channel family. Canonical values are \"amplifier\", \"stim\", \"dcAmplifier\", \"boardAdc\", \"boardDac\", \"boardDigIn\", and \"boardDigOut\". Matching ignores case and punctuation. Short aliases include \"amp\", \"dc\", \"adc\", \"dac\", \"digin\", and \"digout\". Default: \"amplifier\". channels - Channel selection within the chosen family. Use [] for all channels; positive one-based numeric indices for positions in that family; or a character vector, string array, or cell array of native or custom channel names. Names are matched exactly first, then with case and punctuation removed. Default: []. timeRangeSec - Two-element numeric vector [start end] in seconds. A negative start is clamped to zero; Inf as the end reads through the last complete sample. Both calculated endpoint samples are included. Default: [0 Inf]. Outputs: window - Scalar structure containing the selected waveform data. On a read failure it retains source identity and contains empty arrays. status - Scalar structure with ok, message, and filepath. A valid request beyond the recording duration returns ok=true, empty values, and a message stating that no samples were in the requested window. Output Fields: sourcePath - Source RHS path as a character vector. name - Source filename including extension. family - Canonical selected family name. unit - \"microvolts\" for amplifier, \"microamps\" for stim, \"volts\" for DC amplifier and board analog channels, or \"logical\" for digital channels. sampleRateHz - Sample rate in hertz. sampleRange - One-based inclusive [start end] sample indices. channels - String row vector of returned native channel names. timeSec - Sample-by-one time vector in seconds, derived from stored Intan timestamps. values - Samples-by-channels numeric matrix in the unit named by unit. Errors: Throws labkit:rhs:InvalidFilepath for an invalid path, labkit:rhs:InvalidFamily for an unsupported family, labkit:rhs:InvalidChannel for an unknown or out-of-range channel, and labkit:rhs:InvalidTimeRange for a malformed or reversed time range. Missing files, malformed headers, absent channel families, files without complete data blocks, and waveform read failures return status.ok=false. Example: opts = struct(\"family\", \"stim\", ... \"channels\", [\"C-001\", \"C-002\"], ... \"timeRangeSec\", [0.1 0.2]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", opts); if status.ok plot(window.timeSec, window.values) xlabel(\"Time (s)\") ylabel(\"Stimulation current (microamps)\") legend(window.channels) end See also labkit.rhs.indexFile, labkit.rhs.inspectFile"},{"title":"labkit.rhs.version","url":"reference/api/labkit/rhs/version.html","kind":"reference","section":"reference","keywords":"labkit.rhs.version","text":"VERSION Return version information for the RHS API. VERSION Return version information for the RHS API. Usage: info = labkit.rhs.version() Description: Reports the API version and compatibility range used when an app declares a dependency on labkit.rhs. This is not the file-format version stored in an Intan recording. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.rhs\"; facade is \"rhs\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.rhs.version(); fprintf(\"RHS API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.thermal.fileDialogFilter","url":"reference/api/labkit/thermal/fileDialogFilter.html","kind":"reference","section":"reference","keywords":"labkit.thermal.fileDialogFilter","text":"FILEDIALOGFILTER Create a file-selection filter for thermal images. FILEDIALOGFILTER Create a file-selection filter for thermal images. Usage: filterSpec = labkit.thermal.fileDialogFilter() filterSpec = labkit.thermal.fileDialogFilter(\"IncludeAll\", true) Description: Returns the pattern and description rows used by MATLAB file-selection dialogs and LabKit file panels. The thermal row includes .jpg, .jpeg, and .rjpg files. A matching extension does not prove that a JPEG contains radiometric data; use labkit.thermal.inspectFile to inspect its contents. Name-Value Arguments: IncludeAll - Logical or numeric scalar. When true, adds an \"All files\" row after the thermal-image row. Default: false. Outputs: filterSpec - One- or two-row cell array. Column 1 contains wildcard patterns and column 2 contains the text shown in the file dialog. Errors: MATLAB inputParser errors are raised for an unknown name, a missing name-value partner, or an IncludeAll value that is not a logical or numeric scalar. Example: filters = labkit.thermal.fileDialogFilter(\"IncludeAll\", true); assert(isequal(size(filters), [2 2])) See also labkit.thermal.supportedExtensions, labkit.thermal.inspectFile"},{"title":"labkit.thermal.inspectFile","url":"reference/api/labkit/thermal/inspectFile.html","kind":"reference","section":"reference","keywords":"labkit.thermal.inspectFile","text":"INSPECTFILE Check whether a file contains supported radiometric data. INSPECTFILE Check whether a file contains supported radiometric data. Usage: info = labkit.thermal.inspectFile(path) info = labkit.thermal.inspectFile(path, opts) Description: Performs the same format check and radiometric read as readFile, but returns the outcome instead of throwing when the extension, file contents, or calibration data are unsupported. Use this function to label files in a chooser or batch review before deciding which ones to read. Inputs: path - Character vector or string scalar naming one candidate file. opts - Optional scalar structure. See Options. Options: RequireExisting - Logical scalar forwarded to readFile. Default: true. TemperatureCorrection - \"environment\" or \"planck-basic\", forwarded to readFile. Default: \"environment\". Outputs: info - Scalar structure with path, name, supportedExtension, isThermal, format, identifier, and message. supportedExtension reports only the filename extension. isThermal is true only after a supported radiometric payload is read successfully. On failure, identifier and message contain the caught reader error. Failure Behavior: Unsupported extensions and all readFile failures return isThermal=false with identifier and message populated; those reader exceptions are not rethrown. path must still be convertible to scalar text, and malformed MATLAB values may raise the originating conversion error. Example: info = labkit.thermal.inspectFile(\"candidate.jpg\"); if info.isThermal fprintf(\"Readable thermal format: %s\\n\", info.format) else warning(\"%s\", info.message) end See also labkit.thermal.readFile, labkit.thermal.isSupportedPath"},{"title":"labkit.thermal.isSupportedPath","url":"reference/api/labkit/thermal/isSupportedPath.html","kind":"reference","section":"reference","keywords":"labkit.thermal.isSupportedPath","text":"ISSUPPORTEDPATH Test whether a path has a supported thermal extension. ISSUPPORTEDPATH Test whether a path has a supported thermal extension. Usage: tf = labkit.thermal.isSupportedPath(path) Description: Compares the filename extension with the extensions returned by labkit.thermal.supportedExtensions. The comparison is case-insensitive. This function does not access the file system and does not check whether a JPEG contains radiometric data. Use labkit.thermal.inspectFile when the file contents must be verified. Inputs: path - Character vector or string scalar containing a filename or path. Outputs: tf - Logical scalar. The value is true for .jpg, .jpeg, and .rjpg names and false for all other extensions. Failure Behavior: Unknown extensions return false without accessing the file system. path must be convertible to scalar text; unsupported MATLAB values or shapes may raise the originating string or fileparts conversion error. Example: labkit.thermal.isSupportedPath(\"capture.RJPG\") % returns true labkit.thermal.isSupportedPath(\"capture.png\") % returns false See also labkit.thermal.supportedExtensions, labkit.thermal.inspectFile"},{"title":"labkit.thermal.rawToTemperatureC","url":"reference/api/labkit/thermal/rawToTemperatureC.html","kind":"reference","section":"reference","keywords":"labkit.thermal.rawToTemperatureC","text":"RAWTOTEMPERATUREC Convert FLIR raw sensor values to degrees Celsius. RAWTOTEMPERATUREC Convert FLIR raw sensor values to degrees Celsius. Usage: temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration) temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration, opts) [temperatureC, diagnostics] = labkit.thermal.rawToTemperatureC(...) Description: Applies the FLIR Planck calibration stored in a radiometric image. The default correction also compensates for emissivity, reflected radiation, atmospheric transmission, and an infrared window. Select \"planck-basic\" when the raw counts have already been corrected or when only the camera's Planck constants should be applied. Inputs: raw - Numeric array of raw sensor counts. The output has the same size. calibration - Scalar structure containing the five required Planck fields and, for environmental correction, the fields described below. opts - Optional scalar structure. See Options. Calibration Fields: PlanckR1 - Required finite scalar from the camera metadata. PlanckB - Required finite scalar from the camera metadata. PlanckF - Required finite scalar from the camera metadata. PlanckO - Required finite scalar from the camera metadata. PlanckR2 - Required finite scalar from the camera metadata. Emissivity - Object emissivity. Must be positive. Default: 1. ObjectDistanceM - Camera-to-object distance in metres. Negative values are treated as zero. Default: 1. ReflectedApparentTemperatureC - Reflected apparent temperature in degrees Celsius. Default: 20. AtmosphericTemperatureC - Atmospheric temperature in degrees Celsius. Default: 20. IRWindowTemperatureC - Infrared-window temperature in degrees Celsius. Defaults to ReflectedApparentTemperatureC. IRWindowTransmission - Infrared-window transmission. Must be positive. Default: 1. RelativeHumidity - Relative humidity as a fraction from 0 to 1 or as a percentage greater than 2. Default: 0.5. AtmosphericTransAlpha1 - First atmospheric alpha coefficient. Default: 0.006569. AtmosphericTransAlpha2 - Second atmospheric alpha coefficient. Default: 0.01262. AtmosphericTransBeta1 - First atmospheric beta coefficient. Default: -0.002276. AtmosphericTransBeta2 - Second atmospheric beta coefficient. Default: -0.00667. AtmosphericTransX - Atmospheric mixing coefficient. Default: 1.9. Options: Correction - String scalar selecting the conversion model. \"environment\" uses the environmental fields and records every fallback value. \"planck-basic\" uses only the five Planck fields. Default: \"environment\". Outputs: temperatureC - Double array in degrees Celsius with the same size as raw. Pixels for which the Planck expression is undefined are NaN. diagnostics - Scalar structure describing the conversion. correction is the selected mode; usedDefaults is true if an environmental fallback was used; defaultedFields lists those fields; parameterSources maps each environmental field to \"calibration\" or \"default\"; and message provides a readable summary. In \"planck-basic\" mode, no environmental fields are consumed and defaultedFields is empty. Errors: Throws labkit:thermal:MissingCalibration when a required Planck field is absent or not finite, and labkit:thermal:InvalidOptions when Correction is not one of the two supported values. Example: calibration = struct(\"PlanckR1\", 21106.77, \"PlanckB\", 1501, ... \"PlanckF\", 1, \"PlanckO\", -7340, \"PlanckR2\", 0.012545258); raw = [15000 15500; 16000 16500]; [temperatureC, diagnostics] = ... labkit.thermal.rawToTemperatureC(raw, calibration, ... struct(\"Correction\", \"planck-basic\")); See also labkit.thermal.readFile, labkit.thermal.renderImage"},{"title":"labkit.thermal.readFile","url":"reference/api/labkit/thermal/readFile.html","kind":"reference","section":"reference","keywords":"labkit.thermal.readFile","text":"READFILE Read radiometric image data and convert it to degrees Celsius. READFILE Read radiometric image data and convert it to degrees Celsius. Usage: record = labkit.thermal.readFile(path) record = labkit.thermal.readFile(path, opts) Description: Reads the embedded FLIR FFF RawThermalImage record from a radiometric JPEG or RJPEG. The ordinary visible JPEG pixels are not used as temperature values. Raw sensor counts are retained alongside the converted matrix and the calibration metadata used for conversion. Inputs: path - Character vector or string scalar naming one .jpg, .jpeg, or .rjpg file. The file must contain FLIR radiometric data. opts - Optional scalar structure. See Options. Options: RequireExisting - Logical scalar. When true, report a missing file before checking its extension. Default: true. TemperatureCorrection - String scalar selecting the temperature model. Allowed values are \"environment\" and \"planck-basic\". \"environment\" accounts for emissivity, object distance, reflected temperature, atmosphere, humidity, and an infrared window when the metadata is available. \"planck-basic\" applies only the camera's Planck constants to the raw counts. Default: \"environment\". Outputs: record - Scalar structure with fields path, name, format, raw, temperatureC, units, metadata, and message. raw is the decoded sensor matrix. temperatureC is a same-size double matrix in degrees Celsius, or NaN when the raw data cannot be converted. metadata includes the parsed FLIR tags and temperatureConversion diagnostics, including any environmental values that used documented defaults. Errors: Throws labkit:thermal:InvalidPath for a nonscalar path, labkit:thermal:FileNotFound for a missing file when RequireExisting is true, labkit:thermal:UnsupportedFile for another extension or a JPEG with no supported radiometric payload, and labkit:thermal:InvalidOptions for an invalid option value. Typical Call: opts = struct(\"TemperatureCorrection\", \"environment\"); record = labkit.thermal.readFile(\"capture.rjpg\", opts); imagesc(record.temperatureC) axis image colorbar See also labkit.thermal.inspectFile, labkit.thermal.readFiles, labkit.thermal.rawToTemperatureC"},{"title":"labkit.thermal.readFiles","url":"reference/api/labkit/thermal/readFiles.html","kind":"reference","section":"reference","keywords":"labkit.thermal.readFiles","text":"READFILES Read several radiometric image files. READFILES Read several radiometric image files. Usage: records = labkit.thermal.readFiles(paths) records = labkit.thermal.readFiles(paths, opts) [records, report] = labkit.thermal.readFiles(paths, opts) Description: Calls labkit.thermal.readFile for each path in input order. By default, the first unreadable file stops the operation. Set SkipInvalid to true when a batch should keep its readable records and return a failure entry for each skipped file. Inputs: paths - Character vector, string array, or cell array of character vectors and strings. Empty strings are removed. Output record order follows the remaining input order. opts - Optional scalar structure. See Options. Options: AllowEmpty - Logical scalar. When true, an empty path list returns empty records and a zero-count report. When false, an empty list throws labkit:thermal:NoPaths. Default: true. RequireExisting - Logical scalar forwarded to readFile. Default: true. SkipInvalid - Logical scalar. When true, an unreadable file is added to report.failures and processing continues. When false, its exception is rethrown immediately. Default: false. TemperatureCorrection - String scalar forwarded to readFile. Allowed values are \"environment\" and \"planck-basic\". Default: \"environment\". progressFcn - Empty or a function handle called once before each read and again after it loads or is skipped. The callback receives a scalar structure with stage, index, count, path, and name. stage is \"beforeRead\", \"afterRead\", or \"skipped\". Default: []. Outputs: records - Column structure array of successfully read thermal records. See labkit.thermal.readFile for the fields in each element. report - Scalar structure. requested is the number of normalized paths; loaded and skipped are counts. failures is a structure array with path, name, identifier, and message for each skipped file. Errors: In addition to readFile errors, throws labkit:thermal:InvalidOptions when opts or a callback is invalid and labkit:thermal:NoPaths when AllowEmpty is false and no usable path remains. Typical Call: opts = struct(\"SkipInvalid\", true, ... \"TemperatureCorrection\", \"environment\"); [records, report] = labkit.thermal.readFiles(files, opts); fprintf(\"Loaded %d of %d files.\\n\", report.loaded, report.requested) See also labkit.thermal.readFile, labkit.thermal.inspectFile"},{"title":"labkit.thermal.renderImage","url":"reference/api/labkit/thermal/renderImage.html","kind":"reference","section":"reference","keywords":"labkit.thermal.renderImage","text":"RENDERIMAGE Map a thermal matrix to an RGB image. RENDERIMAGE Map a thermal matrix to an RGB image. Usage: rgb = labkit.thermal.renderImage(values) rgb = labkit.thermal.renderImage(values, opts) Description: Linearly maps numeric values between the selected limits onto a thermal colour palette. Values below or above the limits are clipped to the first or last palette colour. NaN and Inf values use the first palette colour. This function changes only the display representation; it does not modify the source temperatures or apply a calibration. Inputs: values - Numeric M-by-N matrix containing temperatures or raw sensor values. opts - Optional scalar structure. See Options. Options: Limits - Two finite, increasing numeric values [low high]. When omitted, the finite minimum and maximum of values are used. An all-nonfinite matrix uses [0 1]. A constant matrix uses [value value+1]. Palette - String scalar naming the colour palette. Allowed values are \"turbo\", \"parula\", \"hot\", \"gray\", and \"iron\". Default: \"turbo\". Levels - Finite numeric scalar giving the palette length. Values are rounded to the nearest integer and must be at least 2. Default: 256. Outputs: rgb - M-by-N-by-3 double array with channel values in the interval [0, 1]. Errors: Throws labkit:thermal:InvalidOptions when Limits, Palette, or Levels is invalid. Example: temperatureC = [20 24 28; 32 36 40]; rgb = labkit.thermal.renderImage(temperatureC, ... struct(\"Limits\", [20 40], \"Palette\", \"iron\", \"Levels\", 256)); assert(isequal(size(rgb), [2 3 3])) See also labkit.thermal.rawToTemperatureC, labkit.thermal.readFile"},{"title":"labkit.thermal.supportedExtensions","url":"reference/api/labkit/thermal/supportedExtensions.html","kind":"reference","section":"reference","keywords":"labkit.thermal.supportedExtensions","text":"SUPPORTEDEXTENSIONS List the supported thermal filename extensions. SUPPORTEDEXTENSIONS List the supported thermal filename extensions. Usage: extensions = labkit.thermal.supportedExtensions() Description: Returns the filename extensions recognized by the thermal file-selection and reading functions. JPEG is a container format, so a listed extension does not guarantee that a file contains FLIR radiometric data. Use labkit.thermal.inspectFile to check a particular file. Outputs: extensions - String row vector [\".jpg\", \".jpeg\", \".rjpg\"]. Extensions are lowercase and include the leading period. Failure Behavior: The function accepts no caller input and performs no file-system access. It returns the fixed facade extension list without a recoverable failure state. Example: extensions = labkit.thermal.supportedExtensions(); pattern = \"*\" + strjoin(extensions, \";*\"); See also labkit.thermal.isSupportedPath, labkit.thermal.fileDialogFilter, labkit.thermal.inspectFile"},{"title":"labkit.thermal.version","url":"reference/api/labkit/thermal/version.html","kind":"reference","section":"reference","keywords":"labkit.thermal.version","text":"VERSION Return version information for the thermal API. VERSION Return version information for the thermal API. Usage: info = labkit.thermal.version() Description: Reports the version and compatibility range used when an app declares a dependency on labkit.thermal. This is the API contract version, not the MATLAB release or the version of a particular thermal camera format. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.thermal\"; facade is \"thermal\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.thermal.version(); fprintf(\"Thermal API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"nerve_response_analysis.analysisRun.analyzeRecording","url":"reference/api/nerve_response_analysis/analysisRun/analyzeRecording.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.analyzeRecording","text":"ANALYZERECORDING Analyze one RHS recording according to a protocol. ANALYZERECORDING Analyze one RHS recording according to a protocol. Usage: analysis = nerve_response_analysis.analysisRun.analyzeRecording(filepath) analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... filepath, protocol) analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... filepath, protocol, opts) Description: Indexes one Intan RHS file, maps protocol roles to amplifier channels, chooses an event source, detects pulse trains, and measures compound action potential features for every configured or inferred differential pair. Waveforms are read lazily over a bounded time range. The function does not open a GUI or write results. File, channel, and pair failures are recorded in analysis.issues whenever the remaining work can continue. This allows a batch analysis to retain successfully detected events or measured pairs instead of discarding the entire recording. Inputs: filepath - Path to one Intan RHS recording. protocol - Optional scalar protocol structure, normally loaded from an RHS Preview protocol JSON. An empty structure still allows stimulation- channel event detection and fallback amplifier detection. Default: struct(). opts - Optional scalar structure containing run and detector options. Protocol Fields: channels.roles - Structure array assigning logical roles to channels. Each entry uses id and may include label, nativeName, and match.anyNativeName. Matching is case-insensitive and ignores punctuation against both native and custom RHS channel names. channels.pairs - Structure array with id, label, positive, and negative role ids. Each pair is analyzed as positive-minus-negative. If pairs are absent, roles ending in \"_positive\" are paired with the corresponding \"_negative\" role when both resolve. A pair's optional mode field is retained in the protocol snapshot but does not change this calculation. Options: recordingId - Text identifier inserted into every event, train, metric, and issue row. Default: \"\". maxDurationSec - Maximum recording duration read from the start of the file. A nonpositive, nonfinite, or omitted value uses the indexed duration; values beyond the file are limited to that duration. Default: Inf. eventDetection - Structure accepted by nerve_response_analysis.analysisRun.detectEventTrains. Default: detector defaults. Event Source Priority: The first usable source is selected in this order: nonzero RHS stimulation current (maximum absolute value across stimulation channels); the amplifier channel assigned to role \"reference\"; channels named by configured response pairs; conventional positive/negative response roles; and finally the first amplifier channel. Amplifier sources are differentiated by detectEventTrains. A fallback source adds a warning to issues. Pair Measurement: Each pair reads its positive and negative amplifier channels and forms a differential response. When a reference role is available, it is supplied to common-mode correction. CAP features use the default windows documented by nerve_response_analysis.analysisRun.measureCapMetrics. Missing pair channels or failed reads add a warning and leave other pairs available. Outputs: analysis - Scalar recording-analysis structure. Analysis Fields: type, version - \"nerveResponseRecordingAnalysis\" and schema version 1. recordingId, filepath - Effective recording identity and source path. protocolSnapshot - Protocol value supplied by the caller. events - Event table from detectEventTrains with recordingId prepended when events exist. trains - Train table from detectEventTrains with recordingId prepended when trains exist. metrics - One row per event and analyzed pair. Columns are recordingId, pairId, pairLabel, followed by all measureCapMetrics columns. issues - Table with recordingId, severity (\"warning\" or \"error\"), and a reader-facing message. An unreadable file produces an error row and no further analysis; missing sources, events, or pairs produce warnings. Failure Behavior: Expected data and IO failures return a partial analysis structure rather than throwing. Invalid MATLAB types or unexpected parser/calculation errors may still throw; analyzeSession converts those exceptions into issue rows. Typical Call: protocol = jsondecode(fileread(\"nerve-response-protocol.json\")); opts = struct(\"recordingId\", \"R001\", \"maxDurationSec\", 30, ... \"eventDetection\", struct(\"stdMultiplier\", 3)); analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... \"recording.rhs\", protocol, opts); disp(analysis.metrics) disp(analysis.issues) See also nerve_response_analysis.analysisRun.analyzeSession, nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.measureCapMetrics, labkit.rhs.indexFile, labkit.rhs.readWindow"},{"title":"nerve_response_analysis.analysisRun.analyzeSession","url":"reference/api/nerve_response_analysis/analysisRun/analyzeSession.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.analyzeSession","text":"ANALYZESESSION Analyze accepted RHS recordings and combine their results. ANALYZESESSION Analyze accepted RHS recordings and combine their results. Usage: analysis = nerve_response_analysis.analysisRun.analyzeSession(session) analysis = nerve_response_analysis.analysisRun.analyzeSession( ... session, protocol) analysis = nerve_response_analysis.analysisRun.analyzeSession( ... session, protocol, opts) Description: Selects accepted rows from an RHS Preview filter record, analyzes each file with analyzeRecording, and vertically concatenates event, train, metric, and issue tables. A failure in one recording becomes an issue row and does not stop later recordings. Inputs: session - Scalar structure whose recordings field is a table or structure array. Required recording columns are recordingId and filePath. label, comment, qcFlag, and keep are optional selection/provenance fields. protocol - Optional channel-role and pair protocol passed unchanged to each recording. See analyzeRecording. Default: struct(). opts - Optional scalar structure. Recording-level fields are forwarded to analyzeRecording after recordingId is replaced by the current row id. Recording Selection: If keep exists, rows with logical keep=true are selected. Otherwise label selects normalized \"good\" rows; accepted synonyms are good, keep, kept, true, 1, yes, and y, while bad/reject synonyms normalize to \"bad\". If label is absent but qcFlag exists, rows equal to \"accepted\" are selected. With no selection column, every row is treated as good. This precedence means keep overrides label and qcFlag when more than one is present. Options: maxRecordings - Positive finite limit applied after recording selection. Zero, nonpositive, nonfinite, or omitted input means no limit. Default: Inf. maxDurationSec - Forwarded to analyzeRecording for every selected file. eventDetection - Forwarded to analyzeRecording for every selected file. Outputs: analysis - Scalar session-analysis structure. Analysis Fields: type, version - \"nerveResponseSessionAnalysis\" and schema version 1. recordingCount - Total rows before acceptance filtering. analyzedCount - Number of selected rows after maxRecordings. This counts attempted recordings, including files that later produce issues. protocolSnapshot - Protocol supplied by the caller. events, trains, metrics - Combined nonempty tables returned by recording analyses. issues - Combined recording issues plus caught exceptions, with recordingId, severity, and message columns. Failure Behavior: A recording-level analysis exception is converted to one issue row and later recordings continue. Missing or unsupported session.recordings input becomes an empty session; a nonempty table without recordingId or filePath, incompatible selection columns, or unappendable result tables may still raise the originating table/field error before or between recordings. Example: recordings = table(strings(0,1), strings(0,1), ... 'VariableNames', {'recordingId', 'filePath'}); session = struct(\"recordings\", recordings); analysis = nerve_response_analysis.analysisRun.analyzeSession(session); assert(analysis.recordingCount == 0 && analysis.analyzedCount == 0) See also nerve_response_analysis.analysisRun.analyzeRecording, nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.measureCapMetrics"},{"title":"nerve_response_analysis.analysisRun.detectEventTrains","url":"reference/api/nerve_response_analysis/analysisRun/detectEventTrains.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.detectEventTrains","text":"DETECTEVENTTRAINS Detect pulse candidates and group them into trains. DETECTEVENTTRAINS Detect pulse candidates and group them into trains. Usage: [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains(timeSec, signal) [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, signal, opts) Description: Detects abrupt signal transitions and groups nearby candidates into pulse trains. Missing signal samples are linearly interpolated before the absolute first difference is calculated. Candidate peaks must exceed both a robust noise threshold and an optional absolute score, and nearby candidates are reduced to the one with the larger score. Reported event times may then be shifted to compensate for the delay between a derivative peak and physical stimulus onset. Inputs: timeSec - Numeric sample-time vector in seconds. signal - Numeric event-source vector with the same number of samples as timeSec. Units are arbitrary but must be consistent with minScore. opts - Optional scalar structure. Options may be supplied directly. A protocol-style structure may instead place source definitions and train policy under opts.eventDetection. Options: eventDetection - Optional scalar protocol structure containing sources and train fields. When omitted, the supplied opts structure itself is interpreted as the event-detection policy. Default: direct opts. sourceId - Text copied to event and train source columns. Default: the first derivative source id in eventDetection.sources, or \"analog_derivative\". stdMultiplier - Numeric scalar multiplier applied to robust score noise. A derivative source's threshold.multiplier takes precedence. Default: 2. minScore - Finite numeric scalar lower bound in signal-difference units. The effective threshold is max(minScore, stdMultiplier*noise). Default: 0. minPeakDistanceSec - Minimum separation between retained candidates. A derivative source's value takes precedence. Default: 0.005 seconds. groupGapSec - A gap larger than this value starts a new train. Supply it in opts.train or opts.eventDetection.train. Default: 0.100 seconds. minDetectedPulses - Minimum pulse count for a valid train. Default: 4. maxTrainDurationSec - Maximum first-to-last event duration for a valid train. Default: 0.060 seconds. isolationWindowSec - Minimum preceding and following inter-train gaps for isIsolated=true. Missing neighboring trains imply an infinite gap. Default: 0.500 seconds. requireIsolation - When true, isolation is also required for train validity. Default: false. stimShiftSec - Offset added to every retained raw event time. Default: -0.0005 seconds, moving derivative peaks 0.5 ms earlier. Detection Details: Robust noise is median(abs(score-median(score)))/0.6745, with ordinary standard deviation used when that estimate is zero or invalid. A local maximum is at least as large as its left neighbor and strictly larger than its right neighbor. Train validity requires pulseCount >= minDetectedPulses, durationSec <= maxTrainDurationSec, and isolation when requested. Outputs: events - One row per retained candidate. trains - One row per temporal group. Event Columns: trainId, pulseIndex - One-based group and within-group indices. timeSec - Shifted event time in seconds. rawTimeSec - Unshifted sample time in seconds. sampleIndex - One-based index into the input vectors. score - Absolute first-difference score. source - Effective sourceId. Train Columns: trainId, startSec, endSec, pulseCount, durationSec - Group identity and shifted-time geometry. isIsolated - True when both neighboring train gaps satisfy isolationWindowSec. isValid - Result of pulse-count, duration, and optional isolation rules. source - Effective sourceId. status - \"valid\" or \"needsReview\". Failure Behavior: Empty input or a signal with no qualifying candidates returns empty tables with stable column names. Errors: nerve_response_analysis:EventSizeMismatch - timeSec and signal lengths differ. Example: sampleRate = 10000; timeSec = (0:1/sampleRate:0.12).'; signal = zeros(size(timeSec)); pulseTimes = 0.03 + (0:4).' * 0.01; signal(round(pulseTimes*sampleRate)+1) = 5; opts = struct(\"sourceId\", \"stim\", \"stimShiftSec\", 0, ... \"train\", struct(\"minDetectedPulses\", 5)); [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, signal, opts); assert(height(events) == 5 && trains.isValid(1)) See also nerve_response_analysis.analysisRun.measureCapMetrics, nerve_response_analysis.analysisRun.analyzeRecording"},{"title":"nerve_response_analysis.analysisRun.measureCapMetrics","url":"reference/api/nerve_response_analysis/analysisRun/measureCapMetrics.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.measureCapMetrics","text":"MEASURECAPMETRICS Measure event-locked CAP response features. MEASURECAPMETRICS Measure event-locked CAP response features. Usage: metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, eventTimesSec) metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, eventTimesSec, opts) Description: Measures baseline, noise, positive and negative response peaks, peak-to-peak amplitude, dominant-peak latency, and signal-to-noise ratio for each stimulus time. All time options and outputs are in seconds. Signal-derived values retain the units of signal; SNR is reported in decibels. Inputs: timeSec - Numeric sample-time vector in seconds. signal - Numeric response vector with the same length as timeSec. eventTimesSec - Numeric vector of stimulus times in seconds. One output row is produced for each value, in input order. opts - Optional scalar structure containing the measurement windows below. Options: segments - Optional scalar protocol structure. segments.capSearch may contain blankingAfterPulseSec and searchEndAfterPulseSec; nested values override direct options. Default: struct(). baselineWindowSec - Duration before each event used to estimate baseline. The inclusive baseline interval is [event-baselineWindowSec, event-blankingAfterPulseSec]. Default: 0.050 seconds. blankingAfterPulseSec - Time excluded immediately before and after the stimulus. It may also be supplied as opts.segments.capSearch. blankingAfterPulseSec. The nested value takes precedence. Default: 0.002 seconds. searchEndAfterPulseSec - End of the inclusive response-search interval, measured after the event. It may also be supplied in opts.segments.capSearch and takes the same precedence. Default: 0.008 seconds. Calculations: baselineMean is the finite-value mean in the baseline interval. noiseRms is the root mean square deviation from that mean. If the interval contains no samples, the recording-wide finite median becomes the baseline and noiseRms remains NaN. The response search is [event+blankingAfterPulseSec, event+searchEndAfterPulseSec]. Peaks are measured after baseline subtraction. peakTimeSec belongs to whichever of the positive or negative peaks has larger absolute amplitude, and latencySec = peakTimeSec-stimTimeSec. When noiseRms is positive and finite, snrDb = 20*log10(abs(peakToPeak)/noiseRms). Outputs: metrics - Table with one row per event. Metric Columns: eventIndex - One-based event order. stimTimeSec - Input stimulus time. baselineMean, noiseRms - Pre-stimulus baseline and noise estimate. peakPositive, peakNegative, peakToPeak - Baseline-subtracted extrema and their difference. peakTimeSec, latencySec - Dominant absolute peak time and event-relative latency. snrDb - Peak-to-peak SNR in decibels, or NaN when noise is unavailable or zero. status - \"ok\" when the search interval contains samples; \"noSamples\" otherwise. Rows without samples retain NaN response metrics. Errors: nerve_response_analysis:MetricSizeMismatch - timeSec and signal lengths differ. Example: timeSec = (0:0.0001:0.04).'; signal = zeros(size(timeSec)); signal(abs(timeSec-0.014) < 0.0002) = 2; signal(abs(timeSec-0.016) < 0.0002) = -1; opts = struct(\"baselineWindowSec\", 0.004, ... \"blankingAfterPulseSec\", 0.002, ... \"searchEndAfterPulseSec\", 0.008); metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, 0.010, opts); assert(metrics.status == \"ok\" && metrics.peakToPeak > 2.5) See also nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.analyzeRecording"},{"title":"response_review_stats.analysisRun.alignSegments","url":"reference/api/response_review_stats/analysisRun/alignSegments.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.alignSegments","text":"ALIGNSEGMENTS Interpolate segment traces to a shared time grid. ALIGNSEGMENTS Interpolate segment traces to a shared time grid. Usage: aligned = response_review_stats.analysisRun.alignSegments(segments) aligned = response_review_stats.analysisRun.alignSegments(segments, opts) Description: Shifts each segment's time axis to a common alignment origin, constructs a shared uniformly spaced grid, and linearly interpolates every trace onto that grid. Duplicate source timestamps retain their first sample. Values outside a segment's aligned source range remain NaN. An optional baseline subtraction is performed after interpolation. Inputs: segments - Structure array with timeSec and values vectors of matching length and a name value for each segment. A segment-specific alignTimeSec may identify the source time that should become zero. opts - Optional scalar structure containing the alignment options below. Options: sampleIntervalSec - Positive output-grid interval in seconds. When omitted, the median of every finite positive source time difference is used. If no source supplies a usable difference, the fallback is 0.0001 seconds. windowSec - Two-element [start end] output interval in aligned seconds. When omitted, the function uses the intersection of all source ranges after alignment-time shifts. alignTimeSec - Global source time shifted to zero for segments without their own alignTimeSec field. Default: 0 seconds. baselineWindowSec - Optional inclusive [start end] interval in aligned seconds. The finite mean in this output-grid interval is subtracted from the entire corresponding segment. Default: no subtraction. Outputs: aligned - Scalar structure with these fields: Aligned Fields: timeSec - G-by-1 uniformly spaced aligned time vector in seconds. values - G-by-N double matrix with one interpolated segment per column. segmentNames - N-by-1 string vector copied from segments.name. status - \"ok\", or \"empty\" when segments is empty. Failure Behavior: Empty segments returns stable empty arrays with status=\"empty\". Nonempty elements must provide compatible timeSec, values, and name fields; invalid option intervals, disjoint derived ranges, or interpolation-incompatible data propagate the originating MATLAB field, colon, or interp1 error. Example: segments = struct( ... \"timeSec\", {[10; 11; 12], [20; 21; 22]}, ... \"values\", {[1; 2; 3], [4; 5; 6]}, ... \"name\", {\"first\", \"second\"}, ... \"alignTimeSec\", {10, 20}); aligned = response_review_stats.analysisRun.alignSegments( ... segments, struct(\"sampleIntervalSec\", 1)); assert(isequal(aligned.timeSec, [0; 1; 2])) assert(isequal(aligned.values, [1 4; 2 5; 3 6])) See also response_review_stats.analysisRun.measureAlignedSegments, response_review_stats.analysisRun.summarizeMetrics"},{"title":"response_review_stats.analysisRun.measureAlignedSegments","url":"reference/api/response_review_stats/analysisRun/measureAlignedSegments.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.measureAlignedSegments","text":"MEASUREALIGNEDSEGMENTS Measure peak-to-peak, noise, and SNR per segment. MEASUREALIGNEDSEGMENTS Measure peak-to-peak, noise, and SNR per segment. Usage: metrics = response_review_stats.analysisRun.measureAlignedSegments(aligned) metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, opts) Description: Baseline-corrects every aligned trace, measures its positive and negative extrema, and estimates baseline noise and peak-to-peak SNR. Each segment is measured independently and produces one output row. Peaks use only finite samples; NaN samples are omitted from baseline and noise means. Inputs: aligned - Structure returned by response_review_stats.analysisRun.alignSegments. timeSec is G-by-1, values is G-by-N, and segmentNames contains N names. opts - Optional scalar structure containing the measurement windows below. Options: baselineWindowSec - Inclusive [start end] interval in aligned seconds used to calculate and subtract the baseline mean. Default: [0.007 0.009]. noiseWindowSec - Two-element numeric [start end] interval used for noise RMS after baseline subtraction. Default: baselineWindowSec. measurementWindowSec - Inclusive interval used for positive and negative peaks. Empty input uses all aligned samples. Default: []. Calculations: PeakToPeak = Peak1Value-Peak2Value, where Peak1 is the finite maximum and Peak2 is the finite minimum in the measurement window. NoiseRMS is the root mean square deviation from the finite noise-window mean. When NoiseRMS is positive and PeakToPeak is finite, SNR_dB is 20*log10(abs(PeakToPeak)/NoiseRMS). A window without usable samples leaves its metrics as NaN. A missing baseline also makes that segment's corrected trace and downstream measurements NaN. Outputs: metrics - N-row table with SegmentName, BaselineStart_s, BaselineEnd_s, PeakToPeak, Peak1Time_s, Peak1Value, Peak2Time_s, Peak2Value, NoiseStart_s, NoiseEnd_s, NoiseRMS, and SNR_dB columns. Failure Behavior: Windows without usable finite samples produce NaN metrics for the affected segment. aligned must provide compatible timeSec, values, and segmentNames fields, and every supplied window must contain two numeric endpoints; malformed values propagate the originating field, indexing, or conversion error. Example: aligned = struct(\"timeSec\", (0:0.001:0.012).', ... \"values\", zeros(13, 1), \"segmentNames\", \"response\", \"status\", \"ok\"); aligned.values(7) = 2; aligned.values(11) = -1; opts = struct(\"baselineWindowSec\", [0 0.002], ... \"noiseWindowSec\", [0 0.002]); metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, opts); assert(metrics.PeakToPeak == 3) See also response_review_stats.analysisRun.alignSegments, response_review_stats.analysisRun.summarizeMetrics"},{"title":"response_review_stats.analysisRun.summarizeMetrics","url":"reference/api/response_review_stats/analysisRun/summarizeMetrics.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.summarizeMetrics","text":"SUMMARIZEMETRICS Compute grouped metric counts and finite means. SUMMARIZEMETRICS Compute grouped metric counts and finite means. Usage: summary = response_review_stats.analysisRun.summarizeMetrics(metrics) Description: Produces compact descriptive statistics from response metric rows. Groups follow their first occurrence. The function performs no weighting, confidence interval, hypothesis test, or outlier removal. Inputs: metrics - Metric table. pairId selects groups when that variable exists; otherwise SegmentName is used, and otherwise all rows belong to \"all\". Amplitude may be named PeakToPeak or peakToPeak. SNR may be named SNR_dB or snrDb. Outputs: summary - Table with one row per group. Summary Columns: Group - Group name in stable first-occurrence order. Count - Number of input rows in the group, including rows whose metrics are nonfinite. MeanPeakToPeak - Arithmetic mean of finite group amplitudes, or NaN when the amplitude variable is absent or has no finite values. MeanSnrDb - Arithmetic mean of finite group SNR values in decibels, or NaN when the SNR variable is absent or has no finite values. Failure Behavior: Empty or non-table input returns one \"all\" row with Count=0 and NaN means. Example: metrics = table([\"cp\"; \"cp\"; \"ta\"], [3; 5; 2], [20; 22; 10], ... 'VariableNames', {'pairId', 'peakToPeak', 'snrDb'}); summary = response_review_stats.analysisRun.summarizeMetrics(metrics); assert(summary.Group(1) == \"cp\" && summary.MeanPeakToPeak(1) == 4) See also response_review_stats.analysisRun.measureAlignedSegments, response_review_stats.analysisRun.alignSegments"},{"title":"rhs_preview.analysisRun.readPreviewWindow","url":"reference/api/rhs_preview/analysisRun/readPreviewWindow.html","kind":"reference","section":"reference","keywords":"rhs_preview.analysisRun.readPreviewWindow","text":"READPREVIEWWINDOW Read the requested RHS preview window from app state. READPREVIEWWINDOW Read the requested RHS preview window from app state. Usage: [S, ok, logMessage] = rhs_preview.analysisRun.readPreviewWindow( ... S, selectedChannels, actionLabel) [S, ok, logMessage] = rhs_preview.analysisRun.readPreviewWindow( ... S, selectedChannels, actionLabel, preserveRoi) Description: Reads a bounded time window from the RHS file recorded in S and stores the returned waveform structure in S.preview. The requested start time is clamped to the indexed recording duration before labkit.rhs.readWindow is called. This function updates data and status fields only; it does not create graphics, open dialogs, or write files. Inputs: S - Scalar RHS Preview state structure. See State Fields for the values read or changed by this function. selectedChannels - Channel identifiers accepted by the channels option of labkit.rhs.readWindow. Supply at least one identifier from S.family. actionLabel - Text stored in S.lastAction after a successful read. This lets the caller distinguish an initial read from a refresh or a move to another part of the recording. preserveRoi - Optional logical scalar. true keeps the previous ROI after clamping it to the new time vector; false resets S.roiSec to [NaN NaN]. Default: false. Outputs: S - Updated state. On success, preview, roiSec, windowStartSec, statusMessage, and lastAction describe the completed read. ok - Logical scalar. true only when labkit.rhs.readWindow returns a status whose ok field is true. logMessage - Text summary for the app log. It reports the sample count on success, describes a read failure, or is empty when no read was attempted because a file or channel selection is missing. State Fields: rhsFile - Path to the indexed RHS file. Empty text prevents the read. family - RHS channel family passed to labkit.rhs.readWindow, such as \"amplifier\" or \"stim\". windowStartSec - Requested start time in seconds. It is replaced by the valid value returned by clampWindowStartSec. windowDurationSec - Requested duration in seconds. The read interval ends at windowStartSec + max(windowDurationSec, eps). index - File index used by clampWindowStartSec to enforce recording bounds. roiSec - Two-element ROI interval in seconds. It is preserved and clamped only when preserveRoi is true and the read contains time samples. preview - Waveform window returned by labkit.rhs.readWindow. A thrown read error clears this field; a returned failure status leaves the returned window here so its diagnostic content remains available. statusMessage - Reader-facing explanation of success, missing input, or a failed read. lastAction - actionLabel after success, or \"Preview read failed\" after a read error. Missing inputs leave the previous value unchanged. Failure Behavior: A missing file or empty channel selection returns ok=false without calling the reader. Exceptions from labkit.rhs.readWindow are caught, copied into statusMessage and logMessage, and clear preview. A nonthrowing failed read returns its status message and ok=false. State-shape errors, such as a missing required field, are programming errors and may still throw. Typical Call: channels = [\"A-000\", \"A-001\"]; [state, ok, message] = rhs_preview.analysisRun.readPreviewWindow( ... state, channels, \"Read preview\", true); See also labkit.rhs.readWindow, rhs_preview.analysisRun.clampWindowStartSec, rhs_preview.analysisRun.clampRoi"},{"title":"ttest_wizard.testRun.runGroupTTests","url":"reference/api/ttest_wizard/testRun/runGroupTTests.html","kind":"reference","section":"reference","keywords":"ttest_wizard.testRun.runGroupTTests","text":"RUNGROUPTTESTS Compare every group after the first with the first group. RUNGROUPTTESTS Compare every group after the first with the first group. Usage: results = ttest_wizard.testRun.runGroupTTests(groups, options) Description: Runs one explicit t-test for each group after the first, always using the first group as vector A/reference and the later group as vector B. It preserves group order, copies per-comparison labels and observations into canonical result snapshots, and performs no plotting or file IO. Inputs: groups - Struct vector with scalar text label and numeric-vector values fields. The first element is the reference group. Nonfinite values are reported through the affected runTTest result. options - Scalar struct with method, alternative, and alpha fields accepted by runTTest. Per-comparison labels are supplied from groups. Outputs: results - Column struct vector of canonical runTTest results in group order. Element k compares groups(1) minus groups(k+1). Options: method - Test label from testRun.choices or \"welch\", \"pooled\", or \"paired\". No default is applied; the field is required. alternative - Alternative label from testRun.choices or \"two_sided\", \"greater\", or \"less\". Direction is reference minus comparison. No default is applied; the field is required. alpha - Finite numeric scalar strictly between zero and one. No default is applied; the field is required. Units: Every group must use the same measurement unit. Means, standard deviations, differences, standard errors, and confidence bounds retain that unit. Test statistics, degrees of freedom, and p-values are dimensionless. Assumptions: Test choice, independence, and pairing remain the caller's scientific responsibility. A paired method pairs each comparison group separately with the first group by displayed row order. No multiple-comparison correction is applied. Failure Behavior: Fewer than two groups throws ttest_wizard:InsufficientGroups. A malformed group throws ttest_wizard:InvalidGroups. Individual scientific failures are returned through the stable runTTest result statuses. Errors: ttest_wizard:InsufficientGroups - groups has fewer than two elements. ttest_wizard:InvalidGroups - A group label is not scalar text or values is not a numeric vector. ttest_wizard:InvalidTestOptions - options is missing method, alternative, or alpha, or runTTest rejects one of their values. Example: groups = struct( ... \"label\", {\"Reference\", \"Treatment 1\", \"Treatment 2\"}, ... \"values\", {[1 2 3], [2 3 4], [1 2 4]}); options = struct(\"method\", \"welch\", ... \"alternative\", \"two_sided\", \"alpha\", 0.05); results = ttest_wizard.testRun.runGroupTTests(groups, options); assert(numel(results) == 2) See also ttest_wizard.testRun.runTTest"},{"title":"ttest_wizard.testRun.runTTest","url":"reference/api/ttest_wizard/testRun/runTTest.html","kind":"reference","section":"reference","keywords":"ttest_wizard.testRun.runTTest","text":"RUNTTEST Run one independent Welch, pooled, or paired t-test. RUNTTEST Run one independent Welch, pooled, or paired t-test. Usage: result = ttest_wizard.testRun.runTTest(vectorA, vectorB, options) Description: Calculates one App-owned t-test with Base MATLAB beta functions. Difference direction is always A minus B. Scientific validation failures return a result row with ok=false and a stable status instead of fabricating a statistic. Inputs: vectorA - Finite numeric vector with at least two values. vectorB - Finite numeric vector with at least two values. options - Scalar struct with method, alternative, alpha, labelA, and labelB. method accepts a label from testRun.choices or welch, pooled, or paired. alternative accepts a choice label or two_sided, greater, or less. alpha is a finite scalar strictly between zero and one. Outputs: result - Scalar struct created by emptyResult. Successful results include vector snapshots, summaries, A-minus-B estimate and confidence bounds, standard error, t statistic, degrees of freedom, and p-value. Units: A and B must use the same measurement unit. Means, standard deviations, the A-minus-B estimate, standard error, and confidence bounds retain that unit. The t statistic, degrees of freedom, and p-value are dimensionless. Assumptions: Independent tests require observations that are independent within and between A and B. The pooled method additionally assumes equal population variances. The paired method requires displayed A(k) and B(k) to describe the same experimental unit; it analyzes A(k)-B(k). Test choice and independence remain the caller's scientific responsibility. Failure Behavior: Invalid options throw ttest_wizard:InvalidTestOptions. Nonfinite vectors, sample sizes below two, unequal paired lengths, or zero standard error return ok=false with status invalid_input, insufficient_n, unequal_pairs, or zero_standard_error. Example: options = struct( ... \"method\", \"welch\", ... \"alternative\", \"two_sided\", ... \"alpha\", 0.05, ... \"labelA\", \"Condition A\", ... \"labelB\", \"Condition B\"); result = ttest_wizard.testRun.runTTest( ... [1.2 1.4 1.3 1.5], [1.8 1.7 2.0 1.9 1.6], options); assert(result.ok) See also ttest_wizard.testRun.choices, ttest_wizard.testRun.emptyResult"},{"title":"video_marker.coordinateExport.buildTable","url":"reference/api/video_marker/coordinateExport/buildTable.html","kind":"reference","section":"reference","keywords":"video_marker.coordinateExport.buildTable","text":"BUILDTABLE Convert confirmed video annotations to a coordinate table. BUILDTABLE Convert confirmed video annotations to a coordinate table. Usage: T = video_marker.coordinateExport.buildTable( ... annotations, skeleton, videoInfo, calibration, opts) Description: Exports a continuous range of confirmed frames in a rectangular table suitable for CSV files or direct MATLAB analysis. Coordinates can remain in pixels or be converted with the project's physical scale. The origin and vertical-axis direction are applied consistently to every keypoint while the stored annotation arrays remain unchanged. Inputs: annotations - Frame annotation structure. annotations.coords must be an F-by-P-by-2 array in [x y] pixel coordinates, and frameStatus must mark every exported frame as \"confirmed\". skeleton - Skeleton structure with P ordered pointIds. Each point ID is converted with matlab.lang.makeValidName for table variable names. videoInfo - Scalar structure with frameRate in frames per second. time_s is calculated as (frame_index-1)/frameRate. calibration - Scale structure with pixelsPerUnit and unit. It is required only when opts.unitMode is \"calibrated_physical\". opts - Coordinate export structure, normally created by video_marker.coordinateExport.options. Options: startFrame - Finite numeric scalar giving the first exported one-based frame index. It is rounded and limited to at least one. Default from options: 1. endFrame - Numeric scalar or Inf giving the last exported frame index, inclusive. It is rounded and limited to the annotation frame count. Default from options: Inf. unitMode - \"pixels\" retains pixel distances and reports \"px\"; \"calibrated_physical\" divides distances by calibration.pixelsPerUnit and reports calibration.unit. Default: \"pixels\". originMode - \"top_left_pixel_center\" subtracts [1 1], so the center of the upper-left pixel is [0 0]. \"first_point\" subtracts the first skeleton point's coordinates at startFrame from all frames. Default: \"top_left_pixel_center\". yAxisMode - \"down\" preserves image-row direction; \"up\" negates vertical displacement so positive y points upward. Default: \"up\". Outputs: T - Table with one row per exported frame. Fixed variables are frame_index, time_s, coordinate_unit, y_axis, origin_mode, origin_frame_index, origin_point_id, and pixels_per_unit. Every skeleton point adds __x and __y numeric variables in skeleton order. Errors: labkit_VideoMarker_app:InvalidFrameRange - The limited end frame precedes the limited start frame. labkit_VideoMarker_app:UnconfirmedFrameRange - Any selected frame is not confirmed. labkit_VideoMarker_app:MissingScaleCalibration - Physical units are requested without a positive finite pixelsPerUnit value. labkit_VideoMarker_app:InvalidOriginMode - originMode is unsupported. labkit_VideoMarker_app:InvalidYAxisMode - yAxisMode is unsupported. Example: skeleton = video_marker.skeletonDefinition.fromText(\"hip, knee\", \"hip-knee\"); annotations = video_marker.frameAnnotations.emptyAnnotations(2, 2); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 1, [11 21; 31 41], \"confirmed\"); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 2, [13 25; 35 45], \"confirmed\"); info = struct(\"frameRate\", 10); opts = video_marker.coordinateExport.options( ... \"startFrame\", 1, \"endFrame\", 2, \"unitMode\", \"pixels\", ... \"originMode\", \"first_point\", \"yAxisMode\", \"up\"); T = video_marker.coordinateExport.buildTable( ... annotations, skeleton, info, struct(), opts); assert(isequal(T.hip__x, [0; 2]) && isequal(T.hip__y, [0; -4])) See also video_marker.coordinateExport.options, video_marker.frameAnnotations.emptyAnnotations, video_marker.skeletonDefinition.fromText"},{"title":"video_marker.motionEstimate.trackPoints","url":"reference/api/video_marker/motionEstimate/trackPoints.html","kind":"reference","section":"reference","keywords":"video_marker.motionEstimate.trackPoints","text":"TRACKPOINTS Track ordered image points between two consecutive video frames. TRACKPOINTS Track ordered image points between two consecutive video frames. Usage: [points, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, previousPoints) [points, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, previousPoints, priorDisplacement) Description: Tracks each point with deterministic base-MATLAB multiscale patch matching. A grayscale pyramid provides up to four resolution levels. At every level, a 7-by-7 mean-centered template is compared by normalized correlation in a plus-or-minus four-pixel search around the predicted location. A parabolic peak fit refines the final match to subpixel coordinates. Points are processed independently and preserve their input order. A flat, out-of-frame, or weakly correlated patch is not treated as a hard error: the output retains the clamped motion-prior prediction and reports zero confidence for that point. Inputs: previousImage - Finite numeric or logical grayscale or RGB source frame. currentImage - Finite frame with exactly the same size as previousImage. previousPoints - Finite N-by-2 array of [x y] coordinates in the previous frame, where x is the column and y is the row. N may be zero. priorDisplacement - Optional finite [dx dy] motion prediction. A 1-by-2 value is shared by all points; an N-by-2 array supplies one prior per point. Default: zeros(N,2). Outputs: points - N-by-2 tracked [x y] coordinates, limited to currentImage bounds. Failed matches contain the limited previousPoints+priorDisplacement. confidence - N-by-1 values in [0,1]. Accepted normalized-correlation scores are mapped with (score+1)/2; failed matches are zero. A correlation of at least 0.55 is required for acceptance. diagnostics - Scalar structure with engine, valid, and failureMessage. engine is \"multiscale_patch\". valid is an N-by-1 logical mask. failureMessage is empty when every point succeeds and explains partial tracking failure otherwise. Errors: labkit_VideoMarker_app:FrameSizeMismatch - Frame arrays differ in size. labkit_VideoMarker_app:InvalidTrackingPoints - previousPoints is not a finite N-by-2 array. labkit_VideoMarker_app:InvalidMotionPrior - priorDisplacement is neither a finite 1-by-2 nor finite N-by-2 array. labkit_VideoMarker_app:InvalidTrackingImage - A frame is not grayscale or RGB after conversion, or contains nonfinite values. Example: rng(7) previousImage = rand(60, 70); currentImage = circshift(previousImage, [-2 3]); [point, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, [35 30]); assert(norm(point - [38 28]) < 0.5) assert(confidence > 0.5 && diagnostics.valid) See also video_marker.motionEstimate.predictForward, video_marker.frameAnnotations.setFramePoints"},{"title":"vt_resistance.analysisRun.computeResistance","url":"reference/api/vt_resistance/analysisRun/computeResistance.html","kind":"reference","section":"reference","keywords":"vt_resistance.analysisRun.computeResistance","text":"COMPUTERESISTANCE Estimate cathodic and anodic resistance from a VT pulse. COMPUTERESISTANCE Estimate cathodic and anodic resistance from a VT pulse. Usage: A = vt_resistance.analysisRun.computeResistance(item) A = vt_resistance.analysisRun.computeResistance(item, opts) Inputs: item - Scalar DTA item struct. item.curve, or the main curve selected from item.tables, must provide T in seconds, Vf in volts, and Im in amperes. Pt is optional; zero-based sample numbers are generated when absent. item.meta may contain pulse timing and current metadata. opts - Optional scalar struct described below. Default: struct(). Options: windowMode - Samples used for steady voltage/current medians: \"Full pulse median\" or \"Center 60% median\". The latter excludes the first and last 20% of each detected phase. Default: \"Full pulse median\". voltageMode - Resistance numerator: \"Baseline-corrected dV/I\" or \"Raw Vf/I\". Default: \"Baseline-corrected dV/I\". pulseMode - Pulse-detection policy: \"Metadata first, then auto\", \"Metadata only\", or \"Auto from Im only\". Default: \"Metadata first, then auto\". Outputs: A - Scalar result struct. Success is indicated by A.ok and A.message=\"OK\". Data-validation failures return ok=false and the fields calculated up to the failed stage. Result Fields: ok - True when the final mean absolute resistance is finite. message - \"OK\" or an explanation of the first failed stage. windowMode - Effective steady-window label. voltageMode - Effective voltage-numerator label. logOnFailure - True when the app should surface the failure in its log. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. pt - Filtered source point numbers or generated zero-based sample numbers. pulse - Pulse geometry returned by labkit.dta.detectPulses. detectMode - Detection method that produced pulse. detectMsg - Pulse-detection status message. cathMask - Logical samples in the selected cathodic steady window. anodMask - Logical samples in the selected anodic steady window. cathSteadyStart, cathSteadyEnd - Cathodic steady-window bounds in seconds. anodSteadyStart, anodSteadyEnd - Anodic steady-window bounds in seconds. Ic_est_A - Median cathodic current in amperes. Ia_est_A - Median anodic current in amperes. Vc_ss_V - Median cathodic steady voltage in volts. Va_ss_V - Median anodic steady voltage in volts. cathBaselineStart, cathBaselineEnd - Requested pre-pulse cathodic baseline bounds in seconds. anodBaselineStart, anodBaselineEnd - Requested post-pulse anodic baseline bounds in seconds. Vc_baseline_V - Median pre-pulse voltage, or 0 when unavailable. Va_baseline_V - Median post-pulse voltage; falls back to the cathodic baseline and then 0. cathBaselineWindow_s - Duration of the requested cathodic baseline window. anodBaselineWindow_s - Duration of the requested anodic baseline window. dVc_V - Vc_ss_V-Vc_baseline_V in volts. dVa_V - Va_ss_V-Va_baseline_V in volts. Rc_raw_ohm - Vc_ss_V/Ic_est_A in ohms. Ra_raw_ohm - Va_ss_V/Ia_est_A in ohms. Rc_dV_ohm - dVc_V/Ic_est_A in ohms. Ra_dV_ohm - dVa_V/Ia_est_A in ohms. Rc_ohm - Selected cathodic raw or baseline-corrected resistance. Ra_ohm - Selected anodic raw or baseline-corrected resistance. Rc_abs_ohm - Absolute Rc_ohm. Ra_abs_ohm - Absolute Ra_ohm. Ravg_abs_ohm - Mean of the finite absolute cathodic and anodic values. Description: computeResistance analyzes one detected biphasic voltage transient and reports phase-specific as well as averaged resistance. Apps may choose raw steady voltage or subtract the adjacent baseline before division. The function performs no plotting, file selection, or export. Calculations: Rows containing NaN in T, Vf, or Im are removed together. Pulse detection is delegated to labkit.dta.detectPulses. Steady values are phase-window medians. Division returns NaN when either operand is nonfinite or current is smaller than eps in magnitude. The reported average uses absolute phase resistances so opposite current signs do not cancel. Failure Behavior: Missing curve data, fewer than five remaining samples, failed pulse detection, undersampled steady windows, or nonfinite final resistance returns ok=false. Malformed structs and unsupported MATLAB value types may still throw from DTA access or numeric operations. Typical Call: [item, status] = labkit.dta.loadFile(\"transient.DTA\", \"chrono\"); assert(status.ok, status.message) opts = struct(\"windowMode\", \"Center 60% median\", ... \"voltageMode\", \"Baseline-corrected dV/I\", ... \"pulseMode\", \"Metadata first, then auto\"); A = vt_resistance.analysisRun.computeResistance(item, opts); assert(A.ok, A.message) fprintf(\"Mean resistance: %.4g ohm\\n\", A.Ravg_abs_ohm) See also labkit.dta.detectPulses, cic.analysisRun.computeCIC"}];
\ No newline at end of file
+window.LABKIT_SEARCH_INDEX = [{"title":"LabKit Documentation","url":"index.html","kind":"overview","section":"general","keywords":"LabKit Documentation","text":"LabKit Documentation LabKit documentation is organized by the question you are trying to answer. Start with a task guide, then open an API reference only when you need exact MATLAB call syntax or returned data shapes. Choose A Starting Point I want to Start here Install, update, or open LabKit [Getting started](getting-started/README.md) Understand every launcher action or call it from MATLAB [LabKit Launcher](apps/labkit-core/launcher/README.md) Choose an app and understand its inputs and outputs [App guide](apps/README.md) Call a reusable `labkit.*` function [Public API reference](reference/README.md) Understand ownership and package boundaries [Architecture](development/build-apps/architecture.md) Create or modify an app [App development](development/build-apps/app-development.md) Run tests or diagnose performance [Testing](development/maintain-and-release/testing.md) Call packaging, profiling, codecheck, or documentation tools [Maintainer tools](development/tools/README.md) Understand documentation sources and generated HTML [Documentation system](development/maintain-and-release/documentation.md) Maintain a private app workspace [Private apps](development/maintain-and-release/private-apps.md) Prepare a release [Release process](development/maintain-and-release/release.md) Documentation Layers getting-started/ installation, launcher, updates, and first-run concepts apps/ one directory per family and one subdirectory per app including the LabKit Launcher under LabKit Core framework/ UI runtime concepts, behavior, and app-authoring contracts libraries/ one directory per reusable public MATLAB facade reference/ generated-function reference landing page development/ reader-task folders for app building, maintenance, and tools history/ chronological change records and the project history index Every Markdown file is published automatically. Its path owns navigation and its first level-one heading owns the page title. Public App manuals are matched to `labkit_launcher(\"list\")`; App-owned function pages are discovered from complete public MATLAB help contracts. Reference Conventions Public function pages follow the MATLAB reference pattern: summary and syntax description and behavior input arguments and options output arguments and data shapes algorithms or scientific semantics where relevant errors and limitations executable examples version history, See Also, and related topics Each app family has a landing page. Each concrete app owns a directory whose `README.md` is its Get Started and detailed behavior page; complex apps can add focused workflow, file-format, or algorithm topics beside it. App pages explain launch, task flow, interaction rules, inputs, outputs, persistence, scientific meaning, non-GUI APIs, errors, limitations, examples, related topics, and component history as applicable. They do not document internal callbacks. Framework and library landing pages follow the Qt module pattern: overview and ownership first, followed by grouped concepts, supported public members, detailed behavior, examples, and related modules. Generated function pages are owned by MATLAB source help blocks. Handwritten HTML is never a source. Project History And Support [Project history](history/README.md) lists all change records and connects them to the affected apps, framework, and libraries. [Support](../.github/SUPPORT.md) explains how to report workflow problems."},{"title":"EIS","url":"apps/electrochemistry/eis.html","kind":"app","section":"apps","keywords":"apps electrochemistry eis EIS","text":"EIS EIS overlays impedance data from one or more Gamry `ZCURVE` tables, supports Nyquist and Bode-style axis combinations, and exports the values currently selected for plotting. Requirements And Launch labkit_EIS_app Inputs Add one or more `.DTA` files containing a readable EIS `ZCURVE`. Files that do not contain the required curve are reported and omitted from the plot. The successfully decoded source list and its order are preserved in project state through portable references. Basic Workflow Add the EIS DTA files. Choose X and Y quantities. Choose **mΩ**, **Ω**, **kΩ**, or **MΩ** for impedance axes. New projects default to **kΩ**. Enable logarithmic X or Y scaling only for strictly positive plotted data. Use **Fit X/Y limits** to re-estimate independent limits from the current data, or **Use equal X/Y scale** when equal data units are wanted. Adjust marker, line, grid, and legend presentation. Export the current plot data CSV. Axis Quantities The available quantities are frequency, log10 frequency, time, point number, real impedance, imaginary impedance, negative imaginary impedance, impedance magnitude, phase, DC current, and DC voltage. Default axes are `Zreal` and `-Zimag`, displayed in kΩ for a new project. Use `Zreal` versus `-Zimag` for the conventional Nyquist orientation. Use frequency versus magnitude or phase for Bode-style views. The log-axis checkbox changes MATLAB axes scaling; choosing `log10(Freq)` changes the data coordinate itself. Do not apply both transformations unless that is explicitly intended. Plot Parameters Parameter Default Impedance unit kΩ Line width 1.4 Marker size 6 Show markers on Log X / Log Y off / off Legend / Grid on / on The app never infers an equal aspect ratio from the selected quantities: a Nyquist plot starts with independently fitted limits. Use **Use equal X/Y scale** only when equal data units are useful for the current comparison. Use **Fit X/Y limits** to return to independent limits after equal scaling or a manual zoom. Equal scaling expands a fitted limit when necessary so X and Y data units have the same on-screen length; it is a one-time reset and does not constrain later wheel zooming. Axis and styling changes preserve the current source set and the current viewport; the two view buttons explicitly replace that viewport. Output **Export current plot CSV** writes the selected X/Y values for each valid file on a shared row index. Each file retains its own X and Y pair, so unequal curve lengths do not imply interpolation. Impedance columns use the selected display unit and include an ASCII unit suffix such as `kohm` in the column name. A result manifest records the selected axes, impedance unit, plot parameters, source references, and output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"spectrum.DTA\", \"eis\"); assert(status.ok, status.message); units = eis.impedanceDisplay.catalog(); x = eis.analysisRun.valuesForAxis(item, \"Zreal\", units.choices(3)); y = eis.analysisRun.valuesForAxis(item, \"-Zimag\", units.choices(3)); plot(x, y, \"o-\"); axis equal `valuesForAxis` is app-owned and not currently part of the published app API catalog. The DTA loader and `getZCurve` are supported reusable APIs. Errors And Limitations Log axes omit or reject nonpositive coordinates according to MATLAB axes behavior; inspect the data rather than treating missing points as zero. Overlaying files does not normalize electrode area or fixture geometry. Changing the impedance display unit rescales impedance axes and exported impedance columns; it does not alter the DTA values stored in base ohms. Axis labels describe parsed DTA columns; they do not validate the experiment configuration recorded by the instrument. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md)"},{"title":"2.1: RHS apps and shared runtime stability","url":"history/records/2026/06/LK-20260621-v2-1-rhs-and-runtime-stability.html","kind":"history","section":"history","keywords":"LK-20260621-v2-1-rhs-and-runtime-stability 2026-06-21 7 feat compatible historical project evolution","text":"2.1: RHS apps and shared runtime stability id: LK-20260621-v2-1-rhs-and-runtime-stability date: 2026-06-21 sequence: 7 type: feat compatibility: compatible scope: historical project evolution Context After v2.0, the UI runtime supported several app families but still repeated important behavior around busy actions, path events, preview zoom, and layout ownership. A new neurophysiology family would amplify those inconsistencies if each app solved them independently. Decision and rationale Add RHS reading and review as a first-class app family while centralizing only the runtime mechanics shared across domains. Filtering choices and response analysis remained in the RHS workflows; transactions, path events, zoom, and layout ownership belonged to the UI layer. Changes Added RHS Preview, Nerve Response Analysis, and Response Review Stats. Folded the separate screening step into preview filtering so file review used one workflow rather than two overlapping stages. Added duplicate Batch Crop tasks for repeated crop layouts. Unified preview scroll zoom and centralized action busy transactions. Centralized path-event contracts and app layout ownership. Added a ZIP-based updater and stabilized image and app interactions before release. Reduced focused test discovery and guardrail cost as the repository grew. User and data impact Neurophysiology users gained file preview, response analysis, and review statistics apps. Existing users saw more consistent zoom, busy feedback, path updates, and responsive layout behavior. The updater provided a repository- owned way to install a packaged release. Compatibility and migration The RHS family was additive. UI implementations that maintained their own copies of busy, path, or layout mechanics were migrated to the shared runtime contracts. Validation The stage added RHS-focused tests, interaction regressions, compatibility input checks, path-event checks, and faster focused test routing. Tag `2.1` points to `76ddf7d0`. Evidence RHS app family `7ddc036f` and preview filtering `ca8a37dd`. Shared interaction mechanics `eb52eb17`, `f3e42b8e`, `1fe89ba2`. Layout ownership `3ce4c8f7`, `09baf7fe`. Updater `e4b02f3a`. 2.1 release stabilization `76ddf7d0`. Known limitations and follow-up The updater still depended on how the launcher was installed, and several image workflows reset zoom or recomputed previews during edits. The following 2.2 and 2.3 releases concentrated on self-contained launch and direct image manipulation."},{"title":"Adjacent navigation for history records","url":"history/records/2026/07/LK-20260716-history-adjacent-navigation.html","kind":"history","section":"history","keywords":"LK-20260716-history-adjacent-navigation 2026-07-16 64 docs compatible documentation `tools/docs/` `site/history/records/`","text":"Adjacent navigation for history records id: LK-20260716-history-adjacent-navigation date: 2026-07-16 sequence: 64 type: docs compatibility: compatible component: `documentation` scope: `tools/docs/` scope: `site/history/records/` Context The complete timeline linked every recorded change, but a reader who opened one detail page had to return to the index before continuing chronologically. Dates and filenames could not safely identify adjacent records because several changes may share one date. Decision and rationale Use validated history `sequence` metadata to add adjacent-record links at the end of every rendered history detail page. **Previous change** means the record with `sequence - 1`; **Next change** means `sequence + 1`. This preserves one unambiguous project-history order without binding authored content to Git. Changes Added responsive previous/next navigation immediately before the generated page footer. Showed only existing directions at the oldest and newest ends. Added regression coverage for link targets, ordering, and page placement. User and data impact Readers can traverse project history linearly without returning to the index. No MATLAB runtime, app state, scientific data, or exports change. Compatibility and migration Existing history URLs and record metadata remain valid. Regenerating `site/` adds navigation to every record page and updates the search index normally. Validation Renderer regression tests verify middle and endpoint records against source sequence metadata. The documentation consistency check independently rebuilds and byte-compares the tracked site. Evidence `renderLabKitHistorySequenceNavigation` owns adjacent link generation. `DocumentationRendererRegressionTest` verifies the generated navigation. Known limitations and follow-up Navigation is intentionally chronological across all project components. It does not filter to the current app or library; component-specific history remains available from each component manual."},{"title":"App SDK explicit contract replaces the retired UI runtime","url":"history/records/2026/07/LK-20260719-ui-explicit-contract-migration.html","kind":"history","section":"history","keywords":"LK-20260719-ui-explicit-contract-migration 2026-07-19 138 refactor breaking labkit.app labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app App Framework DIC Electrochem Gait Image Measurement LabKit Core Neurophysiology Statistics Wearable Project persistence Result provenance","text":"App SDK explicit contract replaces the retired UI runtime id: LK-20260719-ui-explicit-contract-migration date: 2026-07-19 sequence: 138 type: refactor compatibility: breaking component: `labkit.app` | `new -> 1.0.0` component: `labkit_DICPostprocess_app` | `1.4.7 -> 1.5.0` component: `labkit_DICPreprocess_app` | `1.5.8 -> 1.6.0` component: `labkit_ChronoOverlay_app` | `1.4.7 -> 1.5.0` component: `labkit_CIC_app` | `1.4.7 -> 1.5.0` component: `labkit_CSC_app` | `1.4.8 -> 1.5.0` component: `labkit_EIS_app` | `1.4.7 -> 1.5.0` component: `labkit_VTResistance_app` | `1.4.7 -> 1.5.0` component: `labkit_GaitAnalysis_app` | `2.0.8 -> 2.1.0` component: `labkit_BatchImageCrop_app` | `1.7.7 -> 1.8.0` component: `labkit_CurvatureMeasurement_app` | `1.4.6 -> 1.5.0` component: `labkit_FLIRThermal_app` | `1.4.8 -> 1.5.0` component: `labkit_FocusStack_app` | `1.5.6 -> 1.6.0` component: `labkit_ImageEnhance_app` | `1.6.7 -> 1.7.0` component: `labkit_ImageMatch_app` | `1.6.8 -> 1.7.0` component: `labkit_VideoMarker_app` | `1.5.7 -> 1.6.0` component: `labkit_FigureStudio_app` | `0.2.9 -> 0.3.0` component: `labkit_NerveResponseAnalysis_app` | `1.4.8 -> 1.5.0` component: `labkit_ResponseReviewStats_app` | `1.4.7 -> 1.5.0` component: `labkit_RHSPreview_app` | `1.4.6 -> 1.5.0` component: `labkit_TTestWizard_app` | `1.0.1 -> 1.1.0` component: `labkit_ECGPrint_app` | `1.4.6 -> 1.5.0` scope: App Framework scope: DIC scope: Electrochem scope: Gait scope: Image Measurement scope: LabKit Core scope: Neurophysiology scope: Statistics scope: Wearable scope: Project persistence scope: Result provenance Context The retired UI runtime removed substantial per-App lifecycle code, but Apps still registered callback tables, repeated bound values in presenters, authored standard file add/remove/clear behavior, and depended on nested event/service structs. A replacement SDK kernel had already established immutable semantic values, pre-GUI validation, transactional state/presentation commits, project documents, result manifests, resources, and portable sources. The migration then had to restore the complete behavior and visual contract of every tracked App before the retired production facade could be deleted. Decision and rationale Create `labkit.app` as the stable SDK rather than misnaming the expanded contract `labkit.ui` or adapting it back to Runtime V2 transport structs. Keep the public root small, partition authoring by capability, and concentrate complexity in a paved path: direct-callback `layout.*` nodes, strict bindings, runtime-completed `view.Snapshot` values, standard file lifecycle, fixed `CreateSession(project,context)`, and private native adapters. Migrate all 21 tracked Apps through capability waves, treating their previous controls, tabs, layout proportions, interactions, project behavior, results, debug samples, and workflow wording as product contracts rather than reducing the task to launch compatibility. Changes Added the private native MATLAB adapter with semantic component ownership, typed RuntimeKernel callbacks, native dialog results, complete-presentation reconciliation, and rollback to the previous native view after a failed renderer commit. Added `Definition.launch`, fixed renderer `(axes,model)` dispatch, semantic labels, runtime-owned file add/remove/clear and selection, and transient session rebuild after source collection changes. Added strict table view options, typed complete-data edits, and distinct `event.TableCellEdit`, `event.TableCellSelection`, and `event.ListSelection` values; the private adapter absorbs native MATLAB table-value differences. Fixed session construction to `CreateSession(project,context)` so Apps resolve opaque portable sources without reading their representation. Migrated Chrono Overlay to one directly bound export callback, four state bindings, one directly bound two-axis renderer, and a two-operation view snapshot. Partitioned the public SDK into `layout`, `view`, `event`, `project`, `result`, and `dialog`; layout nodes, option parsing, stores, adapters, and runtime execution remain hidden under `internal`. Reduced Chrono's noncomment layout/action/presenter code from 277 lines to 86 while preserving its DTA alignment, plot options, project schema, CSV columns, and result provenance. Migrated T-Test Wizard as the typed editable-table, feature-fragment, and multi-page workspace proof: table selections and edits have explicit payload classes, `+workbench` exposes product assembly, workflow packages own their layout/presentation/actions, and the private adapter owns concrete layout. Migrated VT Resistance to direct file and analysis-setting bindings, a complete summary/table/two-axis snapshot, and an App-owned result-package export. Plot renderers and scientific choices now live with their owning analysis capabilities instead of a technical UI package. Migrated Gait Analysis to capability-owned source adoption, option invalidation, deterministic analysis, step selection/navigation, three-axis rendering, CSV-set export, and result packaging. Migrated DIC Preprocess to two role-bound image sources, paired-anchor registration, managed crop and mask editors, two-axis rendering, edit replay, and result-package exports owned by its analysis, mask, and result capabilities. Migrated Batch Image Crop to framework-owned source selection plus App-owned duplicate crop tasks, direct crop-center and scale-reference interactions, capability snapshots, and result-package export. Added `labkit.app.project.sourceRecord` so pure payload migrations can convert legacy paths into portable sources without constructing the framework-owned reference representation. Corrected folder chooser dispatch to its one-path backend contract and applied table data before table selection during native reconciliation, so a selection may legally target rows introduced by the same snapshot. Preserved cell-valued interaction payloads in the transactional event queue and kept multi-target interaction bridge specifications scalar, enabling paired-anchor gestures across two semantic axes. Removed handler objects, callback tables, renderer registries, and their forwarding from the App authoring contract. Layout controls and plot areas reference concrete functions directly. Migrated the remaining electrochemistry, DIC, image-measurement, neurophysiology, core, wearable, and high-state workflows, including editable tables, workspace pages, file roles, multi-axis plots, managed point/rectangle/interval/scale interactions, long-lived video resources, project recovery, result packages, and synthetic diagnostic samples. Restored shared product presentation: versioned titles and dirty markers, startup progress and failure surfaces, guarded close behavior, utility menus, adjustable pane dividers, scroll and grow policies, numeric panners, adaptive action grids, usage text, plot navigation, pop-out/export tools, and viewport-preserving overlays. Internalized contract compilation, runtime construction, native platform plans, target inventories, and callback-context creation. `Definition` is the sole author-created root; `CallbackContext` is a sealed runtime-injected port; optional concepts remain grouped under purpose-specific packages. Deleted the complete retired `labkit.ui` production facade and the migration-only analyzer, prototypes, compatibility tools, and debt-only tests after source scans and focused App tests proved that no production consumer remained. User and data impact Chrono Overlay retains its input formats, pulse-gap alignment, plot meanings, parameter defaults, CSV table, and version-2 project payload. T-Test Wizard retains its source formats, group/test calculations, plot meaning, two CSV exports, and version-2 project payload. VT Resistance retains its pulse detection, resistance calculations, plot semantics, CSV schema, and version-1 project payload while recomputing the decoded batch under shared settings. Gait Analysis retains its Video Marker payload contract, project migrations, step segmentation, gait metrics, CSV set, and version-3 project payload. File identities and portable paths remain runtime-owned. DIC Preprocess retains its rigid registration, common crop, mask editing, image/mask exports, and version-1 project payload. Batch Image Crop retains duplicate tasks per source, fixed-pixel and physical crops, rotation/padding, scale calibration, scale-bar placement, image/CSV exports, and its version-2 payload. Existing payload migrations remain App-owned. The other 15 Apps retain their documented source formats, scientific calculations, units, thresholds, project payload versions, export schemas, and result meanings. Their product versions advance once from the `main` baseline to identify the new App SDK source contract and restored complete UI behavior. No project payload version was increased merely because the UI framework changed. Compatibility and migration `labkit.app` 1 is a source-breaking replacement contract for App definitions, presenters, callbacks, events, and interactions. Every tracked App migrated before the retired `labkit.ui` boundary was deleted; there is no runtime adapter or dual authoring surface. Public App entrypoint commands remain stable. Project documents retain their format and App payload versions independently of the facade transition. Validation Focused headless tests cover strict values, transactional runtime behavior, project save/restore, authoring defaults, and Chrono calculations/exports. Hidden GUI tests cover native semantic construction, typed control and table callbacks, bound side effects, standard file lifecycle, transient session rebuild, two-axis rendering, viewport preservation, renderer rollback, Chrono export, and project restore. VT Resistance focused tests cover resistance calculations, CSV compatibility, native layout, shared batch recomputation, two-axis rendering, result packaging, and project restore. Gait focused tests cover project migration, pose decoding, scientific calculations, CSV compatibility, typed table navigation, three-axis rendering, folder selection, result packaging, and project restore. DIC Preprocess focused tests cover project state, image loading, edit replay, registration/crop/mask helpers, export manifests, native two-axis layout, paired-anchor alignment, and managed crop interaction. Framework regression tests cover cell-valued event payloads and native multi-axis interaction bridging. Batch Image Crop focused tests cover crop geometry, padding, physical scaling, project migration, duplicate tasks, output planning/writes, native semantic layout, current-center editing, and standard result manifests. Focused framework and App tests additionally cover all 21 semantic layouts, typed events, managed interactions, project migration/recovery, result writing, synthetic sample packs, diagnostics, resource cleanup, window titles, startup success/failure, close behavior, and native adapter reconciliation. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Batch Image Crop](../../../../apps/image-measurement/batch-crop/README.md) [App catalog](../../../../apps/README.md) [LabKit App Framework](../../../../framework/README.md) [Build a Complete App](../../../../development/build-apps/complete-app.md) Known limitations and follow-up Automated GUI evidence does not replace developer-led validation of native dialogs, editable-table feel, pointer interaction, long-lived resource use, representative exports, visual quality, or scientific workflow suitability. That interactive validation remains a release input for the exact integrated commit. labkit.app.project.sourceRecord Create a portable source value during project migration."},{"title":"App SDK supports equal-data-unit plot fitting","url":"history/records/2026/07/LK-20260721-app-sdk-equal-data-unit-fit.html","kind":"history","section":"history","keywords":"LK-20260721-app-sdk-equal-data-unit-fit 2026-07-21 148 fix compatible labkit.app App Framework EIS Overlay","text":"App SDK supports equal-data-unit plot fitting id: LK-20260721-app-sdk-equal-data-unit-fit date: 2026-07-21 sequence: 148 type: fix compatibility: compatible component: `labkit.app` | `1.2.1 -> 1.2.2` scope: App Framework scope: EIS Overlay Context Apps needed an equal X/Y data-unit view for plots such as Nyquist diagrams, but setting an axes aspect mode directly also changed the allocated drawing area and persisted into later wheel and zoom interactions. Decision and rationale Keep viewport geometry in the framework fitter. An App can request equal data units while fitting the current plotted data, after which ordinary framework viewport preservation continues without an aspect lock. Changes Added the public `EqualDataUnits` option to `labkit.app.plot.fitAxesToGraphics`. Fit equal data units by expanding the smaller data span for the current axes pixel geometry, including logarithmic dimensions in log space. Removed the retired EIS axis-label rule; EIS now exposes explicit fit and equal-scale view actions. User and data impact The new option is additive. Existing fit calls keep independent X/Y limits. EIS projects and exports are unchanged; equal scaling is a transient view action and does not alter later zoom or wheel behavior. Compatibility and migration No project or result migration is required. Existing callers retain their independent fit behavior unless they opt into `EqualDataUnits=true`. Validation EIS GUI coverage exercises independent fitting, explicit equal scaling, and later viewport preservation. The App SDK plot helper tests cover finite data, logarithmic axes, and the public option contract. Evidence The fitting helper applies limits from finite plotted data and expands only the smaller working-space span for the axes pixel geometry. The EIS workflow test then verifies that a later framework redraw preserves the chosen viewport. Known limitations and follow-up Developer-led visual validation remains responsible for assessing layout balance on target displays and MATLAB releases. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data."},{"title":"App actions require explanatory hover help","url":"history/records/2026/07/LK-20260720-app-action-tooltips.html","kind":"history","section":"history","keywords":"LK-20260720-app-action-tooltips 2026-07-20 140 feat compatible labkit.app labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app App Framework All tracked Apps","text":"App actions require explanatory hover help id: LK-20260720-app-action-tooltips date: 2026-07-20 sequence: 140 type: feat compatibility: compatible component: `labkit.app` | `1.0.0 -> 1.1.0` component: `labkit_DICPostprocess_app` | `1.5.0 -> 1.5.1` component: `labkit_DICPreprocess_app` | `1.6.0 -> 1.6.1` component: `labkit_ChronoOverlay_app` | `1.5.0 -> 1.5.1` component: `labkit_CIC_app` | `1.5.0 -> 1.5.1` component: `labkit_CSC_app` | `1.5.0 -> 1.5.1` component: `labkit_EIS_app` | `1.5.0 -> 1.5.1` component: `labkit_VTResistance_app` | `1.5.0 -> 1.5.1` component: `labkit_GaitAnalysis_app` | `2.1.0 -> 2.1.1` component: `labkit_BatchImageCrop_app` | `1.8.0 -> 1.8.1` component: `labkit_CurvatureMeasurement_app` | `1.5.0 -> 1.5.1` component: `labkit_FLIRThermal_app` | `1.5.0 -> 1.5.1` component: `labkit_FocusStack_app` | `1.6.0 -> 1.6.1` component: `labkit_ImageEnhance_app` | `1.7.0 -> 1.7.1` component: `labkit_ImageMatch_app` | `1.7.0 -> 1.7.1` component: `labkit_VideoMarker_app` | `1.6.0 -> 1.6.1` component: `labkit_FigureStudio_app` | `0.3.0 -> 0.3.1` component: `labkit_NerveResponseAnalysis_app` | `1.5.0 -> 1.5.1` component: `labkit_ResponseReviewStats_app` | `1.5.0 -> 1.5.1` component: `labkit_RHSPreview_app` | `1.5.0 -> 1.5.1` component: `labkit_TTestWizard_app` | `1.1.0 -> 1.1.1` component: `labkit_ECGPrint_app` | `1.5.0 -> 1.5.1` scope: App Framework scope: All tracked Apps Context The native text-fit adapter copied button text through a column-shaped char conversion. MATLAB therefore received one newline between every character and rendered hover help as a narrow vertical strip. The generated tooltip also only repeated the visible label, so it did not explain the scientific or workflow consequence of an action. Decision and rationale Make explanatory hover help part of the semantic layout contract. Every `layout.button` produces a nonempty Tooltip, and tracked Apps must replace the label-based framework fallback with App-owned explanatory text. File-list actions expose dedicated tooltip fields and retain framework-owned defaults for generic folder, remove, and clear mechanics. Scientific meaning stays in the owning App rather than in the native adapter. All tracked Apps now describe what their actions consume, calculate, mutate, or export. The contract guardrail rejects App tooltips that only repeat the visible action label and also requires an explanatory input-selection tooltip. Changes Preserved char row vectors as one text line during native text fitting, eliminating the injected character-by-character newlines. Added `Tooltip` support with a nonempty label fallback to `layout.button` and dedicated file-list tooltips for choose, folder, recursive folder, remove, and clear actions. Added scientific and workflow-specific text for all 138 tracked App business buttons and all 26 App input selectors. Centralized the shared tooltip contract in the App Framework and App catalog instead of repeating it in every App manual. User and data impact Existing tracked Apps and third-party layouts retain their actions and calculations. Hovering now always shows readable text; tracked Apps additionally carry domain-specific explanations. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation App SDK unit coverage for required and compiled tooltip values. Cross-App definition guardrail for non-label business and input tooltips. Native adapter GUI coverage for exact tooltip text without injected newlines. DIC Postprocess GUI coverage for scientific action and Ncorr input help. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive checks should confirm tooltip timing, width, and line wrapping on supported MATLAB releases and desktop platforms."},{"title":"App alerts through UI facade","url":"history/records/2026/06/LK-20260630-app-alerts-through-ui-facade.html","kind":"history","section":"history","keywords":"LK-20260630-app-alerts-through-ui-facade 2026-06-30 22 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_ECGPrint_app App alerts through UI facade","text":"App alerts through UI facade id: LK-20260630-app-alerts-through-ui-facade date: 2026-06-30 sequence: 22 type: feat compatibility: compatible component: `labkit.ui` | `3.2.7 -> 3.2.8` component: `labkit_DICPostprocess_app` | `1.2.2 -> 1.2.3` component: `labkit_DICPreprocess_app` | `1.2.1 -> 1.2.2` component: `labkit_ChronoOverlay_app` | `1.2.0 -> 1.2.1` component: `labkit_CIC_app` | `1.2.0 -> 1.2.1` component: `labkit_CSC_app` | `1.2.0 -> 1.2.1` component: `labkit_EIS_app` | `1.2.0 -> 1.2.1` component: `labkit_VTResistance_app` | `1.2.0 -> 1.2.1` component: `labkit_BatchImageCrop_app` | `1.3.6 -> 1.3.7` component: `labkit_CurvatureMeasurement_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.2.4 -> 1.2.5` component: `labkit_ImageEnhance_app` | `1.3.3 -> 1.3.4` component: `labkit_ImageMatch_app` | `1.3.4 -> 1.3.5` component: `labkit_ECGPrint_app` | `1.2.1 -> 1.2.2` scope: App alerts through UI facade Context Apps opened alerts directly, which made hidden GUI tests unreliable and led to small differences in modal behavior across workflows. Decision and rationale Route user-visible alerts through one UI service that can display a modal message normally and record it safely during hidden tests. Keep each app responsible for the message and the decision that triggers it. Changes `labkit.ui` `3.2.7 -> 3.2.8` DIC, electrochem, image-measurement, and ECG apps patch bumped where alert routing changed. Routed app alerts through hidden-test-safe `labkit.ui.app.showAlert`. User and data impact Errors continued to appear as app alerts, but test and debug runs could capture them without blocking on an invisible dialog. No saved or exported data changed. Compatibility and migration App error conditions and messages remained app-owned. Only the display route changed, so user data and app project formats required no conversion. Validation Commit `8d7c83b1` migrated alert call sites and updated hidden workflow tests across the listed apps. Evidence Main commit `8d7c83b1`. Known limitations and follow-up The historical `labkit.ui.app.showAlert` entry point was later replaced by the injected `services.dialogs.alert` service in Runtime V2."},{"title":"App diagnostics and hardened UI workflows","url":"history/records/2026/06/LK-20260628-app-diagnostics-and-hardened-ui-workflows.html","kind":"history","section":"history","keywords":"LK-20260628-app-diagnostics-and-hardened-ui-workflows 2026-06-28 15 feat compatible labkit_launcher labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app App diagnostics and hardened UI workflows","text":"App diagnostics and hardened UI workflows id: LK-20260628-app-diagnostics-and-hardened-ui-workflows date: 2026-06-28 sequence: 15 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.1 -> 1.1.2` component: `labkit.ui` | `3.1.0 -> 3.1.2` component: `labkit.ui` | `3.1.2 -> 3.1.3` component: `labkit_BatchImageCrop_app` | `1.3.0 -> 1.3.1` component: `labkit_BatchImageCrop_app` | `1.3.1 -> 1.3.2` component: `labkit_FocusStack_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.2.1 -> 1.2.2` component: `labkit_ImageMatch_app` | `1.2.0 -> 1.2.1` component: `labkit_NerveResponseAnalysis_app` | `1.2.0 -> 1.2.1` component: `labkit_ResponseReviewStats_app` | `1.2.0 -> 1.2.1` component: `labkit_RHSPreview_app` | `1.2.0 -> 1.2.1` scope: App diagnostics and hardened UI workflows Context When an app stalled or caught an exception, screenshots and the visible Log tab often omitted the active callback and stack information needed to reproduce the problem. Decision and rationale Capture active operations, uncaught crashes, caught errors, and suspected stalls in structured local reports while hardening the UI paths that generate those events. Keep reports outside project data so diagnostics cannot alter results. Changes `labkit.ui` `3.1.0 -> 3.1.3` Batch Crop, Focus Stack, Image Enhance/Match, neurophysiology apps, and the launcher patch bumped where runtime behavior changed. Hardened LabKit UI workflows. Added crash reports, active-operation reports, caught-error reports, and stall diagnostics. User and data impact Debug runs produced evidence that could identify the failing operation and app state without copying laboratory inputs into the repository. Normal projects and exports kept their previous formats. Compatibility and migration The diagnostic files were additive and existing app inputs and saved results remained readable. Debug runs began producing more detailed local artifacts. Validation Commits `e966457b` and `f5bc6f98` updated launcher, framework, and affected app workflow tests for diagnostic creation and hardened callbacks. Evidence Main commits `e966457b` and `f5bc6f98`. Known limitations and follow-up Diagnostics report the state and stack available at capture time; they do not replace a reproducible input or a focused regression test."},{"title":"App file-selection and electrochem control fixes","url":"history/records/2026/07/LK-20260703-app-file-selection-and-electrochem-control-fixes.html","kind":"history","section":"history","keywords":"LK-20260703-app-file-selection-and-electrochem-control-fixes 2026-07-03 33 fix compatible labkit_CIC_app labkit_CIC_app labkit_CSC_app labkit_CSC_app labkit_VTResistance_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"App file-selection and electrochem control fixes id: LK-20260703-app-file-selection-and-electrochem-control-fixes date: 2026-07-03 sequence: 33 type: fix compatibility: compatible component: `labkit_CIC_app` | `1.3.1 -> 1.3.2` component: `labkit_CIC_app` | `1.3.2 -> 1.3.3` component: `labkit_CSC_app` | `1.3.1 -> 1.3.2` component: `labkit_CSC_app` | `1.3.2 -> 1.3.3` component: `labkit_VTResistance_app` | `1.3.1 -> 1.3.2` component: `labkit_VTResistance_app` | `1.3.2 -> 1.3.3` component: `labkit_BatchImageCrop_app` | `1.6.2 -> 1.6.3` component: `labkit_FLIRThermal_app` | `1.2.1 -> 1.2.2` component: `labkit_FocusStack_app` | `1.4.1 -> 1.4.2` component: `labkit_ImageEnhance_app` | `1.5.1 -> 1.5.2` component: `labkit_ImageMatch_app` | `1.5.1 -> 1.5.2` scope: historical project evolution Context Adding files to an existing multi-file session could replace or select the wrong entry in several apps. CIC, CSC, and VT Resistance also exposed manual plot controls that no longer matched their intended result-review workflow. Decision and rationale Preserve the complete file list when new selections are appended and select a newly added item predictably. Remove obsolete electrochem controls instead of keeping buttons whose effect was ambiguous or redundant. Changes CIC, CSC, VT Resistance, Batch Crop, FLIR Thermal, Focus Stack, Image Enhance, and Image Match patch bumped for appended file selections. CIC, CSC, and VT Resistance patch bumped again for manual plot-control removal. Preserved appended file selections. Removed electrochem manual plot controls that no longer matched the workflow. User and data impact Users could add another batch without losing files already loaded, across the listed electrochem and image apps. The electrochem workbenches became simpler; calculations and exported result schemas were unchanged. Compatibility and migration Existing loaded-file and result formats remained valid. Appended selections now preserved the active item, and the removed manual plot controls did not alter the underlying calculation options. Validation The file-selection commit added GUI regression coverage to every affected app. The control-removal commit updated the three electrochem GUI workflows and shared test helpers. Exact historical commands were not recorded. Evidence Main commits `6348185e` and `674d5d4b`. Known limitations and follow-up The change covered app-level append handling. Later framework work unified the native file-selection behavior across platforms."},{"title":"App manuals become reader-oriented and focused tests gain one wrapper","url":"history/records/2026/07/LK-20260720-reader-oriented-app-manuals-and-test-wrapper.html","kind":"history","section":"history","keywords":"LK-20260720-reader-oriented-app-manuals-and-test-wrapper 2026-07-20 142 docs compatible App documentation Documentation validation Agent testing workflow Agent skill maintenance","text":"App manuals become reader-oriented and focused tests gain one wrapper id: LK-20260720-reader-oriented-app-manuals-and-test-wrapper date: 2026-07-20 sequence: 142 type: docs compatibility: compatible scope: App documentation scope: Documentation validation scope: Agent testing workflow scope: Agent skill maintenance Context Concrete App manuals repeated shared hover-help and runtime architecture contracts. The repetition displaced workflow and scientific content, became stale when the framework changed, and made every App page appear more implementation-oriented than user-oriented. Focused test execution also required agents to repeatedly assemble low-level runner arguments; omitting GUI inclusion could produce a zero-match selection that looked like a test problem. Decision and rationale Keep shared behavior in the App catalog and framework manual. Concrete App pages describe only their inputs, workflow, App-specific interactions, scientific semantics, outputs, recovery, programmatic surface, and limitations. Protect that boundary with a documentation guardrail. Provide one platform-independent MATLAB wrapper with explicit operations for listing a file's canonical test names, running an exact file, running a canonical class or method within its owning file, and running a suite. Keep broad final gates on the existing `buildtool` interface. Treat recurring reasoning, command construction, discovery, and retry cost as skill-maintenance signals. Improvements must describe durable concepts and semantic operations rather than one product version, App, transient failure, or current repository snapshot. Changes Removed repeated hover-help and Framework Compatibility boilerplate from all tracked App manuals. Rewrote project-state passages around user-visible persistence, recovery, source relinking, and recalculation rather than internal factories, callbacks, and schema implementation. Clarified the App-manual authoring contract and added a guardrail against reintroducing shared framework boilerplate. Added `runLabKitTestTarget.m` to the test-planner skill with automatic repository discovery, GUI inference for exact files and GUI suites, hidden figures, focused artifacts, and fail-on-zero-match behavior. Made exact canonical selectors resolve their unique owning test file, while retaining an explicit file constraint for ambiguous ownership. Made local parallel build tasks retain structured shard state while relaying concise human-readable heartbeats, clear stale transient status at worker startup, and print only failing worker logs instead of remaining silent and then dumping every shard log. Moved child-process orchestration out of `buildfile.m` into a MATLAB/Java runner shared by local macOS, Linux, and Windows, removing generated POSIX scripts and adding an independent ETA to each shard summary. Hosted CI remains single-process pending explicit concurrent-license evidence. Partitioned broad local workers by unique test-class owner file so each worker discovers only its assigned files instead of repeating full-tree discovery. Batched App path registration while keeping every setup call sensitive to newly added App entrypoints, added quiet discovery probes, and removed one unreferenced GUI assertion helper. Updated the skill to list canonical names before exact method reruns and to distinguish selection failures from passing evidence. Added a root agent rule to improve skills and scripts when reusable automation can remove recurring inference or retry cost. User and data impact App behavior, calculations, saved data, and exports are unchanged. Existing manual content about scientific meaning and recovery remains available with less implementation noise. The wrapper is an agent convenience over the official test runner and does not add another CI or public build interface. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Documentation guardrails scan every concrete App manual for shared boilerplate. Documentation rendering and byte-for-byte site comparison cover all edited pages and this record. The wrapper lists and runs canonical GUI methods through the official runner, demonstrating automatic hidden-GUI selection. Test-runner profiling measured fresh full discovery before and after the path change at 5.83 and 4.82 seconds; path setup fell from 1.27 to 0.32 seconds. The retained rescan avoids a stale-cache failure when an App is added during a MATLAB session. Build contracts prove file partitions are disjoint, cover every selected test, accept deeply nested owning files, and preserve explicit failures for missing or ambiguous owners. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Continue reviewing App manuals when user-visible behavior changes; structural guardrails prevent known boilerplate but cannot judge scientific clarity."},{"title":"App tools and presentation controls become more deliberate","url":"history/records/2026/07/LK-20260720-app-tools-menu-and-screenshot-clipboard.html","kind":"history","section":"history","keywords":"LK-20260720-app-tools-menu-and-screenshot-clipboard 2026-07-20 141 feat compatible labkit.app App Framework All tracked Apps","text":"App tools and presentation controls become more deliberate id: LK-20260720-app-tools-menu-and-screenshot-clipboard date: 2026-07-20 sequence: 141 type: feat compatibility: compatible component: `labkit.app` | `1.1.0 -> 1.2.0` scope: App Framework scope: All tracked Apps Context Plot, Screenshot, Save State, and Load State appeared as separate root-level native menu items. Plot was a container while the other items executed immediately, so controls at the same visual level had inconsistent behavior and consumed unnecessary horizontal space. Screenshot also required a file dialog even when the user only needed an image for another application. Short status summaries consumed the same height as detail panels, and Apps could preserve plot zoom but had no declarative one-shot reset revision. Decision and rationale Install one framework-owned **Tools** root with purpose-based **Plots**, **Screenshot**, and conditional **Project State** submenus. Keep scientific workflow controls in the App layout while grouping domain-neutral native utilities predictably. Use MATLAB's existing `copygraphics` clipboard path so the capability remains platform-neutral and introduces no external runtime. Changes Moved plot utilities beneath **Tools > Plots**. Added **Tools > Screenshot > Copy to Clipboard** for the complete App surface and retained file export as **Save to File...**. Moved project persistence beneath **Tools > Project State** when the App owns a project document. Preserved existing executable action tags for compatibility with GUI automation and added stable tags for the new menu containers and clipboard action. Added a line-count hint for compact status summaries and a general plot-view revision that accepts renderer-fitted limits once while preserving user zoom during ordinary redraws. Updated the getting-started and framework manuals. User and data impact App calculations, project files, plots, and exports are unchanged. Existing action tags continue to identify save screenshot, save state, load state, and plot commands, but users now reach them through the Tools hierarchy. Clipboard copy uses the operating system clipboard and may be unavailable in a noninteractive MATLAB session. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Hidden native-adapter GUI coverage verifies the menu hierarchy, labels, conditional Project State menu, stable action tags, and clipboard callback wiring. Focused framework tests validate the App SDK version and native runtime. Documentation rendering and consistency checks cover the updated manuals and history record. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive checks should confirm clipboard image fidelity and native menu appearance on supported MATLAB desktop platforms."},{"title":"App validation no longer relies on sibling App paths","url":"history/records/2026/07/LK-20260717-isolated-app-validation.html","kind":"history","section":"history","keywords":"LK-20260717-isolated-app-validation 2026-07-17 128 test compatible Public App path isolation Changed-file validation routing GUI CI evaluation","text":"App validation no longer relies on sibling App paths id: LK-20260717-isolated-app-validation date: 2026-07-17 sequence: 128 type: test compatibility: compatible scope: Public App path isolation scope: Changed-file validation routing scope: GUI CI evaluation Context The ordinary public test setup adds every App entry folder so family and cross-App contract suites can run efficiently. That broad path can conceal an accidental dependency on a sibling App package. Static source scans rejected direct sibling calls, and individual Gait/private launch tests used isolated paths, but there was no uniform executable contract for every public App or automatic changed-file route to that contract. Decision and rationale Keep the convenient broad path for ordinary suites, but add one fast independent proof. For each public App, restore MATLAB's default path, add only the repository root and the owning App root, load its complete definition, check declared facade compatibility, and write its synthetic debug sample. Run this contract whenever public App source changes. Static call scanning and dynamic isolation cover different risks and remain separate. The former identifies the forbidden source reference; the latter proves metadata, framework requirements, and debug generation work in the same path shape as a single-App package. Changes Added one isolated-path contract covering all 20 public Apps. Routed every App source change to that contract in both conservative and fast changed-file plans. Added a planner regression so future routing changes cannot silently drop isolation coverage. Documented why independent private workspaces must run their own compatibility checks without inheriting public App roots. Evaluated, but did not add, a hidden-GUI job to every pull request. User and data impact There is no App or saved-data behavior change. A new sibling package dependency, stale facade requirement, incomplete definition, or broken debug sample now fails in a focused non-GUI contract instead of passing because all Apps happened to be on the developer's MATLAB path. Compatibility and migration No migration is required. Existing test commands remain unchanged; the changed-file planner adds the focused isolation selection automatically. Validation The all-App isolation contract completed in about 14 seconds locally. Existing changed-file evidence measured representative hidden image workflows at about 18 seconds each and a Gait hidden launch at about 30 seconds, before MATLAB startup and license acquisition. Since those synthetic GUI checks still do not prove native dialogs, pointer feel, or visual quality, ordinary CI keeps the fast non-GUI contract while App-specific GUI checks remain changed-file, scheduled, manual, and release validation. The private workspace's independent suite passes after removing public App roots from its runner path. Evidence [Testing and validation](../../../../development/maintain-and-release/testing.md) [Architecture](../../../../development/build-apps/architecture.md) [App development](../../../../development/build-apps/app-development.md) Known limitations and follow-up MATLAB process-global function caches cannot replace package-boundary source review, so the static sibling-call guard remains required. Hidden GUI tests remain bounded semantic checks rather than substitutes for manual interaction and visual assessment."},{"title":"Base-MATLAB image compatibility","url":"history/records/2026/07/LK-20260713-base-matlab-image-compatibility.html","kind":"history","section":"history","keywords":"LK-20260713-base-matlab-image-compatibility 2026-07-13 45 feat compatible labkit.image labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app Base-MATLAB image compatibility","text":"Base-MATLAB image compatibility id: LK-20260713-base-matlab-image-compatibility date: 2026-07-13 sequence: 45 type: feat compatibility: compatible component: `labkit.image` | `1.1.0 -> 1.2.0` component: `labkit_DICPostprocess_app` | `1.3.4 -> 1.3.5` component: `labkit_DICPreprocess_app` | `1.3.5 -> 1.3.6` component: `labkit_FocusStack_app` | `1.4.7 -> 1.4.8` component: `labkit_ImageEnhance_app` | `1.5.6 -> 1.5.7` component: `labkit_ImageMatch_app` | `1.5.6 -> 1.5.7` scope: Base-MATLAB image compatibility Context Several image workflows appeared toolbox-optional but still called Image Processing Toolbox functions for conversion, grayscale luminance, resizing, smoothing, alignment, or warping. Development machines with the toolbox hid those calls, so a nominally supported Base MATLAB installation could fail only after the user entered a particular workflow. Decision and rationale Own the essential image operations used by LabKit and make their Base MATLAB paths the tested behavior. Optional toolbox acceleration could remain only when the call was explicit, a repository-owned fallback existed, and parity tests protected the app-consumed result. Changes `labkit.image` `1.1.0 -> 1.2.0` `labkit_DICPreprocess_app` `1.3.5 -> 1.3.6` `labkit_DICPostprocess_app` `1.3.4 -> 1.3.5` `labkit_FocusStack_app` `1.4.7 -> 1.4.8` `labkit_ImageEnhance_app` `1.5.6 -> 1.5.7` `labkit_ImageMatch_app` `1.5.6 -> 1.5.7` Added `labkit.image.toDouble` and `labkit.image.toLuma`, and replaced hard Image Processing Toolbox calls in shared image facade code and image-app workflow paths with base-MATLAB implementations. DIC preprocessing now uses a toolbox-free phase-correlation translation path for automatic alignment and a base-MATLAB rigid warp for control-point alignment. DIC postprocessing, Focus Stack, Image Enhance, and Image Match now use app-local or facade-owned image normalization, resizing, smoothing, and luma helpers instead of requiring toolbox functions. Added a project hygiene guardrail that rejects unguarded toolbox image helper calls under `apps/` and `+labkit/`, while still allowing explicit optional toolbox paths with fallbacks. User and data impact DIC alignment and overlays, Focus Stack, Image Enhance, and Image Match could run without Image Processing Toolbox. Users with the toolbox retained compatible workflows, while CI now exercised the installation that previously failed late and silently. Compatibility and migration Existing app workflows and exported schemas are preserved. Optional toolbox acceleration paths remain allowed only when a base-MATLAB fallback is present. Validation The commit expanded DIC, Focus Stack, Image Enhance, Image Match, and image- facade unit suites and added `ToolboxDependencyGuardrailTest` to detect new unguarded calls. GUI layout tests covered the affected DIC workflows. The exact historical command was not recorded. Evidence Mainline commit `bcd5f51f`. Known limitations and follow-up Base MATLAB compatibility does not mean every optional accelerated algorithm is numerically identical by construction. Where a toolbox branch affects scientific values, representative parity and idempotency tests remain required until the repository-owned implementation fully replaces it."},{"title":"Batch Crop assigns state to workflow capabilities","url":"history/records/2026/07/LK-20260716-batch-crop-structure.html","kind":"history","section":"history","keywords":"LK-20260716-batch-crop-structure 2026-07-16 101 refactor compatible labkit_BatchImageCrop_app Image Measurement App structure","text":"Batch Crop assigns state to workflow capabilities id: LK-20260716-batch-crop-structure date: 2026-07-16 sequence: 101 type: refactor compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.2 -> 1.7.3` scope: Image Measurement scope: App structure Context Batch Crop split static metadata across separate files, spread project creation, validation, migration, and session construction through a generic lifecycle package, and placed source decoding, crop-task operations, geometry, scale calibration, export planning, and transient caches together under `+appState`. The App also retained an unused scale-unit adapter and a startup callback whose only job duplicated the shared source-adjacent output-folder default. Decision and rationale Use the compact Runtime V2 definition and one project contract, then assign remaining helpers to the workflow capability that owns their meaning. Crop geometry, scale calibration, and export planning are real App complexity and remain App-owned; generic lifecycle and state buckets are structural debt and are removed. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderers, and optional capabilities in `definition.m`. Concentrated durable creation, validation, and the version-1 upgrade in the single `projectSpec.m` entry; root `createSession.m` rebuilds transient state and lazily loads the selected source. Replaced embedded image data and direct path fields with canonical source records and `sourceId` task references. Assigned file reconstruction to `+sourceFiles`, task operations to `+cropTasks`, coordinate and cache behavior to `+cropGeometry`, calibration semantics to `+scaleCalibration`, and export state to `+resultFiles`. Removed the generic lifecycle/state packages, separate metadata files, redundant startup callback, and unused scale-unit wrapper. User and data impact Multiple crop tasks per source, draggable and resizable ROIs, zoom-preserving preview edits, rotation, edge-continuous padding, pixel and physical crop modes, per-source calibration, scale bars, manifests, and exports keep their existing behavior. Empty launch no longer computes an output folder; importing sources still selects the source-adjacent default. Developers can now find each calculation and state transition under its owning workflow concept without learning a generic App state package. Compatibility and migration Version-1 saved projects upgrade automatically to schema version 2. The migration discards reconstructible embedded pixels, creates portable source records for legacy paths, and preserves crop-task settings. Runtime source reconciliation handles moved or missing required files. Validation Focused unit tests cover project creation, validation and migration, source reading, crop geometry, scale planning, export planning, and manifest output. The hidden GUI workflow loads an image, manipulates the crop workflow, and exports a synthetic crop through the real Runtime callbacks. Evidence [Batch Image Crop](../../../../apps/image-measurement/batch-crop/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Image Library](../../../../libraries/image/README.md) Known limitations and follow-up Automated GUI tests do not replace manual judgment of ROI pointer feel, resampling quality, scale-reference placement, or export suitability for a particular quantitative workflow. Other Apps that still use generic lifecycle or state packages remain scheduled for the same ownership review."},{"title":"Batch Crop file workflow feedback","url":"history/records/2026/06/LK-20260628-batch-crop-file-workflow-feedback.html","kind":"history","section":"history","keywords":"LK-20260628-batch-crop-file-workflow-feedback 2026-06-28 14 feat compatible labkit.ui labkit_BatchImageCrop_app Batch Crop file workflow feedback","text":"Batch Crop file workflow feedback id: LK-20260628-batch-crop-file-workflow-feedback date: 2026-06-28 sequence: 14 type: feat compatibility: compatible component: `labkit.ui` | `3.0.1 -> 3.1.0` component: `labkit_BatchImageCrop_app` | `1.2.0 -> 1.3.0` scope: Batch Crop file workflow feedback Context In a multi-file Batch Crop session, the window and preview did not consistently show which list item supplied the current image and crop settings. Decision and rationale Make the selected file part of the shared window-title context and update Batch Crop feedback whenever selection changes. Keep the title derived from the file panel so the app does not maintain a second selection label. Changes `labkit.ui` `3.0.1 -> 3.1.0` Batch Crop `1.2.0 -> 1.3.0` Added selected-file title context. Improved Batch Crop file workflow feedback. User and data impact The active filename became visible while reviewing or editing a crop task, reducing the chance of applying settings to the wrong image. Crop geometry and exported data were unchanged. Compatibility and migration Batch Crop tasks and exports kept their existing format. The change affected file-selection context and feedback in the window only. Validation Commit `61e8edd3` updated shared title-context and Batch Crop GUI workflow coverage. Evidence Main commit `61e8edd3`. Known limitations and follow-up Later file-panel work extended the same title context and append behavior to the rest of the app fleet."},{"title":"Binding-only controls and default resource cleanup","url":"history/records/2026/07/LK-20260717-binding-and-resource-defaults.html","kind":"history","section":"history","keywords":"LK-20260717-binding-and-resource-defaults 2026-07-17 122 refactor compatible labkit.ui labkit_VideoMarker_app Runtime V2 action registration Runtime V2 resource ownership","text":"Binding-only controls and default resource cleanup id: LK-20260717-binding-and-resource-defaults date: 2026-07-17 sequence: 122 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.5 -> 7.4.6` component: `labkit_VideoMarker_app` | `1.5.4 -> 1.5.5` scope: Runtime V2 action registration scope: Runtime V2 resource ownership Context Runtime V2 already supported controls whose complete behavior is writing one bound state path, but Video Marker still registered an empty action for its skeleton-preset selector. The private resource registry also had a default cleanup policy, while the injected App service required every call to supply a fourth cleanup function. Video Marker therefore kept a second empty function for its plain decoded-video resource. Decision and rationale Use the existing binding-only contract for state-only controls and expose the resource registry's existing default cleanup through the injected service. Apps should declare an action only when a user event has workflow behavior beyond committing the bound value. Plain structs and values require no App-owned cleanup placeholder. Changes Removed Video Marker's empty skeleton-preset change action and left the dropdown as a direct session binding. Made the injected `services.resources.set(scope,id,value)` form select the Runtime's default cleanup, while retaining the optional fourth custom cleanup function. Removed Video Marker's empty video-resource cleanup function. Added Runtime GUI coverage for binding-only state commits and resources registered without a custom cleanup. User and data impact There is no workflow, scientific, project-data, or saved-file change. Selecting a skeleton preset still updates the session immediately, and **Use preset** still performs the only durable skeleton edit. Video cache replacement and session cleanup retain the same observable behavior. Compatibility and migration The change is source-compatible. Existing four-argument resource registration continues to use the supplied cleanup function. No project migration is required. Validation The focused Runtime V2 GUI test exercises both service forms and proves that a binding without `Event` commits state without invoking an App action. Video Marker structure and targeted behavior tests cover the simplified definition. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents bound controls and injected resource ownership. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) describes the preset and decoded-video resource behavior. Known limitations and follow-up Other bound events still require App actions when they normalize values, invalidate results, update previews, or log workflow changes. They should be reviewed by behavior rather than removed merely because their handlers are short."},{"title":"CIC consolidates its project contract without losing lazy loading","url":"history/records/2026/07/LK-20260716-cic-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-cic-project-spec 2026-07-16 87 refactor compatible labkit_CIC_app Electrochemistry Project lifecycle","text":"CIC consolidates its project contract without losing lazy loading id: LK-20260716-cic-project-spec date: 2026-07-16 sequence: 87 type: refactor compatibility: compatible component: `labkit_CIC_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context CIC split static metadata across three files and its first-version project across a generic lifecycle package. Its session factory also encodes an important performance policy: restore only the first source for preview rather than parsing an entire large batch. Decision and rationale Consolidate product metadata in `definition.m` and durable schema behavior in `projectSpec.m`. Keep `createSession.m` separate and explicit because lazy source decoding is real transient reconstruction and a user-visible performance boundary, not lifecycle boilerplate. Changes Moved command metadata, version, update date, and requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved lazy preview restoration to one package-root session factory. Removed separate metadata files and the generic lifecycle package. Kept pulse detection, CIC formulas, units, limits, plots, and exports unchanged. User and data impact Launch, source selection, large-batch behavior, computation, save/load, and CSV exports behave unchanged. The App structure is smaller while the reason for its lazy session factory is now documented next to the code. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing CIC projects require no migration. Validation Unit tests cover the project contract, CIC calculation, units, failed rows, CSV schema, and presentation. The hidden GUI workflow covers real launch, source selection, calculation, save/load, and export through Runtime V2. Evidence [CIC](../../../../apps/electrochemistry/cic/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The session currently reads `reference.originalPath` directly. That internal portable-reference representation will be replaced by a simple framework source-path service across all Apps rather than patched only in CIC."},{"title":"CIC uses Runtime-owned source and workflow mechanics","url":"history/records/2026/07/LK-20260716-cic-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-cic-runtime-source-reconciliation 2026-07-16 112 refactor compatible labkit_CIC_app Electrochemistry App structure Runtime adoption","text":"CIC uses Runtime-owned source and workflow mechanics id: LK-20260716-cic-runtime-source-reconciliation date: 2026-07-16 sequence: 112 type: refactor compatibility: compatible component: `labkit_CIC_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context CIC intentionally registers paths before decoding them so large batches remain lazy, but it still generated, appended, and deleted portable source records in App code. It also wrote directly to `session.workflow.logLines`, requiring its session factory to reproduce Runtime-owned empty workflow and view buckets. Those details increased the amount of framework structure an App maintainer had to understand without expressing CIC analysis behavior. Decision and rationale CIC continues to own the ordered lazy path set, current-index policy, deferred decoding, scientific options, and result workflow. It now passes that ordered path set to `services.project.reconcileSources` after add, removal, and clear operations. Runtime preserves matching identities and allocates new unique ones. Analysis messages use `services.workflow.log`, the same service as other workflow messages. `createSession` returns only CIC-specific selection and decoded-cache state; Runtime creates missing transient buckets before actions run. Changes Removed App-local source-ID generation and source append helpers. Reconciled registered paths through the Runtime project service. Routed analysis messages through the Runtime workflow service. Removed empty workflow and view boilerplate from `createSession`. Added GUI checks for stable and unique identities across batch add, save, reopen, removal, and re-addition. Advanced the CIC App version to 1.4.4. User and data impact Lazy decoding, file order, selected preview, analysis values, plots, exports, logs, and project files retain their behavior. Source identities remain stable for retained files and collision-free for newly registered files. Scientific calculations and output schemas are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing source records and their identities are preserved when reopened. Validation The CIC hidden GUI workflow covers three-source lazy registration, selection, whole-batch recomputation, export, save, clear, reopen, remove, re-add, source identity, viewport refresh, and final clear. Focused calculation/view, App structure, version, documentation, and history tests cover the surrounding contracts. Evidence [Charge-Injection Capacity](../../../../apps/electrochemistry/cic/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up CSC and VT Resistance still own similar source registration helpers. Their result indexing and lazy-load behavior must be checked independently before adopting the Runtime service."},{"title":"CSC consolidates its product and project contracts","url":"history/records/2026/07/LK-20260716-csc-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-csc-project-spec 2026-07-16 88 refactor compatible labkit_CSC_app Electrochemistry Project lifecycle","text":"CSC consolidates its product and project contracts id: LK-20260716-csc-project-spec date: 2026-07-16 sequence: 88 type: refactor compatibility: compatible component: `labkit_CSC_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context CSC split product metadata across three files and its durable schema across a generic lifecycle package even though it has no historical payload migration. The additional entry points did not own distinct electrochemical behavior. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because reloading CV/CT curves and restoring file/cycle selection is genuine transient work. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved decoded curve and selection restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Kept integration formulas, sign splitting, scan-rate handling, area normalization, plots, and export schemas unchanged. User and data impact Launch, source and cycle selection, save/load, calculations, plots, and both export paths behave unchanged. The durable contract is now readable in one place without hiding scientific behavior in the framework. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing CSC projects require no migration. Validation Unit tests cover CT/CV calculations, edge-cycle policy, normalization, result tables, export schemas, project/session contracts, and presentation. The hidden GUI workflow covers real launch, file/cycle selection, calculation, save/load, plot overlays, and exports. Evidence [CSC](../../../../apps/electrochemistry/csc/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Decoded source restoration still reaches into portable-reference internals. That cross-App framework boundary remains scheduled for one shared source-path service rather than an App-specific wrapper."},{"title":"CSC delegates decoded-source identity to Runtime V2","url":"history/records/2026/07/LK-20260716-csc-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-csc-runtime-source-reconciliation 2026-07-16 113 refactor compatible labkit_CSC_app Electrochemistry App structure Runtime adoption","text":"CSC delegates decoded-source identity to Runtime V2 id: LK-20260716-csc-runtime-source-reconciliation date: 2026-07-16 sequence: 113 type: refactor compatibility: compatible component: `labkit_CSC_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context CSC eagerly decodes accepted CV/CT files and keeps decoded item order aligned with durable source order. Its action file nevertheless generated source IDs, appended records, and deleted records by index. The session factory also declared empty workflow and view buckets already guaranteed by Runtime V2. Those mechanics were framework persistence responsibilities rather than CSC curve-selection or scientific behavior. Decision and rationale Continue using successfully decoded item order as the App-owned source order, then reconcile durable records through `services.project.reconcileSources` after additions, removals, and clearing. Runtime preserves records for retained paths and allocates collision-free IDs for new paths. CSC continues to own decoding, active file and curve selection, plot defaults, calculation, exports, and failure messages. Return only the CSC-specific selection fields and decoded item cache from `createSession`; Runtime supplies missing transient buckets. Changes Removed App-local source-ID generation and source append helpers. Reconciled source records from the decoded item list. Replaced the growing failure array with a single first-failure record while retaining per-file workflow logs. Removed empty workflow and view session boilerplate. Added GUI checks for canonical buckets and stable, unique identities across add, save, reopen, removal, and re-addition. Advanced the CSC App version to 1.4.4. User and data impact File order, selected curve, plot resets, CSC calculations, reload, exports, logs, and saved projects retain their behavior. Retained source identities no longer depend on App-local append/delete logic. Scientific values and result schemas are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing portable records retain their IDs when the corresponding paths remain loaded. Validation The CSC hidden GUI workflow covers two-file decoding, file and cycle selection, plot changes, calculation display, export, save, clear, reopen, remove, re-add, source identity, and final plot clearing. Focused CSC calculation/view and repository contract tests cover the remaining behavior. Evidence [Charge-Storage Capacity](../../../../apps/electrochemistry/csc/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up VT Resistance remains the last electrochem batch App with the same local source-ID and append pattern and requires a separate behavior audit."},{"title":"CSC export and viewport policy","url":"history/records/2026/07/LK-20260703-csc-export-and-viewport-policy.html","kind":"history","section":"history","keywords":"LK-20260703-csc-export-and-viewport-policy 2026-07-03 35 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app CSC export and viewport policy","text":"CSC export and viewport policy id: LK-20260703-csc-export-and-viewport-policy date: 2026-07-03 sequence: 35 type: feat compatibility: compatible component: `labkit.ui` | `4.0.0 -> 4.1.0` component: `labkit_DICPostprocess_app` | `1.3.2 -> 1.3.3` component: `labkit_DICPreprocess_app` | `1.3.2 -> 1.3.3` component: `labkit_ChronoOverlay_app` | `1.3.2 -> 1.3.3` component: `labkit_CIC_app` | `1.3.4 -> 1.3.5` component: `labkit_CSC_app` | `1.3.4 -> 1.3.6` component: `labkit_EIS_app` | `1.3.2 -> 1.3.3` component: `labkit_VTResistance_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.6.4 -> 1.6.5` component: `labkit_CurvatureMeasurement_app` | `1.3.2 -> 1.3.3` component: `labkit_FLIRThermal_app` | `1.2.3 -> 1.2.4` component: `labkit_FocusStack_app` | `1.4.3 -> 1.4.4` component: `labkit_ImageEnhance_app` | `1.5.3 -> 1.5.4` component: `labkit_ImageMatch_app` | `1.5.3 -> 1.5.4` component: `labkit_NerveResponseAnalysis_app` | `1.3.2 -> 1.3.3` component: `labkit_ResponseReviewStats_app` | `1.3.2 -> 1.3.3` component: `labkit_RHSPreview_app` | `1.3.2 -> 1.3.3` component: `labkit_ECGPrint_app` | `1.3.3 -> 1.3.4` scope: CSC export and viewport policy Context CSC could export the selected cycle, but comparing activation and stability across a recording required all cycles in one dataset. In parallel, app layouts made their own assumptions about minimum window size and scrollable content, so the same control panel could behave differently in a small viewport. Decision and rationale Add an explicit all-cycle CSC export without changing the existing selected- cycle result. Define viewport behavior in the UI contract so each app declares its content needs while the framework decides when scrolling or minimum sizing is necessary. Changes `labkit.ui` `4.0.0 -> 4.1.0` All supported apps received aligned patch bumps. Added CSC all-cycle export. Added viewport policy support and aligned app contracts with the UI 4.x line. User and data impact CSC users could export every cycle for downstream comparison while retaining the existing focused export. Across the workbench, small windows followed one scrolling and sizing policy instead of clipping controls according to the app that happened to create them. Compatibility and migration The selected-cycle CSC export remained available and the all-cycle table was a new option. Existing app definitions inherited the default viewport behavior until they declared more specific needs. Validation Commit `a69829c6` added a dedicated CSC export suite, extended the CSC GUI layout test, and added workbench viewport coverage. Evidence Main commit `a69829c6`. Known limitations and follow-up All-cycle CSV output was a separate export choice rather than a new session format. Later CSC work refined column naming and voltage/current organization."},{"title":"Callback-safe native layout synchronization","url":"history/records/2026/07/LK-20260721-callback-safe-layout-synchronization.html","kind":"history","section":"history","keywords":"LK-20260721-callback-safe-layout-synchronization 2026-07-21 152 fix compatible labkit.app App Framework Hidden GUI validation","text":"Callback-safe native layout synchronization id: LK-20260721-callback-safe-layout-synchronization date: 2026-07-21 sequence: 152 type: fix compatibility: compatible component: `labkit.app` | `1.2.3 -> 1.2.4` scope: App Framework scope: Hidden GUI validation Context The App SDK synchronizes native layout before calculating pixel-dependent plot geometry and while publishing busy, startup, close-prompt, and popout views. A full MATLAB `drawnow` also executes pending callbacks, which can introduce unrelated UI or timer work during those internal rendering operations. Decision and rationale Keep the required native graphics synchronization, but defer callbacks while the framework is calculating or publishing its own view. Long-running interactive launcher and profiler waits continue to process callbacks so users can close their windows. Changes Use callback-deferred graphics updates for equal-scale fitting and fixed-aspect preview geometry. Use callback-deferred updates while the native App runtime publishes busy, startup, failure, close-prompt, and popout-style state. Verify equal-scale EIS fitting, preview-canvas layout, and native runtime lifecycle behavior with hidden GUI tests. User and data impact Apps retain the same layouts and fitted limits while framework rendering no longer processes unrelated pending callbacks mid-operation. Projects, source records, results, and exports are unchanged. Compatibility and migration This compatible patch does not alter the public API or require project/result migration. Validation Focused hidden-GUI EIS equal-axis, Figure Studio preview, and native adapter lifecycle checks pass locally. The complete platform matrix remains the final validation for native GUI scheduling behavior. Evidence MATLAB documents that full `drawnow` both updates figures and processes pending callbacks, while the `nocallbacks` forms defer those callbacks. The framework uses callback-deferred synchronization only where it must read settled native geometry or publish internal view state. Known limitations and follow-up Automated hidden GUI checks do not substitute for manual visual and interaction assessment on supported MATLAB desktop platforms."},{"title":"Canonical role-based source collections","url":"history/records/2026/07/LK-20260716-canonical-role-based-sources.html","kind":"history","section":"history","keywords":"LK-20260716-canonical-role-based-sources 2026-07-16 73 refactor compatible labkit_RHSPreview_app labkit_NerveResponseAnalysis_app project payload schema source roles and relinking","text":"Canonical role-based source collections id: LK-20260716-canonical-role-based-sources date: 2026-07-16 sequence: 73 type: refactor compatibility: compatible component: `labkit_RHSPreview_app` | `1.4.0 -> 1.4.1` component: `labkit_NerveResponseAnalysis_app` | `1.4.0 -> 1.4.1` scope: project payload schema scope: source roles and relinking Context RHS Preview and Nerve Response Analysis stored external files in separate app-specific fields such as `rhsSource`, `filterSources`, and `protocolSource`. The records already carried explicit roles, but Runtime V2 could only discover the canonical `project.inputs.sources` collection during portable save/load and missing-file relinking. Decision and rationale Store every external dependency in one canonical collection and select records by their app-owned role. The framework remains domain-neutral, while each app continues to distinguish preview recordings, filter recordings, filter JSON, and optional protocols without duplicating persistence mechanics. Changes Advanced both project payload schemas from version 1 to version 2. Added ordered migrations that combine the former role-specific fields without changing source record contents or order. Added app-local role selection and replacement operations used consistently by lifecycle creation, actions, presenters, exports, and validation. Kept at most one primary recording/protocol per relevant app while allowing RHS Preview to retain an ordered collection of filter recordings. User and data impact Project reopen, result provenance, and future missing-file recovery can now see all selected dependencies. Preview, filtering, event detection, CAP metrics, and output formats are unchanged. Compatibility and migration Existing version 1 payloads remain readable and preserve every source record. The old fields are removed only from the migrated in-memory project; saving writes the version 2 canonical collection. Validation Focused unit tests verify role-preserving migrations and project versions. Focused GUI workflows verify RHS indexing, multiple filter-file management, project save/reopen, filter analysis, export, and transient cache rebuilding. Evidence [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) documents its three source roles. [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) documents filter/protocol restoration and relinking. [App Framework](../../../../framework/README.md) defines the canonical project source collection. Known limitations and follow-up This change makes every source discoverable but does not yet rebase relative paths at the final project save destination. That framework serialization change is tracked as the next persistence batch."},{"title":"Canonical single-source app projects","url":"history/records/2026/07/LK-20260716-canonical-single-source-projects.html","kind":"history","section":"history","keywords":"LK-20260716-canonical-single-source-projects 2026-07-16 72 refactor compatible labkit_GaitAnalysis_app labkit_CurvatureMeasurement_app labkit_ECGPrint_app labkit_ResponseReviewStats_app project payload schema source relinking","text":"Canonical single-source app projects id: LK-20260716-canonical-single-source-projects date: 2026-07-16 sequence: 72 type: refactor compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.0 -> 2.0.1` component: `labkit_CurvatureMeasurement_app` | `1.4.0 -> 1.4.1` component: `labkit_ECGPrint_app` | `1.4.0 -> 1.4.1` component: `labkit_ResponseReviewStats_app` | `1.4.0 -> 1.4.1` scope: project payload schema scope: source relinking Context The Runtime V2 project contract discovers external files through the `project.inputs.sources` collection. Four single-source apps instead stored their record under a singular `project.inputs.source` field. Their own actions could open that path, but framework save/load relinking could not discover it. Decision and rationale Use the same canonical source collection for every single-source app. The plural field is a collection even when it currently contains zero or one record, so persistence and relinking do not need app-specific field knowledge. Changes Moved Gait Analysis, Curvature Measurement, ECG Print, and Response Review Stats source ownership to `project.inputs.sources`. Advanced each payload schema by one version and added the corresponding ordered migration. Updated action, presenter, session, validation, export, and manual references to the canonical collection. Corrected the Gait family overview to describe its current strict Video Marker project input instead of retired generic MAT/table inputs. User and data impact New saves expose their source records to the common portable-project resolver. Scientific calculations, UI choices, annotations, parameters, and result values are unchanged. Compatibility and migration Existing payloads remain readable. Loading copies the former singular field to the canonical collection and removes the retired field. A later save writes the new payload version; source record contents are not transformed. Validation Focused unit tests verify each field migration, current project version, and existing calculations. Focused hidden-GUI tests verify that each app still launches and follows its source workflow with the renamed durable field. Evidence [App Framework](../../../../framework/README.md) defines the canonical source collection used by project persistence. [Gait Analysis](../../../../apps/gait/gait-analysis/README.md), [Curvature Measurement](../../../../apps/image-measurement/curvature/README.md), [ECG Print](../../../../apps/wearable/ecg-print/README.md), and [Response Review Stats](../../../../apps/neurophysiology/response-review-stats/README.md) describe their upgraded project behavior. Known limitations and follow-up This record covers single-source apps. Multi-role neurophysiology projects are audited separately because preserving source roles requires a richer migration than renaming one field."},{"title":"Chrono Overlay adopts one version-aware project migration entry","url":"history/records/2026/07/LK-20260716-chrono-overlay-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-chrono-overlay-project-spec 2026-07-16 86 refactor compatible labkit_ChronoOverlay_app Electrochemistry Project lifecycle","text":"Chrono Overlay adopts one version-aware project migration entry id: LK-20260716-chrono-overlay-project-spec date: 2026-07-16 sequence: 86 type: refactor compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context Chrono Overlay split product metadata across three files and represented its only payload upgrade as a separately named `migrateProjectV1ToV2.m` file in a generic lifecycle package. Adding another schema version would have added yet another file and exposed migration sequencing to the App definition. Decision and rationale Make `definition.m` the product declaration and `projectSpec.m` the sole durable-schema entry. Its `Migrate(project, fromVersion)` callback owns App semantics for one version step; Runtime V2 owns the loop, version increment, validation after every step, and unsupported-envelope handling. Changes Consolidated project creation, validation, and migration in one project spec. Replaced the per-version migration file with one version-aware local function. Moved transient DTA reconstruction to a package-root session factory. Removed separate requirements, version, and generic lifecycle files. Kept DTA parsing, pulse-gap alignment, interpolation, plots, and exports unchanged. User and data impact Launch, plotting, save/load, v1 project upgrade, and CSV export behavior remain unchanged. Maintainers now add future schema cases in one ordered migration entry instead of editing both definition wiring and file inventories. Compatibility and migration Version-1 payloads still remove the obsolete decoded `inputs.items` field so portable source records remain the only durable input. Version-2 payloads are unchanged and need no migration. Validation Unit tests call the new migration entry with a synthetic v1 payload and verify validation, session reconstruction, pulse alignment, interpolation, and presenter output. The hidden GUI workflow covers real launch, source loading, plotting, and export through the consolidated definition. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Only v1-to-v2 is currently required. Future cases remain App-owned in the same function; they must not bypass the framework's stepwise validation loop."},{"title":"Chrono Overlay removes App-owned source identity bookkeeping","url":"history/records/2026/07/LK-20260716-chrono-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-chrono-runtime-source-reconciliation 2026-07-16 111 fix compatible labkit_ChronoOverlay_app Electrochemistry App structure Project identity","text":"Chrono Overlay removes App-owned source identity bookkeeping id: LK-20260716-chrono-runtime-source-reconciliation date: 2026-07-16 sequence: 111 type: fix compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Project identity Context Chrono Overlay generated a new source ID from the current record count. After removing an earlier source, adding another file could therefore reuse an ID still owned by a retained record. Runtime validation correctly rejects duplicate IDs, but the App should not have been maintaining this persistence invariant itself. The App also duplicated source append/removal helpers and empty transient workflow/view buckets already supplied by Runtime V2. Decision and rationale After every successful add, removal, or clear operation, derive the ordered path list from the decoded items and delegate durable-record reconciliation to `services.project.reconcileSources`. Chrono Overlay continues to own decoding, pulse-gap alignment, item order, selection, logs, and failure wording. Runtime owns stable identity reuse and collision-free allocation. Only App-specific selection and decoded cache data are returned by `createSession`; Runtime canonicalizes the remaining transient buckets. Changes Removed App-local source record append, removal, and count-based ID logic. Reconciled source records from the accepted decoded item order. Removed empty workflow and view boilerplate from the session factory. Replaced the dynamically growing failure list with one first-failure record while retaining per-file log messages. Added a GUI regression sequence that removes the first of two sources, adds it again, checks unique IDs, saves, clears, and reopens the project. Advanced the Chrono Overlay App version to 1.4.4. User and data impact Normal file loading, ordering, selection, plots, exports, and saved project payloads retain their behavior. The remove-then-add sequence no longer risks a duplicate source ID and failed callback. Persistence IDs remain internal and do not change curve values or scientific output. Compatibility and migration The current payload remains version 2 and no new migration is needed. Existing records retain their IDs when their paths remain in the project. Validation The Chrono Overlay hidden GUI workflow covers two-file loading, selection, remove and re-add, unique identity, export, save, clear, reopen, option redraw, and final plot clearing. Focused unit, structure, version, documentation, and history tests protect the calculation and product contracts. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up CIC, CSC, and VT Resistance still have equivalent App-local source identity helpers and require their own ordering and behavior audits before conversion."},{"title":"Clean-room cross-platform CI","url":"history/records/2026/07/LK-20260717-clean-room-cross-platform-ci.html","kind":"history","section":"history","keywords":"LK-20260717-clean-room-cross-platform-ci 2026-07-17 131 ci compatible project validation and release automation","text":"Clean-room cross-platform CI id: LK-20260717-clean-room-cross-platform-ci date: 2026-07-17 sequence: 131 type: ci compatibility: compatible scope: project validation and release automation Context The previous MATLAB workflow combined ordinary CI, scheduled reports, release validation, and raw tag creation. Its optional Base MATLAB job also mixed static source checks with installed-product dependency analysis, even though that analysis can only report products present on the runner. The design made it difficult to tell which evidence was required and did not exercise every candidate outside the developer's operating system. Decision and rationale Make a Toolbox-free MATLAB installation the baseline for all required CI behavior. Let one CI workflow run complete headless and hidden-GUI tasks on Linux, macOS, and Windows. Keep coverage as an explicit local report. Release creation remains a developer-initiated process after manual App validation, requires a successful main-push CI run for the exact commit, and stops at a draft for final human review. This directly tests the promised runtime instead of trying to infer the same fact from a runner with optional products installed. Changes Replaced the monolithic MATLAB workflow with one ordinary/reusable CI workflow and one manual release workflow. Made cross-platform Base MATLAB headless and hidden-GUI validation mandatory for pull requests and main pushes, and required exact same-commit CI evidence before release. Removed the installed-product dependency-analysis task and all CI Toolbox installation. Kept static known-Toolbox call detection and representative shadowed-helper workflows inside the ordinary headless suite. Kept coverage out of CI because it has no failure threshold and repeats the non-GUI tests; maintainers can still request the local report explicitly. Replaced raw Git-ref creation with a validated annotated tag, tag-blob launcher staging, remote asset verification, and a draft GitHub Release. User and data impact No App runtime, calculation, project, or saved-data behavior changes. CI may now expose operating-system, path, case-sensitivity, layout, or missing- dependency failures that a configured development machine did not reveal. Compatibility and migration Maintainers should use `Continuous Integration` for required checks, `buildtool coverage` for an on-demand local report, and the manual `Release` workflow only after interactive App validation. The retired `buildtool baseMatlab` task has no compatibility alias because clean Base MATLAB execution is now the environment of every required build task rather than a separate partial check. Validation The CI policy contract checks trigger separation, read-only ordinary permissions, three-platform matrices, absence of `products:` installation, public build-task use, timeouts, diagnostics, exact-sha CI evidence, manual release confirmation, tag-blob asset generation, and draft-only publication. Build-task and Toolbox guardrails verify the simplified catalog and retained static/fallback checks. Evidence `.github/workflows/ci.yml` and `release.yml` define the executable pipeline. `tests/cases/contract/project/ci/CiValidationPolicyGuardrailTest.m` protects the new boundaries. Known limitations and follow-up Hidden GUI tests cannot drive native dialogs or judge visual and pointer quality. Developer-led manual App testing therefore remains mandatory before starting a release. CI also detects only Toolbox calls reached by tests or covered by the maintained static-call set; temporary product use still needs its declared fallback, idempotency, and parity evidence."},{"title":"Close guards and caught-exception diagnostics","url":"history/records/2026/06/LK-20260630-close-guards-and-caught-exception-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260630-close-guards-and-caught-exception-diagnostics 2026-06-30 21 feat compatible labkit.ui labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Close guards and caught-exception diagnostics","text":"Close guards and caught-exception diagnostics id: LK-20260630-close-guards-and-caught-exception-diagnostics date: 2026-06-30 sequence: 21 type: feat compatibility: compatible component: `labkit.ui` | `3.2.6 -> 3.2.7` component: `labkit_DICPostprocess_app` | `1.2.1 -> 1.2.2` component: `labkit_BatchImageCrop_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.3.5 -> 1.3.6` component: `labkit_CurvatureMeasurement_app` | `1.2.1 -> 1.2.2` component: `labkit_FocusStack_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.2.3 -> 1.2.4` component: `labkit_ImageEnhance_app` | `1.3.2 -> 1.3.3` component: `labkit_ImageMatch_app` | `1.3.2 -> 1.3.3` component: `labkit_ImageMatch_app` | `1.3.3 -> 1.3.4` component: `labkit_NerveResponseAnalysis_app` | `1.2.3 -> 1.2.4` component: `labkit_ResponseReviewStats_app` | `1.2.2 -> 1.2.3` component: `labkit_RHSPreview_app` | `1.2.1 -> 1.2.2` component: `labkit_ECGPrint_app` | `1.2.0 -> 1.2.1` scope: Close guards and caught-exception diagnostics Context Many app callbacks caught an exception, showed its message, and returned. That protected the UI but discarded the stack and callback context needed to diagnose the failure. Image apps also tracked unsaved or incomplete work in slightly different ways when deciding whether a window could close. Decision and rationale Report caught exceptions to the existing debug trace before presenting the user-facing error, and promote the file-index and close-guard mechanics that were shared by several image apps. Preserve app ownership of what counts as dirty or incomplete work. Changes `labkit.ui` `3.2.6 -> 3.2.7` DIC, Batch Crop, Curvature, Focus Stack, Image Match, neurophysiology apps, and ECG Print patch bumped for diagnostics or close-guard work. Reported caught app-runner exceptions through framework debug diagnostics. Promoted file-entry index helpers. Connected dirty/incomplete workflow state to close guards. User and data impact An app could still recover from a failed load, calculation, or export, while its debug report retained the exception evidence. Closing an image workflow with unfinished state produced a consistent warning instead of silently discarding work. Scientific results and saved schemas were unchanged. Compatibility and migration Existing app state and result files remained valid. Closing an incomplete workflow gained an additional confirmation, and debug reports gained exception details. Validation The exception-reporting commit expanded app/library compatibility checks across the affected runners. The close-guard commit added public-surface and compatibility coverage for promoted file indices and app close behavior. Exact historical commands were not recorded. Evidence Main commits `c0028a81` and `a81853ef`. Known limitations and follow-up Runtime V2 later replaced direct debug-log and close-guard calls with lifecycle and diagnostic services, but retained the requirement that caught exceptions remain observable."},{"title":"Consistent electrochemistry batch analysis","url":"history/records/2026/07/LK-20260713-electrochem-batch-consistency.html","kind":"history","section":"history","keywords":"LK-20260713-electrochem-batch-consistency 2026-07-13 46 fix compatible labkit_CIC_app labkit_VTResistance_app Consistent electrochemistry batch analysis","text":"Consistent electrochemistry batch analysis id: LK-20260713-electrochem-batch-consistency date: 2026-07-13 sequence: 46 type: fix compatibility: compatible component: `labkit_CIC_app` | `1.3.7 -> 1.3.8` component: `labkit_VTResistance_app` | `1.3.7 -> 1.3.8` scope: Consistent electrochemistry batch analysis Context CIC and VT Resistance could retain per-file derived values from different control settings, and CIC could sample outside the recorded time range while displaying a delay without units. Decision and rationale Treat analysis controls as one batch contract and recompute every file before display/export so rows cannot silently mix stale and current parameters. Changes Recompute all loaded files when shared controls change and once more before CSV export. Label CIC delay in microseconds, reject out-of-range sampling, and export the area and delay used for each result. Added family regression coverage and audited sibling electrochem apps. User and data impact Batch rows now represent one consistent area, delay, pulse-detection, resistance-window, and voltage-mode configuration. Invalid delay choices fail instead of extrapolating a misleading value. Compatibility and migration CIC CSV adds trailing `Area_cm2` and `Delay_us` columns. Existing columns keep their names and order; VT Resistance exports remain schema-compatible. Validation Electrochem unit and GUI tests cover batch recomputation, display units, out-of-range handling, and export-time refresh. Evidence The guarded calculations and export builders are app-owned. Commit `67ea2286` introduced the consistent batch-analysis and export behavior. Known limitations and follow-up The fix enforces internal batch consistency but does not choose scientifically appropriate area, delay, or resistance windows for the user."},{"title":"Core, neurophysiology, and ECG project validation ownership","url":"history/records/2026/07/LK-20260716-core-neuro-ecg-project-validation.html","kind":"history","section":"history","keywords":"LK-20260716-core-neuro-ecg-project-validation 2026-07-16 121 refactor compatible labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app remaining public App project schemas App maintenance cost","text":"Core, neurophysiology, and ECG project validation ownership id: LK-20260716-core-neuro-ecg-project-validation date: 2026-07-16 sequence: 121 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.6 -> 0.2.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.4 -> 1.4.5` component: `labkit_ResponseReviewStats_app` | `1.4.4 -> 1.4.5` component: `labkit_RHSPreview_app` | `1.4.4 -> 1.4.5` component: `labkit_ECGPrint_app` | `1.4.4 -> 1.4.5` scope: remaining public App project schemas scope: App maintenance cost Context The remaining public project validators still surrounded their own style, recording-role, metric-window, protocol, ECG, and result rules with repeated canonical bucket and source-record format checks. Decision and rationale Complete the public-App validation ownership split. Runtime owns canonical bucket structs and source-record internals. Each App continues to require its source collection and to enforce its own fields, role/cardinality rules, numeric limits, tables, annotations, and results. Changes Removed 66 net lines of repeated framework structure checks from Figure Studio, the three neurophysiology Apps, and ECG Print. Preserved Nerve Response Analysis's fixed source ID-role pairs and RHS Preview's recording/protocol/filter roles and cardinalities. Added focused project-spec contracts for default acceptance, missing-source rejection, and the retained neurophysiology role rules. User and data impact Valid projects, migrations, signal calculations, previews, and exports are unchanged. Framework-shape failures now have one Runtime owner; domain-schema failures remain attributable to the App that defines them. Compatibility and migration No payload format changed and no migration is required. All supported saved projects continue through their existing centralized `projectSpec.m` migration entry. Validation Focused project-spec tests cover all five defaults and missing-source cases, plus invalid Nerve and RHS source roles. Representative unit and hidden-GUI workflows cover project reconstruction, preview, analysis, and export behavior. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Neurophysiology Apps](../../../../apps/neurophysiology/README.md) [ECG Print](../../../../apps/wearable/ecg-print/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Project validator boilerplate is now removed across the public App fleet. Further App simplification must target independently evidenced action, presentation, or cache patterns rather than weakening domain validation."},{"title":"Cross-platform validation exits cleanly after settled plot fitting","url":"history/records/2026/07/LK-20260721-cross-platform-validation-exit.html","kind":"history","section":"history","keywords":"LK-20260721-cross-platform-validation-exit 2026-07-21 151 fix compatible labkit.app App Framework Continuous Integration","text":"Cross-platform validation exits cleanly after settled plot fitting id: LK-20260721-cross-platform-validation-exit date: 2026-07-21 sequence: 151 type: fix compatibility: compatible component: `labkit.app` | `1.2.2 -> 1.2.3` scope: App Framework scope: Continuous Integration Context Windows headless validation completed every test and report but retained a progress heartbeat resource until the GitHub Actions step timed out. Hosted macOS GUI validation also measured equal-scale plot limits before native layout had settled, producing a platform-specific false failure. Decision and rationale Make lifecycle ownership explicit at the test-runner boundary and measure equal-data-unit limits only after MATLAB has applied pending native layout. This keeps CI diagnostics available while ensuring successful validation exits promptly, without changing the App SDK surface or EIS workflow meaning. Changes Explicitly release each progress plugin and its heartbeat timer when a test runner returns, including nested focused runs. Verify focused runs leave no tagged heartbeat timer behind. Settle native layout before equal-data-unit fitting and assert EIS scaling from the post-action axes allocation. User and data impact Users receive the same transient equal-scale EIS view, now calculated against the displayed axes allocation. Projects, source records, results, and exports are unchanged. Compatibility and migration The patch release is compatible with all 1.x App SDK requirements. No project or result migration is required. Validation Focused nested-runner coverage verifies heartbeat cleanup after return. Focused EIS GUI coverage verifies independent fitting and equal-scale limits. GitHub Actions will re-run the complete platform matrices after delivery. Evidence The timed-out Windows job reported all 468 headless tests and `** Finished headless` more than twenty minutes before the action timeout. The macOS GUI artifact consistently identified the pre-layout equal-scale pixel assertion. Known limitations and follow-up The Windows hosted runner remains the final verification environment for process teardown. Manual visual assessment of plot layout remains outside hidden-GUI automation."},{"title":"Cross-version dialogs and Batch Crop duplication keep portable shapes","url":"history/records/2026/07/LK-20260729-cross-version-dialog-and-duplicate-repair.html","kind":"history","section":"history","keywords":"LK-20260729-cross-version-dialog-and-duplicate-repair 2026-07-29 162 fix compatible labkit.app labkit_BatchImageCrop_app labkit_FigureStudio_app Windows MATLAB file-dialog filter compatibility Batch Crop duplicate task shape alignment diagnostic text fallback Figure Studio R2022b graphics export and layout R2022b-safe diagnostic attribute identifiers oldest/latest MATLAB CI compatibility matrix","text":"Cross-version dialogs and Batch Crop duplication keep portable shapes id: LK-20260729-cross-version-dialog-and-duplicate-repair date: 2026-07-29 sequence: 162 type: fix compatibility: compatible component: `labkit.app` | `2.0.1 -> 2.0.3` component: `labkit_BatchImageCrop_app` | `1.9.0 -> 1.9.1` component: `labkit_FigureStudio_app` | `0.7.0 -> 0.7.1` scope: Windows MATLAB file-dialog filter compatibility scope: Batch Crop duplicate task shape alignment scope: diagnostic text fallback scope: Figure Studio R2022b graphics export and layout scope: R2022b-safe diagnostic attribute identifiers scope: oldest/latest MATLAB CI compatibility matrix Context MATLAB R2024b on Windows rejected string-valued cells passed to `uiputfile` while exporting a diagnostic ZIP. Batch Crop also inserted a duplicate with vertical concatenation even when the native file-list source binding supplied a row struct array, so duplicating from a multi-image list could fail with a dimension mismatch. Decision and rationale The private MATLAB adapter now uses its existing dialog-filter normalizer for ordinary input and output dialogs. Batch Crop normalizes its four parallel task collections to columns at the duplicate callback boundary before inserting the new task. These are the narrow owners of the platform and App shape contracts; no new public API or saved-project migration is needed. The App SDK also canonicalizes text at the portable-source, source-resolution, and native file-label boundaries: a character vector is one scalar value, while string and cellstr collections become columns where the owning contract requires a collection. Apps continue to own ordinary file reading and writing; an architecture guardrail only prevents them from bypassing `fileList` or `CallbackContext` for native file and folder dialogs. Diagnostic export now treats a ZIP failure as a degraded but recoverable Runtime outcome. It writes the already-sanitized in-memory session records to one text file beside the chosen destination when possible, or in MATLAB's user-writable temporary folder otherwise. This remains private Runtime behavior because Apps neither select nor interpret diagnostic formats. Figure Studio now selects graphics primitives at its result-file boundary. R2025a and newer retain `exportgraphics` figure padding; supported older releases use `print`, including its SVG device. Invisible export figures are anchored before their drawable pixel size is assigned, then layout converges against the native renderer's measured text extents. A final figure-coordinate fit catches older Windows renderers that update ruler-label extents only after accepting the offscreen geometry. If R2022b clamps that invisible figure to the desktop despite the requested size, Figure Studio recomputes the data frame from the accepted drawable canvas after reserving measured label and tick insets. Font size remains unchanged, while the constrained plot frame yields enough real outer whitespace for ordinary ruler labels. Titles and axis labels are included explicitly because R2022b does not consistently expose those ruler decorators through descendant text discovery. The compatibility floor also owns identifier limits. Newer MATLAB releases allow names longer than R2022b's 63-character ceiling, so the diagnostic attribute-key contract and repository test names use the R2022b ceiling rather than the development runtime's `namelengthmax`. Changes Converted native input/output dialog filters to character-cell tables before calling `uigetfile` or `uiputfile`. Canonicalized scalar character paths and IDs before collection reshaping, including Unicode, duplicate, drive-letter, and UNC-shaped regression cases. Added a repository guardrail requiring Apps to use the SDK-owned native file dialog boundaries without introducing a generic public file API. Made Batch Crop duplicate insertion preserve one column-aligned task, source, image-cache, and path-cache row per list entry. Added focused regression coverage for the R2024b-compatible filter value and a multi-image row-shaped duplicate state. Expanded every validation profile from one fixed MATLAB release to the supported R2022b floor and the latest release available to the official setup action. macOS remains a latest-release platform sentinel. Grouped the three validation profiles by platform and release so each matrix entry installs MATLAB once while each profile retains a fresh batch session. Bound the R2022b entries to Ubuntu 22.04 and Windows Server 2022 runner images supported by that release instead of testing unsupported latest operating-system images. Gave Linux MATLAB sessions an X virtual framebuffer so hidden-GUI tests do not depend on release-specific behavior when no display server is active. Added a single-file diagnostic fallback for native dialog, ZIP staging, ZIP creation, and publish failures, with the resulting path reported to the user. Kept Figure Studio raster and SVG export operational on R2022b, where `exportgraphics` has neither figure padding nor SVG support. Reserved measured label and tick margins in hidden export canvases. The normal path preserves the configured plot frame and a typography-derived outer margin. Older Windows `print` releases reserve one hardcopy line box plus a half-em white margin; if Windows refuses the requested outer size, the final fit reduces only the rendered data frame to keep that space inside the accepted canvas. Aligned diagnostic struct keys and repository function names with the R2022b 63-character identifier ceiling, eliminating warnings hidden by the newer development runtime's larger limit. Prepared MATLAB's conventional personal folder on Linux runners so repeated reset-path probes do not emit environment-only `userpath` warnings. Retained deterministic production-rendered PNGs below each test profile's `visual-evidence/` folder, included them in the existing platform artifact, and kept automated assertions over the same files. Replaced three disconnected raw test summaries with one platform-level compatibility report that explains each profile, success caveats, actionable failure diagnostics, slow-test signals, and exact artifact ownership. Cancelled or skipped profiles now report an incomplete conclusion instead of mislabeling missing evidence as a compatibility failure. User and data impact Windows users can choose a destination for diagnostic ZIP bundles, projects, screenshots, plots, and other Runtime output dialogs. Batch Crop can duplicate an image task from a multi-image list without changing source images or prior task settings. If diagnostic ZIP export still fails, users receive the path to a readable plain-text fallback containing the surviving sanitized session history. Figure Studio keeps complete ruler labels and styled figure output across the supported MATLAB release boundary. A Windows desktop that refuses the requested outer canvas can constrain the rendered data-frame size without changing the saved project setting. Compatibility and migration The repair is backward compatible. Public App SDK signatures, Batch Crop project payload version 3, scientific crop calculations, and exported result schemas are unchanged. Validation Focused headless specifications cover the Batch Crop duplicate callback and the native dialog-filter value, diagnostic ZIP-to-text degradation, and Figure Studio result layout. The ruler-label regression uses the production PNG export path and checks its bottom and left whitespace rather than treating hidden-window geometry as export evidence. Hidden-GUI workflow coverage also performs a real PNG export. CI runs every full profile on Linux, macOS, and Windows against R2022b and the latest available MATLAB release, while macOS runs the latest release. The R2022b jobs use fixed supported runner images; latest MATLAB uses current runner images, and Linux GUI validation runs with a virtual display. The JUnit artifact records MathWorks qualification and exception diagnostics instead of a generic failure placeholder, and dependency-free Python regressions protect both successful and failed summary layouts. Documentation consistency and the final changed-file gate cover the integrated version and history updates. Evidence The repair branch records focused MATLAB test artifacts and the final changed-file validation result. The supplied sanitized log identifies the original `MATLAB:catenate:dimensionMismatch` and `MATLAB:character:CellsMustContainChars` failures. Known limitations and follow-up Automated tests cannot prove the appearance and interaction quality of the native Windows save dialog. A manual MATLAB R2024b Windows check remains the final platform acceptance boundary."},{"title":"Curvature consolidates migration and analysis state","url":"history/records/2026/07/LK-20260716-curvature-project-structure.html","kind":"history","section":"history","keywords":"LK-20260716-curvature-project-structure 2026-07-16 97 refactor compatible labkit_CurvatureMeasurement_app Image Measurement App structure","text":"Curvature consolidates migration and analysis state id: LK-20260716-curvature-project-structure date: 2026-07-16 sequence: 97 type: refactor compatibility: compatible component: `labkit_CurvatureMeasurement_app` | `1.4.2 -> 1.4.3` scope: Image Measurement scope: App structure Context Curvature split static metadata across files, exposed four generic lifecycle functions including a filename-encoded migration, and stored five computation tasks/results under `+appState`. Session restoration also read the nested portable source path directly. Decision and rationale Use one project specification with a version-aware migration entry. Runtime V2 calls `Migrate(project,fromVersion)` for each missing version; Curvature keeps only the actual version-1 transformation. Put fit and length task/result shapes beside the analysis functions that consume them. Changes Consolidated product metadata and requirements in `definition.m`. Consolidated project creation, validation, and the v1 source migration in `projectSpec.m`. Moved decoded-image reconstruction to root `createSession.m`. Moved all five generic state helpers to `+analysisRun`. Removed separate metadata and lifecycle files. Replaced direct portable-reference access with the Runtime path accessor. Kept managed anchor editing, scale calibration, curve fitting, length measurement, overlay rendering, and exports unchanged. User and data impact Opening current or version-1 projects, tracing/removing anchors, calibrating scale, fitting curvature, measuring length, and exporting results behave unchanged. Developers now find every durable-version rule in one file and every numerical result/task next to the numerical implementation. Compatibility and migration The current payload stays at version 2. Version-1 `inputs.source` is still moved to `inputs.sources` exactly once. No new payload migration is introduced. Validation Unit tests cover current project/session construction, v1 migration, fit and length numerics, physical/pixel scales, densification, invalid points, result tables, and deterministic task fingerprints. The hidden GUI suite covers image loading, anchor editing guidance, managed interactions, viewport behavior, scale calibration, results, and save/load. Evidence [Curvature Measurement](../../../../apps/image-measurement/curvature/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other versioned Apps still use filename-per-step migrations and will move to one project entry during their own behavior audits."},{"title":"DIC Postprocess adopts one product and project declaration","url":"history/records/2026/07/LK-20260716-dic-postprocess-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-dic-postprocess-project-spec 2026-07-16 83 refactor compatible labkit_DICPostprocess_app DIC Project lifecycle","text":"DIC Postprocess adopts one product and project declaration id: LK-20260716-dic-postprocess-project-spec date: 2026-07-16 sequence: 83 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.1 -> 1.4.2` scope: DIC scope: Project lifecycle Context DIC Postprocess split static product metadata across `definition.m`, `requirements.m`, and `version.m`, while its version-1 durable schema occupied three generic `+appLifecycle` files. None of those separations represented a separate scientific capability or independently evolving contract. Decision and rationale Make `definition.m` the product declaration and `projectSpec.m` the only durable-schema entry. Keep `createSession.m` separate because it restores file-backed strain, image, mask, and prepared overlay caches that must not be serialized into the project. Changes Moved command metadata, version, update date, and facade requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved transient reconstruction to one explicitly named package-root entry. Removed the two metadata files and generic lifecycle package. Replaced the stale GUI-free manual example with an executable call matching the real input structure, complete parameter contract, and three outputs. Kept all calculation, parameter, action, presentation, and export behavior unchanged. User and data impact Ncorr loading, overlay preparation, statistics, save/load, and exports behave unchanged. Maintainers can now find the complete product contract in one file and the complete durable schema in one adjacent file. Compatibility and migration The command, project ID, payload version, fields, defaults, validation rules, and source records are unchanged. Existing DIC Postprocess projects require no migration. Validation Unit tests exercise Ncorr loading, cached session reconstruction, numerical preparation, summary/export behavior, and presentation. The hidden GUI flow launches the real App and generates overlays and a summary through Runtime V2. Evidence [DIC Postprocess](../../../../apps/dic/dic-postprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The action file remains a meaningful workflow boundary because it coordinates input selection, calculation, export, diagnostics, and dialogs. Its naming and possible smaller capability boundaries will be compared across the DIC family instead of changed in isolation."},{"title":"DIC Preprocess consolidates product and project declarations","url":"history/records/2026/07/LK-20260716-dic-preprocess-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-dic-preprocess-project-spec 2026-07-16 84 refactor compatible labkit_DICPreprocess_app DIC Project lifecycle","text":"DIC Preprocess consolidates product and project declarations id: LK-20260716-dic-preprocess-project-spec date: 2026-07-16 sequence: 84 type: refactor compatibility: compatible component: `labkit_DICPreprocess_app` | `1.5.1 -> 1.5.2` scope: DIC scope: Project lifecycle Context DIC Preprocess split static product metadata across three files and split one version-1 durable schema across a generic lifecycle package. That structure made a maintainer traverse six declarations before reaching any registration, crop, or mask behavior. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the only durable-schema entry. Keep `createSession.m` explicit because decoding source images and replaying alignment/crop steps reconstructs transient cache state rather than defining persistence. Changes Moved command metadata, version, update date, and facade requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved transient image and edit replay to one package-root session factory. Removed the metadata files and generic lifecycle package. Kept registration, crop, mask, history, managed interactions, export, and scientific parameter behavior unchanged. User and data impact Manual and automatic alignment, zoom-preserving point/ROI editing, crop and mask history, project save/load, and exports behave unchanged. The durable and transient state boundaries are now visible from two adjacent files. Compatibility and migration The command, project ID, payload version, fields, defaults, validation, source records, edit steps, and mask history are unchanged. Existing projects require no migration. Validation Unit tests cover project/session reconstruction, state history, registration, crop/mask calculations, source IO, exports, and presenter models. The hidden GUI workflow covers real launch, manual point matching, ROI crop, mask editing, viewport preservation, and generated outputs. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The `+appState` package mixes edit-history and mask-geometry capabilities. It is retained for this compatibility-preserving checkpoint and will be split or renamed by responsibility after its callers and tests are audited."},{"title":"DIC Preprocess replaces its generic state bucket","url":"history/records/2026/07/LK-20260716-dic-preprocess-state-capabilities.html","kind":"history","section":"history","keywords":"LK-20260716-dic-preprocess-state-capabilities 2026-07-16 85 refactor compatible labkit_DICPreprocess_app DIC App ownership","text":"DIC Preprocess replaces its generic state bucket id: LK-20260716-dic-preprocess-state-capabilities date: 2026-07-16 sequence: 85 type: refactor compatibility: compatible component: `labkit_DICPreprocess_app` | `1.5.2 -> 1.5.3` scope: DIC scope: App ownership Context The generic `+appState` package mixed three unrelated concepts: durable align/crop undo history, mask editing and undo, and a predicate over transient source-image cache state. The name exposed storage mechanics without helping a maintainer find the behavior they needed. Decision and rationale Group these operations by the capability that owns their invariants. `editHistory` owns align/crop snapshots and reset behavior, `maskEditing` owns mask canvas composition and undo snapshots, and `sourceFiles` owns the loaded image-pair predicate. None is promoted to LabKit because the project fields, undo limits, image semantics, and operation order are specific to DIC Preprocess. Changes Moved four edit-history operations into `+editHistory`. Moved five mask composition/history operations into `+maskEditing`. Moved the image-pair readiness predicate into `+sourceFiles`. Updated actions, presenters, preview requests, and direct state tests to use the capability paths. Removed the generic `+appState` package without adding an adapter. User and data impact All controls, calculations, undo limits, project fields, masks, alignment/crop history, and viewport behavior are unchanged. Code navigation now starts from the user capability rather than an undifferentiated state bucket. Compatibility and migration This is an app-internal ownership change. Project payloads and public GUI-free scientific APIs are unchanged, so saved projects require no migration. Validation State tests cover history trimming, reset/restore behavior, mask composition, and image-pair readiness through the new capability paths. The full DIC Preprocess unit and hidden GUI workflows verify the unchanged callers. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up `definitionActions.m` remains a large but cohesive workflow coordinator. Its callbacks will be compared with other interaction-heavy image Apps before any further extraction; file length alone is not a reason to create another technical package."},{"title":"DIC and Gait project validation ownership","url":"history/records/2026/07/LK-20260716-dic-gait-project-validation-ownership.html","kind":"history","section":"history","keywords":"LK-20260716-dic-gait-project-validation-ownership 2026-07-16 120 refactor compatible labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_GaitAnalysis_app DIC and Gait project schemas App maintenance cost","text":"DIC and Gait project validation ownership id: LK-20260716-dic-gait-project-validation-ownership date: 2026-07-16 sequence: 120 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.4 -> 1.4.5` component: `labkit_DICPreprocess_app` | `1.5.5 -> 1.5.6` component: `labkit_GaitAnalysis_app` | `2.0.5 -> 2.0.6` scope: DIC and Gait project schemas scope: App maintenance cost Context DIC Postprocess, DIC Preprocess, and Gait repeated Runtime's canonical project and source-record shape checks around their real domain constraints. The duplication made it harder to see which saved fields these Apps actually own. Decision and rationale Retain each App's required source collection and domain schema while relying on Runtime for canonical bucket structs and source-record internals. DIC continues to own alignment, crop, mask, summary, and parameter fields; Gait continues to own pose-analysis options, numeric limits, and result structure. Changes Removed 26 net lines of repeated framework structure checks from the three project specifications. Preserved every App-required source field and domain-specific validator. Added GUI-free DIC and Gait contracts that accept default projects and reject projects missing their required source collection. User and data impact Valid DIC and Gait projects, migrations, calculations, and exports are unchanged. Malformed framework structures still fail in Runtime and malformed domain fields still fail in the owning App. Compatibility and migration No payload format changed and no migration is required. Supported older projects continue through the same App-owned migration callbacks. Validation Focused project-spec tests cover default acceptance and missing-source rejection. Hidden GUI workflows cover representative DIC load/edit/analysis and Gait project load/step-analysis behavior. Evidence [DIC Apps](../../../../apps/dic/README.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up This boundary cleanup does not change the scientific gait analysis or image registration algorithms. Their domain complexity remains App-owned."},{"title":"DIC uses framework-owned optional source slots","url":"history/records/2026/07/LK-20260716-dic-source-slots.html","kind":"history","section":"history","keywords":"LK-20260716-dic-source-slots 2026-07-16 93 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app DIC Runtime source boundary","text":"DIC uses framework-owned optional source slots id: LK-20260716-dic-source-slots date: 2026-07-16 sequence: 93 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.0 -> 7.4.1` component: `labkit_DICPostprocess_app` | `1.4.2 -> 1.4.3` component: `labkit_DICPreprocess_app` | `1.5.3 -> 1.5.4` scope: DIC scope: Runtime source boundary Context DIC Preprocess and Postprocess use stable semantic source slots such as `referenceImage`, `movingImage`, `maskImage`, and `dicMat`. Those slots are legitimately absent before the user selects a file. Each App therefore carried an identical `pathForId.m` wrapper that returned empty text for an unset slot and otherwise inspected the nested portable reference. The first source accessor required every requested ID to exist. Applying that rule would have preserved the duplicate wrapper instead of lowering App cost. Decision and rationale Make ID-based source lookup preserve the requested shape and return an empty string for a semantic slot that has not been added. Continue to reject malformed records and references. This matches ordinary presenter and session behavior without hiding corrupt project data. Changes Refined `sourcePaths(sources,ids)` for optional semantic slots. Removed both DIC `pathForId.m` wrappers and their duplicate schema knowledge. Migrated DIC loading, summaries, presenters, save defaults, and export paths to the Runtime accessor. Preserved alignment, crop, mask, anchor, rendering, calculation, and export behavior. User and data impact An empty DIC project still presents empty file controls and enables them as the workflow requires. Selected and relinked files load through the same paths. Developers no longer implement a source-ID lookup helper for each App. Compatibility and migration The accessor change relaxes one error case and is compatible within UI 7. No saved project shape changed and no DIC payload migration is required. Validation Runtime unit tests cover mixed present and absent IDs. DIC unit and hidden-GUI tests cover empty startup, file selection, point matching, ROI and mask edits, viewport-preserving interaction, project state, and result export. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [DIC Postprocess](../../../../apps/dic/dic-postprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other families still contain direct portable-reference reads. They will move to the same accessor before the repository-wide no-leak contract is enabled."},{"title":"DTA facade and app ownership boundaries","url":"history/records/2026/05/LK-20260529-dta-facade-and-app-boundaries.html","kind":"history","section":"history","keywords":"LK-20260529-dta-facade-and-app-boundaries 2026-05-29 2 refactor compatible historical project evolution","text":"DTA facade and app ownership boundaries id: LK-20260529-dta-facade-and-app-boundaries date: 2026-05-29 sequence: 2 type: refactor compatibility: compatible scope: historical project evolution Context The first extraction pass separated calculations from the imported GUIs, but the electrochem apps still depended on a broad internal package and shared session helpers. A new app could reuse a parser only by learning implementation details that had nothing to do with reading a DTA file. Decision and rationale Make DTA loading the reusable boundary and keep workflow decisions in the app that owns them. The public layer would load one file, discover files, or load a folder and return documented report structures. Plot labels, callback order, session state, result summaries, and export choices would remain app concerns. This distinction became an early form of LabKit's present architecture: a small library presents stable data operations, while an app owns the laboratory workflow assembled around those operations. Changes Added GUI-independent DTA loading and adopted it in the EIS and Chrono Overlay apps. Added discovery and folder-loading operations, including validation for empty selections, missing folders, and expected experiment kinds. Documented and tested the report schemas returned by batch operations. Moved EIS, Chrono Overlay, CSC, VT Resistance, and CIC out of the original `gamrywb` namespace and into app-owned entry points. Returned calculation details, labels, callback flow, and exports to their owning apps when they were not genuinely reusable. Introduced boundary tests to prevent UI, data, IO, analysis, and utility packages from growing through accidental cross-dependencies. User and data impact Electrochem users kept the same principal workflows, but file-loading failures became more explicit and batch loading gained a defined result report. App authors could use the DTA layer without starting a GUI or depending on the layout of an existing app. No laboratory file format or scientific result schema was intentionally changed by the ownership cleanup. Compatibility and migration The supported app commands replaced direct use of the old `gamrywb` package. Code that reached into that package's internal helpers needed to move either to the DTA facade or into the app that owned the behavior. Validation The period added DTA schema tests, empty- and missing-folder tests, architectural boundary checks, and behavior-focused calculation tests. The repository did not yet have the later unified MATLAB test command, so no single historical validation invocation is available. Evidence GUI-free loading facade `88b19851` and initial app adoption `789ef507`, `0ccc3ad6`. App ownership migration `1ee8e82d` through `1b6042bf`. Discovery and folder loading `c06946d9`, `82f4146d`. Report documentation and contract tests `669eea38`, `e04292c0`. Boundary and failure-handling work from `ad2b6c74` through `7506fa32`. Known limitations and follow-up The public surface was still organized around the first electrochem use cases. The next stage renamed the workbench, narrowed the package map further, and tested whether the same app-first approach could support image and biosignal workflows."},{"title":"Debug sample packs","url":"history/records/2026/07/LK-20260701-debug-sample-packs.html","kind":"history","section":"history","keywords":"LK-20260701-debug-sample-packs 2026-07-01 28 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Debug sample packs","text":"Debug sample packs id: LK-20260701-debug-sample-packs date: 2026-07-01 sequence: 28 type: feat compatibility: compatible component: `labkit.ui` | `3.3.1 -> 3.4.0` component: `labkit_DICPostprocess_app` | `1.2.4 -> 1.3.0` component: `labkit_DICPreprocess_app` | `1.2.2 -> 1.3.0` component: `labkit_ChronoOverlay_app` | `1.2.1 -> 1.3.0` component: `labkit_CIC_app` | `1.2.1 -> 1.3.0` component: `labkit_CSC_app` | `1.2.1 -> 1.3.0` component: `labkit_EIS_app` | `1.2.1 -> 1.3.0` component: `labkit_VTResistance_app` | `1.2.1 -> 1.3.0` component: `labkit_BatchImageCrop_app` | `1.5.1 -> 1.6.0` component: `labkit_CurvatureMeasurement_app` | `1.2.4 -> 1.3.0` component: `labkit_FLIRThermal_app` | `1.1.2 -> 1.2.0` component: `labkit_FocusStack_app` | `1.3.0 -> 1.4.0` component: `labkit_ImageEnhance_app` | `1.4.1 -> 1.5.0` component: `labkit_ImageMatch_app` | `1.4.1 -> 1.5.0` component: `labkit_NerveResponseAnalysis_app` | `1.2.4 -> 1.3.0` component: `labkit_ResponseReviewStats_app` | `1.2.3 -> 1.3.0` component: `labkit_RHSPreview_app` | `1.2.4 -> 1.3.0` component: `labkit_ECGPrint_app` | `1.2.2 -> 1.3.0` scope: Debug sample packs Context Debug reports could identify a failed callback, but reproducing the failure still depended on the original laboratory file and the user's exact sequence of actions. Those inputs were often unavailable to the person investigating the report and were unsuitable as permanent test fixtures. Decision and rationale Let every app create a small, synthetic sample pack that exercises its normal loading path without containing laboratory data. Store the generated inputs and expected output location beside the debug artifacts so a report can be replayed from one self-contained folder. Changes `labkit.ui` `3.3.1 -> 3.4.0` All supported apps moved into the `1.3.x`, `1.4.x`, `1.5.x`, or `1.6.x` debug-sample-pack lines. Added app-owned debug sample packs. Added debug artifact sample and output folders. User and data impact Debug mode gained a repeatable demonstration dataset for each supported app. These packs were synthetic and intended for diagnosis, not as substitutes for real experiment files or as reference scientific results. Compatibility and migration The sample packs were additive debug assets. Existing app inputs and outputs were unchanged, and no user file was copied into a pack. Validation Commit `279befbc` added coverage tests for every app family, focused tests for DIC, electrochem, image, RHS, and wearable sample writers, and layout checks that exercised the generated packs. Evidence Main commit `279befbc`. Known limitations and follow-up Sample packs were deliberately app-owned because each workflow knows which inputs make a useful reproduction. They do not contain or archive a user's original files."},{"title":"Debug workflows, launcher tools, and changelog governance","url":"history/records/2026/07/LK-20260707-debug-workflows-launcher-tools-and-changelog-governance.html","kind":"history","section":"history","keywords":"LK-20260707-debug-workflows-launcher-tools-and-changelog-governance 2026-07-07 39 feat compatible labkit_launcher labkit_launcher labkit_launcher labkit.ui labkit.ui labkit_DICPreprocess_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_FigureStudio_app Debug workflows, launcher tools, and changelog governance","text":"Debug workflows, launcher tools, and changelog governance id: LK-20260707-debug-workflows-launcher-tools-and-changelog-governance date: 2026-07-07 sequence: 39 type: feat compatibility: compatible component: `labkit_launcher` | `1.2.4 -> 1.2.5` component: `labkit_launcher` | `1.2.5 -> 1.2.6` component: `labkit_launcher` | `1.2.6 -> 1.2.7` component: `labkit.ui` | `5.0.0 -> 5.0.1` component: `labkit.ui` | `5.0.1 -> 5.0.2` component: `labkit_DICPreprocess_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.6.6 -> 1.6.7` component: `labkit_FocusStack_app` | `1.4.5 -> 1.4.6` component: `labkit_FigureStudio_app` | `0.1.4 -> 0.1.5` scope: Debug workflows, launcher tools, and changelog governance Context The first complete debug sample packs exposed several false failures. A modal file chooser could be mistaken for a stalled callback, DIC could read an older ROI snapshot than the editor displayed, duplicate crops needed confirmation before export, and Focus Stack's folder workflow was hard to reach from its sample instructions. The launcher was also becoming a practical maintenance and distribution tool. Code Analyzer reports needed a durable home outside launcher source, individual apps needed an offline package format, and private app workspaces needed to remain discoverable without entering the public repository. Finally, the growing changelog needed to explain the direction of the project, not merely repeat tags and commit subjects. Decision and rationale Use v3.1.0 as a stabilization release for reproducible debug workflows. Treat native modal dialogs as active UI, read live editor state when it is newer than the stored snapshot, and make required gestures visible in the app. Move code analysis and packaging into dedicated tools, let the launcher invoke them, and keep private app discovery separate from public release contents. Changes Release tag `v3.1.0` `labkit_launcher` `1.2.4 -> 1.2.7` `labkit.ui` `5.0.1 -> 5.0.2` `labkit_FigureStudio_app` `0.1.4 -> 0.1.5` `labkit_DICPreprocess_app` `1.3.4 -> 1.3.5` `labkit_BatchImageCrop_app` `1.6.6 -> 1.6.7` `labkit_FocusStack_app` `1.4.5 -> 1.4.6` DIC Preprocess ROI mask export now reads the live ROI editor anchors when building a mask, so preview/save do not misreport a drawn ROI as empty when editor state is newer than the app state snapshot. DIC Preprocess keeps the double-click ROI anchor workflow and makes the double-click requirement explicit in the visible details text. Batch Image Crop duplicate tasks now redraw with finite preview overlay coordinates while still requiring users to confirm the duplicated crop center before export. Figure Studio quick PNG/JPG/SVG export actions use runtime-compatible handler signatures. Focus Stack exposes a direct `Choose folder` action for loading all supported images from a focus-stack folder. Debug trace diagnostics no longer write stalled-callback crash reports while a file chooser modal is active. Moved the launcher Code Analyzer scan into `tools/codecheck`, which writes timestamped JSON/HTML report pairs under `artifacts/code-check/` without overwriting earlier runs. Added launcher actions and a deployment tool that package one selected LabKit app into a standalone zip, either as source `.m` files or encoded `.p` files. Added launcher discovery for local private app workspaces under `private_apps/apps/` and roots named by `LABKIT_PRIVATE_APP_ROOTS`. Clarified the public changelog model as a project evolution map organized by reader-facing evolution entries, with release tags and commits kept as anchors and evidence rather than the primary structure. User and data impact The supplied debug packs could be followed without producing a false crash report or an apparently disabled workflow. DIC mask export used the anchors currently visible in the editor, duplicate crops remained blocked until their centers were confirmed, and Focus Stack offered a direct folder action. Maintainers gained timestamped HTML and JSON Code Analyzer reports. A selected app could be packaged for production or offline use without tests, unrelated apps, or repository metadata. Private apps could live beside the checkout, appear in the ordinary launcher, and retain their own repository and history. Compatibility and migration DIC ROI editing still uses double-click to add anchors; no interaction-mode migration is required. Existing file-panel image selection remains available in Focus Stack. Code Analyzer report consumers should read the timestamped `artifacts/code-check/matlab_code_issues_*.json` files. Full LabKit checkout installs are unchanged. Single-app packages can start through either the packaged launcher or the direct run file; P-code packages require MATLAB to run the generated `.p` files. Public apps, public releases, and public CI remain scoped to `apps/`. Validation PR #34 updated debug sample, ROI, crop, folder-loading, launcher-tool, deployment, and private-workspace tests before its squash merge. Tag `v3.1.0` points to release commit `9db01952`; the exact local command sequence was not recorded in this page. Evidence PR #34 squash merge `9db01952` and release tag `v3.1.0`. Known limitations and follow-up The first deployment tool packaged one app at a time. Multi-app bundles were added later. The changelog model introduced here was subsequently replaced by component-linked history pages generated with the documentation site."},{"title":"Declarative app runtime","url":"history/records/2026/07/LK-20260703-declarative-app-runtime.html","kind":"history","section":"history","keywords":"LK-20260703-declarative-app-runtime 2026-07-03 32 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Declarative app runtime","text":"Declarative app runtime id: LK-20260703-declarative-app-runtime date: 2026-07-03 sequence: 32 type: refactor compatibility: compatible component: `labkit.ui` | `3.4.4 -> 3.4.5` component: `labkit_DICPostprocess_app` | `1.3.0 -> 1.3.1` component: `labkit_DICPreprocess_app` | `1.3.0 -> 1.3.1` component: `labkit_ChronoOverlay_app` | `1.3.0 -> 1.3.1` component: `labkit_CIC_app` | `1.3.0 -> 1.3.1` component: `labkit_CSC_app` | `1.3.0 -> 1.3.1` component: `labkit_EIS_app` | `1.3.0 -> 1.3.1` component: `labkit_VTResistance_app` | `1.3.0 -> 1.3.1` component: `labkit_BatchImageCrop_app` | `1.6.1 -> 1.6.2` component: `labkit_CurvatureMeasurement_app` | `1.3.0 -> 1.3.1` component: `labkit_FLIRThermal_app` | `1.2.0 -> 1.2.1` component: `labkit_FocusStack_app` | `1.4.0 -> 1.4.1` component: `labkit_ImageEnhance_app` | `1.5.0 -> 1.5.1` component: `labkit_ImageMatch_app` | `1.5.0 -> 1.5.1` component: `labkit_NerveResponseAnalysis_app` | `1.3.0 -> 1.3.1` component: `labkit_ResponseReviewStats_app` | `1.3.0 -> 1.3.1` component: `labkit_RHSPreview_app` | `1.3.0 -> 1.3.1` component: `labkit_ECGPrint_app` | `1.3.1 -> 1.3.2` scope: Declarative app runtime Context Apps had adopted common shells and controls, but each runner still assembled callbacks, startup steps, refresh behavior, and debug hooks in its own order. Understanding an app meant reading a long construction procedure before reaching the scientific workflow. Decision and rationale Describe an app through a definition containing its layout, actions, state, startup, and rendering contracts, then let the runtime perform the common wiring. Keep action bodies, calculations, and result structures in the app so the definition exposes workflow structure without becoming a generic analysis language. Changes `labkit.ui` `3.4.4 -> 3.4.5` All supported apps received patch bumps. Migrated apps to declarative workflow runtime. User and data impact The visible workflows were intended to remain the same. For maintainers, each app gained a recognizable definition and lifecycle, while repeated setup moved out of its runner. Debug sample packs and focused app tests continued to call the same app-owned operations. Compatibility and migration Users kept the same app entry points and data files. App implementations moved to definitions and runtime actions; unsupported code that reached into old runner construction details needed to adopt the declarative APIs. Validation The migration changed 545 files and updated the affected app, layout, export, calculation, sample-pack, and package-boundary suites in the same commit. The commit message records final profiler and debug evidence; the exact local command was not preserved in this history. Evidence Main commit `568b3e9b`. Known limitations and follow-up The first declarative vocabulary still used the mixed `app`, `spec`, `view`, `tool`, and `diag` package names. The UI 4 and UI 5 passes refined how groups, plots, interactions, and lifecycle responsibilities were named."},{"title":"Default LabKit close protection","url":"history/records/2026/07/LK-20260709-default-labkit-close-protection.html","kind":"history","section":"history","keywords":"LK-20260709-default-labkit-close-protection 2026-07-09 43 fix compatible labkit.ui labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app Default LabKit close protection","text":"Default LabKit close protection id: LK-20260709-default-labkit-close-protection date: 2026-07-09 sequence: 43 type: fix compatibility: compatible component: `labkit.ui` | `5.0.2 -> 5.0.3` component: `labkit_FocusStack_app` | `1.4.6 -> 1.4.7` component: `labkit_ImageEnhance_app` | `1.5.5 -> 1.5.6` component: `labkit_ImageMatch_app` | `1.5.5 -> 1.5.6` scope: Default LabKit close protection Context Only apps that installed a dirty-state guard warned before closing. A newly created public or private app could therefore discard work silently until its author implemented and tested a separate guard. Repeated close shortcuts could also race with a modal confirmation. Decision and rationale Make close confirmation a default property of every framework-owned app window and remove the optional app-facing guard API. Keep one in-window prompt active at a time; a repeated close shortcut confirms that prompt instead of opening another one. Changes `labkit.ui` `5.0.2 -> 5.0.3` `labkit_FocusStack_app` `1.4.6 -> 1.4.7` `labkit_ImageEnhance_app` `1.5.5 -> 1.5.6` `labkit_ImageMatch_app` `1.5.5 -> 1.5.6` LabKit runtime figures now show an in-window confirmation prompt before any framework-owned app window closes, even when the app has not marked itself dirty. Removed the app-facing `labkit.ui.runtime.setCloseGuard` API and migrated existing app close-guard dirty checks to the framework default behavior. Repeating or holding the app close shortcut while the in-window prompt is active confirms the close. User and data impact Every public or private LabKit app asked before its window closed, even if the app had no custom dirty-state logic. Users gained a consistent protection against accidental closure at the cost of one confirmation step. Compatibility and migration Closing LabKit apps now requires one confirmation step by default. App code that calls `labkit.ui.runtime.setCloseGuard` must remove that call; close confirmation is framework-owned. Validation Commit `0c9f472b` expanded the UI busy-state GUI suite for initial close, cancel, confirmation, and repeated-shortcut behavior, then updated public- surface and app-compatibility checks. Evidence Mainline commit `0c9f472b`. Known limitations and follow-up This policy protected the window, not the semantic completeness of each saved project. Runtime V2 later combined default close handling with app-owned dirty state and lifecycle services."},{"title":"Destination-rebased source references","url":"history/records/2026/07/LK-20260716-destination-rebased-source-references.html","kind":"history","section":"history","keywords":"LK-20260716-destination-rebased-source-references 2026-07-16 74 fix compatible labkit.ui project serialization named save, explicit autosave, and recovery","text":"Destination-rebased source references id: LK-20260716-destination-rebased-source-references date: 2026-07-16 sequence: 74 type: fix compatibility: compatible component: `labkit.ui` | `6.0.4 -> 6.0.5` scope: project serialization scope: named save, explicit autosave, and recovery Context Runtime source records were created before an App knew its future project-file location, so they began with an empty `relativePath`. Project-envelope creation copied those records unchanged. As a result, the resolver supported relative references, but ordinary Runtime V2 saves did not actually generate them. Decision and rationale Rebase source references at the serialization boundary using the actual MAT destination. A named project, app-owned autosave, and framework recovery file can live in different folders, so each write must calculate its own relative path rather than reusing a path cached in live state. Changes Passed the real destination path into every Runtime V2 envelope writer. Copied the durable project and refreshed reference schema, relative path, original path, and filename immediately before serialization. Preserved additive app/reference fields while replacing standard path fields. Kept resolved absolute paths in live state so current-session readers do not need to interpret portable references. User and data impact A project and its source directory can now move together to another root or machine and reopen through the saved relative relationship even when the old absolute path no longer exists. Existing projects without a relative path remain readable through their original-path and relink fallbacks. Compatibility and migration The envelope and source-reference schema versions are unchanged. No payload migration is needed: the next named save, explicit autosave, or recovery write adds the destination-correct relative path. Validation The Runtime V2 project GUI test saves a source/project directory tree, verifies the generated relative path, moves the tree, and reloads from the new root. Video Marker verifies that its source-adjacent explicit autosave is rebased from the autosave destination. Existing atomic-save, relink, recovery, and additive field tests remain in the same focused suites. Evidence [Runtime and Data Model](../../../../framework/guides/runtime.md) explains when relative paths are created and how they are resolved. [UI Framework](../../../../framework/README.md) describes Runtime V2 project ownership and persistence. Known limitations and follow-up The portable-reference creation and resolution algorithms remain public in UI 6 for compatibility. They are implementation mechanics rather than App-facing workflow APIs and are reviewed with the remaining Runtime public surface for a single future major-boundary cleanup."},{"title":"Documentation navigation follows topic hierarchy","url":"history/records/2026/07/LK-20260717-hierarchical-documentation-navigation.html","kind":"history","section":"history","keywords":"LK-20260717-hierarchical-documentation-navigation 2026-07-17 132 docs compatible MATLAB documentation site navigation","text":"Documentation navigation follows topic hierarchy id: LK-20260717-hierarchical-documentation-navigation date: 2026-07-17 sequence: 132 type: docs compatibility: compatible scope: MATLAB documentation site navigation Context The documentation site exposed its major areas in the top bar, but each area's left sidebar was a flat list. Related app families, development tasks, and API packages were not visually distinguished, so readers could not readily see the path from an area to a topic and then to a specific page. Decision and rationale Render contextual hierarchy from the existing page and catalog metadata. App families, libraries, development tasks, and MATLAB packages become labeled sidebar branches. This preserves one structured source of navigation truth while making downstream topics visible from their area landing pages. Changes Grouped app navigation by family and indented app manuals below family pages. Grouped function, framework, and development navigation by topic. Grouped API siblings under their owning MATLAB package. Defined the second `nav` entry in `docs/site.json` as the branch label and ordered branches by their earliest page. Added responsive sidebar styling and renderer regression coverage for the hierarchy. User and data impact Readers can move from a top-level area to a topic branch and then to a specific manual without searching a flat list. Documentation content, URLs, MATLAB behavior, and scientific data are unchanged. Compatibility and migration Existing page URLs and top-level navigation remain compatible. Documentation authors should assign new multi-page topics a stable second-level `nav` label in `docs/site.json`. Validation The documentation renderer regression verifies Development topic groups, App family branches, and API package labels. `docsCheck` rebuilds and compares the complete generated site. Evidence The generated area landing pages contain labeled branch headings and parent/child link classes derived from tracked page and app catalog metadata. Known limitations and follow-up Single-page areas do not display an artificial empty branch. History records remain in the generated timeline because reproducing the full record set in the sidebar would make navigation less usable."},{"title":"Documentation rendering avoids repeated model capture","url":"history/records/2026/07/LK-20260717-documentation-render-performance.html","kind":"history","section":"history","keywords":"LK-20260717-documentation-render-performance 2026-07-17 130 perf compatible MATLAB documentation renderer","text":"Documentation rendering avoids repeated model capture id: LK-20260717-documentation-render-performance date: 2026-07-17 sequence: 130 type: perf compatibility: compatible scope: MATLAB documentation renderer Context Two project documentation tests each rebuild the complete tracked site. Profiling showed that most render time was not file generation or tree comparison: every inline Markdown fragment created an anonymous function that captured the complete documentation model before scanning links. Decision and rationale Pass the shared model and current page directly into token protection and dispatch code, image, and link rendering by token type. This preserves the renderer contract and generated bytes while avoiding thousands of large closure allocations. Changes Replaced per-fragment renderer closures with direct token dispatch. Retained the same code, image, link, emphasis, and nested inline-code behavior. Added a focused regression guard against restoring the measured closure pattern. User and data impact Generated HTML is byte-for-byte unchanged. Documentation rebuilds, consistency checks, and the launcher documentation action complete substantially sooner. Compatibility and migration `renderLabKitDocs`, `checkLabKitDocs`, `buildtool docs`, and `buildtool docsCheck` retain their existing syntax, outputs, and failure behavior. No migration is required. Validation On the same MATLAB R2025a host and complete `checkLabKitDocs` scenario, profiler total time decreased from 46.52 seconds to 9.66 seconds, approximately 79 percent. The after-profile run also completed the byte comparison against the tracked site. Evidence The focused renderer regression protects inline link behavior and the allocation fix. `docsCheck` independently rebuilds all narrative and API pages and compares every generated file. Known limitations and follow-up Absolute times vary by machine and filesystem. API help rendering is now the largest measured renderer-owned cost and should be changed only after a separate profile identifies a safe optimization."},{"title":"Documentation search ranks page intent and scopes history explicitly","url":"history/records/2026/07/LK-20260720-ranked-and-scoped-documentation-search.html","kind":"history","section":"history","keywords":"LK-20260720-ranked-and-scoped-documentation-search 2026-07-20 146 docs compatible Documentation system tools/docs/ site search","text":"Documentation search ranks page intent and scopes history explicitly id: LK-20260720-ranked-and-scoped-documentation-search date: 2026-07-20 sequence: 146 type: docs compatibility: compatible scope: Documentation system scope: tools/docs/ scope: site search Context The generated documentation search treated every term in a page title, body, and rendered component-history links as equivalent. A shared history record could therefore make an unrelated App page appear before the App named by a search. Readers also had no way to restrict a query to history records. Decision and rationale Keep component-history links visible on their related pages, but index history records as their own searchable documents. Give title and explicit keywords more weight than body text, then provide a visible section filter for readers who want Apps, APIs, history, or another documentation area. Changes Added section and keyword fields to generated search entries. Excluded rendered component-history link text from narrative page search bodies while retaining complete history-record search entries. Added deterministic field-weighted ranking, result excerpts, and section filtering to the offline-safe client search. Added regression coverage for search-entry separation, filters, and ranking contracts. User and data impact Searching a named App now favors that App and its family over unrelated pages that share a historical record. Readers can search only History when tracing a decision. No App behavior, calculations, projects, exports, or laboratory data changes. Compatibility and migration The generated site remains usable from file:// without network fetches. Existing documentation URLs, history records, and page-level change-history links remain unchanged. Validation Focused documentation search contract tests verify the generated index and client controls. Documentation rendering and consistency checks regenerate the tracked site and validate the history record. Evidence renderLabKitDocs now emits separated search fields. The client script ranks title, keyword, and body matches independently and filters by the generated section field. Known limitations and follow-up Search is intentionally dependency-free and uses exact lexical matching. Typo-tolerant suggestions can be considered later only if they preserve the current deterministic title-first ordering."},{"title":"Documentation structure becomes the navigation contract","url":"history/records/2026/07/LK-20260719-path-derived-documentation.html","kind":"history","section":"history","keywords":"LK-20260719-path-derived-documentation 2026-07-19 136 refactor compatible labkit_launcher Documentation renderer Documentation maintenance","text":"Documentation structure becomes the navigation contract id: LK-20260719-path-derived-documentation date: 2026-07-19 sequence: 136 type: refactor compatibility: compatible component: `labkit_launcher` | `1.5.1 -> 1.5.2` scope: Documentation renderer scope: Documentation maintenance Context The local HTML site duplicated documentation ownership across Markdown files, `site.json`, two catalog JSON files, and generated output. Moving or adding a page therefore required several coordinated edits. Markdown links also embedded many relative paths without a repository-owned way to repair links after a move. Decision and rationale Treat the `docs/` directory structure as the narrative navigation contract. Discover public Apps from `labkit_launcher(\"list\")`, associate each App with its unique path-conventional manual, and discover public API pages from complete MATLAB help contracts. Keep ordinary relative Markdown links so source pages remain useful in GitHub and add a checker that can repair a broken link only when its destination is unambiguous. The generated `site/` remains the offline and local HTML product. It is still produced exclusively by the repository renderer. Changes Removed `docs/site.json` and the App and API catalog JSON files. Reorganized development and framework guides into path-owned navigation groups and moved the API landing page to `docs/reference/README.md`. Made the renderer discover Markdown pages, public Apps, and complete public MATLAB help without duplicated registries. Added `maintainLabKitDocLinks` for checking and uniquely repairing relative Markdown links after file moves. Changed launcher documentation lookup to derive the selected App manual and generated HTML path from App discovery and documentation structure. Removed obsolete compatibility redirect pages instead of preserving legacy documentation routes. User and data impact Local HTML documentation and launcher documentation actions remain available. Contributors add or move a page by placing Markdown in the intended directory and running the link maintainer and renderer; no navigation catalog needs a matching edit. Scientific behavior and project data are unchanged. Compatibility and migration Existing current documentation links are rewritten to their new relative paths. Old generated documentation routes are intentionally retired and are not maintained as redirect pages. Rebuild `site/` after updating a checkout. Validation Focused documentation contract and renderer regression tests cover discovery, navigation, link repair, public API help, and launcher documentation lookup. The documentation checker validates the generated site after a clean local render. Evidence [Documentation Build Tools](../../../../development/tools/documentation.md) [Maintaining LabKit Documentation](../../../../development/maintain-and-release/documentation.md) [LabKit Launcher](../../../../apps/labkit-core/launcher/README.md) [API Reference](../../../../reference/README.md) Known limitations and follow-up Automatic link repair deliberately refuses ambiguous destinations. Authors must resolve those cases explicitly so the checker never guesses between pages with the same filename or title."},{"title":"Durable Video Marker source metadata","url":"history/records/2026/07/LK-20260716-video-marker-durable-metadata.html","kind":"history","section":"history","keywords":"LK-20260716-video-marker-durable-metadata 2026-07-16 70 feat compatible labkit_VideoMarker_app project payload schema downstream gait inputs","text":"Durable Video Marker source metadata id: LK-20260716-video-marker-durable-metadata date: 2026-07-16 sequence: 70 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `1.4.1 -> 1.5.0` scope: project payload schema scope: downstream gait inputs Context Video Marker previously kept frame rate, frame count, duration, and image size only in transient decoded-video state. Its MAT project preserved coordinates but a GUI-independent downstream analysis could not recover time semantics without reopening the original video or inventing a frame rate. Decision and rationale Make immutable decoded-video facts part of the durable Video Marker project. The portable source record continues to own the file path; the metadata record contains only finite numeric facts required to interpret saved annotations. Changes Advanced the Video Marker project payload from version 1 to version 2. Added durable frame count, frame rate, duration, height, and width. Populate metadata when a video or marker CSV is opened and seed rebuilt sessions from saved metadata before optional source decoding. Refresh durable metadata from the reopened source before an explicit **Save autosave**, so a loaded version 1 autosave upgrades in place. Validate metadata values and annotation/frame-count consistency. Added an ordered v1-to-v2 migration that recovers frame count from saved annotation coordinates and leaves unknowable source facts at zero. User and data impact New named projects and source-adjacent autosaves contain enough timing and geometry information for Gait Analysis to treat the MAT document as its authoritative input. Paths are not duplicated into the metadata record. Compatibility and migration Version 1 LabKit project envelopes remain readable. Opening one marks it for an upgrade; saving writes payload version 2. A frame rate absent from version 1 cannot be inferred from coordinates alone and remains zero until the source is opened by the current Video Marker app. Validation Video Marker unit tests verify metadata selection, absence of transient paths, frame-count recovery, payload validation, and mismatched annotation rejection. The existing GUI suite covers video opening, marker CSV import, project saves, and recovery loading. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the persisted fields and downstream use. Gait Analysis input tests exercise the current payload contract separately. Known limitations and follow-up Projects whose old payload never recorded a frame rate must reopen their source video before producing a fully self-describing current project. Opening that source through the saved reference and pressing **Save autosave** performs the upgrade without another location prompt."},{"title":"ECG Print uses one project contract","url":"history/records/2026/07/LK-20260716-ecg-print-structure.html","kind":"history","section":"history","keywords":"LK-20260716-ecg-print-structure 2026-07-16 106 refactor compatible labkit_ECGPrint_app Wearable App structure","text":"ECG Print uses one project contract id: LK-20260716-ecg-print-structure date: 2026-07-16 sequence: 106 type: refactor compatibility: compatible component: `labkit_ECGPrint_app` | `1.4.2 -> 1.4.3` scope: Wearable scope: App structure Context ECG Print kept product metadata in separate requirement and version functions, and split one durable schema across project creation, validation, migration, and session reconstruction files under a generic lifecycle package. Session and presentation code also read the Runtime-owned portable-reference structure directly. Decision and rationale Use the compact Runtime V2 App structure. The definition owns product metadata and optional capabilities, one project contract owns durable schema evolution, and one root session factory rebuilds decoded signal state. ECG-specific parsing, analysis, presentation, and result packages already express real workflow capabilities and remain App-owned. Changes Consolidated command identity, display metadata, version, requirements, layout, actions, presenter, renderer, and debug capability in `definition`. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved transient recording and analysis reconstruction to root `createSession` and reused that factory when clearing a failed decode. Replaced nested portable-reference reads with semantic `sourcePaths` lookup. Removed the generic lifecycle package and separate requirement/version files. Updated the App package structure guardrail so actions, presentation, project persistence, and transient sessions are optional capabilities; Apps that adopt root `projectSpec` cannot regress to split lifecycle metadata. User and data impact Recording import, parsing controls, channel selection, filtering, peak detection, segmentation, template construction, SNR measurements, plots, exports, manifests, project reopening, and user wording are unchanged. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still move their singular source record into the canonical source collection before validation. Runtime V2 now invokes the single migration entry and owns iteration to the current payload version. Current version-2 project files require no data transformation. Validation Focused GUI-free tests cover the migration callback, definition contract, import parsing, analysis products, presentation models, and result tables. The hidden GUI workflow covers launch, recording import, analysis, plots, both exports, project save, and session reconstruction after load. Project guardrails cover the compact optional-capability structure, embedded metadata, version/history ownership, and generated documentation consistency. Evidence [ECG Print](../../../../apps/wearable/ecg-print/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up Automated tests do not judge ECG morphology, peak quality, or the visual suitability of exported waveforms for a particular experiment. Remaining Runtime V2 App migrations and the stale agent guidance require separate review."},{"title":"EIS consolidates its product and project contracts","url":"history/records/2026/07/LK-20260716-eis-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-eis-project-spec 2026-07-16 89 refactor compatible labkit_EIS_app Electrochemistry Project lifecycle","text":"EIS consolidates its product and project contracts id: LK-20260716-eis-project-spec date: 2026-07-16 sequence: 89 type: refactor compatibility: compatible component: `labkit_EIS_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context EIS split static product metadata across three files and one first-version project schema across a generic lifecycle package. Those files did not own separate impedance or plotting contracts. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because restoring decoded ZCURVE items and source selection is genuine transient work. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved decoded curve and selection restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Delayed log-scale assignment until filtered positive data establishes valid automatic limits, and corrected the GUI regression fixture to use a legal positive manual range while testing stale-zoom replacement. Kept impedance values, Nyquist/Bode axis semantics, log scaling, zoom behavior, and export schemas unchanged. User and data impact Launch, multi-file selection, plotting, zoom, save/load, and export behavior remain unchanged. Project and transient state ownership are now adjacent and explicit. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing EIS projects require no migration. Validation Unit tests cover ZCURVE parsing, axis values, Nyquist mode, log scaling, project/session contracts, presentation, and export columns. The hidden GUI workflow covers real launch, file loading, plot interaction, save/load, and export. Evidence [EIS](../../../../apps/electrochemistry/eis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Source restoration still consumes portable-reference internals. The planned shared source-path service will remove that leak across all Apps together."},{"title":"EIS delegates source identity reconciliation to Runtime V2","url":"history/records/2026/07/LK-20260716-eis-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-eis-runtime-source-reconciliation 2026-07-16 110 refactor compatible labkit_EIS_app Electrochemistry App structure Runtime adoption","text":"EIS delegates source identity reconciliation to Runtime V2 id: LK-20260716-eis-runtime-source-reconciliation date: 2026-07-16 sequence: 110 type: refactor compatibility: compatible component: `labkit_EIS_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context EIS already stored only portable source records in its durable project, but its action file still generated source IDs, appended records, and removed records with App-local helpers. That duplicated Runtime V2's source reconciliation service and made the App responsible for an identity invariant owned by project persistence. The EIS session factory also repeated empty workflow and view buckets even though Runtime V2 canonicalizes missing transient buckets. Decision and rationale Use `services.project.reconcileSources` as the single source-record owner after successful file additions, removals, and clearing. The EIS workflow continues to decide which decoded files are accepted and their order; Runtime preserves matching source identities and allocates collision-free identities for new files. Return only EIS-specific selection and decoded-cache state from `createSession`. Runtime supplies the empty workflow and view buckets before the state becomes visible to actions or presenters. Changes Removed EIS-local source-ID generation, append, and removal helpers. Reconciled durable source records from the successfully decoded EIS items. Removed empty workflow and view boilerplate from the session factory. Added GUI assertions for canonical Runtime buckets, unique source IDs, and identity preservation across additions and project reopen. Advanced the EIS App version to 1.4.4. User and data impact File selection, decoding, plotting, export, save, and reopen behavior are unchanged. Existing saved source IDs remain stable when their paths match. Newly added records receive Runtime-managed IDs. IDs are persistence identities and are not displayed as scientific results. Compatibility and migration The project payload remains version 1 and needs no migration. Existing project files continue to load through their stored portable references. Validation The EIS hidden GUI workflow covers two-file loading from separate folders, selection, plotting, export, project save, clear, and reopen. Focused unit and contract tests cover EIS calculations, App structure, version ownership, documentation, and history ordering. Evidence [EIS](../../../../apps/electrochemistry/eis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up Other multi-file Apps still contain equivalent local source-list helpers. They should adopt the same Runtime service only after their role and ordering semantics are verified by their own focused tests."},{"title":"EIS uses explicit plot-view actions","url":"history/records/2026/07/LK-20260721-eis-explicit-plot-views.html","kind":"history","section":"history","keywords":"LK-20260721-eis-explicit-plot-views 2026-07-21 147 fix compatible labkit_EIS_app EIS Overlay","text":"EIS uses explicit plot-view actions id: LK-20260721-eis-explicit-plot-views date: 2026-07-21 sequence: 147 type: fix compatibility: compatible component: `labkit_EIS_app` | `1.5.1 -> 1.5.2` scope: EIS Overlay Context EIS inferred an equal X/Y aspect ratio from specific Nyquist axis selections. That implicit choice could shrink the usable plotting area and make a redraw look unlike the user's current view. Decision and rationale Keep plot semantics in EIS, but use the App framework's viewport revision and data-fitting capability for view behavior. Equal data units are now an explicit one-time view reset rather than a rule tied to axis labels or a constraint on later zooming. Changes Removed the EIS axis-label rule that automatically selected `axis equal`. Added **Fit X/Y limits** for independent data-fitted limits. Added **Use equal X/Y scale** for an explicit equal-data-unit view without changing the plot size allocated by the App layout. Kept ordinary zoom and wheel viewports under the framework-managed viewport preservation path. User and data impact Nyquist plots now begin with independent limits and a stable plot area. Equal scaling preserves the App layout's plot size. The new view buttons change only transient presentation; EIS sources, projects, calculations, and exports are unchanged. Compatibility and migration Existing saved projects remain valid. The selected axes and Log X/Y options keep their existing meaning; the equal-aspect choice is intentionally not saved with the project. Validation The EIS GUI workflow test covers both buttons, independent fitting, explicit equal aspect, and preservation of an ordinary manually chosen viewport. Evidence The renderer now uses `labkit.app.plot.fitAxesToGraphics` for both independent fits and equal-data-unit limits, and receives a new view revision only from an explicit view action. Known limitations and follow-up Automated GUI coverage cannot assess visual balance on every monitor and MATLAB release. Verify the Nyquist layout interactively before a release. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data."},{"title":"Electrochem Apps stop reading portable references","url":"history/records/2026/07/LK-20260716-electrochem-source-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-electrochem-source-boundary 2026-07-16 92 refactor compatible labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app Electrochemistry Runtime source boundary","text":"Electrochem Apps stop reading portable references id: LK-20260716-electrochem-source-boundary date: 2026-07-16 sequence: 92 type: refactor compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.2 -> 1.4.3` component: `labkit_CIC_app` | `1.4.2 -> 1.4.3` component: `labkit_CSC_app` | `1.4.2 -> 1.4.3` component: `labkit_EIS_app` | `1.4.2 -> 1.4.3` component: `labkit_VTResistance_app` | `1.4.2 -> 1.4.3` scope: Electrochemistry scope: Runtime source boundary Context The Electrochem Apps already delegated portable source creation, save-time rebasing, and load-time relinking to Runtime V2. Their session factories, source loaders, actions, and presenters nevertheless read the runtime's nested path field directly. CIC and VT Resistance also carried duplicate local path extraction functions. Decision and rationale Use the public Runtime source-path accessor at every App boundary. This keeps lazy-loading and batch-selection policies App-owned while removing knowledge of the portable-reference storage schema. Changes Replaced direct nested-reference reads in all five Electrochem Apps. Removed duplicate CIC and VT Resistance path extraction functions. Preserved source order and lazy first-item decoding. Kept DTA parsing, formulas, thresholds, plots, exports, and error wording unchanged. User and data impact File selection, project reopen/relink, calculation, preview, and export behavior are unchanged. App code now expresses only its workflow use of paths; Runtime V2 owns how those paths are stored and resolved. Compatibility and migration No project schema or saved source record changed. Existing projects require no migration and remain compatible with the same UI 7 contract range. Validation The Electrochem unit and hidden-GUI suites cover project reconstruction, DTA loading, selection, plotting, computation, export, and saved-state workflows. Package and version guardrails verify the shared API and component metadata. Evidence [Electrochemistry Apps](../../../../apps/electrochemistry/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other App families still contain direct reads and will migrate in their own behavior-tested commits before a repository-wide no-leak guard is enabled."},{"title":"Electrochemistry source-field validation boundary","url":"history/records/2026/07/LK-20260716-electrochem-source-field-validation.html","kind":"history","section":"history","keywords":"LK-20260716-electrochem-source-field-validation 2026-07-16 118 fix compatible labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app electrochemistry project schemas malformed project rejection","text":"Electrochemistry source-field validation boundary id: LK-20260716-electrochem-source-field-validation date: 2026-07-16 sequence: 118 type: fix compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.5 -> 1.4.6` component: `labkit_CIC_app` | `1.4.5 -> 1.4.6` component: `labkit_CSC_app` | `1.4.5 -> 1.4.6` component: `labkit_EIS_app` | `1.4.5 -> 1.4.6` component: `labkit_VTResistance_app` | `1.4.5 -> 1.4.6` scope: electrochemistry project schemas scope: malformed project rejection Context Runtime owns the format of a portable source record but intentionally allows a project whose `inputs` bucket has no `sources` field. Static and embedded-data Apps may not use external sources. The first electrochemistry validator reduction correctly removed record-format duplication but also stopped declaring that these five file-backed Apps require a source collection. Decision and rationale Keep field presence with the App schema and record shape with Runtime. This is the narrow ownership split: the App decides whether sources are required; Runtime decides what every supplied source record means. Changes Restored the `inputs.sources` presence requirement in all five electrochemistry project validators. Added a GUI-free unit contract that accepts each default project and rejects the same project after its App-required source collection is removed. Kept canonical bucket and source-record field validation out of the Apps. User and data impact Valid projects are unchanged. A malformed electrochemistry project missing its entire source collection is rejected by the App validator before session reconstruction instead of failing later while decoded data are rebuilt. Compatibility and migration No saved format changed and no migration is required. Every project created or saved by these Apps already contains `inputs.sources`. Validation The focused project-spec test covers all five accepted defaults and all five missing-source rejection cases. The Runtime project and electrochemistry GUI tests continue to cover canonical record validation and working file flows. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) separates framework-owned record shape from App-owned fields and roles. The [Electrochemistry App manuals](../../../../apps/electrochemistry/README.md) identify the required App source collection. Known limitations and follow-up The same distinction must be applied explicitly while reducing validators in other App families; a generic source format does not imply that every App has the same required source fields or roles."},{"title":"Explicit application autosave","url":"history/records/2026/07/LK-20260716-explicit-autosave.html","kind":"history","section":"history","keywords":"LK-20260716-explicit-autosave 2026-07-16 68 feat compatible labkit.ui labkit_VideoMarker_app Runtime V2 project recovery Video Marker session workflow","text":"Explicit application autosave id: LK-20260716-explicit-autosave date: 2026-07-16 sequence: 68 type: feat compatibility: compatible component: `labkit.ui` | `6.0.2 -> 6.0.3` component: `labkit_VideoMarker_app` | `1.3.0 -> 1.4.0` scope: Runtime V2 project recovery scope: Video Marker session workflow Context Runtime V2 already wrote debounced recovery generations after durable edits, but an app could only expose the named-project save operation. Video Marker users who wanted to force the current recovery point had no direct control and using **Save State** introduced a destination prompt for an unnamed project. Decision and rationale Expose the existing framework-owned recovery writer as an injected action service. The service accepts the current action state, writes the same bounded recovery generations as the timer, and deliberately leaves the named project path and dirty status unchanged. Video Marker exposes that operation as **Save autosave** in its Session panel. Changes Factored the atomic recovery-generation writer out of the timer scheduler. Added `services.project.saveAutosave(state)` for an immediate, pathless recovery write. Added the Video Marker **Save autosave** button and visible workflow log acknowledgement. Kept ordinary **Save State** as the separate operation for choosing or updating a named project file. User and data impact Clicking **Save autosave** does not open a native file dialog. It updates the current app/document recovery file and retains one previous generation. It does not make the recovery file the active named project and does not suppress later unsaved-close protection. Compatibility and migration Existing projects, recovery files, and automatic autosave behavior remain compatible. Apps that do not expose the new service behave exactly as before. Validation The focused Video Marker GUI workflow verifies that the button immediately creates a recovery file while the runtime document path remains empty. The existing Runtime V2 project test continues to cover debouncing, bounded generations, and recovery loading. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) documents the injected recovery operation and its distinction from named saves. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the Session button and no-dialog behavior. Known limitations and follow-up An autosave is a recovery aid, not a user-named archival project. Users still use **Save State** when they need a deliberate project filename or location."},{"title":"Explicit layout action contract","url":"history/records/2026/07/LK-20260717-explicit-layout-action-contract.html","kind":"history","section":"history","keywords":"LK-20260717-explicit-layout-action-contract 2026-07-17 123 refactor compatible labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Runtime V2 layout callback resolution public App maintenance cost","text":"Explicit layout action contract id: LK-20260717-explicit-layout-action-contract date: 2026-07-17 sequence: 123 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.5 -> 1.4.6` component: `labkit_DICPreprocess_app` | `1.5.6 -> 1.5.7` component: `labkit_ChronoOverlay_app` | `1.4.6 -> 1.4.7` component: `labkit_CIC_app` | `1.4.6 -> 1.4.7` component: `labkit_CSC_app` | `1.4.6 -> 1.4.7` component: `labkit_EIS_app` | `1.4.6 -> 1.4.7` component: `labkit_VTResistance_app` | `1.4.6 -> 1.4.7` component: `labkit_GaitAnalysis_app` | `2.0.6 -> 2.0.7` component: `labkit_BatchImageCrop_app` | `1.7.5 -> 1.7.6` component: `labkit_CurvatureMeasurement_app` | `1.4.5 -> 1.4.6` component: `labkit_FLIRThermal_app` | `1.4.5 -> 1.4.6` component: `labkit_FocusStack_app` | `1.5.4 -> 1.5.5` component: `labkit_ImageEnhance_app` | `1.6.5 -> 1.6.6` component: `labkit_ImageMatch_app` | `1.6.5 -> 1.6.6` component: `labkit_VideoMarker_app` | `1.5.5 -> 1.5.6` component: `labkit_FigureStudio_app` | `0.2.7 -> 0.2.8` component: `labkit_NerveResponseAnalysis_app` | `1.4.5 -> 1.4.6` component: `labkit_ResponseReviewStats_app` | `1.4.5 -> 1.4.6` component: `labkit_RHSPreview_app` | `1.4.5 -> 1.4.6` component: `labkit_ECGPrint_app` | `1.4.5 -> 1.4.6` scope: Runtime V2 layout callback resolution scope: public App maintenance cost Context Every public App layout carried the same local `callbackValue` helper. The helper returned an empty value when a callback field was absent, so a misspelled or unregistered action could survive startup as a control that silently did nothing. Twenty copies also obscured that Runtime already creates the complete callback table from each App definition. Decision and rationale Layouts reference Runtime-generated callbacks directly. MATLAB field access therefore validates the action ID while the data-only layout is built. Runtime remains the single callback-adapter owner; Apps own only their semantic action registries and layout references. Changes Removed the duplicated callback fallback from all 20 public App layouts. Replaced static lookups with direct `callbacks.actionId` references and kept dynamic field access only in three small controls whose action names are supplied by the surrounding layout. Added a fleet-wide contract that creates every App definition and layout with the Runtime callback inventory. Updated direct neurophysiology layout tests to supply explicit callbacks derived from their action registries. User and data impact Correctly registered controls behave as before. A broken App definition now fails during layout construction instead of presenting an inert control. No scientific calculation, project payload, saved file, or interaction semantics changed. Compatibility and migration No project migration is required. This is an App-development contract tightening: custom layouts must register every referenced semantic action. Validation The focused fleet contract builds every public App layout and catches missing or misspelled callback fields. The App package guardrail rejects reintroduced silent callback fallbacks, and focused workflow-layout tests cover direct construction outside a running figure. Evidence [Runtime layout and action rules](../../../../framework/guides/runtime.md#layout-and-action-rules) [Apps](../../../../apps/README.md) [App development](../../../../development/build-apps/app-development.md) Known limitations and follow-up This change deliberately does not merge short semantic actions or dynamic control helpers merely to reduce line count. Those remain candidates only when a repeated behavior has one stable, domain-neutral owner."},{"title":"FLIR Thermal separates durable annotations from decoded sources","url":"history/records/2026/07/LK-20260716-flir-thermal-structure.html","kind":"history","section":"history","keywords":"LK-20260716-flir-thermal-structure 2026-07-16 98 refactor compatible labkit_FLIRThermal_app Image Measurement App structure","text":"FLIR Thermal separates durable annotations from decoded sources id: LK-20260716-flir-thermal-structure date: 2026-07-16 sequence: 98 type: refactor compatibility: compatible component: `labkit_FLIRThermal_app` | `1.4.2 -> 1.4.3` scope: Image Measurement scope: App structure Context FLIR Thermal split static metadata across files, spread a version-1 project over a generic lifecycle package, grouped decoded records, durable annotations, and numerical reading updates under `+appState`, and read nested portable reference fields in session, actions, and presentation. Its startup callback also selected an output directory before the user loaded a source. Decision and rationale Use the compact Runtime V2 contract while preserving the boundary between decoded radiometric data and the lightweight annotations safe to persist. Assign each remaining helper to the capability it describes instead of moving thermal semantics into the framework. Changes Consolidated product metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated durable project creation and validation in `projectSpec.m` and moved selected-image reconstruction to root `createSession.m`. Moved decoded item shape to `+sourceFiles`, point/ROI updates to `+analysisRun`, and persistent per-image readings to `+thermalAnnotations`. Removed separate metadata files, generic lifecycle/state packages, and the redundant App startup callback. Replaced all direct portable-reference field access with the Runtime source path accessor. User and data impact Radiometric decoding, Celsius conversion, range controls, point and ROI readings, viewport-preserving overlays, project reopen, and exports keep their existing behavior. Empty launch no longer chooses an output folder; adding sources still establishes the same source-adjacent default. Developers can find transient decoded matrices, scientific readings, and durable annotations under their actual owners without learning a generic state layer. Compatibility and migration The durable payload remains version 1 with identical fields and defaults. Existing projects require no migration. The removed App-specific debug line is replaced by the Runtime's standard debug-startup message. Validation Five unit methods cover radiometric import, thermal values, calibration diagnostics, range controls, independent ROIs, rendering mappings, manifests, and exported matrices. Two hidden GUI workflows cover multi-file selection, session caching, managed region interaction, project save/load reconstruction, shared ranges, and batch export. Evidence [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Thermal Library](../../../../libraries/thermal/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Image Measurement Apps still use generic lifecycle/state packages and will be reviewed individually. Automated GUI tests do not replace manual judgment of pointer feel, visual calibration, or camera-specific accuracy."},{"title":"FLIR display tuning","url":"history/records/2026/07/LK-20260703-flir-display-tuning.html","kind":"history","section":"history","keywords":"LK-20260703-flir-display-tuning 2026-07-03 36 feat compatible labkit_CSC_app labkit_FLIRThermal_app labkit_FLIRThermal_app labkit_FLIRThermal_app FLIR display tuning","text":"FLIR display tuning id: LK-20260703-flir-display-tuning date: 2026-07-03 sequence: 36 type: feat compatibility: compatible component: `labkit_CSC_app` | `1.3.6 -> 1.3.7` component: `labkit_FLIRThermal_app` | `1.2.4 -> 1.2.5` component: `labkit_FLIRThermal_app` | `1.2.5 -> 1.2.6` component: `labkit_FLIRThermal_app` | `1.2.6 -> 1.2.7` scope: FLIR display tuning Context The initial FLIR renderer used a fixed color transfer. Images with a narrow or skewed temperature distribution could therefore hide useful contrast even when the numeric range was correct. CSC voltage/current CSV output also needed clearer cycle organization for direct downstream use. Decision and rationale Separate the displayed color mapping from the temperature values. Apply gamma only while rendering the normalized image and expose it as a user control; keep the temperature matrix, point readings, ROI means, and exports numerically unchanged. Refine CSC CSV structure at its result writer rather than in the UI. Changes `labkit_FLIRThermal_app` `1.2.4 -> 1.2.7` `labkit_CSC_app` `1.3.6 -> 1.3.7` Refined CSC CV export. Added FLIR gamma color mapping and made gamma adjustable. User and data impact FLIR users could reveal contrast in hotter or cooler parts of an image without changing the underlying temperatures. The same gamma was used in displayed and exported color renderings. CSC users received a more directly usable voltage/current table. Compatibility and migration Existing FLIR inputs remained valid, and gamma affected rendered color only. Temperature matrices and measurement values did not require conversion. Validation The commits extended CSC export assertions, FLIR rendering and output tests, and the FLIR layout test for the gamma control. Exact historical commands were not recorded. Evidence Main commits `ee5b8f79`, `65dbf5ae`, and `f076561e`. Known limitations and follow-up Gamma changes visual contrast only. Quantitative interpretation must use the temperature values and scale, not colors sampled from the rendered image."},{"title":"Facade contract baseline and release validation hardening","url":"history/records/2026/06/LK-20260623-facade-contract-baseline-and-release-validation-hardening.html","kind":"history","section":"history","keywords":"LK-20260623-facade-contract-baseline-and-release-validation-hardening 2026-06-23 11 ci compatible labkit.biosignal labkit.dta labkit.rhs labkit.ui labkit.ui labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_CurvatureMeasurement_app Facade contract baseline and release validation hardening","text":"Facade contract baseline and release validation hardening id: LK-20260623-facade-contract-baseline-and-release-validation-hardening date: 2026-06-23 sequence: 11 type: ci compatibility: compatible component: `labkit.biosignal` | `new -> 1.0.0` component: `labkit.dta` | `new -> 1.0.0` component: `labkit.rhs` | `new -> 1.0.0` component: `labkit.ui` | `new -> 2.0.0` component: `labkit.ui` | `2.0.0 -> 2.1.0` component: `labkit.ui` | `2.2.0 -> 2.2.1` component: `labkit_DICPostprocess_app` | `1.0.0 -> 1.0.1` component: `labkit_DICPreprocess_app` | `1.0.0 -> 1.0.1` component: `labkit_CurvatureMeasurement_app` | `1.0.0 -> 1.0.1` scope: Facade contract baseline and release validation hardening Context Reusable packages exposed public MATLAB functions, but apps had no machine-checkable way to state which API versions they required. Release and CI checks also needed one consistent build-task entry path. Decision and rationale Give each reusable package version metadata and let apps declare compatible ranges that are checked at launch and in tests. Route MATLAB CI shards through the same build tasks used by maintainers so release validation exercises the documented commands. Changes `labkit.biosignal` `1.0.0` `labkit.dta` `1.0.0` `labkit.rhs` `1.0.0` `labkit.ui` `2.0.0 -> 2.2.1` DIC Pre/Post and Curvature `1.0.0 -> 1.0.1` Release tags `v2.4.1` and `v2.4.2` Added facade contract metadata and requirement checks. Hardened app lifecycle and release validation contracts. Routed MATLAB CI shards through build tasks. User and data impact An incompatible app/package combination could fail with a direct requirement message instead of producing a later missing-function error. Existing project and result data were unaffected. Compatibility and migration The contract metadata described the existing public facades and was additive for app callers. Apps with an incompatible declared requirement now failed at launch with a version diagnostic instead of continuing unpredictably. Validation The listed commits introduced contract, lifecycle, and CI build-task tests. Release tags `v2.4.1` and `v2.4.2` identify the shipped checkpoints; exact local commands were not recorded. Evidence Main commits `a25b79f9`, `3673e548`, `49d9f41b`, and `7e39b558`. Known limitations and follow-up App and launcher display versions were added in the separate version-metadata change later the same day."},{"title":"Figure Studio adopts one product definition","url":"history/records/2026/07/LK-20260716-figure-studio-single-definition.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-single-definition 2026-07-16 79 refactor compatible labkit_FigureStudio_app labkit.ui LabKit Core App structure","text":"Figure Studio adopts one product definition id: LK-20260716-figure-studio-single-definition date: 2026-07-16 sequence: 79 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.1 -> 0.2.2` component: `labkit.ui` | `7.2.0 -> 7.2.1` scope: LabKit Core scope: App structure Context Figure Studio declared its command, names, family, App version, and LabKit requirement in two files separate from the runtime definition. Its entrypoint joined all three factories even though they described one product. Decision and rationale Use Figure Studio as the first reviewed App migration to the single-definition contract. This validates the reduced structure on an App that also accepts a typed axes handoff, without changing its plotting or export behavior. Changes Moved exact product metadata and the UI requirement into `definition.m`. Reduced the public entrypoint to one definition plus its existing typed request adapter. Removed the redundant `requirements.m` and `version.m` files. Taught the version guard to use `AppVersion` in a migrated definition while comparing the first migration against the previous `version.m` baseline. Fixed the shared source reconciler so it creates the canonical empty source array without first constructing an invalid empty-ID placeholder record. Added `services.results.emptyOutputs()` and used it for Figure Studio's variable-length export manifest instead of constructing an invalid empty-ID placeholder output. User and data impact Launch, axes handoff, style controls, project payload, FIG reading, plot snapshot extraction, and all export formats retain their existing behavior. Maintainers now update one product contract instead of three files. Compatibility and migration The App command and project ID are unchanged. App version metadata requests still return the same fields through the public entrypoint. Existing project payloads remain at version 1 and require no data migration. Validation Focused tests cover ordinary launch, axes handoff, style changes, project round-trip, exports, launcher discovery, requirements/version requests, and version/history governance. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) documents the unchanged workflow and new definition ownership. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the shared product contract. Known limitations and follow-up Figure Studio still has a meaningful version-1 project schema, rebuildable plot session cache, and startup resource installation. These will move to the projectSpec/action capability model after the framework supports that model; they were not deleted merely to reduce file count."},{"title":"Figure Studio consolidates its project schema","url":"history/records/2026/07/LK-20260716-figure-studio-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-project-spec 2026-07-16 82 refactor compatible labkit_FigureStudio_app LabKit Core Project lifecycle","text":"Figure Studio consolidates its project schema id: LK-20260716-figure-studio-project-spec date: 2026-07-16 sequence: 82 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.2 -> 0.2.3` scope: LabKit Core scope: Project lifecycle Context Figure Studio's version-1 project creation and validation lived in separate files under a generic `+appLifecycle` package. The package name described a framework phase rather than the durable capability those functions owned. Decision and rationale Consolidate the complete durable schema behind `projectSpec.m`. Keep `createSession.m` separate at the App package root because it performs a different job: rebuilding transient selection and decoded plot cache from a validated project. Changes Added one project declaration with local create and validate functions. Moved session reconstruction to one explicitly named package-root entry. Removed the three-file generic `+appLifecycle` package. Left meaningful startup, action, presentation, source, style, and export capabilities unchanged. User and data impact Figure loading, axes handoff, styling, project save/load, session restoration, and exports behave unchanged. A maintainer can now understand the entire durable schema in one file. Compatibility and migration The App command, project ID, payload version, fields, validation, and source record format are unchanged. Existing Figure Studio projects need no migration. Validation The existing unit and hidden GUI suites cover FIG reading, source style, project round-trip, axes handoff, canvas resizing, quick export, and data package export through the consolidated project declaration. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The startup hook still owns axes handoff and resize-resource installation. It will be evaluated with other App startup hooks before any shared mount/action capability is introduced."},{"title":"Figure Studio derives its publication preset from the nine-panel reference","url":"history/records/2026/07/LK-20260721-figure-studio-nine-panel-calibration.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-nine-panel-calibration 2026-07-21 155 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio derives its publication preset from the nine-panel reference id: LK-20260721-figure-studio-nine-panel-calibration date: 2026-07-21 sequence: 155 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.1 -> 0.6.4` scope: LabKit Core Context The previous Figure Studio publication profile was described as a visual calibration even though its dimensions had been taken from a separate manual FIG example. It also allowed native figure chrome to reduce the exported drawable height. Decision and rationale Use only the maintained 3-by-3 scientific reference. Normalize every panel by its detected axes frame, measure typography and strokes in pixels, and select one robust profile from all nine panels. Keep those measurements app-local and size the chromeless export client area directly so the configured inner frame survives raster and vector export. Changes Recalibrated the reference plot frame, typography, semantic strokes, frame, grid, legend border, and legend token length from panels A through I. Set the data stroke to 6.5 pt after registered raster checks matched panel A exactly and reduced the remaining one-pixel deficits in panels E and H. Removed menu and toolbar chrome from temporary export figures and preserved the complete figure bounds through `exportgraphics`. Ignored zero-area text extents when computing outer margins so native log rulers cannot inflate an export canvas to tens of thousands of pixels. Added the calibrated 900 px plot width to the selectable sizes and made it the new-project default. Retained source token geometry for **FIG default** while applying the measured long token only to the LabKit preset. Replaced dynamic-array analyzer suppressions with bounded collection and documented the 72-to-96-PPI range used by cross-platform visual metrics. User and data impact New LabKit-style projects open at the nine-panel reference scale. Existing scientific values, limits, source graphics, FIG files, and saved projects are not recalculated. Exports retain the requested inner-frame aspect instead of losing native-window height. Compatibility and migration The style record remains extensible and existing projects stay readable. Switching an existing document to **LabKit figure** opts into the new profile; **FIG default** continues to use source presentation values. Validation Pixel-frame, OCR glyph-height, colored-stroke, and registered-overlay checks against all nine reference panels. Focused Figure Studio result-file and project tests. Hidden-GUI Figure Studio workflow tests. Native logarithmic-axis margin regression. Linux-compatible visual-ratio and analyzer-policy regressions. Repository `changedFast` validation. Evidence After width registration, the nine detected inner-frame height errors range from -1.71 to +2.16 pixels; panel A is exact. Shared OCR glyph heights have a median scale error below one percent. Reference and generated comparison rasters remain untracked validation artifacts. Known limitations and follow-up Raster antialiasing varies slightly by renderer and platform. Exact scientific curve coordinates are source data and are not part of the style preset."},{"title":"Figure Studio edits one native subplot panel at a time","url":"history/records/2026/07/LK-20260721-figure-studio-native-panel-editing.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-native-panel-editing 2026-07-21 153 feat compatible labkit_FigureStudio_app Figure Studio","text":"Figure Studio edits one native subplot panel at a time id: LK-20260721-figure-studio-native-panel-editing date: 2026-07-21 sequence: 153 type: feat compatibility: compatible component: `labkit_FigureStudio_app` | `0.4.0 -> 0.6.0` scope: Figure Studio Context Figure Studio reconstructed a limited portable snapshot from the first axes in a FIG. This could omit composite graphics such as traditional `boxplot` groups, gave no reliable way to choose a subplot from a mixed figure, and made export canvas controls look like live plot-resize controls. Decision and rationale Treat an opened FIG as a transient native document resource and select one axes from it by an ordered subplot panel choice. The selected axes remains the authoritative hierarchy for preview and editable/image export; the portable snapshot remains an explicit secondary data-package representation. This gives ordinary MATLAB charts a broader preservation path without promising that an arbitrary custom graphics class is portable. Use a fixed, visually calibrated single-panel default (1600 by 1333, 6:5). The workbench now displays a raster rendering of that real export canvas, fit as one image into the available workspace. Resizing the window never feeds back into style sizing; supersampling remains export-only. Provide an explicit automatic X/Y-limit recovery action instead of trying to infer limits during unrelated style changes. Changes Added ordered mixed-FIG subplot-panel selection; preview, styling, limits, and exports all operate on the selected axes only. Added private native-source cloning for popout handoff and FIG import, with cleanup through the existing document-resource lifecycle. Preserved native child hierarchies in preview and editable FIG export, including traditional grouped `boxplot` graphics and hidden-handle visual children. Preserved source tick labels and tick configuration when copying native axes. Replaced free-form preview resizing with source/fixed export-size choices, aspect choices, export supersampling, and a stable true-export preview. Added **Recalculate X/Y limits** and project persistence for explicit limit overrides. Migrated project schema 3 to remember the selected panel index. User and data impact Existing projects migrate automatically. A saved mixed FIG project restores the same selected panel when its source is available. Popout projects retain a portable snapshot after reload; their private native source is never saved. No scientific calculation, source file, or source axes is modified. Compatibility and migration Schema 1--3 projects migrate to schema 4. Schema 3 adds `panelIndex = 1`; earlier migrations retain their existing style and limit conversions. Validation Project-schema tests cover migrations through schema 4. Hidden-GUI tests cover four-panel FIG selection, native boxplot preservation for FIG import and popout handoff, export, limit recovery, and a stable export-canvas preview through workspace resizing. Focused visual review checks a generated single-panel scientific probe at the default 1600-by-1333 reference size. Evidence The focused test artifacts and generated local visual probe are supporting evidence. No external laboratory data was used. Known limitations and follow-up Developer-led visual review remains necessary for unusual custom charts, native dialogs, and publication-specific annotation overlap. Portable data packages intentionally support fewer object types than native FIG export."},{"title":"Figure Studio fixes publication-frame preview and axis controls","url":"history/records/2026/07/LK-20260721-figure-studio-plot-frame-preview.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-plot-frame-preview 2026-07-21 154 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio fixes publication-frame preview and axis controls id: LK-20260721-figure-studio-plot-frame-preview date: 2026-07-21 sequence: 154 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.0 -> 0.6.1` scope: LabKit Core Context Figure Studio had treated a requested output size as a whole figure canvas. That made labels and titles consume the configured plot area, and resizing the preview could make the displayed typography unrelated to the eventual export. Copied UIAxes also needed a portable reconstruction path for reliable export. Decision and rationale Treat the requested width and aspect as the inner axes frame. Keep all publication-style calibration within Figure Studio, then calculate an outer figure from the live rendered text extents so visual styling cannot reduce the configured data area. Changes Defined the plot width and aspect controls as the inner axes frame; export now calculates an outer figure around that fixed frame from visible labels, ticks, titles, legends, and annotations. Calibrated the LabKit publication preset from the maintained single-panel visual measurements, including category-specific typography and strokes. Kept the preview as an interactive axes, reflowing its display scale when its allocated area changes without changing saved/export settings. Added explicit X/Y minimum and maximum controls, plus visible-data recalculation with a finite-data envelope expanded by 50 percent. Rebuilt UIAxes sources through the portable renderer for conventional export, retaining displayed scientific axis exponents. User and data impact Existing Figure Studio projects remain compatible. Existing style sizes retain their meaning as a plot frame; the enclosing export figure may grow to avoid clipping external text. No scientific calculations or source figure data are changed. Compatibility and migration The project schema migrates existing style records without changing their stored data. UIAxes exports use the portable display snapshot only for the screen-specific aspect geometry that cannot be transferred to normal axes. Validation Focused Figure Studio result-file and source-axes tests. Hidden-GUI Figure Studio layout tests, including UIAxes export and preview reflow behavior. Pixel-scale visual metrics for the calibrated single-panel output. Evidence The calibrated profile fixes the inner frame at 1237 by 942 pixels. Its generated output reports that same axes size while the enclosing figure grows only for required outer text and annotations. Known limitations and follow-up Manual review remains appropriate for native MATLAB graphics that cannot be represented by the portable package."},{"title":"Figure Studio names its post-layout initialization capability","url":"history/records/2026/07/LK-20260716-figure-studio-initializer.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-initializer 2026-07-16 109 refactor compatible labkit_FigureStudio_app LabKit Core App structure","text":"Figure Studio names its post-layout initialization capability id: LK-20260716-figure-studio-initializer date: 2026-07-16 sequence: 109 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.4 -> 0.2.5` scope: LabKit Core scope: App structure Context Figure Studio was the only public App with a generic `startup.m` file. Its definition already declared the function through Runtime V2's optional `Start` capability, but the filename obscured who invoked it and made the function look like an independent lifecycle entrypoint. Decision and rationale Keep the Runtime-owned `Start` hook because Figure Studio genuinely needs a post-layout phase: it consumes an optional axes handoff, resolves the managed preview axes, and registers a cleanup-owned resize resource. Rename the App callback to `initializeWorkbench` so its capability and invocation boundary are explicit. Do not move GUI/service work into the GUI-free session factory or reintroduce App-owned timers and readiness state. Changes Renamed the Start callback from generic `startup` to `initializeWorkbench` and declared the new handle in `definition`. Documented the callback timing and the request, preview, resource, dialog, workflow, and debug services supplied by Runtime V2. Tightened the App structure guardrail so compact Apps cannot add a generic `startup.m` file outside their declared capability vocabulary. Advanced the Figure Studio App version to 0.2.5. User and data impact Normal launch, default output folder selection, popout axes handoff, source style adoption, preview canvas resize behavior, debug logging, project data, and exports are unchanged. No project migration is required. Compatibility and migration The public command and Runtime definition contract are unchanged. `startup` was an App-internal callback referenced only by the definition; callers should launch the App rather than invoke either callback directly. Validation The Figure Studio hidden GUI suite covers ordinary launch, style controls, FIG loading, preview resize, quick/package export, axes handoff, project save/load, stable canvas sizing, and popout-to-Studio transfer. It also asserts that the definition exposes the semantic initializer handle. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up The Start capability remains necessary for post-layout resources; removing it would require a different framework service phase rather than a file move. App and agent guidance still need a repository-wide terminology audit."},{"title":"Figure Studio preserves common graphics and styles semantic categories","url":"history/records/2026/07/LK-20260720-figure-studio-semantic-restyling.html","kind":"history","section":"history","keywords":"LK-20260720-figure-studio-semantic-restyling 2026-07-20 145 feat compatible labkit_FigureStudio_app labkit.app Figure Studio App Framework GUI validation Test runner","text":"Figure Studio preserves common graphics and styles semantic categories id: LK-20260720-figure-studio-semantic-restyling date: 2026-07-20 sequence: 145 type: feat compatibility: compatible component: `labkit_FigureStudio_app` | `0.3.1 -> 0.4.0` component: `labkit.app` | `1.2.0 -> 1.2.1` scope: Figure Studio scope: App Framework GUI validation scope: Test runner Context Figures sent from Apps, reopened from FIG files, or recreated from Figure Studio data packages could omit common composite graphics such as bars, error bars, areas, and rectangles. Legends were reconstructed only from display names, and the preset exposed one broad line-width control. This made restyling incomplete and could enlarge unrelated text enough to overlap data. Separately, the official hidden-GUI policy was applied while native App windows were constructed but the final show operation could make direct-entrypoint tests visible. Decision and rationale Preserve the supported visible plot structure before applying a presentation preset. Style text and strokes by semantic role so users can establish a coherent baseline and then refine categories that have different visual weights. A preset may style an existing legend, but it retains source placement by default because moving or creating a legend can cover data. Keep GUI-test visibility in the private native adapter. App authors and test methods should not maintain a second hiding convention, and no new public API is needed for a framework-owned launch consistency rule. Keep broad tests in one MATLAB process. A complete local comparison showed that three workers increased both wall-clock and CPU time, so portability work around child-process orchestration would preserve cost without user benefit. Changes Added bar, error-bar, area, and display-only rectangle extraction and reconstruction for previews, FIG imports, and portable recreation scripts. Preserved constant-line labels without interpreting scientific text as a MATLAB line specification. Preserved existing legend text, visibility, placement, orientation, columns, font, interpreter, and border through every supported reconstruction path. Preserved explicit axis ticks, tick labels, rotations, locations, and tick geometry instead of asking MATLAB to infer them again during reconstruction. Included visible hidden-handle graphics in snapshots while retaining their exclusion from automatic legend discovery. Made the LabKit preset the initial style for direct plot handoff while retaining the captured source style as **FIG default**. Added separate controls for title, axis-label, tick, annotation, legend, data, uncertainty, graphic-boundary, reference, and axes styling, plus source/horizontal/45-degree X tick labels. Calibrated the LabKit preset against a representative standard Figure: a 720-by-600 reference canvas, 24/20/15 pt semantic text tiers, 1.1/1.2 pt semantic strokes, Helvetica when available, boxed axes and legend, and a 600 dpi export at the default scale. Scaled text and strokes dynamically from each style's reference canvas while compensating for preview fitting, and reduced UI steps to 0.5 pt for fonts, 0.1 pt for strokes, and 10 px for canvas dimensions. Migrated schema 1 projects to the expanded semantic style model while preserving every previously saved style value. Made the final native show operation honor hidden and minimized GUI validation modes. Bound heartbeat work at timer creation so path-isolation contracts cannot remove a method needed by an active progress callback. Removed internal worker planning, partitioning, process launch, shard selectors, and speculative JUnit shard estimates. Local and CI broad tasks now share the same single-process runner. User and data impact Existing schema 1 documents migrate automatically to schema 2, keeping saved values and supplying defaults only for newly introduced style categories. Supported figures retain more visible content, while unsupported MATLAB chart classes continue to produce explicit warnings. Normal App launches remain visible; only official hidden or minimized validation modes change final window presentation. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Result-file tests exercise extraction, FIG reopening, recreation-script execution, legend metadata, constant-line labels, semantic styling, and idempotent preset application. Figure Studio source-axis and hidden-GUI suites cover source defaults, LabKit handoff defaults, the new control surface, preview stability, and popout handoff. Project tests cover schema 1 style migration. Framework GUI coverage verifies that the final show operation retains hidden mode, and a direct DIC Preprocess entrypoint test exercises the full launch path. A same-machine cold-process comparison measured 470 seconds wall time and 876 seconds CPU time for the serial headless gate versus 490 seconds wall time and 1,215 seconds CPU time for three workers. Worker artifacts also confirmed that heartbeat updates survived the isolated-path contract. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led visual review remains responsible for aesthetic judgment, unusual third-party chart classes, native window behavior, and plot-specific overlap after manual legend placement."},{"title":"Figure Studio preserves overlays above source images","url":"history/records/2026/07/LK-20260722-figure-studio-image-overlay-stacking.html","kind":"history","section":"history","keywords":"LK-20260722-figure-studio-image-overlay-stacking 2026-07-22 156 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio preserves overlays above source images id: LK-20260722-figure-studio-image-overlay-stacking date: 2026-07-22 sequence: 156 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.4 -> 0.6.5` scope: LabKit Core Context Figure Studio copied every visible child from native MATLAB axes, but reversed the source child vector before copying it into the interactive preview. Because MATLAB stores axes children from front to back and `copyobj` preserves vector order, an opaque image became the frontmost child and hid line, marker, and text overlays that had been drawn above it. Decision and rationale Pass the native axes child order through unchanged. The source axes remains the authority for stacking, and Figure Studio must not infer a new order from graphics classes or special-case images. Changes Preserved native front-to-back child order when copying an axes into the interactive preview. Added a synthetic image, line, and text stack regression at the renderer boundary. Added a hidden-GUI regression covering both FIG-file import and axes handoff. User and data impact Image-backed plots now show their visible overlays in Figure Studio and in preview-derived exports. Source files, scientific values, coordinates, object styles, and project data are unchanged. Compatibility and migration No project migration is required. Existing projects receive the corrected stacking when their native source is rebuilt. Validation Exact source-axes stacking regression. Hidden-GUI FIG import and axes-handoff stacking regression. Visual inspection of an authorized local diagnostic FIG through both paths; the real file remains untracked and is not a test fixture. Evidence Both real-source paths retained all 12 visible graphics children with the opaque image last and the 11 overlay children in front. Temporary preview rasters confirmed the overlays were visible. Known limitations and follow-up Custom graphics classes still depend on MATLAB supporting their native parent transition. The portable data package remains narrower than the native preview and export path."},{"title":"Figure Studio removes source-reference knowledge","url":"history/records/2026/07/LK-20260716-figure-studio-source-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-source-boundary 2026-07-16 94 refactor compatible labkit_FigureStudio_app LabKit Core Apps Runtime source boundary","text":"Figure Studio removes source-reference knowledge id: LK-20260716-figure-studio-source-boundary date: 2026-07-16 sequence: 94 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.3 -> 0.2.4` scope: LabKit Core Apps scope: Runtime source boundary Context Figure Studio read both the current path and stored filename from the nested portable source reference. It repeated those reads in session reconstruction, actions, file-list presentation, and selection lookup. Decision and rationale Resolve source paths once through Runtime V2 at each workflow boundary. Derive the displayed filename from the resolved path so the App needs no knowledge of any portable-reference field. Changes Migrated FIG session reconstruction, action loading, file presentation, and selection lookup to `labkit.ui.runtime.sourcePaths`. Derived the opened-file status name through `fileparts`. Removed every direct portable-reference field read from Figure Studio. Preserved imported axes data, default style adoption, source order, current selection, editing, and export behavior. User and data impact FIG selection and display behave unchanged. Figure Studio now owns only how it uses a resolved FIG file; Runtime V2 owns how that file remains portable. Compatibility and migration No project or source-record field changed. Existing Figure Studio projects require no migration. Validation Source-axes and result-file unit tests cover import and export semantics. The hidden GUI suite covers empty startup, FIG addition and selection, style presentation, save/load, and output workflows. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The repository-wide portable-reference guard remains deferred until all remaining App families have migrated."},{"title":"File-panel layout stabilization","url":"history/records/2026/06/LK-20260630-file-panel-layout-stabilization.html","kind":"history","section":"history","keywords":"LK-20260630-file-panel-layout-stabilization 2026-06-30 19 fix compatible labkit.ui labkit.ui File-panel layout stabilization","text":"File-panel layout stabilization id: LK-20260630-file-panel-layout-stabilization date: 2026-06-30 sequence: 19 type: fix compatibility: compatible component: `labkit.ui` | `3.2.3 -> 3.2.4` component: `labkit.ui` | `3.2.4 -> 3.2.5` scope: File-panel layout stabilization Context The single-file panel allocated too much height to its rows and could render inconsistently as the containing app resized. This was especially noticeable in file-heavy apps, where the panel competed with analysis controls and plots. Decision and rationale Give the panel explicit, compact row sizing and protect that geometry with GUI layout tests. The change stayed in the shared file-panel builder because the same visual defect appeared wherever the component was used. Changes `labkit.ui` `3.2.3 -> 3.2.5` Stabilized and compacted single file-panel layout. User and data impact File names and actions occupied less vertical space, leaving more room for the workflow below them. The change affected layout only; selections and loaded file data were unchanged. Compatibility and migration App definitions and selected files required no conversion. The shared panel kept the same controls and callbacks with revised row geometry. Validation Both commits extended `GuiLayoutUiDeclarativeAppTest` with the expected panel geometry. Exact historical test commands were not recorded. Evidence Main commits `7f8df1cd` and `02b2f1b6`. Known limitations and follow-up Later runtime generations replaced this private UI 3.x builder, but retained the principle that shared components own and test their responsive geometry."},{"title":"File-panel migration","url":"history/records/2026/06/LK-20260624-file-panel-migration.html","kind":"history","section":"history","keywords":"LK-20260624-file-panel-migration 2026-06-24 12 refactor breaking labkit.dta labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app File-panel migration","text":"File-panel migration id: LK-20260624-file-panel-migration date: 2026-06-24 sequence: 12 type: refactor compatibility: breaking component: `labkit.dta` | `1.0.0 -> 2.0.0` component: `labkit.ui` | `2.2.1 -> 3.0.0` component: `labkit_DICPostprocess_app` | `1.0.1 -> 1.2.0` component: `labkit_DICPreprocess_app` | `1.0.1 -> 1.2.0` component: `labkit_ChronoOverlay_app` | `1.0.0 -> 1.2.0` component: `labkit_CIC_app` | `1.0.0 -> 1.2.0` component: `labkit_CSC_app` | `1.0.0 -> 1.2.0` component: `labkit_EIS_app` | `1.0.0 -> 1.2.0` component: `labkit_VTResistance_app` | `1.0.0 -> 1.2.0` component: `labkit_BatchImageCrop_app` | `1.0.0 -> 1.2.0` component: `labkit_CurvatureMeasurement_app` | `1.0.1 -> 1.2.0` component: `labkit_FocusStack_app` | `1.0.0 -> 1.2.0` component: `labkit_ImageEnhance_app` | `1.0.0 -> 1.2.0` component: `labkit_ImageMatch_app` | `1.0.0 -> 1.2.0` component: `labkit_NerveResponseAnalysis_app` | `1.0.0 -> 1.2.0` component: `labkit_ResponseReviewStats_app` | `1.0.0 -> 1.2.0` component: `labkit_RHSPreview_app` | `1.0.0 -> 1.2.0` component: `labkit_ECGPrint_app` | `1.0.0 -> 1.2.0` scope: File-panel migration Context Apps implemented file selection through separate task-input adapters, which produced inconsistent lists, selection events, and status feedback. The DTA session helper also coupled parsing to that older UI model. Decision and rationale Move supported apps to one reusable file-panel model and make DTA loading a GUI-free file/curve API. Accept the breaking package versions because retaining both task-input and file-panel paths would preserve ambiguous callback behavior. Changes `labkit.dta` `1.0.0 -> 2.0.0` `labkit.ui` `2.2.1 -> 3.0.0` All supported apps moved from `1.0.x` into the `1.2.0` workflow line. Replaced task inputs with file panels. Removed the old DTA session helper surface. User and data impact File lists, current selection, add/remove actions, and status display became consistent across the migrated apps. App code written against the removed task inputs or DTA session helpers required an update. Compatibility and migration This was a breaking workflow migration. Older app code expecting task inputs or the removed DTA session helpers needed migration. Validation Commit `b145c904` migrated app GUI workflows and package compatibility tests to the new file-panel and DTA APIs. Evidence Main commit `b145c904`. Known limitations and follow-up This first file-panel generation was later refined for append behavior, native dialog edge cases, and Runtime V2 events."},{"title":"Focus Stack adopts the compact App contract","url":"history/records/2026/07/LK-20260716-focus-stack-structure.html","kind":"history","section":"history","keywords":"LK-20260716-focus-stack-structure 2026-07-16 95 refactor compatible labkit_FocusStack_app Image Measurement App structure","text":"Focus Stack adopts the compact App contract id: LK-20260716-focus-stack-structure date: 2026-07-16 sequence: 95 type: refactor compatibility: compatible component: `labkit_FocusStack_app` | `1.5.1 -> 1.5.2` scope: Image Measurement scope: App structure Context Focus Stack split static metadata across `requirements.m` and `version.m`, spread a version-1 project over a generic lifecycle package, grouped three calculation concepts under `+appState`, and ran a startup callback only to fill an output path and emit an App-specific debug line. It also duplicated the portable-reference path loop in session, actions, and presentation. Decision and rationale Use the compact Runtime V2 contract: one product definition, one durable project specification, and one explicit session factory. Put calculation result shape, presets, and run fingerprinting beside the computation. Resolve output folders only when file registration or export actually needs them. Changes Consolidated command metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated project creation and validation in `projectSpec.m`. Moved transient image reconstruction to root `createSession.m`. Moved three `+appState` helpers into their owning `+analysisRun` capability. Removed the metadata files, generic lifecycle/state packages, and redundant App startup callback. Replaced all direct portable-reference reads with the Runtime path accessor. User and data impact Focus loading, registration, fusion, previews, duplicate-run detection, project reopen, and exports keep the same scientific behavior. A new App no longer mutates its durable output folder during startup. After inputs are selected, output defaults remain source-adjacent as before. The App package loses six structural files and one generic package while its real workflow capabilities become easier to locate. Compatibility and migration The durable payload remains version 1 with identical fields, defaults, and validation. Existing projects require no migration. The removed startup debug line is replaced by the Runtime's standard debug-startup message. Validation Unit tests cover the definition/project/session contract, numerical fusion, registration, deterministic run fingerprints, summaries, readers, and invalid inputs. The hidden GUI test covers debug startup, file and folder import, fusion, presentation, export manifest, save/load reconstruction, and append. Evidence [Focus Stack](../../../../apps/image-measurement/focus-stack/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The remaining Image Measurement Apps still use the older structural packages and will be reviewed individually rather than copied from this App blindly."},{"title":"Framework guides and function reference keep distinct routes","url":"history/records/2026/07/LK-20260717-framework-guide-and-function-routes.html","kind":"history","section":"history","keywords":"LK-20260717-framework-guide-and-function-routes 2026-07-17 134 fix compatible Documentation information architecture","text":"Framework guides and function reference keep distinct routes id: LK-20260717-framework-guide-and-function-routes date: 2026-07-17 sequence: 134 type: fix compatibility: compatible scope: Documentation information architecture Context An initial Framework documentation alignment also classified individual `labkit.ui.*` and `labkit.contract.*` API pages as Framework pages. Entering those pages from the global Functions index then changed the active top-level area, which mixed conceptual ownership with page type and made return paths hard to predict. Decision and rationale Keep conceptual and workflow guides under Framework. Keep every exact MATLAB function contract under Functions. Visible API group labels still explain that `labkit.ui` is the Framework implementation, but the top navigation remains stable while browsing function reference pages. Changes Moved the canonical compatibility guide to `framework/contracts.html`. Kept a tracked legacy page at `libraries/contracts/index.html` that links to the new guide and the Functions reference. Restored Functions ownership for all individual UI and contract API pages. Updated framework, library, and regression-test links to the canonical guide. User and data impact Readers now follow one predictable route for Framework guides and another for exact function syntax. Existing contract-guide bookmarks still reach a compatibility page. MATLAB code and scientific data are unchanged. Compatibility and migration The old guide URL remains as a compatibility landing page. Public MATLAB symbols remain unchanged. Validation Renderer regression verifies the Framework guide route, legacy landing page, and Functions-active runtime API page. `docsCheck` verifies the complete site. Evidence The generated Framework landing page links directly to `framework/contracts.html`, while `labkit.ui.runtime.launch` highlights Functions and labels its sibling group Framework Runtime. Known limitations and follow-up The legacy page is a visible handoff rather than an HTTP redirect because the tracked static-site renderer does not currently emit redirect responses."},{"title":"Framework owns UI and compatibility documentation","url":"history/records/2026/07/LK-20260717-framework-documentation-ownership.html","kind":"history","section":"history","keywords":"LK-20260717-framework-documentation-ownership 2026-07-17 133 docs compatible App Framework documentation","text":"Framework owns UI and compatibility documentation id: LK-20260717-framework-documentation-ownership date: 2026-07-17 sequence: 133 type: docs compatibility: compatible scope: App Framework documentation Context The documentation described `labkit.ui` as the App Framework but presented its public API and the compatibility contracts through the generic Functions area. The visible information architecture therefore disagreed with the framework terminology used by app development guides. Decision and rationale Make Framework the documentation owner for `labkit.ui.*` and `labkit.contract.*`. Compatibility requirements belong in the Framework because App definitions and startup consume them. Keep the stable `labkit.contract` MATLAB namespace because it checks UI and domain facades alike; moving it below `labkit.ui` would create a misleading dependency and break existing App definitions. Changes Added compatibility contracts as a Framework sidebar branch. Classified UI and contract API pages under the Framework top navigation. Replaced raw UI package headings with visible Framework API labels while retaining fully qualified MATLAB symbols. Removed the separate Contracts entry from the Functions guide table. User and data impact Readers now enter framework runtime, layout, plotting, interaction, debugging, and compatibility material through one consistent Framework route. MATLAB calls, saved projects, scientific results, and URLs remain compatible. Compatibility and migration No code migration is required. Existing `labkit.ui.*` and `labkit.contract.*` symbols are unchanged. Validation Renderer regression tests verify Framework ownership for the contracts guide and a runtime API page. `docsCheck` rebuilds and compares the complete site. Evidence Generated UI and contract pages highlight Framework in the product navigation, and the Framework landing sidebar links the compatibility guide. Known limitations and follow-up The global API reference still indexes all public symbols together so exact MATLAB functions remain searchable from one place."},{"title":"Framework public APIs become an explicit last resort","url":"history/records/2026/07/LK-20260720-public-api-escalation-order.html","kind":"history","section":"history","keywords":"LK-20260720-public-api-escalation-order 2026-07-20 144 docs compatible Architecture guidance Agent skills","text":"Framework public APIs become an explicit last resort id: LK-20260720-public-api-escalation-order date: 2026-07-20 sequence: 144 type: docs compatibility: compatible scope: Architecture guidance scope: Agent skills Context A convenient framework helper can appear attractive for one App-specific need, but every public name becomes a long-lived compatibility and documentation commitment. Conversely, forcing unrelated behavior into an existing API can turn a focused contract into an ambiguous option bucket. Decision and rationale Use a consistent escalation order for new App behavior: keep it App-local, extend an existing focused API when the addition is natural, add private framework/runtime capability when shared implementation does not require an App-authored contract, and add a new public API only for stable use by multiple Apps or when the nearest existing API would otherwise lose a clear single purpose. This makes a new public API the final solution without prohibiting one when a genuine reusable boundary has emerged. Changes Added the escalation order to the root, framework, and App agent contracts. Updated the App builder and boundary guard skills to require evidence for multiple consumers or an anti-bucket clarity need before introducing a new public name. Updated the documentation maintainer skill to distinguish documentation of an existing focused extension from review of a genuinely new public surface. User and data impact Runtime behavior, public signatures, App calculations, projects, and exports are unchanged. The rule affects future design choices and review evidence. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Skill package validation covers every edited skill. Documentation history rendering and consistency checks cover this record. Repository searches confirm the escalation order appears at the three ownership layers and in the relevant skills. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up When a proposed public API crosses the stated threshold, record its consumers, ownership rationale, alternatives considered, and compatibility contract in the component history."},{"title":"Gait Analysis active-swing workflow","url":"history/records/2026/07/LK-20260716-gait-analysis-active-swing-workflow.html","kind":"history","section":"history","keywords":"LK-20260716-gait-analysis-active-swing-workflow 2026-07-16 71 feat breaking labkit_GaitAnalysis_app Video Marker input contract treadmill swing segmentation gait visualization and exports","text":"Gait Analysis active-swing workflow id: LK-20260716-gait-analysis-active-swing-workflow date: 2026-07-16 sequence: 71 type: feat compatibility: breaking component: `labkit_GaitAnalysis_app` | `1.1.1 -> 2.0.0` scope: Video Marker input contract scope: treadmill swing segmentation scope: gait visualization and exports Context The migrated Gait app accepted loosely shaped coordinate files but did not recover the legacy workflow's step segmentation, per-step skeleton report, or complete translation and angle measurements. A coordinate file alone also could not prove its frame rate, skeleton order, or calibration. Decision and rationale Treat a current Video Marker payload-version-2 project or autosave as the sole file input. It is the first durable artifact that owns coordinates, timing, skeleton, calibration, and annotation provenance together. Segment the legacy treadmill active swing from a prominent foot-X maximum (lift-off) to the following minimum (landing), including a final completed swing that has no later lift-off. Changes Load only the named `labkitProject` variable and reject generic tables, arbitrary MAT variables, legacy marker payloads, and missing frame rate. Show all overlaid skeleton trajectories immediately after import. Detect active swings with app-owned prominence, peak-height, and temporal separation logic; compute cycle/stance measures when a following lift-off exists. Present one selected swing with connected landmarks, joint/segment traces, five endpoint translations, and joint minimum, maximum, and range of motion. Export per-frame, coordinate, step, summary, and provenance tables with lift-off/landing and `swing_time_s` terminology. Migrate version-1 Gait option names and invalidate its scientifically incompatible cached result. User and data impact Users first inspect all trajectories, then run analysis and move between segmented swings. Existing Gait project settings are migrated, but old cached results are recalculated. Older or generic pose files must be converted by opening and saving the source in current Video Marker. Compatibility and migration This is an intentional input and output-schema break. The durable project migration preserves equivalent thresholds while renaming stride/contact-era fields to active-swing terms. Video Marker project payload version 2 is required. Validation Focused unit tests cover source metadata recovery, rejection of incomplete and legacy MAT files, lift-off/landing pairs, retention of the final completed swing, migration of old option names, scientific table fields, and exports. The focused GUI test covers trajectory orientation, analysis, step preview, export, and project reopen. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) documents the source, calculation, visualization, and export contracts. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the durable payload used as input. Known limitations and follow-up The detector represents image-kinematic treadmill events, not force-plate contact. Frame annotation status is preserved but is not yet an automatic step exclusion rule."},{"title":"Gait Analysis app","url":"history/records/2026/07/LK-20260714-gait-analysis-app.html","kind":"history","section":"history","keywords":"LK-20260714-gait-analysis-app 2026-07-14 55 feat compatible labkit_GaitAnalysis_app Gait Analysis app","text":"Gait Analysis app id: LK-20260714-gait-analysis-app date: 2026-07-14 sequence: 55 type: feat compatibility: compatible component: `labkit_GaitAnalysis_app` | `new -> 1.0.0` scope: Gait Analysis app Context Existing gait work used a script chain after pose tracking: import coordinates, smooth marker traces, make per-step figures, compute step and joint metrics, select useful steps, and export tables for downstream statistics. That work belongs in its own app family because the downstream task is not image annotation, signal import, or electrochemistry; it is gait-specific pose analysis from already tracked coordinates. Decision and rationale Add an independent Gait Analysis app instead of extending Video Marker or recreating a model-training workflow. The app accepts several coordinate-table shapes, keeps gait event detection and metric definitions app-local, and exports simple CSV tables that can be consumed by plotting or statistical programs. Changes Added `labkit_GaitAnalysis_app` under the new Gait family. Added CSV/TSV/TXT and MAT pose-coordinate import, including generic `point_x`/`point_y` and LabKit `point__x`/`point__y` column shapes. Added smoothing, foot-relative step-event detection, hip/knee/ankle angle calculation, segment lengths, per-step translations, stride length, step time, ROM, summary metrics, and trajectory/angle/step-event previews. Added CSV set export for frame metrics, step metrics, summary metrics, and per-frame coordinates that keep raw pixel columns alongside optional scale-calibrated and first-frame-origin-shifted columns. User and data impact Users can analyze gait from multiple pose-coordinate sources without tying the workflow to a specific tracking model. The exported tables are plain CSV and separate frame-level, coordinate, step-level, and summary data for downstream overlay, plotting, or statistics. Compatibility and migration The app is additive and does not change Video Marker, image measurement apps, public `+labkit` facades, or existing exports. Existing script outputs can be imported when they provide wide coordinate columns or MAT `coords` and `pointNames`. Validation `GaitAnalysisTest` covers coordinate import, synthetic step detection, metric tables, MAT import, coordinate export calibration/origin semantics, coordinate CSV readback, and CSV set export. `GuiLayoutGaitAnalysisTest` covers the hidden GUI launch and semantic control contract. These tests were added with the app in commit `49863964`. Evidence Initial Gait Analysis app `49863964`. Known limitations and follow-up The first version analyzes one tracked subject at a time and does not perform tracking, model training, group-level statistics, EMG/CAP synchronization, multi-limb phase analysis, or automatic step quality classification."},{"title":"Gait Analysis keeps schema history in one project contract","url":"history/records/2026/07/LK-20260716-gait-analysis-structure.html","kind":"history","section":"history","keywords":"LK-20260716-gait-analysis-structure 2026-07-16 104 refactor compatible labkit_GaitAnalysis_app Gait App structure","text":"Gait Analysis keeps schema history in one project contract id: LK-20260716-gait-analysis-structure date: 2026-07-16 sequence: 104 type: refactor compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.3 -> 2.0.4` scope: Gait scope: App structure Context Gait Analysis spread creation, validation, session reconstruction, and two ordered schema upgrades through a generic lifecycle package. Its generic state package actually contained four analysis concepts: defaults, source-derived options, result construction, and duplicate-run fingerprints. Session and presentation code also inspected the Runtime's nested source-reference fields. Decision and rationale Concentrate project history behind one `projectSpec.m`, keep transient pose reconstruction in root `createSession.m`, and assign all former state helpers to `+analysisRun`. This preserves the real complexity—Video Marker parsing, step segmentation, kinematics, QC, and exports—while removing structural categories that did not explain the workflow. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderer, and debug capability in `definition.m`. Replaced two public migration files with one migration callback that selects the version-1 or version-2 transformation from `fromVersion`. Moved default options, pose-derived option resolution, empty results, and deterministic task fingerprints from `+appState` to `+analysisRun`. Replaced direct portable-reference access with semantic `sourcePaths` lookup in session reconstruction and presentation. Removed generic lifecycle/state packages and separate requirement/version files, and updated GUI-free documentation calls to the owning package. User and data impact Current Video Marker MAT loading, full-trajectory inspection, active-swing segmentation, one-step review, joint angles, translations, timing, QC, duplicate-run detection, CSV export, project save, and project reopen retain their behavior. The image-coordinate Y direction and time-series coordinate direction remain deliberately distinct. Developers can now follow project history through one entry and find all analysis policy under the package that owns the scientific workflow. Compatibility and migration Durable schema version 3 is unchanged. Version-1 option renames and result invalidation still run before the version-2 source collection upgrade. Runtime V2 now owns the loop and validates the resulting current payload. Validation Focused tests cover current Video Marker input, timing/scale/role extraction, active-swing segmentation, final-swing retention, gait parameters, CSV round-trip, and both historical migrations. The hidden GUI workflow covers MAT import, trajectory preview, analysis, export, project save, and decoded pose reconstruction after reopen. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not establish scientific validity for recordings outside the documented treadmill model or replace manual inspection of tracked points and segmented events. Neurophysiology and wearable Apps with generic lifecycle packages remain scheduled for the same review."},{"title":"Gait consumes the Video Marker file contract without a sibling App dependency","url":"history/records/2026/07/LK-20260717-gait-app-path-isolation.html","kind":"history","section":"history","keywords":"LK-20260717-gait-app-path-isolation 2026-07-17 124 fix compatible labkit_GaitAnalysis_app Gait App packaging Video Marker file contract","text":"Gait consumes the Video Marker file contract without a sibling App dependency id: LK-20260717-gait-app-path-isolation date: 2026-07-17 sequence: 124 type: fix compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.7 -> 2.0.8` scope: Gait scope: App packaging scope: Video Marker file contract Context Gait Analysis correctly parsed saved Video Marker MAT documents in production, but its synthetic debug writer created those documents by calling `video_marker.projectSpec`, `video_marker.skeletonDefinition`, and `video_marker.frameAnnotations`. Tests added every App root to the MATLAB path, while the launcher adds only the selected App root. Debug startup could therefore pass tests and fail in a real isolated launch. Decision and rationale Treat the saved `labkitProject` payload as the boundary between the producer and consumer. Gait owns its parser, diagnostics, and synthetic consumer fixture. Video Marker owns serialization and schema validation. One explicit integration test may load both Apps to prove their current file contracts agree, but neither Gait production nor debug code may call the Video Marker package. Changes Rebuilt the Gait debug sample from the documented payload fields without loading Video Marker source code. Split consumer parser tests from a producer-consumer integration test that constructs a current Video Marker project, validates it, saves it, and reads it through Gait. Added an isolated-path debug test using only the repository root and Gait App root. Added a repository guardrail that rejects executable calls from one App package into a sibling App package while allowing documentation references and integration tests. Documented the saved-data boundary in the App development guide and Gait manual. User and data impact Gait debug startup and its packaged App behavior no longer depend on every public App being present on the MATLAB path. Users continue to open the same current Video Marker project and autosave MAT files; parsing, timing, calibration, gait calculations, and exports are unchanged. Developers receive an immediate guardrail failure if a shared test path hides a new sibling-App runtime dependency. Compatibility and migration No project or output migration is required. Video Marker payload version 2 remains the accepted input contract. Validation Focused validation covers isolated debug sample generation, consumer parsing, current Video Marker producer-to-Gait import, Gait calculations and hidden GUI workflow, and the App package boundary guardrail. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [App Development](../../../../development/build-apps/app-development.md#cross-app-data-contracts) Known limitations and follow-up The integration test protects the current saved schema, not scientific validity of manually marked trajectories. Those data and workflow checks remain App-owned."},{"title":"Gait trajectory image-coordinate preview","url":"history/records/2026/07/LK-20260716-gait-image-coordinates.html","kind":"history","section":"history","keywords":"LK-20260716-gait-image-coordinates 2026-07-16 67 fix compatible labkit_GaitAnalysis_app trajectory preview","text":"Gait trajectory image-coordinate preview id: LK-20260716-gait-image-coordinates date: 2026-07-16 sequence: 67 type: fix compatibility: compatible component: `labkit_GaitAnalysis_app` | `1.1.0 -> 1.1.1` scope: trajectory preview Context Marker coordinates use the image convention with an upper-left origin and Y increasing downward. Gait Analysis plotted those values on MATLAB's default Cartesian axes, so the trajectory preview appeared vertically flipped relative to the source video. Decision and rationale Preserve the imported coordinate values and scientific calculations. Reverse only the trajectory preview's Y axis so its visual orientation matches Video Marker and Image Marker. Time-series angle and step plots explicitly retain the conventional upward Y direction. Changes Render trajectory previews with a reversed Y axis. Restore the normal Y direction when switching to Angles or Steps. Document the preview coordinate convention. User and data impact Tracked motion now appears in the same orientation as the source image. Stored coordinates, calculations, tables, and exports are unchanged. Compatibility and migration The change is display-only and requires no project or data migration. Validation The hidden Gait Analysis workflow test checks both trajectory and time-series axis directions while exercising source load and analysis. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) documents the image-coordinate preview convention. Known limitations and follow-up The preview assumes imported marker coordinates follow the documented image coordinate convention. Generic coordinate files with a Cartesian convention must be converted by their producer before import."},{"title":"Headless validation follows current documentation and App contracts","url":"history/records/2026/07/LK-20260719-headless-validation-repair.html","kind":"history","section":"history","keywords":"LK-20260719-headless-validation-repair 2026-07-19 137 fix compatible labkit_TTestWizard_app Continuous integration Documentation validation Statistics","text":"Headless validation follows current documentation and App contracts id: LK-20260719-headless-validation-repair date: 2026-07-19 sequence: 137 type: fix compatibility: compatible component: `labkit_TTestWizard_app` | `1.0.0 -> 1.0.1` scope: Continuous integration scope: Documentation validation scope: Statistics Context The first main-branch validation after the path-derived documentation change still asserted several retired documentation paths. The same run exposed T-Test Wizard code-quality findings and a framework GUI test file that had grown beyond its review budget. Decision and rationale Align validation with the current path-owned documentation structure and keep Runtime-owned empty session buckets out of App factories. Resolve the static analysis findings without changing calculations, export values, or visible workflow behavior. Split the workspace-specific GUI coverage by capability so each test file remains focused. Changes Updated documentation guardrails to use the current testing, Runtime, and complete-App guide paths. Removed an empty `view` placeholder that Runtime already supplies. Replaced dynamic array growth in group-label and group-reassignment helpers with bounded collection. Centralized the box-plot label and documented the plot-jitter and significance-display constants. Split workspace-page and dynamic-table GUI coverage into its own focused test file. User and data impact T-Test Wizard calculations, project data, CSV values, plots, and workflows are unchanged. The App version advances to 1.0.1 to record the internal validation repair. Compatibility and migration Existing version-1 projects remain compatible and require no data migration. Runtime continues to provide `selection`, `workflow`, `view`, and `cache` in the final session state. Validation Focused contract and T-Test core tests cover the original headless failures. Hidden GUI tests cover the T-Test workflow and the extracted Runtime workspace coverage. Documentation rendering and changed-file gates validate the final main-branch handoff. Evidence [T-Test Wizard](../../../../apps/statistics/ttest-wizard/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Testing LabKit](../../../../development/maintain-and-release/testing.md) Known limitations and follow-up Interactive native-dialog behavior remains a developer-led manual validation responsibility; hidden GUI automation does not replace it."},{"title":"History records use one canonical authored format","url":"history/records/2026/07/LK-20260721-canonical-history-record-format.html","kind":"history","section":"history","keywords":"LK-20260721-canonical-history-record-format 2026-07-21 149 docs compatible Project History Documentation Search","text":"History records use one canonical authored format id: LK-20260721-canonical-history-record-format date: 2026-07-21 sequence: 149 type: docs compatibility: compatible scope: Project History scope: Documentation Search Context History records mixed a transport-oriented schema marker with several related but inconsistent metadata conventions. Authors had to infer valid fields and values from renderer internals and agent guidance. Decision and rationale Use one compact, authored record format without a schema field. A single public format page defines every legal field, value, ordering rule, and narrative section so a record can be written and reviewed from repository documentation. Changes Rewrote every existing record into the canonical metadata form, preserved its identity, date, sequence, narrative, and component history links, and added a strict renderer parser. Scopes now feed the documentation search index. User and data impact Readers retain the complete history timeline and component links while gaining more useful search terms. This documentation-only change does not alter saved projects, experimental data, or App behavior. Compatibility and migration The source format changes incompatibly for record authors: remove `schema` and replace an introduction entry with a `component` transition using `new ->`. Existing records are migrated in this same change, so no compatibility reader is retained. Validation The documentation renderer validates every migrated record, rebuilds the site, and documentation guardrails verify the canonical source and generated output. Evidence The renderer parser is the executable enforcement of the published format; the record format page provides its human-readable counterpart. Known limitations and follow-up No known follow-up is required. Future history changes use this format directly rather than adding compatibility metadata variants."},{"title":"Image App project validation ownership","url":"history/records/2026/07/LK-20260716-image-project-validation-ownership.html","kind":"history","section":"history","keywords":"LK-20260716-image-project-validation-ownership 2026-07-16 119 refactor compatible labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app image App project schemas App maintenance cost","text":"Image App project validation ownership id: LK-20260716-image-project-validation-ownership date: 2026-07-16 sequence: 119 type: refactor compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.4 -> 1.7.5` component: `labkit_CurvatureMeasurement_app` | `1.4.4 -> 1.4.5` component: `labkit_FLIRThermal_app` | `1.4.4 -> 1.4.5` component: `labkit_FocusStack_app` | `1.5.3 -> 1.5.4` component: `labkit_ImageEnhance_app` | `1.6.4 -> 1.6.5` component: `labkit_ImageMatch_app` | `1.6.4 -> 1.6.5` component: `labkit_VideoMarker_app` | `1.5.3 -> 1.5.4` scope: image App project schemas scope: App maintenance cost Context The seven image workflow validators repeated Runtime's canonical project-bucket checks and, in six cases, the standard portable source-record field list. Those checks obscured the App-specific rules that actually matter: crop task relationships, parameter ranges, annotation shapes, video metadata, skeleton dimensions, and result schemas. Decision and rationale Keep the existence of each App's required source collection and every domain-specific relationship in `projectSpec.m`. Rely on Runtime for the five canonical bucket structs and the internal shape of every supplied source record. This preserves strict malformed-project rejection while making each validator read as its App schema. Changes Removed 71 net lines of repeated Runtime structure checks from the seven image App project specifications. Preserved required `inputs.sources` fields and all item/source, annotation/source, skeleton/frame, parameter, and result constraints. Added a GUI-free contract that accepts all seven default projects and rejects each project when its App-required source collection is removed. User and data impact Valid projects, migrations, image calculations, annotations, and exports are unchanged. Malformed source records continue to fail in Runtime; malformed App fields continue to fail in the owning App validator. Compatibility and migration No payload format changed and no migration is required. The refactor changes which layer reports framework-owned structure failures, not which valid data the Apps accept. Validation The focused project-spec contract covers default acceptance and missing-source rejection for every affected App. Existing focused unit and hidden-GUI tests cover the domain relationships and representative load, edit, analyze, and export workflows. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines canonical project and source validation. Each affected [Image Measurement App](../../../../apps/image-measurement/README.md) manual lists its remaining project and session responsibilities. Known limitations and follow-up This change does not shorten action handlers or scientific workflow code. Those remain App-owned unless a separately measured repeated behavior proves to be stable and domain-neutral."},{"title":"Image Apps stop creating invalid placeholder outputs","url":"history/records/2026/07/LK-20260716-image-manifest-output-arrays.html","kind":"history","section":"history","keywords":"LK-20260716-image-manifest-output-arrays 2026-07-16 80 fix compatible labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_ImageEnhance_app labkit_ImageMatch_app Image Measurement Result manifests","text":"Image Apps stop creating invalid placeholder outputs id: LK-20260716-image-manifest-output-arrays date: 2026-07-16 sequence: 80 type: fix compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.1 -> 1.7.2` component: `labkit_FLIRThermal_app` | `1.4.1 -> 1.4.2` component: `labkit_ImageEnhance_app` | `1.6.1 -> 1.6.2` component: `labkit_ImageMatch_app` | `1.6.1 -> 1.6.2` scope: Image Measurement scope: Result manifests Context Four image Apps preallocated variable-length result arrays by first asking the Runtime output factory to create a record with an empty ID. Runtime V2 rejects empty result IDs, so otherwise valid export workflows could stop before their standard manifest was written. Decision and rationale Use the canonical empty result array introduced by `labkit.ui` and append only validated real outputs. This preserves strict IDs without requiring each App to duplicate the manifest struct shape. Changes Updated Batch Crop crop outputs. Updated FLIR image, colorbar, and temperature-CSV outputs. Updated Image Enhance batch outputs. Updated Image Match batch outputs. User and data impact The exported images, numeric CSV data, filenames, roles, statuses, and manifest schemas are unchanged. Exports no longer fail during internal output-array initialization. Compatibility and migration No saved project or export schema migration is required. Existing result files remain readable. Validation Each affected App's hidden synthetic GUI export workflow verifies its domain output plus the standard LabKit manifest. Version and component-history guardrails cover all four products. Evidence [Batch Crop](../../../../apps/image-measurement/batch-crop/README.md) [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Image Match](../../../../apps/image-measurement/image-match/README.md) Known limitations and follow-up The Apps still need their separate single-definition and projectSpec reviews; this record only fixes the shared manifest construction defect."},{"title":"Image Enhance assigns state helpers to workflow capabilities","url":"history/records/2026/07/LK-20260716-image-enhance-structure.html","kind":"history","section":"history","keywords":"LK-20260716-image-enhance-structure 2026-07-16 99 refactor compatible labkit_ImageEnhance_app Image Measurement App structure","text":"Image Enhance assigns state helpers to workflow capabilities id: LK-20260716-image-enhance-structure date: 2026-07-16 sequence: 99 type: refactor compatibility: compatible component: `labkit_ImageEnhance_app` | `1.6.2 -> 1.6.3` scope: Image Measurement scope: App structure Context Image Enhance split static product metadata across files, spread a version-1 project over a generic lifecycle package, and placed decoded items, durable annotations, numerical histories, preview scaling, UI normalization, and export fingerprints together under `+appState`. Source reconstruction and presentation also read nested portable-reference fields directly. Decision and rationale Use the compact Runtime V2 definition and project contracts, then assign each remaining concept to the workflow capability that owns its behavior. Preserve the important preview/export boundary: pixel-radius parameters scale for the downsampled preview while full-size export continues to use source coordinates. Changes Consolidated product metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated durable creation and validation in `projectSpec.m` and moved transient reconstruction to root `createSession.m`. Assigned decoded items and lazy selected-image loading to `+sourceFiles`, histories and preview replay to `+analysisRun`, durable per-image data to `+enhancementAnnotations`, UI value clamping to `+userInterface`, and export fingerprints to `+resultFiles`. Removed separate metadata files, generic lifecycle/state packages, and the redundant App startup callback. Replaced direct portable-reference access with the Runtime path accessor. Corrected the GUI-free manual example to write the first returned image from the documented cell-array result. User and data impact Shared and per-image histories, white ROI calibration, pending previews, preview-coordinate radius scaling, original-resolution processing, duplicate export detection, project reopen, and outputs keep their existing behavior. Empty launch no longer chooses an output folder; importing sources retains the source-adjacent default. Developers can locate source decoding, calculations, durable annotations, and exports without learning one generic state package. Compatibility and migration The durable payload remains version 1 with identical fields and defaults, so existing projects require no migration. The Runtime's standard debug-startup message replaces the removed App-specific line. Validation The focused unit suite covers numerical tools, white ROI behavior, preview radius scaling, source reading, full-size exports, manifests, per-image steps, task fingerprints, and project/presenter contracts. The hidden GUI workflow covers file append and selection, independent histories, tool application, export, save/load, and transient cache reconstruction. Evidence [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Image Library](../../../../libraries/image/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Apps still using generic lifecycle/state packages will be reviewed individually. Automated GUI tests do not replace manual judgment of visual quality, ROI pointer feel, or enhancement suitability for quantitative images."},{"title":"Image Match adopts capability-owned structure","url":"history/records/2026/07/LK-20260716-image-match-structure.html","kind":"history","section":"history","keywords":"LK-20260716-image-match-structure 2026-07-16 96 refactor compatible labkit_ImageMatch_app Image Measurement App structure","text":"Image Match adopts capability-owned structure id: LK-20260716-image-match-structure date: 2026-07-16 sequence: 96 type: refactor compatibility: compatible component: `labkit_ImageMatch_app` | `1.6.2 -> 1.6.3` scope: Image Measurement scope: App structure Context Image Match split metadata and project lifecycle across eight structural files, used an App startup callback only for an output-folder default and debug line, and grouped unrelated source, analysis, and export structures under `+appState`. Session, actions, and presentation also read portable-reference path fields directly. Decision and rationale Adopt one definition, one project spec, and one session factory. Place each data constructor beside the capability that owns its meaning: loaded image items with source reading, match steps with analysis, and idempotent export tasks with result writing. Changes Consolidated command metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated version-1 project creation and validation in `projectSpec.m`. Moved selected-image reconstruction to root `createSession.m`. Replaced `+appState` with capability-owned source, analysis, and result functions. Removed the metadata files, generic lifecycle package, and redundant startup callback. Replaced all portable-reference field reads with the Runtime path accessor. Corrected the GUI-free manual example to use the real `applyPipeline` argument order and cell output. User and data impact Reference/source selection, lazy preview loading, match history, calculation, duplicate-export detection, project reopen, and export formats behave unchanged. A new project no longer writes an environment-specific output path during startup; source selection still establishes the same adjacent default. The directory now answers where an item, step, or export task belongs without requiring knowledge of a generic state layer. Compatibility and migration The durable payload remains version 1 with identical fields and validation. Existing Image Match projects require no migration. Validation Unit tests cover the definition/project/session contract, matching methods, pipeline replay, preview scaling, result tables, export formats, manifests, and task fingerprints. The hidden GUI suite covers reference/source loading, selection, history, preview, export, and save/load workflows. Evidence [Image Match](../../../../apps/image-measurement/image-match/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Image Measurement Apps retain older lifecycle or state packages and will be reviewed against their own capabilities."},{"title":"Image app workflow improvements","url":"history/records/2026/07/LK-20260701-image-app-workflow-improvements.html","kind":"history","section":"history","keywords":"LK-20260701-image-app-workflow-improvements 2026-07-01 27 feat compatible labkit_launcher labkit.image labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_FLIRThermal_app labkit_ImageEnhance_app labkit_ImageMatch_app Image app workflow improvements","text":"Image app workflow improvements id: LK-20260701-image-app-workflow-improvements date: 2026-07-01 sequence: 27 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.5 -> 1.1.6` component: `labkit.image` | `1.0.0 -> 1.1.0` component: `labkit.ui` | `3.2.10 -> 3.3.0` component: `labkit.ui` | `3.3.0 -> 3.3.1` component: `labkit_BatchImageCrop_app` | `1.4.0 -> 1.5.0` component: `labkit_BatchImageCrop_app` | `1.5.0 -> 1.5.1` component: `labkit_FLIRThermal_app` | `1.0.0 -> 1.1.0` component: `labkit_FLIRThermal_app` | `1.1.0 -> 1.1.2` component: `labkit_ImageEnhance_app` | `1.4.0 -> 1.4.1` component: `labkit_ImageMatch_app` | `1.4.0 -> 1.4.1` scope: Image app workflow improvements Context Image apps used full-resolution data for several previews even when the screen could not display that detail. Controls also accepted ranges that were awkward for the current image, and FLIR point and ROI measurements were not yet a complete direct-manipulation workflow. Decision and rationale Set an explicit preview budget, derive sensible control bounds from the loaded image, and preserve full-resolution data for calculations and exports. Expand the FLIR app around point and ROI temperature tools rather than adding more separate analysis dialogs. Changes `labkit.image` `1.0.0 -> 1.1.0` `labkit.ui` `3.2.10 -> 3.3.1` Batch Crop `1.4.0 -> 1.5.1` FLIR Thermal `1.0.0 -> 1.1.2` `labkit_launcher` `1.1.5 -> 1.1.6` Added preview-budget helpers. Improved image app range and preview controls. Improved image measurement workflows. User and data impact Large images produced bounded previews while exports continued to use source resolution. Batch Crop controls reflected the crop and scale being edited, and FLIR users could place temperature points or regions directly on the image and read their values in the detail panel. Compatibility and migration Existing image inputs and app exports remained valid. Preview budgeting changed display resolution only; calculations and exports continued to use the source data required by each workflow. Validation The two commits expanded Batch Crop export tests, FLIR operation and layout tests, image-facade preview tests, busy-state tests, scale-bar gesture checks, and launcher coverage. Exact historical commands were not recorded. Evidence Main commits `15a798ba` and `70bfcfd4`. Known limitations and follow-up Preview budgeting improved responsiveness but did not by itself remove every eager image read. Batch Crop file selection was made lazy in the following profiling work."},{"title":"Interactive recovery of missing project sources","url":"history/records/2026/07/LK-20260716-runtime-source-relink.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-source-relink 2026-07-16 66 fix compatible labkit.ui project loading portable source references","text":"Interactive recovery of missing project sources id: LK-20260716-runtime-source-relink date: 2026-07-16 sequence: 66 type: fix compatibility: compatible component: `labkit.ui` | `6.0.1 -> 6.0.2` scope: project loading scope: portable source references Context Saved projects could describe required external files with portable source records, but an unresolved path produced a framework error unless the owning app implemented the advanced `Project.RelinkSources` hook. Production apps using standard source records therefore had no common recovery interaction when a project moved between folders, drives, or operating systems. Decision and rationale Make missing-file recovery the default Runtime V2 behavior for canonical source records. The runtime already owns project loading, injected dialogs, and transactional state replacement, so it can provide one consistent prompt without duplicating native chooser code in every app. The advanced app hook remains available for nonstandard source schemas. Changes Report the saved filename and source role when automatic resolution fails. Let the user locate each missing required file with one native file chooser. Rebuild the selected source reference relative to the loaded project before constructing the app session. Mark a successfully repaired document as unsaved so the replacement path can be retained on the next save. Mark migrated payloads, snapshots, and declared legacy variables as unsaved; **Save State** then upgrades the opened MAT path to the current project format through the existing atomic replacement. Treat cancellation as an atomic load cancellation without changing the live project or view. User and data impact Projects whose external files remain in their recorded locations open exactly as before. When a required file moved, the user can locate it instead of seeing only an unresolved-source error. The runtime never searches arbitrary folders or silently substitutes a different file. Merely opening an old document does not rewrite it; saving the migrated document upgrades that same path. Compatibility and migration The change is compatible with current project envelopes, declared legacy imports, and existing custom `Project.RelinkSources` callbacks. Selecting a replacement updates only the candidate project; saving writes the current project format. Validation The hidden Runtime V2 project test covers successful default relinking, relative-reference rebuilding, dirty-document state, cancellation rollback, and the existing custom relink hook. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) documents the default missing-source interaction. Runtime source resolution reruns before fresh-session construction and state commit. Known limitations and follow-up The default flow asks once for each unresolved source. Apps with a specialized multi-file schema may continue to provide a custom relinking callback."},{"title":"LabKit name and the first multi-domain app families","url":"history/records/2026/05/LK-20260530-app-family-expansion.html","kind":"history","section":"history","keywords":"LK-20260530-app-family-expansion 2026-05-30 3 feat compatible historical project evolution","text":"LabKit name and the first multi-domain app families id: LK-20260530-app-family-expansion date: 2026-05-30 sequence: 3 type: feat compatibility: compatible scope: historical project evolution Context By the end of the electrochem extraction, the repository had reusable DTA operations and several app entry points, but its naming and package layout still reflected the original Gamry workbench. It was not yet clear whether the design could serve workflows with different data, interaction, and visualization needs. Decision and rationale Rename the project LabKit and treat apps, rather than one scientific domain, as the main deliverables. Keep a limited set of reusable packages underneath them, then exercise that structure with image registration, region editing, curvature measurement, wearable data import, and ECG viewing. The purpose was not to turn LabKit into one analysis program. It was to make a common MATLAB foundation useful to several independent laboratory tools without erasing the workflow decisions that made each tool understandable. Changes Renamed the workbench namespace to `labkit` and reduced the public package surface to app-facing UI and domain operations. Standardized the basic workbench shell and DTA file panels across the electrochem apps. Added DIC preprocessing and postprocessing workflows with iterative crop, mask, registration, zoom, and preview interactions. Added the Curvature Measurement app and reused its anchor-curve editor across image workflows where the interaction was genuinely the same. Added a biosignal facade and the ECG Print app, including wearable CSV import and signal-column diagnostics. Added MATLAB CI and reorganized tests by source responsibility. User and data impact LabKit now served electrochem, image, and biosignal work from distinct app entry points. Image users gained direct manipulation of regions and anchors; biosignal users gained a viewer for imported wearable recordings. Existing DTA files remained external inputs and were not converted into a central project format. Compatibility and migration The namespace rename was an implementation migration for repository code. The new image and biosignal apps were additive. Scripts that referenced the former workbench namespace needed to adopt `labkit` or an app entry point. Validation Focused DIC interaction tests, biosignal import tests, layout tests, and the first MATLAB CI workflow were added during this stage. The exact historical command used for each commit was not recorded. Evidence Public-surface cleanup and LabKit rename `9bd8ec8f` through `179885e1`. Unified workbench and file panels `2de7dd0f` through `1be52b9d`. DIC family `aa96ae88` through `fd808a56`. Curvature Measurement `9c1f3798`, `8bcde252`. Biosignal and ECG work `d7c31369` through `b7cabd52`. Initial MATLAB CI `5102640c`. Known limitations and follow-up The new apps exposed interaction problems that ordinary callback tests did not capture well, including popout axes, anchor editing, busy-state reentry, and image zoom ownership. Those issues drove the managed interaction work that followed before v1.0."},{"title":"Launcher adopts the App SDK diagnostic launch contract","url":"history/records/2026/07/LK-20260720-launcher-app-sdk-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260720-launcher-app-sdk-diagnostics 2026-07-20 139 feat compatible labkit_launcher LabKit Core App Framework","text":"Launcher adopts the App SDK diagnostic launch contract id: LK-20260720-launcher-app-sdk-diagnostics date: 2026-07-20 sequence: 139 type: feat compatibility: compatible component: `labkit_launcher` | `1.5.2 -> 1.6.0` scope: LabKit Core scope: App Framework Context The Launcher still passed the retired `RequestAdapter` startup seam after Apps had moved to `labkit.app.Definition`. Normal launch therefore failed before the selected App could create its window. Removing that argument restored startup, but temporarily also removed the user's diagnostic launch path. Decision and rationale Treat the App entrypoint and `Definition.launch` as the only Launcher-to-App runtime boundary. A normal launch uses the SDK defaults. A debug launch passes one typed `labkit.app.diagnostic.Options` value that requests verbose persisted events and the App-owned anonymous synthetic sample. The Launcher continues to read catalog metadata from the single app-owned `definition.m` source without executing the SDK. This preserves its recovery role when an installed framework is incomplete or damaged. Changes Replaced retired runtime-adapter injection with direct App SDK entrypoint calls for normal launch and profiling. Restored **Open Debug** using verbose typed diagnostics and each App's `BuildDebugSample` contract. Added one isolated session folder per debug launch under `artifacts/diagnostics/launcher/`, with the event stream, manifests, and anonymous fixture artifacts. Kept Launcher UI state in a Launcher-owned view record instead of the retired framework registry name. Updated Definition metadata discovery for current name-value syntax while retaining the older literal form for repair-oriented catalog compatibility. Excluded hidden class-folder implementation methods from public API and generated-documentation discovery. User and data impact Existing normal launch, profiling, packaging, update, and documentation actions keep their public behavior. Debug launch now creates explicit diagnostic artifacts and opens the App with its synthetic scenario; it no longer invokes the retired string-mode or request-adapter contracts. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Focused Launcher GUI, catalog, progress, profiling, and documentation contracts. App SDK diagnostic integration through DIC Preprocess, including `events.jsonl` and `sample-pack.json` evidence. Documentation source synchronization and public API discovery guardrails. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive validation should confirm the visible debug launch, the selected App's synthetic state, and the usefulness of the generated diagnostic bundle on the deployment MATLAB version. labkit.app.Definition Compile and launch one immutable App SDK contract."},{"title":"Launcher and maintainer tool manuals","url":"history/records/2026/07/LK-20260716-launcher-and-maintainer-manuals.html","kind":"history","section":"history","keywords":"LK-20260716-launcher-and-maintainer-manuals 2026-07-16 63 docs compatible labkit_launcher `docs/apps/labkit-core/launcher/` `docs/development/tools/` `docs/development/maintain-and-release/testing.md`","text":"Launcher and maintainer tool manuals id: LK-20260716-launcher-and-maintainer-manuals date: 2026-07-16 sequence: 63 type: docs compatibility: compatible component: `labkit_launcher` scope: `docs/apps/labkit-core/launcher/` scope: `docs/development/tools/` scope: `docs/development/maintain-and-release/testing.md` Context Launcher behavior was split between Getting Started, architecture notes, source comments, and historical records. Maintainer tools under `tools/` had callable MATLAB entry points but no coherent manual set; profiling details were embedded in the testing command matrix while packaging and codecheck behavior were mostly discoverable only from source. Decision and rationale Document the user-operated launcher beside LabKit Core apps, even though its self-repair implementation remains a root-level standalone file. Document source-checkout tools under Development because they support maintenance rather than app workflows. Keep the existing Testing page as the test-system owner, add a conceptual execution map there, and move profiler call details into the maintainer tool reference. This structure lets readers start from the product they operate or the maintenance task they intend, without mirroring every source directory or mixing tool APIs into reusable `labkit.*` function reference. Changes Added a LabKit Launcher manual covering window actions, programmatic calls, discovery, installation, recovery, tools, limitations, and development milestones. Added a maintainer-tool landing page and detailed references for Code Analyzer reports, app deployment packages, documentation builds, and performance profiling. Added a test-system execution map explaining build tasks, runner discovery, test layers, selectors, plugins, artifacts, and manual-validation boundaries. Connected Getting Started, Apps, LabKit Core, Development, Architecture, Documentation, Testing, Release, the root README, and generated navigation to the new manuals. User and data impact Users can now understand launcher controls and history from the Apps section. Maintainers can copy direct MATLAB calls and inspect every supported option and output without reading tool implementation files. No app workflow, scientific calculation, saved project, input file, or exported result changes. Compatibility and migration This is an additive documentation change. Existing page URLs remain valid. The new launcher and tool pages are generated from Markdown and included in search; no handwritten HTML or third-party documentation package is added. Validation Documentation contracts verify source ownership, generated launcher history, tool entry-point coverage, local links, search indexing, and byte-for-byte agreement between structured sources and the tracked site. Evidence `docs/apps/labkit-core/launcher/README.md` owns the launcher manual. `docs/development/tools/` owns direct maintainer-tool references. `ProjectSurfaceDocumentationTest` protects placement and generated content. Known limitations and follow-up Private app documentation remains in each private repository. Internal private helpers beneath tool folders are intentionally described through their public entry point rather than receiving standalone reference pages."},{"title":"Launcher app version history","url":"history/records/2026/07/LK-20260713-launcher-app-version-history.html","kind":"history","section":"history","keywords":"LK-20260713-launcher-app-version-history 2026-07-13 53 feat compatible labkit_launcher Launcher app version history","text":"Launcher app version history id: LK-20260713-launcher-app-version-history date: 2026-07-13 sequence: 53 type: feat compatibility: compatible component: `labkit_launcher` | `1.3.0 -> 1.4.0` scope: Launcher app version history Context The structured changelog could be parsed by maintainers, but launcher users could not inspect the history of the selected app. Earlier normalization also attached broad release descriptions without recording every app version event, which made histories such as CIC appear to begin years of versions too late. Decision and rationale Expose component-filtered history in the launcher and make exact component events the durable lookup key. A user should be able to move from the current app catalog directly to its evolution record without reading Git history or understanding changelog internals. Changes Added `labkit_launcher(\"history\", appCommand)` for programmatic history lookup and a Version History viewer for the selected launcher app. Added explicit component introduction events and reconstructed every tracked launcher, facade, and app version transition from `origin/main` history. Added continuous-history validation so missing introductions, gaps, and current-version mismatches fail the release guardrail. User and data impact Launcher users can inspect dated version transitions, compatibility, rationale, impact, and evidence for the selected app. App calculations and user data are unchanged. Compatibility and migration The launcher command and button are additive. Existing list, version, package, maintenance, and app-launch workflows remain available. Validation `LauncherGuiTest` covers programmatic filtering and the hidden GUI viewer; `ChangelogGuardrailTest` validates complete version chains against current metadata. These checks were included in the combined mainline change `4a6c41dd`. Evidence Component history, launcher viewer, and changelog validation were included in `4a6c41dd`. Historical component events were reconstructed from version-file changes on the then-current `origin/main` history. Known limitations and follow-up History begins when version metadata was first tracked for each component. Behavior before that point can only be inferred from older source commits and is not assigned invented version numbers."},{"title":"Launcher code-analysis export","url":"history/records/2026/07/LK-20260701-launcher-code-analysis-export.html","kind":"history","section":"history","keywords":"LK-20260701-launcher-code-analysis-export 2026-07-01 29 feat compatible labkit_launcher Launcher code-analysis export","text":"Launcher code-analysis export id: LK-20260701-launcher-code-analysis-export date: 2026-07-01 sequence: 29 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.6 -> 1.2.0` scope: Launcher code-analysis export Context The launcher offered a Code Analyzer action, but its earlier implementation carried substantial custom export code. That made a maintenance tool harder to maintain than the MATLAB analysis it was exposing. Decision and rationale Use MATLAB's native issue representation and export path, and keep the launcher responsible only for choosing the target and destination. This reduced custom conversion logic while preserving a launcher-level entry point for the report. Changes `labkit_launcher` `1.1.6 -> 1.2.0` Exported launcher Code Analyzer issues natively. User and data impact The launcher could export Code Analyzer findings in a MATLAB-supported form without requiring a separate script. This action inspected source code only and did not start or modify an app project. Compatibility and migration Existing launcher actions remained available. Newly exported issue files used MATLAB's native representation rather than the removed custom conversion. Validation `LauncherGuiTest` gained coverage for the native issue-export path and the implementation in `8fd3ddff` removed more launcher code than it added. Evidence Main commit `8fd3ddff`. Known limitations and follow-up Code analysis remained a maintainer tool. It did not become a prerequisite for launching an app or a replacement for the repository's automated checks."},{"title":"Launcher installation and App startup expose actionable progress","url":"history/records/2026/07/LK-20260726-launcher-install-and-startup-feedback.html","kind":"history","section":"history","keywords":"LK-20260726-launcher-install-and-startup-feedback 2026-07-26 160 fix compatible labkit_launcher Launcher double-click startup feedback Duplicate App launch prevention Standalone install and repair target selection Stable main and release-tag download selection","text":"Launcher installation and App startup expose actionable progress id: LK-20260726-launcher-install-and-startup-feedback date: 2026-07-26 sequence: 160 type: fix compatibility: compatible component: `labkit_launcher` | `1.7.0 -> 1.7.1` scope: Launcher double-click startup feedback scope: Duplicate App launch prevention scope: Standalone install and repair target selection scope: Stable main and release-tag download selection Context Opening an App from the Launcher was synchronous. Although the callback assigned an opening message before calling the App entry point, MATLAB could enter the expensive startup call before repainting the Launcher. Users therefore saw no visible acknowledgement, could interpret a legitimate startup as a hang, and could issue repeated double-clicks without knowing which stage was active. Existing GUI coverage asserted only the final opened state. The standalone `labkit_launcher.m` also claimed it could bootstrap an otherwise empty folder, while its safety check and regression test rejected that exact case. Its repair window exposed neither a target folder nor a download-source choice, and did not distinguish a new installation from replacement of an existing one. Decision and rationale Treat startup acknowledgement as part of the Launcher interaction contract. Publish and paint a small number of honest stages before the expensive App entry point, disable launch surfaces while the action is active, and preserve the concrete entry-point failure when startup does not complete. Stages identify the current ownership boundary without inventing percentage progress that the Launcher cannot measure. Keep the standalone entry self-contained but make its narrow responsibility complete: choose and validate a target before network access, distinguish installation from repair, offer stable/main/explicit-tag archives, confirm replacement, validate the extracted candidate, and retain transactional rollback. The installed Launcher continues to own rich version browsing and App catalog behavior. Changes Added visible path-preparation and App-window-initialization stages. Changed the launch button text and pointer while startup is active. Disabled the App table and all action buttons until startup finishes. Restored the normal controls after success or failure. Extended the native Launcher test to inspect status and control state at the instant the selected App entry point begins. Replaced the one-button repair window with target, source, a fetched published-version selector, confirmation, truthful operation stages, and status details. The UI neither requires users to type repository tags nor claims percentage progress for work it cannot measure. Allowed safe installation into a new, empty, or standalone-launcher-only folder while rejecting filesystem roots, Git checkouts, and unrelated nonempty folders before download. Added current-main and selected published-release downloads without importing the installed version manager into the standalone file. User and data impact Users receive immediate acknowledgement after a button press or row double-click, can see whether startup is preparing the path or waiting in the named App entry point, and cannot accidentally queue another launch through the Launcher while the first is active. Scientific data and App projects are unchanged. A user who downloads only `labkit_launcher.m` can now choose where to install, understand whether the operation is a new installation or repair, and select a published stable version or deliberate development source. Unsafe target folders fail before network or replacement activity. Compatibility and migration The launcher command, catalog, table fields, visual layout, update tools, and App entry points are unchanged. The patch is compatible with existing installations and Apps. The standalone window adds choices but preserves latest stable as its recommended default. Validation The focused hidden-GUI Launcher specification verifies the starting message, disabled table, wait pointer, temporary button label, selected command, final opened message, and restoration of controls. Documentation consistency and launcher version/history guardrails cover the published contract. Bootstrap specifications verify standalone UI choices, root-only and selected empty-folder installation, stable-release fallback, exact main and tag archive URLs, candidate validation, Git and unrelated-folder rejection, preservation, rollback, and actionable network failure. Evidence The direct-main fix commit, focused Launcher dispatch result, generated documentation check, and exact main-push CI run are the delivery evidence. Known limitations and follow-up The Launcher can identify that control has entered a named App entry point, but cannot report finer App-owned initialization phases until that App creates its Runtime diagnostics. Native dialog appearance, folder-picker behavior, and perceived responsiveness remain manual checks."},{"title":"Launcher manager and stale callback fix","url":"history/records/2026/06/LK-20260626-launcher-manager-and-stale-callback-fix.html","kind":"history","section":"history","keywords":"LK-20260626-launcher-manager-and-stale-callback-fix 2026-06-26 13 fix compatible labkit_launcher labkit_launcher labkit.ui Launcher manager and stale callback fix","text":"Launcher manager and stale callback fix id: LK-20260626-launcher-manager-and-stale-callback-fix date: 2026-06-26 sequence: 13 type: fix compatibility: compatible component: `labkit_launcher` | `1.0.0 -> 1.1.0` component: `labkit_launcher` | `1.1.0 -> 1.1.1` component: `labkit.ui` | `3.0.0 -> 3.0.1` scope: Launcher manager and stale callback fix Context The launcher could update LabKit but did not offer an explicit choice among a recent release, a tag, and the main branch. Separately, image drag tools could leave old callbacks attached after an interaction ended. Decision and rationale Add a managed version selector backed by an install manifest, and make image interaction cleanup restore or release every temporary callback. Both changes reduce hidden state that otherwise survives beyond the user's selected action. Changes `labkit_launcher` `1.0.0 -> 1.1.1` `labkit.ui` `3.0.0 -> 3.0.1` Added the launcher version manager and managed-manifest requirement. Released stale image drag callbacks. User and data impact Users could deliberately install a supported release or development revision. Ending an image drag no longer left the figure responding to an obsolete tool. Saved app data did not change. Compatibility and migration Existing launcher installations remained usable. Managed installations needed the launcher manifest for version replacement; image editors required no saved- data conversion after their stale callbacks were released. Validation The listed commits added launcher-manager/manifest coverage and regression checks for image callback cleanup. Exact historical commands were not recorded. Evidence Main commits `fe8654c9`, `ef89cf77`, and `3d23b7f1`. Known limitations and follow-up Version selection still depended on network access for remote revisions; already installed local versions remained usable offline."},{"title":"Launcher reads the single App definition","url":"history/records/2026/07/LK-20260716-launcher-definition-metadata.html","kind":"history","section":"history","keywords":"LK-20260716-launcher-definition-metadata 2026-07-16 78 refactor compatible labkit_launcher App discovery Product metadata ownership","text":"Launcher reads the single App definition id: LK-20260716-launcher-definition-metadata date: 2026-07-16 sequence: 78 type: refactor compatibility: compatible component: `labkit_launcher` | `1.5.0 -> 1.5.1` scope: App discovery scope: Product metadata ownership Context The launcher discovered entrypoints independently from App runtime creation, but obtained versions by parsing a separate package `version.m`. That kept the old metadata file alive even after Runtime V2 gained a single definition contract. Decision and rationale Keep discovery lightweight and self-contained while changing its source of truth. The launcher now reads `AppVersion` and `Updated` literals from the App's `definition.m`; it does not execute the definition or start the App. Changes Preferred `definition.m` product metadata during public and private App discovery. Added a synthetic private-App catalog test with no `version.m` file. Retained a documented internal fallback only for Apps awaiting migration. User and data impact Launcher catalogs continue to show version and update dates. A migrated App does not need a second metadata file solely for discovery. Compatibility and migration The change is compatible. Existing Apps remain discoverable through the temporary fallback; migrated Apps use only their definition. The fallback is removed after the last App migration. Validation The launcher catalog test creates an isolated App definition, points private discovery at it, and verifies the returned version and date without a `version.m` file. Evidence [LabKit Launcher](../../../../apps/labkit-core/launcher/README.md) explains definition-backed discovery. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the App product metadata fields. Known limitations and follow-up P-code packaging metadata will be audited with the deployment workflow before the transitional parser fallback is removed."},{"title":"Launcher update reliability","url":"history/records/2026/07/LK-20260701-launcher-update-reliability.html","kind":"history","section":"history","keywords":"LK-20260701-launcher-update-reliability 2026-07-01 25 fix compatible labkit_launcher labkit_launcher Launcher update reliability","text":"Launcher update reliability id: LK-20260701-launcher-update-reliability date: 2026-07-01 sequence: 25 type: fix compatibility: compatible component: `labkit_launcher` | `1.1.3 -> 1.1.4` component: `labkit_launcher` | `1.1.4 -> 1.1.5` scope: Launcher update reliability Context The self-contained launcher could install a ZIP release, but the replacement path performed more filesystem work than necessary and duplicated logic for locating, unpacking, and replacing the workbench. Slow replacement made a successful update look stalled; duplicated recovery paths made failures harder to reason about. Decision and rationale Minimize the files touched during an update and give ZIP replacement one straight-line implementation. Preserve the existing installation until the download and unpack steps have produced a usable replacement. Changes `labkit_launcher` `1.1.3 -> 1.1.5` Sped up launcher zip updates. Simplified launcher zip replacement. User and data impact ZIP updates completed with less waiting and fewer intermediate operations. A failure before replacement left the existing checkout available, while a successful update continued to present the same launcher entry point. Compatibility and migration Existing managed installations remained updateable. The replacement algorithm changed internally and did not require users to reinstall a working checkout. Validation Both commits extended `LauncherGuiTest` around download, replacement, and recovery behavior. The exact historical commands were not recorded. Evidence Main commits `ebf86cf2` and `becf9391`. Known limitations and follow-up The updater still depended on the release artifact being trustworthy. Later release work added stronger tag-to-asset verification and update diagnostics."},{"title":"Legacy import and first app workbench","url":"history/records/2026/05/LK-20260528-initial-app-workbench-foundation.html","kind":"history","section":"history","keywords":"LK-20260528-initial-app-workbench-foundation 2026-05-28 1 feat compatible historical project evolution","text":"Legacy import and first app workbench id: LK-20260528-initial-app-workbench-foundation date: 2026-05-28 sequence: 1 type: feat compatibility: compatible scope: historical project evolution Context LabKit began with a direct import of older MATLAB analysis and GUI code. The electrochem workflows mixed file parsing, calculations, session state, plots, and exports inside large entry scripts, and several root commands exposed the same legacy implementation through different paths. Decision and rationale Separate the imported code by responsibility before adding new features. Move Gamry parsing and pulse detection into reusable DTA operations; keep CIC, CSC, VT resistance, EIS, and chrono calculations with their workflows; then expose each workflow through one package-backed app command. Extract shared UI pieces only after the app behavior is visible in more than one workflow. Changes Imported the legacy MATLAB tree without rewriting its scientific formulas. Extracted chrono, EIS, and CV/CT DTA parsers plus pulse detection and named synthetic fixtures. Separated chrono alignment/export, CIC, CSC, VT resistance, and EIS result operations from their GUI assembly. Added named app entry points and package-backed runners, then removed the duplicate root legacy entry points and legacy GUI directory. Introduced the first shared two-pane/tabbed shells, file panels, plot controls, log panels, and axes helpers used by the electrochem apps. User and data impact Users gained named electrochem app commands instead of opening the old GUI scripts directly. Calculations and exported values were intended to remain unchanged while code ownership moved; laboratory files remained external inputs rather than being copied into a central LabKit database. Compatibility and migration Component/app version files did not exist yet. Old root GUI entry points were removed as package-backed app commands became the supported launch path. Validation The first regression evidence included named DTA fixtures, calculation tests, an optional GUI smoke runner, and GUI compatibility checks. A single standard test command had not yet been established. Evidence Initial import `5973bde0`. Parser and calculation extraction from `fc70f9b2` through `9f7c6e4e`. App entrypoint and package migration from `40f46561` through `4d58066c`. First shared app-shell work from `f05f4a35` through `61a8cb87`. Known limitations and follow-up This stage improved structure but still contained several broad shared UI helpers and session abstractions that were narrowed during the following DTA facade and app-boundary work."},{"title":"Legacy import preserves portable references without exposing their schema","url":"history/records/2026/07/LK-20260716-opaque-source-import.html","kind":"history","section":"history","keywords":"LK-20260716-opaque-source-import 2026-07-16 102 feat compatible labkit.ui App Framework Project portability","text":"Legacy import preserves portable references without exposing their schema id: LK-20260716-opaque-source-import date: 2026-07-16 sequence: 102 type: feat compatibility: compatible component: `labkit.ui` | `7.4.2 -> 7.4.3` scope: App Framework scope: Project portability Context The GUI-free source-record factory accepted filepaths, but an App importing an older MAT variable could already receive a portable reference containing the only usable relative location. Recreating the record from its original path would discard that portability; preserving it required the importer to copy the Runtime's private nested reference schema. Decision and rationale Keep one public source factory and allow its source value to be either a normal filepath or an existing portable reference passed as one opaque struct. The Runtime validates and canonicalizes the latter. Apps still never construct or read nested reference fields. Changes Extended `labkit.ui.runtime.sourceRecord` with an opaque-reference overload. Added strict validation for the supported portable-reference schema, scalar text fields, filename, and usable relative or original path. Canonicalized imported references so unrelated legacy fields do not enter a current project. Documented the distinction between normal filepath creation and legacy reference preservation. User and data impact Moved legacy projects retain their relative source fallback during upgrade. App importers no longer need to duplicate Runtime serialization fields or choose between architectural ownership and data portability. Compatibility and migration Existing filepath calls and injected source-record services are unchanged. The overload accepts only the current supported reference schema; malformed or newer references fail before they can enter a project. Validation Focused Runtime tests cover normal path creation, opaque-reference canonicalization, relative-path preservation, missing fields, unsupported schema versions, source path access, and the public package surface. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Framework](../../../../framework/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up Opaque-reference import is intended for trusted project importers, not as a general App-defined serialization extension. Video Marker is the first legacy importer scheduled to consume the overload."},{"title":"Local validation uses one focused route and CI owns full coverage","url":"history/records/2026/07/LK-20260721-local-validation-routing.html","kind":"history","section":"history","keywords":"LK-20260721-local-validation-routing 2026-07-21 150 test compatible Test routing Continuous Integration","text":"Local validation uses one focused route and CI owns full coverage id: LK-20260721-local-validation-routing date: 2026-07-21 sequence: 150 type: test compatibility: compatible scope: Test routing scope: Continuous Integration Context The repository exposed both `changedFast` and a conservative `changed` task. For documentation, agent guidance, and CI workflow changes they selected the same complete project suite, so running both locally duplicated work that the cross-platform CI already repeated more comprehensively. Decision and rationale Keep one local changed-file task, `changedFast`, for a focused pre-push check. PR and main-push CI remain the complete validation gate because they run the full headless and hidden-GUI suites on every supported platform. Changes Removed the public conservative `changed` task and its runner entry. Guidance, templates, workflows, and CI helpers now route to their owning documentation or CI guardrails instead of the complete project suite. Broad GUI candidate selection remains reduced to representative workflows for the local route. User and data impact App behavior, saved projects, analysis results, and laboratory data do not change. Contributors receive faster local feedback while the same complete platform coverage remains mandatory before delivery. Compatibility and migration Use `buildtool changedFast` for the local pre-push checkpoint. The retired `buildtool changed` command is intentionally unavailable; use CI results for complete validation rather than substituting another local broad run. Validation Build-task catalog and route-contract tests verify the single public local route and its owner-specific selections. The changed-file gate exercises the selected guardrails, while CI continues to run full headless and hidden-GUI coverage. Evidence The CI workflow invokes `headless` and `gui` on Linux, macOS, and Windows. The validation planner records a reason for every selected local guardrail. Known limitations and follow-up Local `changedFast` is intentionally not a substitute for CI or manual App review. If a new shared surface lacks an owning test route, add that route rather than broadening unrelated local checks."},{"title":"MATLAB-compatible image conversion API","url":"history/records/2026/07/LK-20260713-matlab-compatible-image-conversion.html","kind":"history","section":"history","keywords":"LK-20260713-matlab-compatible-image-conversion 2026-07-13 47 refactor breaking labkit.image labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app MATLAB-compatible image conversion API","text":"MATLAB-compatible image conversion API id: LK-20260713-matlab-compatible-image-conversion date: 2026-07-13 sequence: 47 type: refactor compatibility: breaking component: `labkit.image` | `1.2.0 -> 2.0.0` component: `labkit_DICPostprocess_app` | `1.3.5 -> 1.3.6` component: `labkit_BatchImageCrop_app` | `1.6.7 -> 1.6.8` component: `labkit_CurvatureMeasurement_app` | `1.3.4 -> 1.3.5` component: `labkit_FocusStack_app` | `1.4.8 -> 1.4.9` component: `labkit_ImageEnhance_app` | `1.5.7 -> 1.5.8` component: `labkit_ImageMatch_app` | `1.5.7 -> 1.5.8` scope: MATLAB-compatible image conversion API Context The `toDouble`, `toLuma`, and `toRgbDouble` names combined class conversion, channel shaping, and clipping in ways that differed from familiar MATLAB APIs. Decision and rationale Use MATLAB-compatible names and call contracts for replacement functions, and keep orthogonal RGB shaping explicit so users do not need to learn a composite LabKit normalization rule. Changes Added base-MATLAB `labkit.image.im2double` and `labkit.image.rgb2gray`. Kept channel shaping in `ensureRgb` and made clipping explicit at call sites. Removed the ambiguous conversion helpers and centralized Rec.601 ownership. User and data impact Base-MATLAB users receive familiar image conversion behavior without hidden toolbox requirements. Existing app image results retain their intended ranges and channel shapes through explicit pipelines. Compatibility and migration This is a breaking facade rename. External callers replace removed helper names with `im2double`, `rgb2gray`, and `ensureRgb` as separately needed. Validation Image facade, downstream app, toolbox-shadow, and base-MATLAB ownership tests cover class conversion, luma values, and representative workflows. Evidence The current API contracts are documented in the [Image Library](../../../../libraries/image/README.md). Commit `e3f71c2d` aligned the conversion functions with MATLAB call behavior. Known limitations and follow-up The compatibility layer intentionally covers the LabKit-used MATLAB contracts, not every Image Processing Toolbox function. labkit.image.im2double Convert image data to double using MATLAB's im2double contract. labkit.image.rgb2gray Convert RGB data using MATLAB's rgb2gray call contract."},{"title":"Managed image interactions and diagnostic tracing","url":"history/records/2026/06/LK-20260604-managed-image-interactions.html","kind":"history","section":"history","keywords":"LK-20260604-managed-image-interactions 2026-06-04 4 feat compatible historical project evolution","text":"Managed image interactions and diagnostic tracing id: LK-20260604-managed-image-interactions date: 2026-06-04 sequence: 4 type: feat compatibility: compatible scope: historical project evolution Context Image and ECG apps had made the workbench broader, but they also revealed a class of GUI failures that did not appear in parser or calculation tests. An anchor editor could re-enter a callback, a scale bar could lose ownership, an axes popout could stop reflecting the source, and a long operation could leave controls in an ambiguous state. Decision and rationale Move repeatable interaction mechanics into the UI foundation while leaving image-specific geometry and workflow state in each app. Give long-running actions an explicit busy-state guard, make image axes and tools managed resources, and record callback progress in diagnostic traces that could be inspected after a failure. Changes Added axes popout support and corrected image aspect and menu refresh behavior. Added shared UI control helpers and a reusable ECG peak detector facade. Added the Focus Stack app with automatic and manual image selection. Improved anchor insertion, curvature controls, and scale-bar ownership. Added a busy-state guard to prevent overlapping actions. Introduced a managed image-axes runtime, layered UI facades, and app debug trace logging. Split large app entry points into app-owned private workflow helpers rather than widening the public framework API. User and data impact Users gained Focus Stack and more predictable image editing. Popout views, anchors, scale bars, and long operations provided clearer feedback and were less likely to leave an app in a broken intermediate state. Debug traces made it possible to distinguish a slow or failed callback from an unresponsive window. The changes affected UI state and presentation; they did not intentionally alter existing scientific results. Compatibility and migration Apps gradually moved from direct axes and interaction setup to the managed UI facades. App-private geometry helpers remained private, so the framework did not promise a public API for every interaction detail. Validation The period added public API option documentation, app-private helper tests, interaction regression coverage, and focused checks for UI layout ownership. No single historical validation command covers the complete stage. Evidence Axes popout and control helpers `1e9022c4` through `2136009f`. ECG detector and filtering work `e8450652` through `adb7dfbf`. Focus Stack `08518e91`, `cc4cdf92`. Busy guard `ac36bd54`. Image interaction and scale-bar work `f58b8559` through `5304fa20`. Managed axes, traces, and layered UI facades `27bdbdd8`, `e86e4ea3`, `da0663f1`, `c4cae633`. Known limitations and follow-up The interaction layer was still evolving and the test infrastructure mixed custom runners with newer MATLAB tests. The v1.0 stabilization pass addressed test entry points, package ownership, and remaining compatibility aliases."},{"title":"Managed scientific and conversion constants","url":"history/records/2026/07/LK-20260713-managed-calculation-constants.html","kind":"history","section":"history","keywords":"LK-20260713-managed-calculation-constants 2026-07-13 48 refactor compatible labkit.dta labkit.rhs labkit.biosignal labkit_ChronoOverlay_app labkit_CSC_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Managed scientific and conversion constants","text":"Managed scientific and conversion constants id: LK-20260713-managed-calculation-constants date: 2026-07-13 sequence: 48 type: refactor compatibility: compatible component: `labkit.dta` | `2.0.0 -> 2.0.1` component: `labkit.rhs` | `1.0.0 -> 1.0.1` component: `labkit.biosignal` | `1.0.0 -> 1.0.1` component: `labkit_ChronoOverlay_app` | `1.3.5 -> 1.3.6` component: `labkit_CSC_app` | `1.3.9 -> 1.3.10` component: `labkit_NerveResponseAnalysis_app` | `1.3.4 -> 1.3.5` component: `labkit_ResponseReviewStats_app` | `1.3.4 -> 1.3.5` scope: Managed scientific and conversion constants Context Scientific coefficients, device-format gains, unit conversions, and numeric tolerances were sometimes left as unexplained literals or repeated across callers, making origin and change impact difficult to audit. Decision and rationale Give each semantic calculation constant one named owner and a nearby source or purpose comment, while exempting ordinary indices and explicit UI geometry that do not encode scientific meaning. Changes Named and documented Rec.601, sRGB/CIE, DTA/RHS gains, SI conversions, tolerances, and empirical policies across facades and apps. Centralized repeated CSC charge-density, CIC display-unit, and curvature tolerance contracts. Added repository-wide magic-number and rectangle-interaction guardrails. User and data impact Calculation results are preserved, but future changes now expose the constant's meaning and provenance instead of silently editing an unexplained literal. Compatibility and migration No user migration is required. Public function call contracts and output schemas are unchanged by this record. Validation `MagicNumberGovernanceTest`, rectangle governance, module compatibility tests, and affected app tests cover centralized ownership and preserved numeric behavior. Evidence Source comments use the `Constant:` marker and guardrail diagnostics name the unmanaged file and line. Commit `125338c0` introduced the calculation-constant checks and the corresponding source annotations. Known limitations and follow-up The scanner targets semantically suspicious precision and notation; review is still required for simple integers or short decimals whose meaning is hidden."},{"title":"Migration helper cleanup","url":"history/records/2026/06/LK-20260630-migration-helper-cleanup.html","kind":"history","section":"history","keywords":"LK-20260630-migration-helper-cleanup 2026-06-30 23 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_ImageEnhance_app labkit_RHSPreview_app labkit_RHSPreview_app historical project evolution","text":"Migration helper cleanup id: LK-20260630-migration-helper-cleanup date: 2026-06-30 sequence: 23 type: refactor compatibility: compatible component: `labkit.ui` | `3.2.8 -> 3.2.9` component: `labkit_DICPostprocess_app` | `1.2.3 -> 1.2.4` component: `labkit_BatchImageCrop_app` | `1.3.7 -> 1.3.8` component: `labkit_BatchImageCrop_app` | `1.3.8 -> 1.3.9` component: `labkit_ImageEnhance_app` | `1.3.4 -> 1.3.5` component: `labkit_RHSPreview_app` | `1.2.2 -> 1.2.3` component: `labkit_RHSPreview_app` | `1.2.3 -> 1.2.4` scope: historical project evolution Context Several apps still split one small decision across multiple temporary helper files created during earlier package migrations. That made simple state and preview behavior harder to follow without creating a reusable API. Decision and rationale Consolidate related values behind one clearly named operation and delete pass-through helpers whose only purpose was reducing file length. Preserve the visible app behavior while making each calculation or state summary traceable from its caller. Changes DIC Post, Batch Crop, and RHS Preview patch bumped. Retired migration helper debt. Consolidated RHS preview window bounds, Batch Crop scale state, and Image Enhance export helpers. User and data impact No workflow or result format changed. The cleanup reduced internal indirection in Image Enhance export, Batch Crop scale summaries, RHS Preview window bounds, and a small set of DIC/electrochem helpers. Compatibility and migration The consolidated helpers preserved the app-facing state and result structures. No project or export format changed as the duplicate implementations were removed. Validation Commits `7f73b71b`, `e3349af6`, `733fb951`, `98a2b02c`, and `391540a7` added or updated focused tests for each consolidated operation. Evidence Main commits `7f73b71b`, `e3349af6`, `733fb951`, `98a2b02c`, and `391540a7`. Known limitations and follow-up This was a behavior-preserving cleanup. Later workflow-first packages replaced some of the package names shown in the historical commits."},{"title":"Minimal App contract and current authoring manuals","url":"history/records/2026/07/LK-20260716-minimal-app-contract.html","kind":"history","section":"history","keywords":"LK-20260716-minimal-app-contract 2026-07-16 115 docs compatible labkit-project App authoring Runtime V2 contract guardrails","text":"Minimal App contract and current authoring manuals id: LK-20260716-minimal-app-contract date: 2026-07-16 sequence: 115 type: docs compatibility: compatible component: `labkit-project` scope: App authoring scope: Runtime V2 contract guardrails Context Runtime V2 already supplied default project, session, action, presenter, and startup capabilities, but the complete-App tutorial and several current manuals still taught the retired split metadata and generic lifecycle package shape. The App structure guardrail also prohibited those files only when an App already owned `projectSpec.m`, leaving static Apps unprotected. Decision and rationale Make progressive capability the documented and tested authoring contract. A static App consists of a thin entrypoint, one definition, and one data-only layout. Actions, presentation, durable project state, transient reconstruction, and startup are added independently only when the product needs them. Changes Rewrote the complete-App tutorial around the three-file starting point and one `projectSpec.m` create/validate/migrate entry. Updated architecture, framework, private-App, library, testing, release, and history manuals plus scoped App rules and the App-builder skill to use definition-owned metadata and progressive optional capabilities. Added a hidden-GUI launch test whose definition omits every optional Runtime V2 component. Corrected the tutorial's numeric field declaration after the real launch test exposed its implicit text-control mismatch. Made the App package guardrail reject retired metadata, lifecycle, state, startup, and per-version migration files for every App shape. User and data impact App behavior and scientific results do not change. A developer can begin with three source files and add each larger architectural concept only when a real workflow requires it. Public and private App documentation now teach the same contract. Compatibility and migration This change documents and protects the already migrated Runtime V2 structure. It does not alter saved project payloads or remove the temporary low-level legacy project-migration reader used by framework tests. Validation Focused contract and GUI tests cover both the forbidden retired structures and an actual launch from the minimal definition. A documentation contract checks the tutorial, scoped App rules, and App-builder skill together. Documentation consistency checks and a regenerated site validate navigation and history metadata. Evidence [Build a Complete App](../../../../development/build-apps/complete-app.md) gives the progressive file-by-file tutorial. [Architecture](../../../../development/build-apps/architecture.md) defines the current package shape and ownership boundaries. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) specifies every optional component and callback. Known limitations and follow-up The minimal contract proves framework defaults, not that every complex App has already reached its lowest maintainable complexity. App-by-App workflow and framework-boundary audits continue separately."},{"title":"Minimal App definitions","url":"history/records/2026/07/LK-20260716-minimal-app-definition.html","kind":"history","section":"history","keywords":"LK-20260716-minimal-app-definition 2026-07-16 76 feat compatible labkit.ui App authoring Runtime V2 definition defaults","text":"Minimal App definitions id: LK-20260716-minimal-app-definition date: 2026-07-16 sequence: 76 type: feat compatibility: compatible component: `labkit.ui` | `7.0.0 -> 7.1.0` scope: App authoring scope: Runtime V2 definition defaults Context Runtime V2 already normalized missing project and session buckets, but every definition still had to provide project create/validate callbacks, at least one action, and a presenter. A static App therefore needed lifecycle files and placeholder functions that owned no behavior. Decision and rationale Make lifecycle components proportional to product behavior. Every App still declares a stable ID, title, and semantic layout. The framework supplies empty canonical state and no-op dynamic behavior until the App opts into durable data, transient cache, actions, or presentation. Changes Made `Project`, `Actions`, and `Present` optional in `labkit.ui.runtime.define`; `CreateSession` was already optional. Added a framework-owned version-1 empty project specification and empty presenter model. Allowed an empty action registry when no layout event or startup phase references an action. Added a GUI contract test that launches a definition containing only `Id`, `Title`, and `Layout` and verifies both canonical state roots. Rewrote App-development entry guidance around capability tiers instead of a fixed list of placeholder files. User and data impact Existing Apps behave unchanged. A new static App does not need `createProject.m`, `createSession.m`, `validateProject.m`, `definitionActions.m`, or a presenter. Those components are introduced only when the App gains corresponding behavior. Compatibility and migration The change is additive within UI 7. Existing complete definitions remain valid. Saved project and source schemas do not change. Validation The minimal-definition GUI test exercises real launch, canonical project and session creation, layout construction, empty presentation, and readiness. Definition validation and public documentation guardrails cover both minimal and full definitions. Evidence [App Framework](../../../../framework/README.md) introduces the minimal definition. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) lists required and optional definition components. [App Development](../../../../development/build-apps/app-development.md) maps files to capabilities instead of treating them as universal boilerplate. Known limitations and follow-up Apps that own a project schema still use explicit project specification. The next lifecycle cleanup consolidates each App's project factory, validator, and ordered migration steps behind one `projectSpec.m` entry point rather than scattered version-step files."},{"title":"Multi-app launcher packages","url":"history/records/2026/07/LK-20260709-multi-app-launcher-packages.html","kind":"history","section":"history","keywords":"LK-20260709-multi-app-launcher-packages 2026-07-09 42 feat compatible labkit_launcher Multi-app launcher packages","text":"Multi-app launcher packages id: LK-20260709-multi-app-launcher-packages date: 2026-07-09 sequence: 42 type: feat compatibility: compatible component: `labkit_launcher` | `1.2.7 -> 1.3.0` scope: Multi-app launcher packages Context The deployment tool could package one app, but laboratory workflows often used a small related set. Creating one ZIP per app duplicated the shared LabKit runtime, while manually merging ZIP files risked inconsistent entry files and manifests. The launcher's selected row also could not double as a multi-select without making Open and Debug ambiguous. Decision and rationale Add a separate Package checkbox to each launcher row. Keep the ordinary row selection for Open and Debug, and let checked rows produce one bundle with one entry file per app and a manifest describing the complete package. Changes `labkit_launcher` `1.2.7 -> 1.3.0` Project deployment tooling, multi-app bundle support. Added an independent `Package` checkbox column to the launcher app table so users can choose multiple apps without changing the row selected for Open or Debug. `Package Checked` and `Checked P-code` now create one zip containing every checked app, one direct entry file per app, and a multi-app manifest. Kept single-app package names, result fields, and manifest schema compatible when only one app is supplied to `packageLabKitApp`. User and data impact Users could distribute a related group of apps in one source or P-code ZIP without including the rest of the workbench. Opening and debugging still acted on the highlighted app, so preparing a package did not change normal launcher navigation. Compatibility and migration Existing direct calls that package one app continue to produce the original single-app package contract. Validation The commit expanded deployment unit tests, launcher packaging GUI coverage, selection helpers, changed-file routing, and build-task efficiency checks. The exact historical command was not recorded. Evidence Mainline commit `8a23a52`. Known limitations and follow-up Bundles intentionally included selected apps and their LabKit dependencies, not tests, full documentation, or unrelated private workspaces. P-code packages still required a compatible MATLAB runtime."},{"title":"Nerve Response Analysis uses fixed source identities","url":"history/records/2026/07/LK-20260716-nerve-response-structure.html","kind":"history","section":"history","keywords":"LK-20260716-nerve-response-structure 2026-07-16 107 refactor compatible labkit_NerveResponseAnalysis_app Neurophysiology App structure","text":"Nerve Response Analysis uses fixed source identities id: LK-20260716-nerve-response-structure date: 2026-07-16 sequence: 107 type: refactor compatibility: compatible component: `labkit_NerveResponseAnalysis_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context Nerve Response Analysis split one project schema across generic lifecycle files and kept two additional lifecycle helpers to filter and replace source records by role. Product metadata remained in separate requirement/version functions, while session and presentation code read nested portable-reference fields directly. Decision and rationale Use `filterRecord` and `protocol` as the two stable App-owned source IDs and matching roles. Runtime V2 already supports semantic path lookup by ID and an injected upsert service, so role-filter wrappers duplicate framework behavior without adding scientific meaning. Keep event detection, recording analysis, CAP measurement, presentation, and result writing in their existing concrete workflow packages. Changes Consolidated command identity, display metadata, version, requirements, project, session, layout, actions, presenter, renderer, and debug capability through the single definition. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved parsed JSON and transient workflow reconstruction to root `createSession`. Replaced App-owned role filtering/replacement helpers with `sourcePaths` and the injected `upsertSource` service. Strengthened validation so the two source IDs are unique, supported, and match their declared roles. Removed the generic lifecycle package and separate requirement/version files. User and data impact Filter/protocol selection, run limits, event and train detection, CAP metrics, issue handling, previews, output-folder defaults, JSON export, manifest output, reset, and project reopening are unchanged. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still combine their separate filter and protocol records into the canonical source collection. Runtime V2 invokes the single migration entry and owns iteration. Current version-2 files already written with the documented fixed IDs load without data transformation. Validation Focused unit tests cover migration, source identity validation, event trains, differential/common-mode calculations, CAP metrics, filter labels, and the legacy keep-column input. The hidden GUI workflow covers filter selection, analysis, counts/issues presentation, JSON and manifest export, project save, reset, reopen, and reanalysis. Evidence [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not establish that a selected protocol, stimulation source, or timing window is physiologically correct. RHS Preview still uses the older split lifecycle structure and requires its own independent convergence review."},{"title":"One project migration entry per App","url":"history/records/2026/07/LK-20260716-single-project-migration-entry.html","kind":"history","section":"history","keywords":"LK-20260716-single-project-migration-entry 2026-07-16 81 feat compatible labkit.ui Runtime V2 projects App authoring","text":"One project migration entry per App id: LK-20260716-single-project-migration-entry date: 2026-07-16 sequence: 81 type: feat compatibility: compatible component: `labkit.ui` | `7.2.1 -> 7.3.0` scope: Runtime V2 projects scope: App authoring Context Runtime V2 already applied project migrations sequentially, but each App had to expose a cell array containing every historical version-step function. Schema growth therefore increased files and wiring even though the framework owned the actual migration loop. Decision and rationale Give each persistent App one migration entry. `Migrate(project,fromVersion)` upgrades exactly one step; Runtime V2 supplies each missing version in order, validates every intermediate payload, and continues to the current version. Changes Added the scalar `Project.Migrate` callback. Kept per-step serialization validation, newer-version rejection, atomic load behavior, final project validation, session rebuild, and dirty upgrade state unchanged. Converted the framework's version-1-to-3 round-trip fixture to one callback with two version cases. Retained `Project.Migrations` only as a temporary bridge for existing Apps. User and data impact Saved projects behave as before. App maintainers can keep create, validate, and every schema transition as local functions in one `projectSpec.m` instead of maintaining a growing migration-file registry. Compatibility and migration The contract is additive within UI 7. Existing Apps continue to load during family-sized migration. An App must not declare both migration forms. Validation The Runtime V2 project test opens a version-1 payload in a version-3 App, executes both steps through one callback, verifies final data and source relinking, saves the current payload version, and checks failure atomicity. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents the project declaration and loop. [App Development](../../../../development/build-apps/app-development.md) describes the single project file. Known limitations and follow-up Existing Apps still need their lifecycle functions consolidated into `projectSpec.m`. The legacy migration-array bridge will be removed after the last family is migrated."},{"title":"Output folder prompts","url":"history/records/2026/06/LK-20260630-output-folder-prompts.html","kind":"history","section":"history","keywords":"LK-20260630-output-folder-prompts 2026-06-30 20 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Output folder prompts","text":"Output folder prompts id: LK-20260630-output-folder-prompts date: 2026-06-30 sequence: 20 type: feat compatibility: compatible component: `labkit.ui` | `3.2.5 -> 3.2.6` component: `labkit_DICPostprocess_app` | `1.2.0 -> 1.2.1` component: `labkit_DICPreprocess_app` | `1.2.0 -> 1.2.1` component: `labkit_BatchImageCrop_app` | `1.3.3 -> 1.3.4` component: `labkit_FocusStack_app` | `1.2.1 -> 1.2.2` component: `labkit_ImageEnhance_app` | `1.3.1 -> 1.3.2` component: `labkit_ImageMatch_app` | `1.3.1 -> 1.3.2` component: `labkit_NerveResponseAnalysis_app` | `1.2.1 -> 1.2.3` component: `labkit_ResponseReviewStats_app` | `1.2.1 -> 1.2.2` scope: Output folder prompts Context Exporting from different apps opened `uigetdir` directly, chose starting folders differently, and could not substitute a noninteractive chooser in a test. The repeated dialog code also made cancellation handling inconsistent. Decision and rationale Provide one output-folder prompt that selects a safe default, returns an explicit cancellation flag, remembers a successful folder, and accepts an injected chooser for tests. Apps would still decide when an output folder was needed and what they wrote there. Changes `labkit.ui` `3.2.5 -> 3.2.6` DIC apps, Batch Crop, Focus Stack, Image Enhance/Match, Nerve Response, and Response Review patch bumped. Added `promptOutputFolder`. Migrated output-folder prompts with chooser injection and safe defaults. User and data impact Output dialogs began in a useful folder and cancellation returned cleanly to the app. The selected folder was remembered as a preference; no output was created until the owning app performed its export. Compatibility and migration Existing output locations and exported file formats remained valid. Apps moved from direct folder dialogs to the shared prompt without changing what they wrote after selection. Validation Commit `c5055b98` added `AppHookHelpersTest` coverage for defaults, cancellation, successful selection, and chooser injection, then updated the affected app compatibility checks. Evidence Main commit `c5055b98`. Known limitations and follow-up Runtime V2 later moved dialog access into injected services, preserving the same separation between app decisions and platform dialog mechanics."},{"title":"Owner-minimal validation routing compiles canonical test groups","url":"history/records/2026/07/LK-20260722-owner-minimal-validation-routing.html","kind":"history","section":"history","keywords":"LK-20260722-owner-minimal-validation-routing 2026-07-22 157 test compatible Test routing Test ownership","text":"Owner-minimal validation routing compiles canonical test groups id: LK-20260722-owner-minimal-validation-routing date: 2026-07-22 sequence: 157 type: test compatibility: compatible scope: Test routing scope: Test ownership Context Changed-file validation could widen an App capability change to a family-level test folder, depend on implicit contract discovery, or execute the same test through overlapping folder routes. The migration ledger recorded the required zero-debt replacement: explicit App owners, framework consumer closure, and one execution of each selected official test. Decision and rationale Treat route selection and execution as separate concerns. Preserve every semantic owner route with its reason, discover the matching official tests, then union canonical identities into one non-GUI and one hidden-GUI execution group. This keeps coverage reviewable without paying for duplicate execution. Changes Moved App-specific tests into explicit `appContract`, `workbench`, source capability, `smoke`, and `isolatedPath` owners; moved cross-App audits to project ownership. Removed family-root and overlap-based route suppression. Added canonical test-union compilation, deterministic feature-tagged smoke selection, direct facade-consumer routing, and safe all-smoke fallback when required feature metadata is absent. Preserved physical suite-folder case during selector discovery and made an isolated App sample derive the repository root from the shared test setup, so owner paths behave consistently on Linux, macOS, and Windows. Focused ordinary App-manual, history, and library-documentation routes while retaining complete contracts for renderer and policy changes. Added dynamic guards for every public App's owned contracts and smoke proof, valid source-capability scopes, unique canonical execution, and focused documentation routes. Test classes also reject migrated test-shaped methods stranded in non-discoverable static-private blocks. User and data impact No App behavior, saved project, scientific result, or export changes. Local `changedFast` feedback is smaller and deterministic; CI remains the complete headless and hidden-GUI validation gate. Compatibility and migration No saved-data migration is required. The active test-routing migration ledger is retired; future routing changes must extend the durable ownership rules and their guardrails rather than restore compatibility fallbacks. Validation Focused build-routing guardrails, App ownership contracts, and moved cross-App contracts. Same-machine MATLAB R2025a before/after benchmark for a representative Figure Studio source-capability change. One final `buildtool changedFast` run for the completed diff. Evidence The planner prints all semantic routes and the canonical execution count, including the number of repeated selections removed. The final local gate and CI provide the executable evidence for the exact merged diff. For a change to the Figure Studio `sourceAxes/copyToPreview` capability, the pre-migration planner at the retained mainline baseline selected 46 test executions across three runner groups: 45 unique tests plus one repeated isolated-path contract. Those groups took 120.396 seconds after 5.284 seconds of planning. The migrated planner retained four explicit semantic routes, selected 11 unique tests with no repeated execution, and compiled them into one headless and one hidden-GUI group. Those groups took 51.134 seconds after 6.212 seconds of planning. Test execution time fell by 69.262 seconds (57.5%) while retaining the source-capability unit tests, the owning App's isolated path contract, its launch smoke test, and the relevant source-axes GUI tests. Known limitations and follow-up Hidden GUI smoke tests validate bounded launch/layout behavior, not native dialogs, pointer feel, visual quality, or scientific interpretation; those remain manual review responsibilities."},{"title":"Preview-area per-axis wheel zoom","url":"history/records/2026/07/LK-20260709-preview-area-per-axis-wheel-zoom.html","kind":"history","section":"history","keywords":"LK-20260709-preview-area-per-axis-wheel-zoom 2026-07-09 44 feat compatible labkit.ui Preview-area per-axis wheel zoom","text":"Preview-area per-axis wheel zoom id: LK-20260709-preview-area-per-axis-wheel-zoom date: 2026-07-09 sequence: 44 type: feat compatibility: compatible component: `labkit.ui` | `5.0.3 -> 5.0.4` scope: Preview-area per-axis wheel zoom Context The preview area applied the same two-dimensional wheel zoom to every registered axes. That was appropriate for an image or main plot, but awkward for a narrow histogram or color-scale axes: horizontal zoom changed a dimension whose layout was meant to remain fixed. Decision and rationale Let a preview-area definition choose `xy`, `x`, or `y` wheel zoom for each axes. Preserve `xy` as the default so existing apps keep their behavior, while side axes can opt into only the dimension that carries data. Changes `labkit.ui` `5.0.3 -> 5.0.4` Added a `scrollZoomAxes` preview-area layout option so apps can declare whether each preview axis should mouse-wheel zoom in `xy`, `x`, or `y`. Preview-area side axes can now remain horizontally stable while still allowing app-selected vertical wheel zoom. User and data impact A histogram or temperature scale could remain horizontally stable while still supporting vertical exploration. Main images and plots continued to zoom in both dimensions unless their app explicitly chose another mode. Compatibility and migration Existing preview areas keep default `xy` wheel zoom unless they opt into another per-axis setting. Validation The commit extended UI layout unit tests and axes-workbench GUI coverage for the default and per-axis modes. The exact historical command was not recorded. Evidence Mainline commit `3c143eb`. Known limitations and follow-up The option controlled wheel navigation only. Toolbar zoom, pan, linked axes, and app-specific limit policies remained separate concerns."},{"title":"Profiling and validation speedups","url":"history/records/2026/07/LK-20260702-profiling-and-validation-speedups.html","kind":"history","section":"history","keywords":"LK-20260702-profiling-and-validation-speedups 2026-07-02 30 ci compatible labkit_launcher labkit_launcher labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_ECGPrint_app Profiling and validation speedups","text":"Profiling and validation speedups id: LK-20260702-profiling-and-validation-speedups date: 2026-07-02 sequence: 30 type: ci compatibility: compatible component: `labkit_launcher` | `1.2.0 -> 1.2.1` component: `labkit_launcher` | `1.2.1 -> 1.2.2` component: `labkit.ui` | `3.4.0 -> 3.4.1` component: `labkit.ui` | `3.4.1 -> 3.4.2` component: `labkit_BatchImageCrop_app` | `1.6.0 -> 1.6.1` component: `labkit_ECGPrint_app` | `1.3.0 -> 1.3.1` scope: Profiling and validation speedups Context Startup and file-selection complaints could not be resolved reliably from wall clock impressions alone. At the same time, focused changes paid avoidable test discovery cost, GUI tests waited longer than the behavior required, and Batch Crop read every selected image before the user requested a preview or export. Decision and rationale Add a profiler that records launcher, startup, callback, and close costs with source attribution. Use its evidence to remove repeated UI updates and eager image reads. Route changed-file validation to the smallest owning suites and bound GUI waits without weakening their behavioral assertions. Changes `labkit_launcher` `1.2.0 -> 1.2.2` `labkit.ui` `3.4.0 -> 3.4.2` `labkit_BatchImageCrop_app` `1.6.0 -> 1.6.1` `labkit_ECGPrint_app` `1.3.0 -> 1.3.1` Added LabKit profiling and build-managed test routing to the launcher. Reduced GUI profiling overhead and deferred Batch Crop image reads until preview/export. Compressed validation runtime with bounded GUI waits. User and data impact Maintainers could launch a profiled target and receive a summarized report instead of interpreting MATLAB's raw profile table. Selecting many Batch Crop files returned sooner because images were decoded only for the current preview or final export. Routine validation also completed with less discovery and wait overhead. Compatibility and migration Profiling and test routing were maintainer tools. Batch Crop kept its task and export schemas; selected images were simply decoded later in the workflow. Validation The profiling tool received a dedicated unit suite. Later commits added launcher profiler coverage, deferred-read tests, debug-trace mirroring checks, routing guardrails, and bounded GUI-wait tests. Commit `25912c54` records successful `buildtool changed`, integration validation, and remote MATLAB tests. Evidence Main commits `c07dfc0a`, `74025fee`, `eadcca82`, `25912c54`, and `fcfc36d8`. Known limitations and follow-up These changes reduced measured work but did not eliminate the white window shown before all startup setup completed. The following startup pass changed when windows were painted and when scroll navigation was installed."},{"title":"Project restore distinguishes missing sources from damaged sources","url":"history/records/2026/07/LK-20260717-strict-project-session-restore.html","kind":"history","section":"history","keywords":"LK-20260717-strict-project-session-restore 2026-07-17 125 fix compatible labkit.ui labkit_FigureStudio_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageMatch_app labkit_ImageEnhance_app labkit_NerveResponseAnalysis_app Runtime project restore App session reconstruction","text":"Project restore distinguishes missing sources from damaged sources id: LK-20260717-strict-project-session-restore date: 2026-07-17 sequence: 125 type: fix compatibility: compatible component: `labkit.ui` | `7.4.6 -> 7.4.7` component: `labkit_FigureStudio_app` | `0.2.8 -> 0.2.9` component: `labkit_FLIRThermal_app` | `1.4.6 -> 1.4.7` component: `labkit_FocusStack_app` | `1.5.5 -> 1.5.6` component: `labkit_ImageMatch_app` | `1.6.6 -> 1.6.7` component: `labkit_ImageEnhance_app` | `1.6.6 -> 1.6.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.6 -> 1.4.7` scope: Runtime project restore scope: App session reconstruction Context Runtime already resolved missing required source paths and offered interactive relinking before it rebuilt an App session. Several session factories then caught every decoder exception and returned an empty cache. A file that still existed but was damaged, unsupported, or exposed a programming error could therefore look like an unresolved source or an empty project. Decision and rationale Keep path resolution and cancellation in Runtime, and make reconstruction of an existing source strict. A session factory may accept an explicitly absent optional source, but it must not reinterpret an exception from an existing file. Runtime wraps reconstruction failures with `inputs.sources` IDs, roles, and filenames, records the exception in diagnostics, and preserves the prior state and presentation. Changes Removed broad exception recovery from Figure Studio, Focus Stack, Image Match, Image Enhance, FLIR Thermal, and Nerve Response Analysis session reconstruction. Made FLIR restore use strict thermal decoding while retaining skip-and-report behavior for interactive batch import. Added Runtime diagnostic context and a field-specific `ProjectSessionRestoreFailed` error without replacing the decoder cause. Added atomic corrupt-source, diagnostic-delivery, cross-App factory, and no-broad-catch regression coverage. User and data impact Missing required files still open the relink workflow, and cancelling it leaves the current project untouched. Existing corrupt or unsupported files now stop the load with the relevant project source identities and filenames instead of showing an empty preview. The prior document remains usable. App developers can leave ordinary decoder failures uncaught in `createSession.m`; Runtime owns the user-visible load failure and diagnostic record. Compatibility and migration No saved-project migration is required. Project envelopes and source-reference formats are unchanged. This change only corrects failure handling while reconstructing transient state. Validation Focused validation covers successful project round trips, missing-source relink, cancellation rollback, existing corrupt source rollback, diagnostic delivery, all affected session factories, and the repository guardrail that forbids broad catches in `createSession.m`. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md#session-actions-presentation-and-renderers) [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Focus Stack](../../../../apps/image-measurement/focus-stack/README.md) [Image Match](../../../../apps/image-measurement/image-match/README.md) [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) Known limitations and follow-up Automated hidden GUI tests verify state rollback and semantic error delivery; they do not replace manual review of native dialog wording or damaged third-party file variants."},{"title":"Protected image enhancement workflows","url":"history/records/2026/06/LK-20260629-protected-image-enhancement-workflows.html","kind":"history","section":"history","keywords":"LK-20260629-protected-image-enhancement-workflows 2026-06-29 16 feat compatible labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"Protected image enhancement workflows id: LK-20260629-protected-image-enhancement-workflows date: 2026-06-29 sequence: 16 type: feat compatibility: compatible component: `labkit_ImageEnhance_app` | `1.2.2 -> 1.3.0` component: `labkit_ImageMatch_app` | `1.2.1 -> 1.3.0` scope: historical project evolution Context The existing appearance adjustments could change a subject together with its background. Image Enhance and Image Match needed modes that protected subject detail while correcting bright backgrounds and tone. Decision and rationale Add subject-preserving enhancement and protected tone matching as explicit methods with their own parameters, descriptions, and tests. Keep these methods inside the image apps because they encode workflow-specific decisions rather than generic image primitives. Changes Image Enhance `1.2.2 -> 1.3.0` Image Match `1.2.1 -> 1.3.0` Added Subject-preserving enhance to Image Enhance. Added Protected tone to Image Match. Added calculation and GUI coverage for both methods. User and data impact Users could brighten or normalize a background with less change to the main subject. The new methods were optional; existing histories and other matching methods retained their previous behavior. Compatibility and migration Existing enhancement steps and Image Match inputs remained valid. The new protected methods were additional choices and did not rewrite saved source images. Validation Commit `1768dd57` added focused Image Enhance and Image Match unit tests and updated both GUI workflow tests. Evidence Main commit `1768dd57`. Known limitations and follow-up The protected methods reduce unwanted subject changes but cannot infer semantic foreground perfectly. Their result still requires visual review."},{"title":"Public API help is complete, discoverable, and behavior checked","url":"history/records/2026/07/LK-20260717-complete-public-api-help-contract.html","kind":"history","section":"history","keywords":"LK-20260717-complete-public-api-help-contract 2026-07-17 127 docs compatible labkit.biosignal labkit.dta labkit.rhs labkit.thermal labkit.image labkit.ui labkit_DICPreprocess_app labkit_DICPostprocess_app labkit_CSC_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Public MATLAB help Generated API reference Documentation contract tests","text":"Public API help is complete, discoverable, and behavior checked id: LK-20260717-complete-public-api-help-contract date: 2026-07-17 sequence: 127 type: docs compatibility: compatible component: `labkit.biosignal` | `1.0.2 -> 1.0.3` component: `labkit.dta` | `2.0.2 -> 2.0.3` component: `labkit.rhs` | `1.0.2 -> 1.0.3` component: `labkit.thermal` | `1.1.1 -> 1.1.2` component: `labkit.image` | `2.0.1 -> 2.0.2` component: `labkit.ui` | `7.5.0 -> 7.5.1` component: `labkit_DICPreprocess_app` | `1.5.7 -> 1.5.8` component: `labkit_DICPostprocess_app` | `1.4.6 -> 1.4.7` component: `labkit_CSC_app` | `1.4.7 -> 1.4.8` component: `labkit_BatchImageCrop_app` | `1.7.6 -> 1.7.7` component: `labkit_FLIRThermal_app` | `1.4.7 -> 1.4.8` component: `labkit_ImageMatch_app` | `1.6.7 -> 1.6.8` component: `labkit_VideoMarker_app` | `1.5.6 -> 1.5.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.7 -> 1.4.8` component: `labkit_ResponseReviewStats_app` | `1.4.6 -> 1.4.7` scope: Public MATLAB help scope: Generated API reference scope: Documentation contract tests Context LabKit's generated function pages came from MATLAB help, but the contract test covered a hand-maintained subset of packages and only checked broad section presence. A public function, implemented option, default, legal value, failure mode, or related API could therefore be omitted while the documentation suite still passed. Several App calculation pages exposed exactly those gaps. Decision and rationale Treat the callable public surface as discoverable source truth. The contract now scans every non-private function below `+labkit` plus each App API declared in the API catalog. It derives standard option reads from implementation code and requires those fields to have documented values and defaults, while required options remain explicitly distinguishable from optional ones. Every public function also states failure behavior and a `See also` relationship. This keeps a generated page useful for standalone MATLAB callers instead of merely repeating a short summary. Changes Completed help for the Contract, Biosignal, DTA, RHS, Thermal, Image, UI, and cataloged App calculation surfaces. Added exact option/default/legal-value checks, package-wide discovery, failure-behavior checks, and related-API checks to the documentation contract. Added a synthetic regression proving the guard detects an undocumented option, an omitted default, a vague value domain, missing failure behavior, and a missing related-API link. Made `labkit.image.resizeToFit` reject unsupported interpolation method names instead of silently treating them as bilinear. Linked owning library and App manuals to the detailed generated reference. User and data impact Readers can now determine valid calls, defaults, option domains, outputs, empty-result behavior, errors, and adjacent functions from the generated page for a public symbol. Developers adding a public function or a standard option read receive a focused contract failure when the corresponding help is incomplete. Compatibility and migration No saved project or data migration is required. Existing valid calls are unchanged. A call to `labkit.image.resizeToFit` with a method other than `\"bilinear\"` or `\"nearest\"` now raises `labkit:image:InvalidResizeMethod`; such values were never part of the documented contract. Validation The exact `PublicApiDocumentationContractTest` suite passes all eight tests, including execution of cataloged examples and the new synthetic defect regression. Image facade unit tests cover both supported resize methods and the unsupported-method error. Generated-site consistency and changed-scope validation are run after this record and its component manuals are rendered. Evidence [Public API index](../../../../reference/README.md) [LabKit App Framework](../../../../framework/README.md) [App catalog](../../../../apps/README.md) [Documentation maintenance](../../../../development/maintain-and-release/documentation.md) Known limitations and follow-up Implementation-derived option discovery intentionally recognizes the repository's standard option access patterns; unusual dynamic field access still requires review. Private helpers remain outside the public-page contract, and interactive `Typical Call:` sketches are not executed as file-independent examples. labkit.image.resizeToFit Resize an image to fit within maximum row/column limits."},{"title":"Public library help contracts and reference validation","url":"history/records/2026/07/LK-20260716-public-api-help-contracts.html","kind":"history","section":"history","keywords":"LK-20260716-public-api-help-contracts 2026-07-16 62 docs compatible labkit.biosignal labkit.dta labkit.image labkit.rhs labkit.thermal public MATLAB help generated API reference documentation contract tests","text":"Public library help contracts and reference validation id: LK-20260716-public-api-help-contracts date: 2026-07-16 sequence: 62 type: docs compatibility: compatible component: `labkit.biosignal` | `1.0.1 -> 1.0.2` component: `labkit.dta` | `2.0.1 -> 2.0.2` component: `labkit.image` | `2.0.0 -> 2.0.1` component: `labkit.rhs` | `1.0.1 -> 1.0.2` component: `labkit.thermal` | `1.1.0 -> 1.1.1` scope: public MATLAB help scope: generated API reference scope: documentation contract tests Context The public library reference identified functions and basic syntax but did not consistently explain option values, units, returned structures, failure behavior, or the distinction between executable examples and calls that need a user-supplied file. Readers still had to inspect implementation files to use several GUI-free APIs correctly. Decision and rationale Treat each public MATLAB help block as the source-level call contract and render that contract into one API page per function. Detailed library validation is kept separate from project-navigation validation so both remain focused and within the repository's effective-code-line budget. The patch versions record a compatible documentation-contract improvement. Function signatures, calculations, returned data, and error identifiers are unchanged. Changes Expanded public help with syntax, inputs, outputs, option names and legal values, defaults, units, errors, examples or typical calls, and related APIs. Distinguished self-contained `Example:` sections from interactive or file-dependent `Typical Call:` sections. Added generated-reference checks for thermal conversion and rendering, DTA schemas and pulse modes, and RHS lazy reads and waveform units. Corrected stale biosignal documentation links in package guidance and an implementation contract. Made label-ownership and package-boundary scans ignore prose-only substring collisions while continuing to reject literals and forbidden MATLAB tokens in executable code. User and data impact Users can discover the supported call contract through both `help` and the generated HTML reference without launching an app. No file format, saved project, calculation, or exported value changes. Compatibility and migration This is a patch-level compatible change. Existing calls require no migration. Validation Documentation consistency checks validate generated sections and executable examples. Project guardrails validate version/history ownership, literal ownership, package boundaries, and effective code-line budgets. Evidence Public help blocks and generated API pages are the maintained contract. Known limitations and follow-up Interactive calls and calls requiring laboratory files remain `Typical Call:` sketches until the repository provides synthetic, non-sensitive fixtures that make them executable examples."},{"title":"RHS Preview separates source roles from reference mechanics","url":"history/records/2026/07/LK-20260716-rhs-preview-structure.html","kind":"history","section":"history","keywords":"LK-20260716-rhs-preview-structure 2026-07-16 108 refactor compatible labkit_RHSPreview_app Neurophysiology App structure","text":"RHS Preview separates source roles from reference mechanics id: LK-20260716-rhs-preview-structure date: 2026-07-16 sequence: 108 type: refactor compatibility: compatible component: `labkit_RHSPreview_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context RHS Preview split one durable project across generic lifecycle files and used two additional lifecycle helpers to select or replace records by role. It also constructed variable filter-source IDs itself and read Runtime-owned reference fields in session, actions, and presentation code. Product metadata remained in separate requirement/version functions. Decision and rationale Keep the three App-owned roles because they express real workflow meaning: one preview recording, one optional protocol, and an ordered collection of filter recordings. Use Runtime V2 for portable path access, fixed-source upserts, and stable reconciliation of the variable collection. Keep only the small App-owned role-to-path selection policy under `sourceFiles`; do not introduce a new framework public role API for one App-specific collection shape. Changes Consolidated command identity, display metadata, version, requirements, project, session, layout, actions, presenter, renderer, and debug capability through the single definition. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved indexing, initial preview reads, filter discovery, and transient view reconstruction to root `createSession`. Added `sourceFiles.pathsForRole` to select App-owned source roles while delegating reference decoding to `labkit.ui.runtime.sourcePaths`. Replaced fixed-source construction with `upsertSource` and variable filter ID construction with `reconcileSources`, preserving filter order and stable identities across rediscovery. Removed the generic lifecycle package and separate requirement/version files. User and data impact RHS indexing, lazy waveform windows, channel selection, managed ROI/scroll interaction, protocol editing, multi-file filter labels, refresh/removal, exports, manifests, reset, and project reopening retain their behavior. New filter selections use Runtime-generated stable IDs; those IDs are internal project identities and do not change exported recording paths or labels. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still combine their separate recording, protocol, and filter source fields before validation. Existing version-2 source IDs and ordering are preserved when their resolved files remain selected. Runtime V2 owns migration iteration and later saves remain version 2. Validation Focused unit tests cover source migration, role-path ordering, summary/detail models, channel-role drafts, filter labels, and preview bounds. The hidden GUI workflow covers indexing and drawing a synthetic RHS file, channel tables, two-file filter discovery, protocol/filter export, manifests, project save, reset, reopen, and reconstruction of both preview and filter sources. Evidence [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not assess native file-dialog feel, physical channel-role correctness, or waveform interpretation. The remaining special startup path and repository guidance still require separate convergence review."},{"title":"Release validation gate and GUI CI hardening","url":"history/records/2026/07/LK-20260708-release-validation-gate-and-gui-ci-hardening.html","kind":"history","section":"history","keywords":"LK-20260708-release-validation-gate-and-gui-ci-hardening 2026-07-08 40 ci compatible historical project evolution","text":"Release validation gate and GUI CI hardening id: LK-20260708-release-validation-gate-and-gui-ci-hardening date: 2026-07-08 sequence: 40 type: ci compatibility: compatible scope: historical project evolution Context Release-candidate tags did not yet require one explicit summary gate over the headless, coverage, and GUI jobs. Some GUI assertions also depended on exact pixel ordering or timing that varied across CI display backends. Decision and rationale Require every release test job before publication, while testing semantic grid structure instead of platform-specific pixel rounding. Increase the shared GUI idle allowance so slower hosted displays can finish registered UI work. Changes Project validation workflow, no component version change. Release candidate tags now run the full MATLAB test workflow gate before publication: headless tests, coverage, GUI tests, and a release summary gate. GUI layout tests now assert structural grid contracts instead of platform-dependent flattened pixel ordering or width comparisons. Shared GUI test idle waiting allows slower CI display backends more time to finish registered UI work. User and data impact Published release candidates gained a single pass/fail validation signal. Application behavior and data formats did not change; the GUI suite became less sensitive to harmless platform layout differences. Compatibility and migration No known manual migration. Validation Commit `f359518` updated CI policy tests, declarative UI tests, launcher GUI tests, and shared wait behavior. Evidence Mainline commit `f359518`. Known limitations and follow-up The gate covers automated checks only. Interactive workflow feel and scientific visual review remain outside hosted CI."},{"title":"Release validation includes Base MATLAB compatibility","url":"history/records/2026/07/LK-20260717-release-validation-hardening.html","kind":"history","section":"history","keywords":"LK-20260717-release-validation-hardening 2026-07-17 129 ci compatible scheduled and release-candidate validation","text":"Release validation includes Base MATLAB compatibility id: LK-20260717-release-validation-hardening date: 2026-07-17 sequence: 129 type: ci compatibility: compatible scope: scheduled and release-candidate validation Context LabKit already exposed `buildtool baseMatlab`, but GitHub Actions did not run the broad MATLAB product-ownership analysis. A release candidate could therefore pass headless, coverage, and GUI jobs while the explicit Base MATLAB compatibility gate remained a local-only check. Decision and rationale Run the existing public `baseMatlab` task in scheduled and manually dispatched workflows, and require it before a validated release tag can be created. Ordinary pull requests and main pushes retain the smaller headless gate because the static call scan and representative fallback workflows already run there. Changes Added a dedicated Base MATLAB compatibility job with JUnit, active-test, log, and artifact reporting. Added that job to the release-test dependency gate. Corrected the CI contract test so it discovers actual `tasks:` values and checks them against the executable build-task catalog. Updated artifact upload jobs to the current Node.js 24 action generation. User and data impact No App behavior or saved data changes. A release tag is now blocked when MATLAB resolves production source to an undeclared MathWorks product. Compatibility and migration The public `buildtool baseMatlab` command is unchanged. GitHub-hosted runners meet the action runtime requirement; no self-hosted runner migration is introduced. Validation The CI policy contract verifies job scope, public-task ownership, diagnostics, timeouts, parent checkout depth, and release-gate dependencies. The Base MATLAB task remains covered by its own dependency guardrail. Evidence `.github/workflows/matlab-tests.yml` is the executable release gate; `tests/cases/contract/project/ci/CiValidationPolicyGuardrailTest.m` protects its routing and dependency structure. Known limitations and follow-up The product-ownership analysis depends on MATLAB's dependency report and cannot replace manual scientific or interactive App validation."},{"title":"Response Review Stats uses one project contract","url":"history/records/2026/07/LK-20260716-response-review-structure.html","kind":"history","section":"history","keywords":"LK-20260716-response-review-structure 2026-07-16 105 refactor compatible labkit_ResponseReviewStats_app Neurophysiology App structure","text":"Response Review Stats uses one project contract id: LK-20260716-response-review-structure date: 2026-07-16 sequence: 105 type: refactor compatibility: compatible component: `labkit_ResponseReviewStats_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context Response Review Stats kept project creation, validation, its one source-schema migration, and session reconstruction in four generic lifecycle files. Session and presentation code also read the Runtime's nested source path directly, and the reset action depended on the generic lifecycle package. Decision and rationale Use the standard compact Runtime V2 structure: one definition, one project contract, and one root session factory. Metric alignment, measurement, summaries, source parsing, exports, and presentation already had clear owners and remain unchanged. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderer, and debug capability in `definition.m`. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec.m`. Moved transient metric/alignment reconstruction to root `createSession.m` and reused it for the reset action. Replaced direct portable-reference reads with semantic `sourcePaths` lookup. Removed the generic lifecycle package and separate requirement/version files. User and data impact Analysis JSON and segment CSV loading, automatic window recomputation, aligned preview, metric summaries, reset, output-folder choice, CSV export, and result manifest behavior are unchanged. Developers now see the entire durable schema history through one entry instead of four lifecycle files. Compatibility and migration Durable schema version 2 is unchanged. Version-1 projects still move their singular source to the canonical source collection before validation and the next save. Validation Focused tests cover source migration, segment parsing, time-grid alignment, measurement, pair-group summaries, and default-grid selection. The hidden GUI workflow loads representative metrics, presents them, and exports the CSV and manifest through real Runtime callbacks. Evidence [Response Review And Stats](../../../../apps/neurophysiology/response-review-stats/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) Known limitations and follow-up Automated checks do not validate biological interpretation of a chosen baseline/noise window. The larger RHS Preview and Nerve Response Analysis Apps still require independent ownership review."},{"title":"Runtime V2 lifecycle ownership across the app fleet","url":"history/records/2026/07/LK-20260715-runtime-v2-app-migration.html","kind":"history","section":"history","keywords":"LK-20260715-runtime-v2-app-migration 2026-07-15 59 refactor breaking labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app `docs/` `docs/ui-runtime-redesign.md` runtime migration guidance","text":"Runtime V2 lifecycle ownership across the app fleet id: LK-20260715-runtime-v2-app-migration date: 2026-07-15 sequence: 59 type: refactor compatibility: breaking component: `labkit.ui` | `5.2.0 -> 6.0.0` component: `labkit_DICPostprocess_app` | `1.3.6 -> 1.4.0` component: `labkit_DICPreprocess_app` | `1.4.0 -> 1.5.0` component: `labkit_ChronoOverlay_app` | `1.3.6 -> 1.4.0` component: `labkit_CIC_app` | `1.3.8 -> 1.4.0` component: `labkit_CSC_app` | `1.3.10 -> 1.4.0` component: `labkit_EIS_app` | `1.3.4 -> 1.4.0` component: `labkit_VTResistance_app` | `1.3.8 -> 1.4.0` component: `labkit_GaitAnalysis_app` | `1.0.0 -> 1.1.0` component: `labkit_BatchImageCrop_app` | `1.6.8 -> 1.7.0` component: `labkit_CurvatureMeasurement_app` | `1.3.5 -> 1.4.0` component: `labkit_FLIRThermal_app` | `1.3.0 -> 1.4.0` component: `labkit_FocusStack_app` | `1.4.9 -> 1.5.0` component: `labkit_ImageEnhance_app` | `1.5.8 -> 1.6.0` component: `labkit_ImageMatch_app` | `1.5.8 -> 1.6.0` component: `labkit_VideoMarker_app` | `1.2.0 -> 1.3.0` component: `labkit_FigureStudio_app` | `0.1.5 -> 0.2.0` component: `labkit_NerveResponseAnalysis_app` | `1.3.5 -> 1.4.0` component: `labkit_ResponseReviewStats_app` | `1.3.5 -> 1.4.0` component: `labkit_RHSPreview_app` | `1.3.4 -> 1.4.0` component: `labkit_ECGPrint_app` | `1.3.5 -> 1.4.0` scope: `docs/` scope: `docs/ui-runtime-redesign.md` scope: runtime migration guidance Context App lifecycle, callback ordering, UI-derived state, project persistence, graphics resources, and result packaging were implemented repeatedly across apps. Migrating only the lifecycle names would have preserved the main author cost: each app would still need to understand raw controls, figure callbacks, and multiple competing state copies. Decision and rationale Make Runtime V2 the only write path and give the framework ownership of launch, startup, the FIFO event queue, atomic state commits, deterministic presentation, dialogs, interaction resources, project persistence, and result manifests. Apps retain explicit project/session/presentation/result contracts and all domain calculations, workflow decisions, plots, schemas, and exports. This deliberately optimizes ownership and learning cost rather than total app line count. Explicit app contracts can add code in a large scientific app, but they replace hidden closure state and callback plumbing with one inspectable model. The remaining 36 public UI functions have distinct reviewed contracts; the earlier 32-function planning target was not forced through vague APIs. Changes Migrated all twenty public apps to Runtime V2 definitions, standard launch, canonical project/session state, semantic events, pure presentation, and managed resources. Standardized current project writes and result manifests while retaining app-owned payload migrations and existing output files. Retired Runtime V1 writes and removed the public control-registry mutation, standalone editor/runtime, preview mutation, dialog, title, and dispatch compatibility surfaces. Kept supported V1 snapshots and named legacy app projects as read-only import formats; subsequent saves use the current project codec. Updated app requirements to `labkit.ui >=6 <7` and made UI 6 the breaking public facade boundary. Replaced Video Marker's optional `vision.PointTracker` branch with one deterministic app-owned multiscale patch matcher, retaining confidence, constant-velocity fallback, subpixel coordinates, and forward-cache behavior without Computer Vision Toolbox. Added traceable temporary MathWorks-product debt declarations: rapid app work must ship a base-MATLAB fallback plus deterministic and Toolbox-parity tests, and product analysis must continue to see the dependency until replacement. Re-audited already migrated apps, removed five dead or presenter-only helper files, and updated helper-quality classification from retired role packages to the current workflow-first lifecycle/state/result/UI boundaries. Corrected Runtime V2 interaction event construction so paired-anchor cell payloads remain one scalar semantic event. DIC Preprocess point matching now accepts alternating reference/moving points, enables alignment after two complete pairs, and completes the in-workbench rigid registration flow. Hardened the same scalar-envelope invariant for app-managed Runtime V2 resources and `runBusy` callback capture, including legitimate cell-form MATLAB callbacks and cell-valued resource payloads. Made project replacement invalidate the prior presentation cache before the fresh session is rendered, so opening an unchanged project still rebuilds app-owned preview graphics and other ephemeral visual resources. Corrected shared file-event index decoding for R2025a string results, so CIC and the other multi-file V2 apps can select and remove imported items. Stopped presentation commits from rerunning unchanged preview requests, and made DIC point-label updates preserve image handles and zoom viewports. Routed anchor-editor wheel input to the shared image zoom implementation without the retired wrapper's same-name recursion. Deferred CIC and VT batch DTA decoding until a file becomes visible or the batch is exported, so multi-file selection no longer blocks on every source. Let Gait Analysis read current Video Marker project/recovery envelopes and legacy Video/Image Marker autosaves directly as pose-coordinate inputs. Added interaction-mode subtitles for curve anchors, point marking, paired anchors, scale references, and fixed point slots; restored axes context menus after renderer resets; and promoted screenshot/project commands to top-level window entries. Moved Video Marker's Session controls to the top of the Video page, added an `Open MAT` project shortcut, and made New setup explicitly cancel, save, or discard before clearing the current project. Reorganized human documentation into getting-started, app workflow, public API, development, and focused-guide layers. Split app authoring from the user catalog, added the missing contract API reference, and made a project guardrail verify that every public `+labkit` function remains indexed under its owning facade. User and data impact App entrypoint names, scientific calculations, workflow decisions, plots, and existing export filenames remain stable. Current projects have a consistent save/reopen path, external sources use portable references, and result exports include a standard manifest. Runtime errors and modal interactions now pass through framework services, which also makes hidden validation deterministic. Compatibility and migration The migrated app versions require `labkit.ui >=6 <7`; they must not be copied into a UI 5 checkout. Supported V1 snapshots and documented legacy projects remain importable but are not written again. After import, save a new current project if continued editing or recovery is required. Validation Focused Runtime V2, project, interaction-hub, DIC point-matching, CIC, Figure Studio, app-boundary, and public-surface tests passed. The latest repair checkpoint additionally passed CIC/VT GUI workflows (2/2), Gait/app compatibility tests (21/21), Curvature and Video Marker GUI methods (6/6), focused framework menu/interaction tests (8/8), and three targeted interaction-hint methods (3/3). A real local legacy Video Marker autosave was read successfully without copying it into the repository. The documentation hierarchy checkpoint passed relative-link validation and 44/44 focused documentation, app-structure, build-task, and history guardrails. The Phase-6 `buildtool changedFast` checkpoint passed 15 framework GUI tests, 284 headless tests with one environment-assumption skip, and six representative GUI workflows. The final broad gate and any manual pointer or visual checks are reported with the merge handoff rather than embedded as mutable history. Video Marker replacement tests exercise integer and subpixel translation, flat-patch fallback, repeated-input determinism, prediction caching, and coordinate parity with `vision.PointTracker` when that product is installed. `buildtool baseMatlab` confirms the production source resolves only to MATLAB. Evidence `4454ca30` introduced the Runtime V2 kernel; the app migrations then ran from Chrono Overlay (`5fe76ee4`) through Figure Studio (`21936e8d`). `1ef46bfd` removed the retired UI runtime, and `8e109ff4` completed the framework contracts used by the migrated apps. The commits listed in the migration sequence above preserve the individual app checkpoints; this record explains their shared architectural result. Known limitations and follow-up Explicit domain contracts mean that large apps do not necessarily have fewer production lines. Further extraction is justified only by repeated mechanics, not by file-size targets. Pointer feel, drag ergonomics, and scientific visual judgment still require the documented manual app review before merge."},{"title":"Runtime diagnostics and workflow repair converge on one product path","url":"history/records/2026/07/LK-20260726-runtime-diagnostics-and-workflow-repair.html","kind":"history","section":"history","keywords":"LK-20260726-runtime-diagnostics-and-workflow-repair 2026-07-26 159 feat breaking labkit_launcher labkit.app labkit.dta labkit.image labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app Always-on session diagnostics and semantic App events Clean startup and explicit synthetic-input generation Launcher repair boundary and preserved product interaction Batch Crop native ROI interaction and preview resolution Explicit DTA units and EIS impedance display units","text":"Runtime diagnostics and workflow repair converge on one product path id: LK-20260726-runtime-diagnostics-and-workflow-repair date: 2026-07-26 sequence: 159 type: feat compatibility: breaking component: `labkit_launcher` | `1.6.0 -> 1.7.0` component: `labkit.app` | `1.2.4 -> 2.0.0` component: `labkit.dta` | `2.0.3 -> 3.0.0` component: `labkit.image` | `2.0.2 -> 2.0.3` component: `labkit_DICPostprocess_app` | `1.5.1 -> 1.6.0` component: `labkit_DICPreprocess_app` | `1.6.1 -> 1.7.0` component: `labkit_ChronoOverlay_app` | `1.5.1 -> 1.6.0` component: `labkit_CIC_app` | `1.5.1 -> 1.6.0` component: `labkit_CSC_app` | `1.5.1 -> 1.6.0` component: `labkit_EIS_app` | `1.5.2 -> 1.6.0` component: `labkit_VTResistance_app` | `1.5.1 -> 1.6.0` component: `labkit_GaitAnalysis_app` | `2.1.1 -> 2.2.0` component: `labkit_BatchImageCrop_app` | `1.8.1 -> 1.9.0` component: `labkit_CurvatureMeasurement_app` | `1.5.1 -> 1.6.0` component: `labkit_FLIRThermal_app` | `1.5.1 -> 1.6.0` component: `labkit_FocusStack_app` | `1.6.1 -> 1.7.0` component: `labkit_ImageEnhance_app` | `1.7.1 -> 1.8.0` component: `labkit_ImageMatch_app` | `1.7.1 -> 1.8.0` component: `labkit_VideoMarker_app` | `1.6.1 -> 1.7.0` component: `labkit_FigureStudio_app` | `0.6.5 -> 0.7.0` component: `labkit_NerveResponseAnalysis_app` | `1.5.1 -> 1.6.0` component: `labkit_ResponseReviewStats_app` | `1.5.1 -> 1.6.0` component: `labkit_RHSPreview_app` | `1.5.1 -> 1.6.0` component: `labkit_TTestWizard_app` | `1.2.0 -> 1.3.0` component: `labkit_ECGPrint_app` | `1.5.1 -> 1.6.0` scope: Always-on session diagnostics and semantic App events scope: Clean startup and explicit synthetic-input generation scope: Launcher repair boundary and preserved product interaction scope: Batch Crop native ROI interaction and preview resolution scope: Explicit DTA units and EIS impedance display units Context Debug launching had combined synthetic data, diagnostic persistence, and App startup into one mode, while ordinary incidents often left too little useful history. App-owned Log tabs and status/error adapters competed with a partial framework recorder. Batch Crop also exposed the practical consequence of testing construction without native interaction: its ROI could be visible yet fail to respond, and ordinary images were unnecessarily downsampled. The root Launcher had simultaneously accumulated installed-product behavior, repair implementation, and maintenance-tool logic in one rescue file. DTA inputs also carried ambiguous unit inference into downstream electrochemistry workflows. Decision and rationale Every normal App launch now owns one clean Runtime session with an always-on, privacy-bounded event journal. Runtime instruments framework operations; Apps add stable semantic events through `CallbackContext.log`. Trace capture, session viewing, and bundle export are runtime tools, while synthetic input generation is a separate explicit developer action that never loads its output or changes the open project. The public App SDK advances to 2.0 because launch diagnostic options and the old status, error, checkpoint, count, recorder, and debug-sample contracts were removed rather than kept as misleading compatibility aliases. Each migrated App requires `labkit.app >=2 <3`. The root Launcher remains a small self-contained repair entry. The installed launcher keeps the established catalog, status, App information, double-click launch, version browsing, and visual identity, while independent maintenance operations live under `tools/`. Changes Added canonical session events, durable bounded journals, active-session leases, degradation reporting, operation/result versus rollback semantics, a live session viewer, trace control, and diagnostic bundle export. Migrated all public Apps to semantic user/developer events, removed duplicate Log tabs, and exposed explicit anonymous synthetic-input generation from the ordinary Tools menu. Kept default launches data-free and action-free; hidden GUI tests now own their visibility fixture instead of relying on a test tag. Restored Batch Crop ROI body drag, center drag, and click-to-place behavior through managed native interaction, while preserving the viewport and using full-resolution previews unless an explicit high-resolution budget applies. Made DTA item units explicit, rejected unknown pulse modes, and added EIS impedance display choices from milliohms through megohms with kilohms as the default. Split Launcher rescue, installed composition, version management, cleanup, and documentation responsibilities without removing product capabilities. User and data impact Users can diagnose a problem after it occurs in an ordinary session, export a sanitized history, and deliberately generate anonymous reproduction inputs without restarting in a special mode. App status remains concise, while developer detail is available through Diagnostics. Existing scientific source files are not rewritten. Batch Crop keeps source resolution for ordinary images and provides responsive native ROI editing. EIS changes only display and export scaling selected by the user; calculations retain canonical ohms. Launcher repair remains explicit and never downloads or replaces files merely because it was opened. Compatibility and migration `labkit.app` 2.0 intentionally removes the former launch/debug and diagnostic adapter APIs. App source using those APIs must migrate to `BuildSyntheticSample` and semantic `CallbackContext.log` events. Saved App project schemas and their declared migrations are unchanged. `labkit.dta` 3.0 replaces ambiguous numeric unit inference with explicit unit metadata. Consumers must use declared canonical values. `labkit.image` 2.0.3 removes library-owned default megapixel policy; Apps choose preview budgets according to their own workflow. Validation Focused framework suites cover event schema, RNG preservation, operation outcomes, multi-process leases, privacy allowlists, projection degradation, viewer behavior, bundle export, Runtime instrumentation, and repository architecture. All 63 public hidden native App smoke cases pass. Every migrated App family has focused workflow coverage, including Batch Crop native ROI and preview-resolution contracts, EIS units, DTA parity, Launcher dispatch/repair, and synthetic-input state neutrality. Evidence The `debug-repair` branch contains purpose-based implementation and focused-test commits. Final evidence is the branch-review validation result, pull-request CI, merged-main CI, and developer-led manual checks recorded in the pull request. Known limitations and follow-up Hidden GUI tests do not establish pointer feel, dialog usability, visual quality, or the usefulness and privacy of exported incident bundles. Those remain explicit manual acceptance gates before merge."},{"title":"Runtime exposes GUI-free source-record creation","url":"history/records/2026/07/LK-20260716-gui-free-source-records.html","kind":"history","section":"history","keywords":"LK-20260716-gui-free-source-records 2026-07-16 100 feat compatible labkit.ui Runtime V2 sources App project migration","text":"Runtime exposes GUI-free source-record creation id: LK-20260716-gui-free-source-records date: 2026-07-16 sequence: 100 type: feat compatibility: compatible component: `labkit.ui` | `7.4.1 -> 7.4.2` scope: Runtime V2 sources scope: App project migration Context Runtime V2 owned the portable-reference schema and exposed a stable path accessor, but source-record creation was available only through callback-bound project services. GUI-free project migrations and legacy importers therefore could not create canonical sources without copying private reference fields. Batch Crop and Video Marker both contained such schema copies. Decision and rationale Expose the canonical source record, not the portable-reference implementation. `labkit.ui.runtime.sourceRecord` accepts stable App identity, semantic role, path, and required status, then delegates the nested representation to the Runtime. The injected callback service uses the same factory. Changes Added the GUI-free `labkit.ui.runtime.sourceRecord` factory with validation and complete MATLAB help. Routed `services.project.sourceRecord`, `upsertSource`, and `reconcileSources` through the same public contract. Extended source tests to cover factory output and invalid inputs. Updated the framework API map and portable-source guidance. User and data impact User project behavior is unchanged. App authors can now implement project migrations and legacy imports without opening a UI callback or constructing runtime-owned reference fields. Current file paths remain available only through `sourcePaths`. Compatibility and migration The addition is compatible within UI 7. Existing source records and injected services retain their shape and behavior. Apps should replace copied portable reference construction when their project/import code is migrated. Validation Focused Runtime source tests cover empty arrays, canonical creation, resolved path lookup, ordered/optional ID lookup, invalid references, and invalid factory arguments. Package-surface, version, documentation, and history guardrails protect the new public contract. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Framework](../../../../framework/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up Existing Apps that still copy or read portable-reference fields must migrate to `sourceRecord` and `sourcePaths` before the legacy source boundary is fully retired."},{"title":"Runtime owns portable source-reference details","url":"history/records/2026/07/LK-20260716-source-path-accessor.html","kind":"history","section":"history","keywords":"LK-20260716-source-path-accessor 2026-07-16 91 feat compatible labkit.ui Runtime source boundary App maintenance cost","text":"Runtime owns portable source-reference details id: LK-20260716-source-path-accessor date: 2026-07-16 sequence: 91 type: feat compatibility: compatible component: `labkit.ui` | `7.3.0 -> 7.4.0` scope: Runtime source boundary scope: App maintenance cost Context Runtime V2 owned source creation, rebasing, relinking, and validation, but an App still had to read `reference.originalPath` whenever it loaded or displayed a source. This exposed the nested persistence representation throughout session factories, actions, presenters, and workflow packages. Several Apps then added their own identical path loops or ID lookup functions. Decision and rationale Keep source `id`, `role`, and `required` as stable App-facing project data, but treat the nested portable reference as a runtime-owned value. Add one pure `labkit.ui.runtime.sourcePaths` accessor that works before UI construction and therefore serves session creation as well as actions and presentation. Passing UI-bound injected services into `CreateSession` was rejected: it would couple otherwise pure state reconstruction to figure, queue, dialog, and resource-service construction merely to read a path. Changes Added `sourcePaths(sources)` for paths in source order. Added `sourcePaths(sources,ids)` for strict lookup in requested ID order. Defined empty, malformed-reference, and unknown-ID behavior in executable public help and unit tests. Stopped documenting portable-reference fields as an App read contract. Added the accessor to the guarded public UI runtime surface. User and data impact Saved projects and file resolution behave unchanged. App code can now read, compare, load, and present source paths without knowing how a portable reference stores its current or relative location. Compatibility and migration The addition is compatible within UI 7. Existing direct field reads continue to work temporarily, but tracked Apps will move to the accessor during their family-by-family Runtime V2 consolidation. No project migration is required. Validation Unit tests cover source order, requested ID order, empty results, missing IDs, and invalid runtime references. Package-surface and documentation guardrails cover the new public contract. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Complete App Tutorial](../../../../development/build-apps/complete-app.md) Known limitations and follow-up Existing App-local path loops remain until their owning App commits are migrated and behavior-tested. Once all consumers use this accessor, a contract guard will prevent production Apps from reading portable-reference fields."},{"title":"Runtime uses one source contract for launch, migration, and layout","url":"history/records/2026/07/LK-20260717-retired-runtime-compatibility.html","kind":"history","section":"history","keywords":"LK-20260717-retired-runtime-compatibility 2026-07-17 126 refactor breaking labkit.ui Runtime launch contract Project payload migration Semantic layout validation","text":"Runtime uses one source contract for launch, migration, and layout id: LK-20260717-retired-runtime-compatibility date: 2026-07-17 sequence: 126 type: refactor compatibility: breaking component: `labkit.ui` | `7.4.7 -> 7.5.0` scope: Runtime launch contract scope: Project payload migration scope: Semantic layout validation Context UI Runtime had already introduced one-definition App launch, one version-aware `Project.Migrate` callback, and presenter-owned managed interactions. Transitional branches still accepted separate requirements and version factories, ordered `Project.Migrations` cells, and a `toolPanel` layout kind whose public constructor no longer existed. No public or accepted private App used those branches; framework tests and stale guidance were their only remaining consumers. Decision and rationale Keep one writable source contract for each capability. `launch` receives one definition factory and reads product metadata and requirements from that definition. Runtime calls the App's single `Migrate(project,fromVersion)` callback for each missing payload step. Layout sections contain supported semantic controls, while presenter interaction specs own axes tools. This removes source-level compatibility that could hide an incomplete App migration. Read-only support for current saved project envelopes, declared legacy MAT imports, and older supported payload versions remains intact. Changes Removed requirements/version factory dispatch from `runtime.launch` and added a direct diagnostic for the retired call form. Rejected `Project.Migrations` and removed its project-reader fallback. Removed `toolPanel` validation, sizing, building, diagnostics, and current manual guidance. Converted Runtime tests to complete single definitions and added regression checks for both retired contracts. User and data impact Current Apps and their project files behave the same. App developers have one place to review identity, version, requirements, layout, and optional runtime capabilities. Source code still using a three-factory launch or `Project.Migrations` now fails immediately with migration guidance instead of silently taking a legacy path. Compatibility and migration This is a source-contract cleanup within UI 7. Call `labkit.ui.runtime.launch(@app.definition,varargin{:})`, move product metadata and `Requirements` into `definition.m`, and replace migration callback cells with one callback that switches on `fromVersion`. No saved-data rewrite is required. Validation Focused Runtime GUI tests cover single-definition launch, rejection of the retired launch form, ordered project migration and atomic load, rejection of `Project.Migrations`, managed interactions, and startup progress. Contract tests cover App layout structure and public surface drift. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) [App development](../../../../development/build-apps/app-development.md) [Public API reference](../../../../reference/README.md) Known limitations and follow-up Historical records retain their original descriptions of the transitional APIs. They document how the architecture evolved and are not current usage guidance."},{"title":"Runtime-only P-code app packages","url":"history/records/2026/07/LK-20260708-runtime-only-p-code-app-packages.html","kind":"history","section":"history","keywords":"LK-20260708-runtime-only-p-code-app-packages 2026-07-08 41 refactor compatible historical project evolution","text":"Runtime-only P-code app packages id: LK-20260708-runtime-only-p-code-app-packages date: 2026-07-08 sequence: 41 type: refactor compatibility: compatible scope: historical project evolution Context Single-app P-code packages included a P-coded copy of the full launcher even though their purpose was to run one protected app. That exposed maintenance and packaging actions that were meaningful only in a source checkout. Decision and rationale Make P-code output a minimal runtime package with a direct app entry script. Keep the full launcher in source and source-package distributions, where its installation, profiling, and packaging tools are available. Changes Project deployment tooling, no component version change. `Package P-code` now creates a runtime-only single-app package instead of shipping a P-coded LabKit launcher and launcher maintenance tools. P-code package manifests and README instructions point users to the direct `run_` entry file. P-code packaging no longer requires `labkit_launcher.m` or `labkit_launcher.p` to exist in the package root being used as the runtime source. User and data impact Recipients of a P-code package start the protected app with `run_` and no longer see unrelated launcher maintenance actions. The protected app and its data formats are unchanged. Compatibility and migration Users of P-code packages should run `run_` from the unzipped package instead of `labkit_launcher`. Source packages still include and support the launcher. Validation Commit `75f63f1` expanded `PackageLabKitAppToolTest` for runtime-only package contents and updated the generated package instructions. Evidence Mainline commit `75f63f1`. Known limitations and follow-up P-code packages intentionally omit source-oriented launcher features. Users who need installation or packaging tools should use a source or source-package distribution."},{"title":"Runtime-owned project shape validation","url":"history/records/2026/07/LK-20260716-runtime-owned-project-shape-validation.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-owned-project-shape-validation 2026-07-16 117 refactor compatible labkit.ui labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app Runtime V2 project validation electrochemistry App maintenance cost","text":"Runtime-owned project shape validation id: LK-20260716-runtime-owned-project-shape-validation date: 2026-07-16 sequence: 117 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.4 -> 7.4.5` component: `labkit_ChronoOverlay_app` | `1.4.4 -> 1.4.5` component: `labkit_CIC_app` | `1.4.4 -> 1.4.5` component: `labkit_CSC_app` | `1.4.4 -> 1.4.5` component: `labkit_EIS_app` | `1.4.4 -> 1.4.5` component: `labkit_VTResistance_app` | `1.4.4 -> 1.4.5` scope: Runtime V2 project validation scope: electrochemistry App maintenance cost Context Runtime already validated canonical project buckets and portable source records after actions committed. During project restore, however, the App validator ran before that framework validation. Every persistent App consequently repeated the same bucket and source-record checks so malformed loaded projects would fail safely. Decision and rationale Validate framework-owned project structure before invoking App-owned project validation on both initial state and project restore. App validators should describe only their own schema and scientific rules. This gives canonical shape and source records one owner while retaining strict validation at the load boundary. Changes Added one private Runtime project-shape validator shared by restore and semantic state validation. Runtime now rejects a malformed loaded payload before its App validator can inspect domain fields. Removed 67 net lines of repeated canonical bucket and source-record checks from the five electrochemistry `projectSpec.m` files. Kept each App's legal choices, numeric bounds, result fields, and other domain-specific validation unchanged. User and data impact Valid projects and scientific results are unchanged. Malformed projects now receive the framework's consistent invalid-state error for canonical structure failures instead of an App-specific bucket or source error. No payload migration is required. Compatibility and migration The accepted project schema is unchanged. This is a validation-ownership change, not a data-format change, and current or older supported project versions continue through the same migration callbacks. Validation The Runtime project GUI test loads a payload missing a canonical bucket and verifies atomic rejection before App validation. Focused electrochemistry GUI tests exercise load, analysis, plotting, and export paths with the reduced App validators. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the framework and App validation boundary. [App Development](../../../../development/build-apps/app-development.md) tells App authors which validation remains in `projectSpec.m`. Each affected [Electrochemistry App](../../../../apps/electrochemistry/README.md) manual identifies its domain-owned validator responsibilities. Known limitations and follow-up Other App families still repeat some canonical structure checks. They will move to the same boundary only after their role and cross-field constraints are separated from the generic checks and covered by focused tests."},{"title":"Runtime-owned session defaults across App families","url":"history/records/2026/07/LK-20260716-runtime-owned-session-defaults.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-owned-session-defaults 2026-07-16 116 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Runtime V2 session construction App maintenance cost","text":"Runtime-owned session defaults across App families id: LK-20260716-runtime-owned-session-defaults date: 2026-07-16 sequence: 116 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.3 -> 7.4.4` component: `labkit_DICPostprocess_app` | `1.4.3 -> 1.4.4` component: `labkit_DICPreprocess_app` | `1.5.4 -> 1.5.5` component: `labkit_GaitAnalysis_app` | `2.0.4 -> 2.0.5` component: `labkit_BatchImageCrop_app` | `1.7.3 -> 1.7.4` component: `labkit_CurvatureMeasurement_app` | `1.4.3 -> 1.4.4` component: `labkit_FLIRThermal_app` | `1.4.3 -> 1.4.4` component: `labkit_FocusStack_app` | `1.5.2 -> 1.5.3` component: `labkit_ImageEnhance_app` | `1.6.3 -> 1.6.4` component: `labkit_ImageMatch_app` | `1.6.3 -> 1.6.4` component: `labkit_VideoMarker_app` | `1.5.2 -> 1.5.3` component: `labkit_FigureStudio_app` | `0.2.5 -> 0.2.6` component: `labkit_NerveResponseAnalysis_app` | `1.4.3 -> 1.4.4` component: `labkit_ResponseReviewStats_app` | `1.4.3 -> 1.4.4` component: `labkit_RHSPreview_app` | `1.4.3 -> 1.4.4` component: `labkit_ECGPrint_app` | `1.4.3 -> 1.4.4` scope: Runtime V2 session construction scope: App maintenance cost Context Runtime V2 already adds the canonical `selection`, `workflow`, `view`, and `cache` session buckets after an App factory returns. Its workflow service also creates `logLines` on the first log operation. Fifteen App factories still repeated empty buckets or initialized that framework-owned log, making their state declarations longer and obscuring which transient fields were genuinely App-specific. Decision and rationale Treat canonical session shape and workflow-log initialization as Runtime control-plane responsibilities. An App session factory now returns only the selection, workflow, view, and cache fields that express its own workflow. This preserves explicit App state while removing framework boilerplate. Changes Removed empty canonical session buckets from affected App factories. Removed every App-owned `logLines` initializer; the injected workflow service remains the only writer and lazy initializer. Removed Image Enhance's direct log-panel presenter binding; Runtime commits the workflow log to every declared log panel. Added `services.project.newState()` and moved full-project reset actions onto that Runtime-owned creation and normalization path. Added a fleet-wide structure guard that rejects empty canonical buckets and direct workflow-log initialization in `createSession.m`. Updated every affected App manual and patch version. User and data impact There is no workflow, scientific, persistence, or saved-data change. Runtime normalization produces the same complete in-memory session before validation, presentation, or the first callback. Existing project files remain compatible. Compatibility and migration The change is source-compatible within Runtime V2 and requires no project payload migration. Direct tests of an App's raw session factory should inspect only App-owned fields; tests that need canonical empty buckets should create state through Runtime. Validation The fleet structure guard covers every public App factory. Focused App unit and hidden-GUI suites verify restored state, initial presentation, workflow logs, and representative source-backed sessions across the affected families. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines canonical session normalization and workflow logging. [App Development](../../../../development/build-apps/app-development.md) requires `createSession.m` to rebuild only App-specific transient data. Each affected App manual describes its remaining session-owned fields. Known limitations and follow-up Most factories still perform necessary source decoding or reconstruction. They should not be removed merely to reduce file count. Further simplification requires evidence that a repeated reconstruction pattern is domain-neutral and belongs behind a stable Runtime service."},{"title":"Searchable MATLAB-generated documentation site and launcher workflow groups","url":"history/records/2026/07/LK-20260715-documentation-site.html","kind":"history","section":"history","keywords":"LK-20260715-documentation-site 2026-07-15 60 feat compatible labkit_launcher `docs/` `site/` `tools/docs/` `buildfile.m`","text":"Searchable MATLAB-generated documentation site and launcher workflow groups id: LK-20260715-documentation-site date: 2026-07-15 sequence: 60 type: feat compatibility: compatible component: `labkit_launcher` | `1.4.0 -> 1.5.0` scope: `docs/` scope: `site/` scope: `tools/docs/` scope: `buildfile.m` Context The documentation had become a set of large Markdown pages with no generated site, global search, code-entity pages, or enforced relationship between the public MATLAB surface and its reference material. Launcher actions were also presented as a mostly flat list, making routine app launch, installation, maintenance, profiling, and packaging appear equally related. Decision and rationale Keep authoring in tracked Markdown, structured navigation/catalog metadata, and MATLAB help blocks, then use one repository-owned MATLAB compiler to emit the tracked static site. Bind every non-private `labkit.*` API and each explicitly cataloged app-owned scientific API to its source declaration. Keep private helpers out of detailed reference pages. Group launcher actions by user intent and make documentation regeneration a first-level maintenance action. This follows the separation used by MATLAB and Qt documentation while keeping the build offline and dependency-free. Changes Added a MATLAB-only static documentation compiler, responsive site chrome, global client-side search, source links, related-API links, and deterministic generated-tree comparison. Added structured page and app-owned API catalogs. Generated reference now covers reusable library functions plus explicitly supported GUI-free app calculation entry points, while rejecting `private/` paths. Added public documentation build and consistency tasks and project guardrails for catalog coverage, search visibility, and generated output. Reorganized launcher buttons into Run Apps, Versions and Install, Development and Maintenance, and Package and Publish groups. Added Update Documentation to the launcher. It rebuilds `site/`, opens the generated home page, and reports narrative-page, API-page, file, and output location details. User and data impact Users can browse and search documentation without a MATLAB documentation server, move between related public functions, and regenerate the same site after source changes. Existing app data, projects, calculations, and exports are unchanged. Launcher maintenance actions are easier to distinguish from normal app launch and installation tasks. Compatibility and migration The feature is additive. Existing Markdown links remain valid in the source repository. `site/` is generated and must not be edited manually. Source-only or P-code app packages that omit documentation tools show the launcher action disabled with an explanatory tooltip. Validation The compiler generated 20 narrative pages, 133 public API pages, and 157 tracked files. Focused documentation/build guardrails passed after verifying a byte-for-byte rebuild. Hidden-GUI launcher layout, documentation generation, and missing-tool behavior tests passed 3/3. Evidence `tools/docs/renderLabKitDocs.m` and `docs/site.json` define the deterministic build and navigation contract. `docs/catalogs/api.json` defines the supported app-owned public API surface. Initial searchable site and launcher integration `bdeba572`. Per-app documentation publication `82734a47`. Later information-architecture rebuild `8b55e2b9`. Known limitations and follow-up The first generated site still contains transitional narrative pages that must be split into app-family, per-app, framework, and focused API guides. Cataloged scientific APIs need richer standalone examples and algorithm/unit sections before the documentation rewrite is complete."},{"title":"Session diagnostics expose actions and teardown failures","url":"history/records/2026/07/LK-20260726-actionable-session-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260726-actionable-session-diagnostics 2026-07-26 161 fix compatible labkit.app Session log filter and action terminology Runtime close and resource-cleanup diagnostics","text":"Session diagnostics expose actions and teardown failures id: LK-20260726-actionable-session-diagnostics date: 2026-07-26 sequence: 161 type: fix compatibility: compatible component: `labkit.app` | `2.0.0 -> 2.0.1` scope: Session log filter and action terminology scope: Runtime close and resource-cleanup diagnostics Context The live session viewer exposed internal `Audience` and `Root` terminology without explaining their meaning. Root-action choices were bare `op-*` identifiers, so users could not tell which click or workflow event a correlated call chain represented. The TRACE-disabled notice also implied that ordinary lifecycle evidence might be missing even though only TRACE-severity records were disabled. Runtime callback and startup failures were instrumented, but application resource cleanup ran before recorder close without its own operation boundary. A cleanup exception could therefore prevent orderly adapter and journal teardown while leaving no canonical lifecycle failure. Decision and rationale Present diagnostic concepts in user-task language while retaining stable machine identifiers underneath. Reader intent becomes **View**, root correlation becomes **Action**, and each action choice combines time, a safe semantic message or callback alias, and its `op-*` identifier. Treat Runtime close as an ordinary instrumented lifecycle operation. Attempt independent resource and native cleanup even after one fails, persist the terminal close result, close the journal, and only then return the original or combined cleanup exception. Changes Replaced the viewer's Audience wording with Useful, Everything, User workflow, and Developer details choices plus explanatory tooltips. Replaced Root wording with Action and generated readable correlation labels without changing the underlying root-action filter value. Clarified that TRACE-off sessions still capture DEBUG lifecycle boundaries and every warning or error. Added `runtime.close.started`, `runtime.close.completed`, and `runtime.close.failed` operation records. Made sibling cleanup continue after a resource or adapter failure and guaranteed recorder close before the cleanup exception is returned. User and data impact Users can select a recognizable action instead of guessing which `op-*` value belongs to an incident. Default views remain concise, while earlier DEBUG callback boundaries remain available without having enabled TRACE. Scientific state, project data, App callbacks, and saved schemas are unchanged. Diagnostic records remain sanitized and use the existing bounded journal. Compatibility and migration This is a compatible App SDK patch. Public App-author APIs and the event schema are unchanged. The projection adds a private display-label field, and existing root-action identifiers remain stable for filtering and exported bundles. Validation Focused projection specifications verify audience filtering, readable action labels, and accurate TRACE limitations. Hidden-GUI viewer specifications verify the visible terminology and separate label/value behavior. Runtime logging specifications inject an application-resource cleanup failure and require both the failed close operation and orderly session close in memory and the durable journal. Evidence The focused framework results, generated documentation check, changed-file gate, and exact main-push CI run are the delivery evidence. Known limitations and follow-up No logger can synthesize a failed terminal record for native events that never reach Runtime, exceptions deliberately swallowed without logging, or a process that hangs or terminates abruptly. The last retained boundary and journal lease state remain the evidence in those cases."},{"title":"Shared image facade","url":"history/records/2026/06/LK-20260630-shared-image-facade.html","kind":"history","section":"history","keywords":"LK-20260630-shared-image-facade 2026-06-30 24 feat compatible labkit.image labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"Shared image facade id: LK-20260630-shared-image-facade date: 2026-06-30 sequence: 24 type: feat compatibility: compatible component: `labkit.image` | `new -> 1.0.0` component: `labkit_BatchImageCrop_app` | `1.3.9 -> 1.4.0` component: `labkit_CurvatureMeasurement_app` | `1.2.3 -> 1.2.4` component: `labkit_FocusStack_app` | `1.2.5 -> 1.3.0` component: `labkit_ImageEnhance_app` | `1.3.5 -> 1.4.0` component: `labkit_ImageMatch_app` | `1.3.5 -> 1.4.0` scope: historical project evolution Context Batch Crop, Focus Stack, Image Enhance, and Image Match each carried similar code for supported extensions, path normalization, image reads, preview sizing, and basic enhancement. Small differences between those copies produced inconsistent file and display behavior. Decision and rationale Introduce `labkit.image` for GUI-free operations with neutral meaning, then move the image apps to that shared API. Keep crop geometry, focus fusion, matching, protected enhancement, and export schemas in their respective apps. Changes `labkit.image` `1.0.0` Batch Crop, Curvature, Focus Stack, Image Enhance, and Image Match advanced within their image-facade adoption lines. Added a GUI-free image facade for file input, display normalization, basic processing, and preview support. Adopted that facade across image-measurement apps. User and data impact Supported image selection, display-name handling, preview normalization, and common enhancement primitives became consistent across the migrated apps. Scientific app outputs and source files did not change format. Compatibility and migration Image apps kept their entry points and exports while adopting the shared operations. App code that called the retired local helpers needed to use the image facade or the owning app operation. Validation Commit `7023e87e` added a dedicated `LabKitImageFacadeTest` suite and updated app compatibility, package-boundary, public-surface, and focused image-app tests. Evidence Main commit `7023e87e`. Known limitations and follow-up The first facade release covered common MATLAB image formats and basic processing only. Thermal decoding and workflow-specific algorithms remained outside `labkit.image`."},{"title":"Single-click DIC rigid point matching","url":"history/records/2026/07/LK-20260713-dic-rigid-point-editor.html","kind":"history","section":"history","keywords":"LK-20260713-dic-rigid-point-editor 2026-07-13 50 feat compatible labkit.ui labkit_DICPreprocess_app Single-click DIC rigid point matching","text":"Single-click DIC rigid point matching id: LK-20260713-dic-rigid-point-editor date: 2026-07-13 sequence: 50 type: feat compatibility: compatible component: `labkit.ui` | `5.0.4 -> 5.1.0` component: `labkit_DICPreprocess_app` | `1.3.6 -> 1.4.0` scope: Single-click DIC rigid point matching Context DIC manual rigid matching had draggable points but maintained a separate pointer implementation and required a less consistent placement workflow than the ROI-center anchors used by related reconstruction workflows. Decision and rationale Extend the existing app-neutral anchor editor with a discrete point mode, then keep moving/fixed pair order, numbering, minimum pair count, and rigid-fit policy inside DIC. Changes Added `mode=\"points\"`: one blank click appends a point, dragging refines it, no connecting curve is drawn, and deletion remains under explicit controls. Migrated the DIC modal to two shared point-mode editors while preserving ordered moving/fixed pairs, labels, undo, cancel, and acceptance rules. Retained toolbox-free image display and rigid alignment behavior. User and data impact Feature placement now follows the same direct click-and-drag model as Imager ROI anchors. Point coordinates and the resulting rigid transform keep their existing N-by-2 pixel-coordinate contract. Compatibility and migration The default anchor-editor curve mode is unchanged. DIC exports and transform math are unchanged; this is an additive interaction improvement. Validation UI anchor-editor tests cover discrete point append and no-path behavior. The DIC GUI workflow covers toolbox-free modal cancellation and app launch wiring. Evidence Primary sources are `labkit.ui.interaction.anchorEditor` and `dic_preprocess.userInterface.selectRigidPointPairs`. Commit `392a073e` introduced their shared DIC rigid-point interaction. Known limitations and follow-up Automated hidden-GUI tests cannot judge pointing ergonomics; final interaction feel still requires a short manual placement-and-drag check."},{"title":"Single-definition App product contract","url":"history/records/2026/07/LK-20260716-single-definition-contract.html","kind":"history","section":"history","keywords":"LK-20260716-single-definition-contract 2026-07-16 77 feat compatible labkit.ui App authoring Runtime V2 launch metadata","text":"Single-definition App product contract id: LK-20260716-single-definition-contract date: 2026-07-16 sequence: 77 type: feat compatibility: compatible component: `labkit.ui` | `7.1.0 -> 7.2.0` scope: App authoring scope: Runtime V2 launch metadata Context Every App repeated product metadata and facade requirements in `version.m` and `requirements.m`, while `definition.m` separately described the product's runtime behavior. Thin entrypoints had to join all three factories, and the launcher and release guardrails treated the duplicate files as separate facts. Decision and rationale Make `definition.m` the one App product contract. Product identity, version, requirements, layout, and optional runtime capabilities now have one owner. The public entrypoint can delegate through `launch(@definition, varargin{:})`. Changes Added `Command`, optional `DisplayName`, `Family`, `AppVersion`, `Updated`, and `Requirements` fields to `labkit.ui.runtime.define`. Added single-definition launch and lightweight `version` and `requirements` request handling. Kept the previous three-factory launch form only as a temporary bridge while existing Apps move in reviewed family-sized changes. Extended the minimal real-GUI launch test to obtain all metadata from the same definition. User and data impact App users see no workflow or scientific change. App authors no longer need to keep three product declarations synchronized after their App is migrated. Compatibility and migration The addition is compatible within UI 7. Existing definitions continue to run during the migration window. Each App will move its exact metadata and facade ranges into `definition.m` before its redundant files are removed. The bridge is not a permanent supported architecture. Validation Focused runtime tests exercise normal launch plus `version` and `requirements` requests from one minimal definition. Documentation, facade-version, and App contract tests protect the metadata shape during the family migrations. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents the single definition and field meanings. [App Development](../../../../development/build-apps/app-development.md) shows the reduced static App file set. Known limitations and follow-up Existing Apps, launcher static discovery, release version guards, tutorials, and the App-builder skill still need family-by-family migration. The legacy launch bridge is removed only after those consumers no longer use it."},{"title":"Source-adjacent Video Marker autosave","url":"history/records/2026/07/LK-20260716-source-adjacent-video-autosave.html","kind":"history","section":"history","keywords":"LK-20260716-source-adjacent-video-autosave 2026-07-16 69 fix compatible labkit.ui labkit_VideoMarker_app Runtime V2 project recovery Video Marker autosave location","text":"Source-adjacent Video Marker autosave id: LK-20260716-source-adjacent-video-autosave date: 2026-07-16 sequence: 69 type: fix compatibility: compatible component: `labkit.ui` | `6.0.3 -> 6.0.4` component: `labkit_VideoMarker_app` | `1.4.0 -> 1.4.1` scope: Runtime V2 project recovery scope: Video Marker autosave location Context The first explicit Runtime V2 autosave action wrote only the framework's hidden recovery generation. Video Marker already had a product contract for a visible, stable autosave beside the source video, and users select those MAT files directly in downstream workflows such as Gait Analysis. Decision and rationale Keep generic debounced recovery framework-owned, but let an app provide a deterministic autosave destination when that location is part of its workflow. Video Marker derives the destination from the source video and never asks the user to choose it. Changes Added `services.project.saveAutosave(state,filepath)` without changing named project ownership or dirty status. Restored the visible `Video Marker Autosaves` folder and stable `.video_marker.autosave.mat` filename. Enabled **Save autosave** only after a video is open and report write failures through the standard diagnostics and app-parented alert services. User and data impact Clicking **Save autosave** immediately overwrites the source video's current autosave using the current `labkitProject` envelope. It opens no location dialog. The file remains easy to locate, copy, and load into Gait Analysis. Compatibility and migration The filename and source-adjacent folder match the earlier Video Marker autosave convention. Generic Runtime recovery remains available independently. Validation The focused Video Marker GUI test opens a synthetic video, verifies the button is enabled, writes the expected source-adjacent MAT, and confirms the runtime still has no named project path. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the exact no-dialog destination. [Runtime and lifecycle](../../../../framework/guides/runtime.md) distinguishes generic recovery from an app-targeted autosave. Known limitations and follow-up The source folder must be writable. A write failure leaves the current project untouched and is shown to the user."},{"title":"Startup responsiveness","url":"history/records/2026/07/LK-20260702-startup-responsiveness.html","kind":"history","section":"history","keywords":"LK-20260702-startup-responsiveness 2026-07-02 31 perf compatible labkit_launcher labkit.ui Startup responsiveness","text":"Startup responsiveness id: LK-20260702-startup-responsiveness date: 2026-07-02 sequence: 31 type: perf compatibility: compatible component: `labkit_launcher` | `1.2.2 -> 1.2.3` component: `labkit.ui` | `3.4.2 -> 3.4.4` scope: Startup responsiveness Context Profiling showed that a substantial part of perceived startup delay occurred after MATLAB had created the window but before it allowed the operating system to paint it. Launcher discovery and preview scroll setup also ran before their results were needed, leaving users with a blank or apparently frozen window. Decision and rationale Paint the launcher and app shell as soon as their basic structure exists, then defer discovery and preview navigation setup until the UI can remain responsive. Preserve the same controls and app catalog once initialization finishes. Changes `labkit_launcher` `1.2.2 -> 1.2.3` `labkit.ui` `3.4.2 -> 3.4.4` Painted launcher and app windows earlier. Deferred launcher app discovery and lazy preview scroll setup. User and data impact The window appeared and responded earlier, making initialization visible instead of presenting a long white-screen interval. App discovery and scroll navigation still completed automatically; the change moved their timing rather than removing those capabilities. Compatibility and migration App definitions and saved data were unchanged. The launcher and runtime altered only when discovery and interaction setup occurred during startup. Validation Launcher profiler assertions checked the earlier paint point, while UI axes layout tests covered deferred scroll navigation in `7d4ef11e`. Evidence Main commit `7d4ef11e`. Known limitations and follow-up Earlier painting improved perceived responsiveness, but it also made progress communication important. Later runtime work introduced explicit startup stages and lifecycle-managed readiness reporting."},{"title":"Structured documentation history","url":"history/records/2026/07/LK-20260715-component-owned-history.html","kind":"history","section":"history","keywords":"LK-20260715-component-owned-history 2026-07-15 61 docs breaking `docs/history/records/` `tools/docs/` `tools/release/` `CHANGELOG.md`","text":"Structured documentation history id: LK-20260715-component-owned-history date: 2026-07-15 sequence: 61 type: docs compatibility: breaking scope: `docs/history/records/` scope: `tools/docs/` scope: `tools/release/` scope: `CHANGELOG.md` Context Project evolution was stored in one 3,571-line root changelog. App, framework, library, validation, launcher, and release decisions were mixed together, while the new documentation site already had component pages capable of presenting the relevant history in context. Decision and rationale Store each change explanation once as an independent Markdown page in the chronological `docs/history/records///` tree. Let the component metadata, rather than the file's physical folder, associate a record with each affected app, framework, or library. The documentation compiler then generates both component-specific history lists and a searchable project-wide index. Changes Split the existing recorded history into one Markdown file per Change ID and placed every public record under one chronological source tree. Removed the root aggregate changelog, duplicated current-version lookup, and the dedicated release-history parser. Replaced the launcher's custom version-history window with a direct link to the selected App's generated Documentation and History page. Routed version governance through owned component docs, distributed history, generated-site consistency, and source version metadata. User and data impact Users see instructions, APIs, scientific context, and relevant changes from one app or library page. Source readers can find every public history record under `docs/history/records/`. Search still reaches the complete historical record. No project files, calculations, or experimental data formats change. Compatibility and migration Links that targeted the old root changelog must move to the generated Project History page or a component page. The old parser and launcher history mode are intentionally removed rather than maintained as compatibility aliases. Validation The documentation compiler validates unique history identities, required metadata, source pages, generated links, search indexing, and deterministic output. Project release guardrails validate the distributed record set and component aggregation. Evidence `docs/history/README.md` defines the human entry point. `tools/docs/private/loadLabKitDocumentation.m` discovers history pages. `DocumentationHistoryGuardrailTest` protects the distributed model. Known limitations and follow-up Some older records retain the wording used when their changes were originally made. Component metadata, rather than folder placement, determines where each record is shown."},{"title":"Structured evolution records","url":"history/records/2026/07/LK-20260713-structured-evolution-records.html","kind":"history","section":"history","keywords":"LK-20260713-structured-evolution-records 2026-07-13 52 docs compatible repository changelog and release governance","text":"Structured evolution records id: LK-20260713-structured-evolution-records date: 2026-07-13 sequence: 52 type: docs compatibility: compatible scope: repository changelog and release governance Context The previous `Unreleased` and `Pending` model mixed delivery state with historical meaning and required records to be moved after merge. Its prose was useful to humans but had no reliable machine contract. Decision and rationale Use stable, status-free records with a small parseable metadata header and required explanatory sections. Git branches, PRs, tags, and evidence identify delivery state without duplicating it in the changelog. Changes Added schema-v1 metadata for Change ID, date, type, compatibility, component introductions and version transitions, and unversioned repository scopes. Added a base-MATLAB parser and release guardrail for schema integrity. Normalized every historical entry into the same schema-v1 record contract. User and data impact Users and maintainers can search one durable ID and obtain the reason, impact, compatibility, validation, and evidence for a change without reconstructing it from commit messages. No scientific data or runtime behavior changes. Compatibility and migration Existing links to `CHANGELOG.md` remain valid. Automation that searched the old `Unreleased` or `Version History` headings must use the parser and stable Change IDs. Validation `ChangelogGuardrailTest` parses every schema-v1 record and checks current version lookup coverage and release-policy documentation. Evidence The parser lives at `tools/release/parseLabKitChangelog.m`; the normalized historical baseline was checked against the dated mainline log and release tags. Known limitations and follow-up Some older records do not name exact test commands because that evidence was not recorded at the time; they state that limitation instead of inventing it."},{"title":"T-Test Wizard adds table selection and first-versus-each comparisons","url":"history/records/2026/07/LK-20260718-ttest-wizard-and-table-workspace.html","kind":"history","section":"history","keywords":"LK-20260718-ttest-wizard-and-table-workspace 2026-07-19 135 feat compatible labkit_TTestWizard_app labkit.ui Statistics CSV exchange App Framework presentation","text":"T-Test Wizard adds table selection and first-versus-each comparisons id: LK-20260718-ttest-wizard-and-table-workspace date: 2026-07-19 sequence: 135 type: feat compatibility: compatible component: `labkit_TTestWizard_app` | `new -> 1.0.0` component: `labkit.ui` | `7.5.1 -> 7.6.0` scope: Statistics scope: CSV exchange scope: App Framework presentation Context Laboratory comparison data appeared as manually maintained workbook blocks, same-shape files, and rectangular App exports containing identifiers beside numeric metrics. Schema-first mapping made unfamiliar sources harder to use. The required workflow also included several treatments compared with one control, while the first implementation boundary held only A and B. Runtime presentation could update table values but not their spreadsheet headers or collapse unused workspace panels. An App therefore could not show an unfamiliar table faithfully while giving each wizard step the full workspace. Decision and rationale Display CSV, TSV, XLSX, and XLS sources as visible cell grids. Let the user append each numeric selection as an ordered durable group. The first group is the explicit reference; one chosen t-test specification is then applied to every later group versus that first group. Keep Data, Test/Plot, and Export controls short. Put Data and Plot in user-selectable right-side workspace pages, merge result review with the test, and keep completed comparisons as immutable snapshots. Plot controls consume those snapshots and cannot recalculate the result family. Keep CSV as a low-constraint rectangular interchange boundary. Known producers may add conveniences later, but visible cell selection remains the fallback. Changes Added the Statistics family and T-Test Wizard 1.0.0. Added direct visible-cell selection from CSV, TSV, XLSX, and XLS, including worksheet selection, numeric-text acceptance, mixed-cell feedback, and groups captured from different sources. Added an ordered `Group`/`Value` observation editor, manual paste/editing, existing-group capture choices, first-appearance reference ordering, and batch reassignment or deletion of selected observation rows. Added layered spreadsheet-header suggestions for new group names and coalesced completed-selection delivery so range dragging does not repeatedly refresh the App. Added first-versus-each Welch, pooled equal-variance, and paired t-tests with two-sided and directional alternatives. Added Base MATLAB Student-t probabilities and intervals, stable per-result failure statuses, and immutable result snapshots. Added reference-style bar and box plots with pastel groups, explicit numeric axes, standard x-axis tick labels, boxed grid-free axes, and stacked `NS/*/**/***/****` brackets. Added multi-column group CSV and multi-row result CSV exports. Added project schema version 2 migration from durable A/B vectors to two ordered groups. Extended Runtime presentation so result tables can update headers and hidden rows collapse. Coalesced rapid table-selection events before App presentation and replaced stale standalone file-context titles instead of appending duplicate suffixes. Preserved tick labels, limits, and graphics stacking order when plots are popped into standalone figures, keeping error bars above their bars, and added a copied-figure control for switching X-axis group labels between angled and horizontal presentation. Responsive popout margins keep enlarged ticks, axis labels, and titles inside the standalone window. Extended `labkit.ui.layout.workspace` to accept two or more tab pages without adding a parallel workspace-specific public constructor. User and data impact Users can select any number of groups from an unfamiliar readable table without defining a schema or removing unrelated text. Running creates one comparison row for every group after the first. Paired testing follows displayed row order for each comparison with the first group. The App exports ordinary CSVs instead of requiring another program to decode a project MAT file. Imported sources are not modified. Compatibility and migration The App and Statistics family are additive. Version-1 T-Test Wizard projects migrate A to group 1 and B to group 2, preserving labels, values, and source metadata. An earlier completed A/B result remains a one-row result family. `labkit.ui` remains compatible with `>=7 <8`. Existing presentation structs that omit dynamic table headers and visibility behave as before. No multiple-comparison correction is silently introduced. This preserves the meaning of each requested t-test and makes the limitation explicit. Validation Unit tests cover mixed-cell extraction, source preservation, Welch/pooled/ paired reference values, first-versus-each ordering, result-family identity, and multi-group CSV round trips. A focused framework GUI check proves rapid selections commit only the final range and repeated file-context presentation does not duplicate plot titles. A hidden App GUI workflow covers three captured ranges, batch group changes, two comparisons with the first group, equal-height source/editor tables, native Data/Plot pages, compact stale-plot messaging, bar/box selection, boxed grid-free rendering, and both exports. Framework GUI tests cover native workspace pages, invalid page composition, dynamic table headers, missing-cell display, full-width previews, collapsed hidden rows, and invalid presentation diagnostics. Representative private workbook structures informed the visual and workflow requirements without copying paths, filenames, identifiers, or values into the repository. Evidence [T-Test Wizard](../../../../apps/statistics/ttest-wizard/README.md) [Statistics Apps](../../../../apps/statistics/README.md) [Simple Scientific CSV Exchange](../../../../development/data-and-designs/scientific-csv-interchange.md) [T-Test Wizard design](../../../../development/data-and-designs/group-comparison-app-design.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Version 1.0.0 does not infer experimental units, independence, pairing, exclusions, or groups from filenames. It does not apply multiple-comparison correction, ANOVA, nonparametric tests, regression, or mixed modeling. Automatic numeric-column suggestions remain a possible convenience only after real use demonstrates a stable need."},{"title":"T-Test plots gain truthful limits and an explicit view reset","url":"history/records/2026/07/LK-20260720-ttest-plot-viewport-and-density.html","kind":"history","section":"history","keywords":"LK-20260720-ttest-plot-viewport-and-density 2026-07-20 143 feat compatible labkit_TTestWizard_app Statistics Apps App Framework","text":"T-Test plots gain truthful limits and an explicit view reset id: LK-20260720-ttest-plot-viewport-and-density date: 2026-07-20 sequence: 143 type: feat compatibility: compatible component: `labkit_TTestWizard_app` | `1.1.1 -> 1.2.0` scope: Statistics Apps scope: App Framework Context T-Test Wizard used the absolute data magnitude as a minimum padding span, so box plots with a narrow nonzero range could receive excessive empty space. Bar-chart zoom could move the zero baseline away from the y-axis edge, and the App had no visible action for returning to the fitted view. Two short status summaries also occupied detail-panel height. Decision and rationale Fit limits from the graphics that carry statistical meaning: distribution or bar extents, optional observations, standard-deviation error bars, and significance brackets. Keep zero at the appropriate edge for one-sided bar data because truncated bar baselines distort magnitude; mixed-sign data must instead show zero inside the axis. Keep box plots distribution-fitted. Expose an App-owned reset action backed by the framework's general transient plot-view revision. Use the framework's line-count hint for short summaries rather than adding T-Test-specific pixel sizes. Changes Corrected the fitted span and padding used by bar, box, and annotation geometry. Kept zero at the lower or upper y-limit for positive or negative bar plots across programmatic, toolbar, and wheel-driven limit changes. Added **Reset plot view** without recalculating tests or changing project data. Refined box fill, outlines, whiskers, observation overlays, bar opacity, error-bar caps, and outward ticks. Reduced **What will run** and **Result family** to two-line summary panels with the framework's summary reading size. User and data impact Project schema 2, completed comparison values, CSV outputs, formulas, and test choices are unchanged. T-Test Wizard 1.2.0 requires `labkit.app` 1.2 or later for compact summaries and one-shot plot viewport revisions. Existing projects open without migration. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation The T-Test hidden-GUI workflow checks fitted limits, zero-edge persistence, box-plot independence, compact summary sizing, and explicit view reset. Framework contract tests cover plot viewport preservation and revision resets. Documentation rendering and history consistency cover the versioned change. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led visual review should compare box, bar, error-bar, and bracket appearance at typical desktop sizes and with real scientific label lengths."},{"title":"Test catalog cutover replaces the legacy test tree","url":"history/records/2026/07/LK-20260723-test-catalog-cutover.html","kind":"history","section":"history","keywords":"LK-20260723-test-catalog-cutover 2026-07-23 158 test breaking Test catalog Test authoring Cross-platform validation","text":"Test catalog cutover replaces the legacy test tree id: LK-20260723-test-catalog-cutover date: 2026-07-23 sequence: 158 type: test compatibility: breaking scope: Test catalog scope: Test authoring scope: Cross-platform validation Context The former test tree mixed stage folders, route wrappers, shared helpers, and executor policy. Authors had to infer where a specification belonged and which selector would exercise it. On Windows, an additional case-sensitive path comparison could reject valid catalog specifications before any behavior ran. Decision and rationale Make production ownership, behavioral contract, and execution environment the only selection model. Keep framework mechanics private in `labkittest`; keep only genuinely cross-owner synthetic data in a named fixture package. A test path comparison must be case-insensitive because Windows resolves an existing path with filesystem casing that need not match the configured root spelling. Changes Replaced the old runner and `tests/cases` tree with exact catalog specifications under `tests/specs`. Added semantic authoring and execution entry points: `explain`, `createSpec`, and `run`. Removed unreferenced legacy GUI, App-contract, repository, and assertion helpers from the former `tests/shared` bucket. Moved the remaining cross-owner synthetic DTA, thermal, RHS, chrono, and gait inputs into `tests/+testfixtures` and qualified every consumer. Normalized catalog filesystem paths before ownership comparison and added a Windows regression for differently cased specification-root input. Updated the current testing manual, scoped test rules, migration ledger, and generated documentation site. User and data impact No App behavior, saved project, scientific calculation, export schema, or laboratory data changes. Test authors gain a path-free insertion workflow and machine-readable run artifacts. All fixtures remain synthetic. Compatibility and migration This intentionally removes the unsupported legacy test folders, runner, and selector model. Contributors must use `tests/specs`, `labkittest` semantic selectors, and `tests/+testfixtures` where a synthetic input is genuinely cross-owner. No data migration is required. Validation Catalog/build regression: 23 tests passed. DTA, RHS, and thermal synthetic-fixture regressions: 8 tests passed. Complete headless catalog: 202 tests passed. Documentation render and `docsCheck`: 372 generated files current. PR CI was inspected; Windows separator handling and the shell-sensitive clean-checkpoint revision syntax are covered by catalog regressions and require the updated commit to run remotely. POSIX CI also exposed an interactive Git pager inherited by MATLAB; planner queries now use `git --no-pager` so the progress watchdog can never stall on a pager. Its case-sensitive filesystem also verified that semantic owner matching must resolve the physical specification-directory spelling exactly. Evidence The catalog writes exact identities and semantic reasons into each run's `plan.json`; the final headless `summary.json` records 202 passed identities with no failures or incomplete tests. The PR workflow independently executes headless and hidden-GUI profiles on Linux, macOS, and Windows. Known limitations and follow-up Hidden-GUI checks remain structural and do not replace manual validation of native dialogs, visual quality, pointer behavior, real-data suitability, or scientific interpretation. The PR must receive green cross-platform CI before merge."},{"title":"Thermal facade and FLIR app","url":"history/records/2026/07/LK-20260701-thermal-facade-and-flir-app.html","kind":"history","section":"history","keywords":"LK-20260701-thermal-facade-and-flir-app 2026-07-01 26 feat compatible labkit.thermal labkit.ui labkit_FLIRThermal_app Thermal facade and FLIR app","text":"Thermal facade and FLIR app id: LK-20260701-thermal-facade-and-flir-app date: 2026-07-01 sequence: 26 type: feat compatibility: compatible component: `labkit.thermal` | `new -> 1.0.0` component: `labkit.ui` | `3.2.9 -> 3.2.10` component: `labkit_FLIRThermal_app` | `new -> 1.0.0` scope: Thermal facade and FLIR app Context FLIR radiometric JPEGs contain both a visible preview and embedded thermal data plus camera metadata. Treating them as ordinary images loses the temperature meaning, while placing all parsing and conversion code inside one GUI would make the scientific operations unusable from MATLAB scripts. Decision and rationale Create a thermal library for reading radiometric files, converting raw sensor values to temperature, and rendering temperature images. Build the FLIR Thermal Postprocess app on those public operations, keeping file lists, range controls, measurement state, and export flow in the app. Changes `labkit.thermal` `1.0.0` `labkit.ui` `3.2.9 -> 3.2.10` `labkit_FLIRThermal_app` `1.0.0` Added the thermal facade. Added the FLIR Thermal Postprocess app. User and data impact Users gained a FLIR app with file browsing, temperature display, range control, summary values, and export-oriented views. The same thermal functions could be called without opening the GUI, so scripts could inspect temperature matrices and metadata directly. Compatibility and migration The thermal facade and FLIR app were additive. Existing image apps and files were unchanged; scripts could begin using `labkit.thermal` without a project- file migration. Validation The change added thermal-facade and app-operation unit suites, a FLIR layout suite, package-surface checks, and a synthetic radiometric JPEG writer. No laboratory image was committed as a fixture. Evidence Main commit `977c9457`. Known limitations and follow-up Camera-specific metadata support depended on the formats represented by the synthetic and available local samples. Later changes refined display ranges, preview cost, and point and ROI measurement tools."},{"title":"Tool-panel hosts and image app fixes","url":"history/records/2026/06/LK-20260629-tool-panel-hosts-and-image-app-fixes.html","kind":"history","section":"history","keywords":"LK-20260629-tool-panel-hosts-and-image-app-fixes 2026-06-29 18 fix compatible labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_ImageEnhance_app labkit_ImageMatch_app Tool-panel hosts and image app fixes","text":"Tool-panel hosts and image app fixes id: LK-20260629-tool-panel-hosts-and-image-app-fixes date: 2026-06-29 sequence: 18 type: fix compatibility: compatible component: `labkit.ui` | `3.2.0 -> 3.2.2` component: `labkit.ui` | `3.2.2 -> 3.2.3` component: `labkit_BatchImageCrop_app` | `1.3.2 -> 1.3.3` component: `labkit_CurvatureMeasurement_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.3.0 -> 1.3.1` component: `labkit_ImageMatch_app` | `1.3.0 -> 1.3.1` scope: Tool-panel hosts and image app fixes Context Reusable interaction tools were being attached to generic empty sections, while several image apps had separate problems with file-entry identity, reported output size, and White ROI updates. Decision and rationale Add a semantic `toolPanel` host for composed tools and fix the image-app behaviors at their actual data owners. Normalize file entries and regenerate ids deterministically so presentation updates do not change source identity. Changes `labkit.ui` `3.2.0 -> 3.2.3` Batch Crop, Curvature, Image Enhance, and Image Match patch bumped where layouts or image-app behavior changed. Hardened file-panel entry normalization and deterministic ID regeneration. Fixed output-size reporting and White ROI responsiveness. Added semantic `toolPanel` hosts. User and data impact White ROI controls responded to the selected image, Batch Crop reported output dimensions consistently, and reusable tools occupied a stable named layout area. Existing source and result formats were unchanged. Compatibility and migration Existing file entries and image results remained readable. Deterministic ids were regenerated in memory, and the new layout host did not alter saved data. Validation The listed commits updated file-panel, tool-host, and focused image-app tests. Exact historical commands were not recorded. Evidence Main commits `f2189aef`, `77084fbe`, and `871739cd`. Known limitations and follow-up The first `toolPanel` API was later replaced by the current semantic layout and managed-interaction model."},{"title":"Traceable FLIR temperature calibration","url":"history/records/2026/07/LK-20260713-flir-calibration-provenance.html","kind":"history","section":"history","keywords":"LK-20260713-flir-calibration-provenance 2026-07-13 49 feat compatible labkit.thermal labkit_FLIRThermal_app Traceable FLIR temperature calibration","text":"Traceable FLIR temperature calibration id: LK-20260713-flir-calibration-provenance date: 2026-07-13 sequence: 49 type: feat compatibility: compatible component: `labkit.thermal` | `1.0.0 -> 1.1.0` component: `labkit_FLIRThermal_app` | `1.2.8 -> 1.3.0` scope: Traceable FLIR temperature calibration Context A successful Celsius conversion did not reveal whether emissivity and environmental values came from the file or from model defaults, which could make an absolute temperature appear more certain than its metadata justified. Decision and rationale Preserve conversion provenance with the thermal record and show fallback use in the app, rather than silently treating every parameter as measured. Changes Added optional conversion diagnostics with correction mode, defaulted fields, parameter sources, and fallback status. Stored diagnostics in thermal record metadata and surfaced warnings in FLIR file status and details. Documented embedded calibration requirements and environmental fallbacks. User and data impact Users can distinguish radiometric values based entirely on embedded metadata from values affected by fallback assumptions before interpreting temperatures. Compatibility and migration Existing one-output conversion calls remain valid. The diagnostics output and record metadata are additive; app workflows need no migration. Validation Thermal facade and FLIR app tests cover metadata sources, fallback reporting, and one-output compatibility. Evidence The model and provenance contract are documented in `docs/api/thermal.md`; branch checkpoint `7391e293` carries the implementation before the final squash merge. Known limitations and follow-up Diagnostics describe source and fallback use; they do not estimate a physical uncertainty interval for a particular camera, surface, or environment."},{"title":"Traceable and base-MATLAB CI validation","url":"history/records/2026/07/LK-20260713-traceable-base-matlab-ci.html","kind":"history","section":"history","keywords":"LK-20260713-traceable-base-matlab-ci 2026-07-13 51 ci compatible GitHub Actions and MATLAB validation routing","text":"Traceable and base-MATLAB CI validation id: LK-20260713-traceable-base-matlab-ci date: 2026-07-13 sequence: 51 type: ci compatibility: compatible scope: GitHub Actions and MATLAB validation routing Context CI failures and stalls could end without enough information to identify the active test, and ordinary success on a toolbox-rich development machine did not prove that base-MATLAB users could run representative workflows. Decision and rationale Make the existing official runner publish progress and active-test state, and add a distinct compatibility gate that combines static calls, product ownership, and toolbox-shadowed behavior. Changes Added per-test progress, heartbeat, active-test, timeout-summary, and artifact publication behavior to CI. Added `buildtool baseMatlab` and representative toolbox-shadow workflows. Improved changed-file routing to target direct consumers while retaining explicit owners such as the launcher GUI suite. Corrected result aggregation so assumption-filtered tests remain visible as skipped without being misreported as shard failures. User and data impact Base-MATLAB compatibility is now an explicit supported path, and failed or stalled CI runs provide enough state to identify the last active test. Runtime scientific outputs are not changed by this record. Compatibility and migration Existing build tasks remain available. Maintainers can add `baseMatlab` to local validation without installing or uninstalling toolboxes. Validation CI policy, build-task framework, changed-routing, toolbox dependency, and representative workflow tests guard the new behavior. Evidence The command matrix is in `docs/development/maintain-and-release/testing.md`; CI uploads official runner logs and active-test artifacts. Commit `28ff8edb` made MATLAB failures traceable, `37bd7fd5` enforced Base MATLAB compatibility, and `2c9b8792` preserved launcher validation ownership. Known limitations and follow-up Shadow tests cover known dependency risks and cannot simulate every licensed toolbox combination; MATLAB product-ownership analysis remains the broad check."},{"title":"UI 5 facade redesign, app migration, and plot refresh","url":"history/records/2026/07/LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh.html","kind":"history","section":"history","keywords":"LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh 2026-07-06 38 refactor breaking labkit_launcher labkit.ui labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app UI 5 facade redesign, app migration, and plot refresh","text":"UI 5 facade redesign, app migration, and plot refresh id: LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh date: 2026-07-06 sequence: 38 type: refactor compatibility: breaking component: `labkit_launcher` | `1.2.3 -> 1.2.4` component: `labkit.ui` | `4.2.0 -> 4.2.1` component: `labkit.ui` | `4.2.1 -> 5.0.0` component: `labkit_DICPostprocess_app` | `1.3.3 -> 1.3.4` component: `labkit_DICPreprocess_app` | `1.3.3 -> 1.3.4` component: `labkit_ChronoOverlay_app` | `1.3.3 -> 1.3.5` component: `labkit_CIC_app` | `1.3.5 -> 1.3.7` component: `labkit_CSC_app` | `1.3.7 -> 1.3.9` component: `labkit_EIS_app` | `1.3.3 -> 1.3.4` component: `labkit_VTResistance_app` | `1.3.5 -> 1.3.7` component: `labkit_BatchImageCrop_app` | `1.6.5 -> 1.6.6` component: `labkit_CurvatureMeasurement_app` | `1.3.3 -> 1.3.4` component: `labkit_FLIRThermal_app` | `1.2.7 -> 1.2.8` component: `labkit_FocusStack_app` | `1.4.4 -> 1.4.5` component: `labkit_ImageEnhance_app` | `1.5.4 -> 1.5.5` component: `labkit_ImageMatch_app` | `1.5.4 -> 1.5.5` component: `labkit_FigureStudio_app` | `new -> 0.1.0` component: `labkit_FigureStudio_app` | `0.1.0 -> 0.1.1` component: `labkit_FigureStudio_app` | `0.1.1 -> 0.1.2` component: `labkit_FigureStudio_app` | `0.1.2 -> 0.1.4` component: `labkit_NerveResponseAnalysis_app` | `1.3.3 -> 1.3.4` component: `labkit_ResponseReviewStats_app` | `1.3.3 -> 1.3.4` component: `labkit_RHSPreview_app` | `1.3.3 -> 1.3.4` component: `labkit_ECGPrint_app` | `1.3.4 -> 1.3.5` scope: UI 5 facade redesign, app migration, and plot refresh Context The declarative runtime had made app structure more consistent, but its public packages still reflected the order in which features had been extracted: `app`, `spec`, `view`, `tool`, and `diag` mixed lifecycle, layout, plotting, interaction, and diagnostics. App authors often had to know implementation history to guess where an operation belonged. Plot behavior exposed the cost of that ambiguity. Apps independently cleared axes, fitted limits, translated image coordinates, registered previews, and handled empty states. After removing or changing a file, an electrochem plot could retain limits, legends, or annotations from the previous selection. Launcher operations also needed visible progress, and the growing collection of popout-figure actions no longer fit comfortably in every app. Figure cleanup, style adjustment, data export, and reconstruction were substantial enough to justify their own workflow. Decision and rationale Name the UI facade by responsibility: runtime, layout, controls, plots, interactions, and debugging. Move reusable axes mechanics into the plot layer, then migrate every supported app in one breaking version so old and new package names cannot coexist indefinitely. Give long launcher operations one busy and progress model, and move advanced figure work into Figure Studio rather than expanding the app shell further. Changes `labkit_launcher` `1.2.3 -> 1.2.4` `labkit.ui` `4.2.0 -> 5.0.0` `labkit_FigureStudio_app` `0.1.0 -> 0.1.4` `labkit_ChronoOverlay_app` `1.3.3 -> 1.3.5` `labkit_CIC_app` `1.3.5 -> 1.3.7` `labkit_CSC_app` `1.3.7 -> 1.3.9` `labkit_EIS_app` `1.3.3 -> 1.3.4` `labkit_VTResistance_app` `1.3.5 -> 1.3.7` `labkit_DICPreprocess_app` `1.3.3 -> 1.3.4` `labkit_DICPostprocess_app` `1.3.3 -> 1.3.4` `labkit_BatchImageCrop_app` `1.6.5 -> 1.6.6` `labkit_CurvatureMeasurement_app` `1.3.3 -> 1.3.4` `labkit_FLIRThermal_app` `1.2.7 -> 1.2.8` `labkit_FocusStack_app` `1.4.4 -> 1.4.5` `labkit_ImageEnhance_app` `1.5.4 -> 1.5.5` `labkit_ImageMatch_app` `1.5.4 -> 1.5.5` `labkit_RHSPreview_app` `1.3.3 -> 1.3.4` `labkit_NerveResponseAnalysis_app` `1.3.3 -> 1.3.4` `labkit_ResponseReviewStats_app` `1.3.3 -> 1.3.4` `labkit_ECGPrint_app` `1.3.4 -> 1.3.5` Reorganized the UI facade into `labkit.ui.runtime`, `layout`, `control`, `plot`, `interaction`, and `debug` so app authors can find lifecycle, data-only layout, control update, plot-area, pointer/tooling, and diagnostic APIs by responsibility. Replaced the old app/spec/view/tool/diag UI paths with UI 5 names and moved every app to `definition(..., \"Layout\", @buildWorkbenchLayout)` plus `labkit.ui.layout.*`, `labkit.ui.control.*`, `labkit.ui.plot.*`, `labkit.ui.interaction.*`, and `labkit.ui.debug.*`. Added framework-owned plot helpers for registered axes lookup, clearing, empty-state messages, fitted limits, canvas framing, image preview redraw, and data/fraction coordinate conversion. Reset electrochem plot axes and legends when files are cleared, removed, or redrawn so old ranges, markers, shaded windows, and annotations do not remain after the file list changes. Refit CIC, Chrono Overlay, CSC all-cycle, and VT Resistance redraws to the current plotted data instead of preserving stale manual zoom limits. Staggered CIC Emc/Ema marker labels with readable white backgrounds so key extrema labels are less likely to be hidden by voltage-step or window annotations. Added visible busy/progress feedback for launcher actions that can wait on file scans, artifact cleanup, app startup, profiling, GitHub version lookup, or update/install work. Added launcher and version-manager busy gates so repeated clicks do not start overlapping synchronous operations. Replaced top workbench utility buttons with native window utility menus. Changed workbench plot popout/copy/save actions to operate on every registered preview axes in a multi-axes app. Replaced icon-only popout figure tools with visible text buttons for font, plotted-line, axes, grid, and Studio handoff controls. Added the LabKit Core Figure Studio app for opening MATLAB `.fig` files, switching between the measured LabKit single-panel style and the imported FIG defaults, tuning font/line/grid parameters, and exporting visible graphics data packages with reconstruction scripts. Normalized imported FIG axes before applying Studio canvas and style constraints so source layout/aspect metadata and file-selection titles cannot collapse the preview. Centered the managed preview canvas in the app preview grid so styled FIG labels and plot content render in the visible canvas instead of the corner cell. Added a `labkit.ui.plot.fitCanvas` canvas-frame helper so fixed-size preview axes use the framework-owned preview grid policy instead of app-owned row/column layout code. User and data impact App plots now followed the current file selection: clearing or changing data also cleared stale ranges, legends, markers, and annotations. CIC kept Emc and Ema labels readable on dense traces, and multi-axes apps offered one consistent set of plot utilities. Launcher progress distinguished a long synchronous operation from a frozen MATLAB session and rejected repeated clicks while that operation was active. Figure Studio added a separate place to open a `.fig`, compare the imported style with a LabKit single-panel style, adjust visible graphics, and export the axes data with a reconstruction script. These operations worked on figure presentation and visible graphics; they did not replace the scientific export owned by the source app. For app authors, the breaking rename made API discovery more direct. Lifecycle code used `labkit.ui.runtime`, layout descriptions used `labkit.ui.layout`, and plot or interaction code no longer depended on a broad historical bucket. Compatibility and migration Breaking UI facade migration: app code must use the UI 5 package paths and require `labkit.ui >=5 <6`. Validation Commit `78f4827e` migrated 383 source and test files and added focused plot- helper and neurophysiology layout coverage. Launcher progress received its own GUI suite in `edbc79d8`. Figure Studio added GUI and result-export suites in `4c841d6f`, followed by source-axes import tests in `7edd619f`. Exact local commands for the combined release were not recorded. Evidence UI 5 facade and app migration `78f4827e`. Launcher progress and plot utilities `edbc79d8`. Figure Studio `4c841d6f` and source-axes cleanup `8aeee1f1`. FIG import normalization `7edd619f`. Known limitations and follow-up UI 5 clarified package ownership, but app code still performed much of its own lifecycle and rendering coordination. Runtime V2 later took explicit ownership of startup, callbacks, presenters, injected services, and serializable state."},{"title":"UI 7 public runtime boundary","url":"history/records/2026/07/LK-20260716-ui7-public-runtime-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-ui7-public-runtime-boundary 2026-07-16 75 refactor breaking labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app public API ownership Runtime V2 busy and persistence mechanics","text":"UI 7 public runtime boundary id: LK-20260716-ui7-public-runtime-boundary date: 2026-07-16 sequence: 75 type: refactor compatibility: breaking component: `labkit.ui` | `6.0.5 -> 7.0.0` component: `labkit_DICPostprocess_app` | `1.4.0 -> 1.4.1` component: `labkit_DICPreprocess_app` | `1.5.0 -> 1.5.1` component: `labkit_ChronoOverlay_app` | `1.4.0 -> 1.4.1` component: `labkit_CIC_app` | `1.4.0 -> 1.4.1` component: `labkit_CSC_app` | `1.4.0 -> 1.4.1` component: `labkit_EIS_app` | `1.4.0 -> 1.4.1` component: `labkit_VTResistance_app` | `1.4.0 -> 1.4.1` component: `labkit_GaitAnalysis_app` | `2.0.1 -> 2.0.2` component: `labkit_BatchImageCrop_app` | `1.7.0 -> 1.7.1` component: `labkit_CurvatureMeasurement_app` | `1.4.1 -> 1.4.2` component: `labkit_FLIRThermal_app` | `1.4.0 -> 1.4.1` component: `labkit_FocusStack_app` | `1.5.0 -> 1.5.1` component: `labkit_ImageEnhance_app` | `1.6.0 -> 1.6.1` component: `labkit_ImageMatch_app` | `1.6.0 -> 1.6.1` component: `labkit_VideoMarker_app` | `1.5.0 -> 1.5.1` component: `labkit_FigureStudio_app` | `0.2.0 -> 0.2.1` component: `labkit_NerveResponseAnalysis_app` | `1.4.1 -> 1.4.2` component: `labkit_ResponseReviewStats_app` | `1.4.1 -> 1.4.2` component: `labkit_RHSPreview_app` | `1.4.1 -> 1.4.2` component: `labkit_ECGPrint_app` | `1.4.1 -> 1.4.2` scope: public API ownership scope: Runtime V2 busy and persistence mechanics Context The UI 6 runtime exposed three implementation-level helpers alongside its App lifecycle: direct busy-state execution and the creation and resolution of portable file-reference structs. Production Apps did not need those entry points. Semantic callbacks already owned busy transactions, while injected project services and the save/load boundary owned source references. Keeping the helpers public created two competing ways to perform the same framework-owned work. It also made tests exercise a direct busy mode that no production App used. Decision and rationale UI 7 keeps lifecycle and semantic construction public while moving callback transactions and reference serialization into the runtime implementation. `labkit.ui.runtime.create` remains public: it has a distinct supported purpose for callers that already own a complete declarative layout and need only the workbench shell, without project lifecycle or action orchestration. Changes Removed `labkit.ui.runtime.runBusy` from the public facade. Layout actions and other semantic callbacks now reach one private transaction helper. Removed `createPortableFileReference` and `resolvePortableFileReference` from the public facade. Runtime save, load, recovery, and relink code continue to use those algorithms privately. Removed the unused direct-busy behavior that froze callbacks and graphics hit testing. Semantic transactions preserve callback-owned interaction changes. Updated all tracked Apps to declare the UI 7 compatibility range without changing their domain behavior or project payload versions. Replaced tests that fabricated references through a public helper with project-level save/move/load coverage or explicit legacy fixtures. User and data impact App users see no workflow or saved-data change. App developers initiate work through semantic actions and create source records through `services.project.sourceRecord`; the runtime owns the corresponding busy and serialization mechanics. Code that called the three removed UI 6 functions must move to those lifecycle contracts before adopting UI 7. Compatibility and migration The change is a deliberate public API break and therefore increments the UI facade major version. Every tracked App now requires `labkit.ui >=7 <8`. Runtime V2 project envelopes, App payload versions, and portable-reference field names are unchanged, so saved projects require no data migration. Validation Validation covers the exact UI package surface, semantic busy transactions, Runtime V2 project save/move/load and relink behavior, affected App migrations, App requirement/version consistency, generated API removal, and documentation history contracts. Evidence [UI Framework](../../../../framework/README.md) maps the supported public packages and runtime entry points. [Runtime and Data Model](../../../../framework/guides/runtime.md) explains semantic transactions, injected services, and portable source behavior. Known limitations and follow-up This boundary change does not redesign the lower-level declarative workbench builder. `labkit.ui.runtime.create` remains supported and will be judged by its own layout-only contract rather than by production App call count."},{"title":"UI diagnostics and release v3.0.0","url":"history/records/2026/06/LK-20260629-ui-diagnostics-and-release-v3-0-0.html","kind":"history","section":"history","keywords":"LK-20260629-ui-diagnostics-and-release-v3-0-0 2026-06-29 17 feat compatible labkit_launcher labkit.ui UI diagnostics and release v3.0.0","text":"UI diagnostics and release v3.0.0 id: LK-20260629-ui-diagnostics-and-release-v3-0-0 date: 2026-06-29 sequence: 17 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.2 -> 1.1.3` component: `labkit.ui` | `3.1.3 -> 3.2.0` scope: UI diagnostics and release v3.0.0 Context The diagnostic work completed during the 3.x UI cycle needed a stable release checkpoint, clearer validation documentation, and CI behavior that would not run duplicate workflows for the same change. Decision and rationale Publish the UI diagnostic improvements as `v3.0.0`, document how they are validated, and avoid redundant CI triggers. Keep the release compatible with the app data produced by the preceding 2.x workbench releases. Changes Release tag `v3.0.0` `labkit.ui` `3.1.3 -> 3.2.0` `labkit_launcher` `1.1.2 -> 1.1.3` Improved UI diagnostics and validation documentation. Published the v3.0.0 release line around UI diagnostics, validation docs, and duplicate CI avoidance. User and data impact Users gained a named release to install or restore, while failure reports contained better callback context. Project and scientific result formats did not change in this release record. Compatibility and migration The v3.0.0 release retained the preceding app data formats. Users could update or roll back the workbench without converting saved scientific results. Validation Commit `21eff4dc` carried the diagnostic and validation changes; tag commit `349a7549` published `v3.0.0`. Exact historical local commands were not recorded. Evidence Main commits `21eff4dc` and release tag commit `349a7549`. Known limitations and follow-up This record marks the release boundary; individual diagnostic mechanics are described in their earlier component history records."},{"title":"UI groups migration","url":"history/records/2026/07/LK-20260703-ui-groups-migration.html","kind":"history","section":"history","keywords":"LK-20260703-ui-groups-migration 2026-07-03 34 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app UI groups migration","text":"UI groups migration id: LK-20260703-ui-groups-migration date: 2026-07-03 sequence: 34 type: refactor compatibility: compatible component: `labkit.ui` | `3.4.5 -> 4.0.0` component: `labkit_DICPostprocess_app` | `1.3.1 -> 1.3.2` component: `labkit_DICPreprocess_app` | `1.3.1 -> 1.3.2` component: `labkit_ChronoOverlay_app` | `1.3.1 -> 1.3.2` component: `labkit_CIC_app` | `1.3.3 -> 1.3.4` component: `labkit_CSC_app` | `1.3.3 -> 1.3.4` component: `labkit_EIS_app` | `1.3.1 -> 1.3.2` component: `labkit_VTResistance_app` | `1.3.3 -> 1.3.4` component: `labkit_BatchImageCrop_app` | `1.6.3 -> 1.6.4` component: `labkit_CurvatureMeasurement_app` | `1.3.1 -> 1.3.2` component: `labkit_FLIRThermal_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.4.2 -> 1.4.3` component: `labkit_ImageEnhance_app` | `1.5.2 -> 1.5.3` component: `labkit_ImageMatch_app` | `1.5.2 -> 1.5.3` component: `labkit_NerveResponseAnalysis_app` | `1.3.1 -> 1.3.2` component: `labkit_ResponseReviewStats_app` | `1.3.1 -> 1.3.2` component: `labkit_RHSPreview_app` | `1.3.1 -> 1.3.2` component: `labkit_ECGPrint_app` | `1.3.2 -> 1.3.3` scope: UI groups migration Context The declarative runtime described actions as a mostly flat list with optional group hints. Apps with several phases could not reliably express which controls belonged together, and layout code had to infer more structure than the app had actually declared. Decision and rationale Make groups the primary unit of control layout. Each group would carry its title and controls, giving the runtime enough structure to render related actions together without learning app-specific labels or workflow rules. Changes `labkit.ui` `3.4.5 -> 4.0.0` All supported apps received patch bumps. Replaced action groups with UI groups. Moved the reusable UI contract into the 4.x line. User and data impact Apps retained their actions, but related controls appeared in explicit visual sections. App definitions became easier to scan because their grouping matched the panels users saw. Compatibility and migration App workflow definitions had to align with the new grouped UI contract. Validation The migration updated UI specification tests, basic-control and workbench GUI tests, app package structure checks, and all supported app definitions. The exact historical command was not recorded. Evidence Main commit `e81243a3`. Known limitations and follow-up This was a breaking definition-format change and established the UI 4.x line. UI 5 later renamed facade packages by responsibility but retained explicit groups as the layout model."},{"title":"UI utility snapshots and popout tools","url":"history/records/2026/07/LK-20260704-ui-utility-snapshots-and-popout-tools.html","kind":"history","section":"history","keywords":"LK-20260704-ui-utility-snapshots-and-popout-tools 2026-07-04 37 feat compatible labkit.ui UI utility snapshots and popout tools","text":"UI utility snapshots and popout tools id: LK-20260704-ui-utility-snapshots-and-popout-tools date: 2026-07-04 sequence: 37 type: feat compatibility: compatible component: `labkit.ui` | `4.1.0 -> 4.2.0` scope: UI utility snapshots and popout tools Context Users could tune controls and plots during a session, but restoring that work required repeating the settings manually. Popout axes were useful for visual inspection, yet copying data or recreating the plot in a script still required ad hoc commands. Decision and rationale Give the runtime a serializable snapshot contract for app state and expose save and load as workbench utilities. Extend popout axes with explicit styling, image copy, data export, and reconstruction-script actions derived from the graphics objects already present on the axes. Changes `labkit.ui` `4.1.0 -> 4.2.0` Added UI state snapshot save/load APIs. Added workbench utility controls. Improved axes popout export and copy tools. User and data impact Users could save a supported app state and restore it later from the workbench. Popout plots could be restyled, copied, exported as visible graphics data, or used to generate a MATLAB reconstruction script without changing the original app result. Compatibility and migration Apps that did not declare serializable state continued to run without snapshot support. Existing app data and figure files were not converted automatically. Validation The commit expanded app-runtime and axes-workbench GUI suites and added package- surface checks for the snapshot APIs. The exact historical command was not recorded. Evidence Main commit `0155cd12`. Known limitations and follow-up Snapshots covered state declared serializable by the app; they were not raw MATLAB workspace dumps. Figure Studio later provided a dedicated workflow for more extensive figure cleanup and export."},{"title":"VT Resistance consolidates its project contract","url":"history/records/2026/07/LK-20260716-vt-resistance-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-vt-resistance-project-spec 2026-07-16 90 refactor compatible labkit_VTResistance_app Electrochemistry Project lifecycle","text":"VT Resistance consolidates its project contract id: LK-20260716-vt-resistance-project-spec date: 2026-07-16 sequence: 90 type: refactor compatibility: compatible component: `labkit_VTResistance_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context VT Resistance split static product metadata across three files and its first-version project across a generic lifecycle package. Its session factory also encodes a real performance policy by loading only the first preview source. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because lazy DTA decoding and analysis is transient reconstruction, not lifecycle boilerplate. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved lazy preview restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Added a direct project/session contract assertion to the VT export suite. Kept pulse detection, steady windows, voltage definitions, resistance formulas, plots, and export schemas unchanged. User and data impact Launch, batch loading, selection, calculation, save/load, plotting, and CSV exports behave unchanged. The durable contract and lazy reconstruction policy are now explicit and adjacent. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing VT Resistance projects require no migration. Validation Unit tests cover project/session boundaries, pulse detection, window policies, raw and baseline-corrected resistance, failed rows, and CSV output. The hidden GUI workflow covers real launch, selection, calculation, save/load, plots, and export. Evidence [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The lazy session still reaches into portable-reference internals. The shared source-path boundary will replace this across all Apps in one framework change."},{"title":"VT Resistance removes source and workflow storage boilerplate","url":"history/records/2026/07/LK-20260716-vt-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-vt-runtime-source-reconciliation 2026-07-16 114 refactor compatible labkit_VTResistance_app Electrochemistry App structure Runtime adoption","text":"VT Resistance removes source and workflow storage boilerplate id: LK-20260716-vt-runtime-source-reconciliation date: 2026-07-16 sequence: 114 type: refactor compatibility: compatible component: `labkit_VTResistance_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context VT Resistance deliberately keeps batch source registration lazy, decoding only the selected preview until export. The App nevertheless generated and appended portable source records itself and wrote analysis messages directly into `session.workflow.logLines`. Its session factory consequently repeated empty workflow and view buckets owned by Runtime V2. Decision and rationale Keep lazy ordered path registration, selection, resistance analysis, plots, and exports in VT Resistance. Delegate the conversion from that ordered path set to stable portable records to `services.project.reconcileSources`. Runtime preserves retained identities and allocates new unique identities. Route analysis messages through `services.workflow.log` and return only App-specific selection and cache state from `createSession`. Changes Removed local source-ID generation, record append, and registration lookup. Reconciled ordered source paths after add, removal, and clear operations. Routed resistance-analysis messages through the Runtime workflow service. Removed empty workflow and view session boilerplate. Added GUI checks for Runtime-supplied buckets and stable, unique source IDs across batch add, save, reopen, removal, and re-addition. Advanced the VT Resistance App version to 1.4.4. User and data impact Lazy loading, current selection, resistance values, plots, logs, export columns, and project files retain their behavior. Retained file identities are stable and newly added files cannot collide with them. Scientific calculations and exported numeric values are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing portable source records retain their IDs when reopened. Validation The VT Resistance hidden GUI workflow covers three-source lazy registration, selection, whole-batch recomputation, export, save, clear, reopen, remove, re-add, and source identity. Focused resistance/export and repository contract tests cover the surrounding calculation and product behavior. Evidence [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up The broader App inventory must still be scanned for local source-record construction that represents migrations or semantic fixed slots rather than replaceable Runtime bookkeeping."},{"title":"Validated runtime identity contracts","url":"history/records/2026/07/LK-20260716-runtime-identity-contracts.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-identity-contracts 2026-07-16 65 fix compatible labkit.ui runtime definitions layouts and presentations projects and result manifests","text":"Validated runtime identity contracts id: LK-20260716-runtime-identity-contracts date: 2026-07-16 sequence: 65 type: fix compatibility: compatible component: `labkit.ui` | `6.0.0 -> 6.0.1` scope: runtime definitions scope: layouts and presentations scope: projects and result manifests Context Runtime ids connect app definitions, layout controls, actions, axes, interactions, durable source records, result outputs, and managed resources. Several registries previously accepted duplicate ids or dangling references until a callback ran. Recovery directory names also used MATLAB identifier normalization, which is not an injective mapping and could make two distinct app ids share storage. Decision and rationale Keep semantic ids developer-owned, but validate each namespace at the earliest transaction boundary. App ids now have one documented portable syntax and a collision-free filesystem encoding. References from layouts, presentations, and interactions must resolve before the runtime changes visible handles or managed resources. Same-scope resource replacement remains intentional: a resource id is an idempotent ownership slot, while resources that must coexist use distinct ids. Changes Rejected invalid app ids, duplicate preview-axis ids, duplicate durable source ids, and duplicate result-output ids. Rejected layout, interaction, and presentation references to unknown actions, controls, targets, axes, or renderers before committing UI changes. Encoded app recovery storage with the exact app id while retaining read-only discovery of recovery files written under the earlier normalized folder. Added a repository contract test requiring literal public app ids to be globally unique. User and data impact Valid current apps and saved projects behave as before. Configuration mistakes now fail with a specific runtime error instead of overwriting another object or appearing later as a broken callback. Existing recovery files remain discoverable and new recovery files cannot collide through name normalization. Compatibility and migration This patch is compatible for documented definitions. A private app with an invalid app id, duplicate semantic ids, or a dangling reference must correct that declaration. Its app id should otherwise remain unchanged because the id is part of saved-project and recovery compatibility. Validation Focused unit, contract, and hidden-GUI runtime tests cover invalid definitions, duplicate namespaces, dangling references, result manifests, and recovery storage behavior. Evidence `labkit.ui.runtime.define` documents the public identity and resource rules. Runtime validation rejects invalid identity graphs before commit. The app identity contract scans every public app definition. Known limitations and follow-up Identity validation protects one runtime and the public app catalog. External private-app catalogs remain responsible for running the same contract test in their own workspace."},{"title":"Version metadata baseline","url":"history/records/2026/06/LK-20260623-version-metadata-baseline.html","kind":"history","section":"history","keywords":"LK-20260623-version-metadata-baseline 2026-06-23 10 feat compatible labkit_launcher labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Version metadata baseline","text":"Version metadata baseline id: LK-20260623-version-metadata-baseline date: 2026-06-23 sequence: 10 type: feat compatibility: compatible component: `labkit_launcher` | `new -> 1.0.0` component: `labkit.ui` | `2.1.0 -> 2.2.0` component: `labkit_DICPostprocess_app` | `new -> 1.0.0` component: `labkit_DICPreprocess_app` | `new -> 1.0.0` component: `labkit_ChronoOverlay_app` | `new -> 1.0.0` component: `labkit_CIC_app` | `new -> 1.0.0` component: `labkit_CSC_app` | `new -> 1.0.0` component: `labkit_EIS_app` | `new -> 1.0.0` component: `labkit_VTResistance_app` | `new -> 1.0.0` component: `labkit_BatchImageCrop_app` | `new -> 1.0.0` component: `labkit_CurvatureMeasurement_app` | `new -> 1.0.0` component: `labkit_FocusStack_app` | `new -> 1.0.0` component: `labkit_ImageEnhance_app` | `new -> 1.0.0` component: `labkit_ImageMatch_app` | `new -> 1.0.0` component: `labkit_NerveResponseAnalysis_app` | `new -> 1.0.0` component: `labkit_ResponseReviewStats_app` | `new -> 1.0.0` component: `labkit_RHSPreview_app` | `new -> 1.0.0` component: `labkit_ECGPrint_app` | `new -> 1.0.0` scope: Version metadata baseline Context The repository had release tags and package contracts, but the launcher and individual apps did not display their own component versions. Decision and rationale Add lightweight `version` requests to every app and the launcher, show those versions in titles and the launcher catalog, and validate their format so a debug report can identify the exact component being run. Changes Release tag `v2.4.0` `labkit_launcher` `1.0.0` All supported apps `1.0.0` `labkit.ui` `2.1.0 -> 2.2.0` Added app and launcher version metadata. Added versioned titles, lightweight version requests, launcher catalog version display, and version guardrails. User and data impact Users and maintainers could read the launcher and app version without inspecting Git history. The change added metadata only and did not modify saved data or numerical results. Compatibility and migration Version requests and titles were additive. Existing app commands, inputs, and saved results continued to work without conversion. Validation Commit `d70c2607` added version-request, title, catalog, and format checks. Release tag `v2.4.0` identifies the shipped baseline. Evidence Main commit `d70c2607`. Known limitations and follow-up These component versions describe displayed app/library builds; compatibility between reusable packages is handled separately by `labkit.contract`."},{"title":"Video Marker app","url":"history/records/2026/07/LK-20260713-video-marker-app.html","kind":"history","section":"history","keywords":"LK-20260713-video-marker-app 2026-07-13 54 feat compatible labkit_VideoMarker_app Video Marker app","text":"Video Marker app id: LK-20260713-video-marker-app date: 2026-07-13 sequence: 54 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `new -> 1.0.0` scope: Video Marker app Context Image measurement workflows needed a generic video annotation tool for ordered 2D keypoints and skeleton connections. The app belongs with image measurement tools because it uses the same axes interaction, scale calibration, and export style, but the source media and frame navigation are video-specific. Decision and rationale Add a standalone Video Marker app instead of extending an existing image-only app or adding a public video facade. The first version keeps marker semantics app-local, uses existing UI interaction tools for ordered points and scale calibration, and avoids training, inference, multi-subject tracking, or per-point missing-state semantics. Changes Added `labkit_VideoMarker_app` under Image Measurement with video loading, indexed frame navigation, ordered keypoint placement, skeleton overlays, and previous-confirmed-frame inheritance as draft annotations. Added a self-describing marker CSV that stores pixel coordinates for every frame and can be imported back into the app for overlay or editing. Added a separate coordinate CSV export for continuous confirmed frame ranges, with selectable pixel or calibrated units, top-left or first-point origin, and up/down Y-axis convention. Added project MAT save/load and debug sample-pack coverage for synthetic video marker assets. User and data impact Users can manually annotate video frames without depending on a model-training workflow. Marker CSV remains the editable round-trip format; coordinate CSV is plain plotting data and does not mutate the saved marker annotations. Compatibility and migration The app is additive and does not change existing image measurement apps or public LabKit facades. No existing project files or exports require migration. Validation `VideoMarkerTest` covers skeleton parsing, frame inheritance, marker CSV round trip, coordinate transforms, and project save/load. `GuiLayoutVideoMarkerTest` covers the hidden GUI launch and semantic control contract. Both accompanied the app in commit `b41f5254`. Evidence Initial Video Marker app `b41f5254`. Known limitations and follow-up The first version is manual and single-subject. It intentionally does not perform automatic interpolation, optical flow, model inference, multi-camera synchronization, 3D annotation, or per-point missing/out-of-frame labeling."},{"title":"Video Marker concentrates project history behind one contract","url":"history/records/2026/07/LK-20260716-video-marker-structure.html","kind":"history","section":"history","keywords":"LK-20260716-video-marker-structure 2026-07-16 103 refactor compatible labkit_VideoMarker_app labkit_GaitAnalysis_app Image Measurement App structure","text":"Video Marker concentrates project history behind one contract id: LK-20260716-video-marker-structure date: 2026-07-16 sequence: 103 type: refactor compatibility: compatible component: `labkit_VideoMarker_app` | `1.5.1 -> 1.5.2` component: `labkit_GaitAnalysis_app` | `2.0.2 -> 2.0.3` scope: Image Measurement scope: App structure Context Video Marker had already separated skeleton, annotations, tracking, video IO, export, and UI capabilities, but its Runtime contract remained spread across seven generic lifecycle files plus separate requirement and version files. A path lookup wrapper also inspected the Runtime's nested portable-reference fields, and the legacy importer constructed that schema itself. Decision and rationale Keep the App's real persistence requirements—schema migration, old MAT import, and current-frame resume—but expose them together through one `projectSpec.m`. Keep decoded-video reconstruction in root `createSession.m`. Use the Runtime's source factory and accessor instead of App-owned reference adapters. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderers, and debug capability in `definition.m`. Concentrated create, validate, migrate, legacy import, resume creation, and resume application behind one project-spec entry. Replaced the ordered migration-cell declaration with the Runtime's single migration callback. Replaced App-side source-reference construction and nested-field lookup with `sourceRecord` opaque import and `sourcePaths` semantic lookup. Removed the generic lifecycle package, source-path wrapper, and separate requirement/version files. Updated Gait's synthetic Video Marker fixture to construct the current payload through the project contract rather than a retired lifecycle file. User and data impact Skeleton setup, continuous point marking, prediction, calibration, coordinate export, explicit autosave, save-before-new confirmation, recovery, source relinking, and old MAT upgrade retain their behavior. The saved current-frame resume remains navigation convenience; durable coordinates and provenance are unchanged. Developers now find all project-history policy in one file and do not need to learn the Runtime's portable-reference representation. Compatibility and migration Current schema version 2 and the recognized old `videoMarkerProject` variable are unchanged. Version-1 payloads still derive durable video metadata, and old portable references retain their relative path. New saves continue to use the current `labkitProject` envelope. Validation Focused unit tests cover skeleton editing, annotation provenance, deterministic motion tracking, Toolbox parity, CSV and coordinate semantics, metadata, migration, legacy import, validation, and resume. Five hidden GUI workflows cover launch, continuous marking, viewport preservation, explicit autosave, new-project choices, named save/load, recovery, and old-format upgrade. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) Known limitations and follow-up Automated GUI checks do not establish tracking validity for every recording or replace manual judgment of point placement and prediction quality. Remaining Apps with generic lifecycle packages will be reviewed independently."},{"title":"Video Marker predictive frame navigation","url":"history/records/2026/07/LK-20260714-video-marker-predictive-navigation.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-predictive-navigation 2026-07-14 58 feat compatible labkit_VideoMarker_app labkit.ui `docs/development/build-apps/architecture.md` `docs/api/ui.md` runtime dependency policy","text":"Video Marker predictive frame navigation id: LK-20260714-video-marker-predictive-navigation date: 2026-07-14 sequence: 58 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `1.1.0 -> 1.2.0` component: `labkit.ui` | `5.1.1 -> 5.2.0` scope: `docs/development/build-apps/architecture.md` scope: `docs/api/ui.md` scope: runtime dependency policy Context The first tracking implementation exposed separate interpolation and previous-frame buttons. Its integer grayscale block search could only model a small translation, and coordinate interpolation did not follow the actual trajectory of articulated points. Users still had to request every estimate manually. Decision and rationale Make prediction an automatic part of forward navigation. Use a mature pyramidal Kanade-Lucas-Tomasi tracker with forward/backward rejection for short-range point propagation, and treat every complete human edit as a new anchor. This matches the app's correction-oriented workflow without claiming unattended long-term or occlusion tracking. Keep the implementation MATLAB-local. Project policy now forbids incidental third-party runtimes, Python environments, model downloads, and first-run network installation; additional dependencies require an explicit architecture decision. Changes Removed the user-facing Track and Interpolate actions. Added automatic forward prediction for adjacent frames and forward jumps. Replaced integer block matching with cropped four-level pyramidal KLT, forward/backward reliability checks, subpixel coordinates, and a constant-velocity fallback for rejected points. Added manual/predicted frame provenance and per-point confidence to the recoverable project payload while preserving marker CSV coordinates. Added reusable portable external-file references to `labkit.ui.runtime` and used them for both Video Marker projects and video-adjacent autosaves. Added a reusable field-labeled manual relinking fallback for malformed or unresolved references; app imports retain file-format validation ownership. Project and autosave payloads now prefer a path relative to the MAT file, fall back to the original path or same-folder filename, and offer video relinking when no automatic candidate exists. Made manual edits immutable anchors that reset subsequent prediction; only predicted drafts may be refreshed by the tracker. Added the repository runtime-dependency boundary to the root constitution, app rules, and human architecture documentation. User and data impact After users mark a complete frame, moving forward presents editable predicted points automatically. Correcting those points establishes a fresh anchor. Older projects are upgraded in memory with provenance defaults; marker and coordinate CSV formats are unchanged. Project/video directory trees can move between users, cloud-drive roots, and operating systems without editing paths. Compatibility and migration Existing Video Marker projects and autosaves remain readable. New project payloads contain additive provenance and confidence fields. Systems without the MATLAB KLT implementation retain coordinates through the motion fallback and remain manually editable; no third-party installation is attempted. Validation Synthetic tests cover subpixel translated-texture tracking, provenance upgrade, and marker CSV round trips. Hidden GUI tests cover automatic prediction on forward navigation and the absence of manual assist buttons. Local untracked evaluation used adjacent frames from a representative video across marker, eye, ear, nose, and toe textures; repository fixtures contain no real sample paths or pixels. Evidence Predictive marking and portable file references `6bfd74c8`. Known limitations and follow-up KLT is a short-range tracker. Low-texture, occluded, or strongly deforming points can be rejected and require human correction; that correction is the next anchor rather than an error state."},{"title":"Video Marker startup validation","url":"history/records/2026/07/LK-20260714-video-marker-startup-fix.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-startup-fix 2026-07-14 56 fix compatible labkit_VideoMarker_app `tests/shared/guiTestHelpers.m`","text":"Video Marker startup validation id: LK-20260714-video-marker-startup-fix date: 2026-07-14 sequence: 56 type: fix compatibility: compatible component: `labkit_VideoMarker_app` | `1.0.0 -> 1.0.1` scope: `tests/shared/guiTestHelpers.m` Context Video Marker declared its preview axes as `video` but reset an empty preview through the nonexistent `raw` axes id. The runtime correctly converted that deferred startup exception into a visible failure status, but the structural GUI test continued because it checked controls before asserting that startup had completed successfully. Decision and rationale Use the declared `video` axes id in the app and make the shared standard workbench assertion wait for the startup lifecycle. A startup failure now fails the GUI test with the runtime diagnostic instead of leaving the test green after only verifying that the shell was constructed. Changes Corrected the empty-preview reset to target `videoAxes/video`. Added a shared startup-success assertion to standard workbench GUI checks. Added a synthetic regression proving deferred startup failures are rejected. User and data impact Video Marker opens normally before a video is selected. This change does not alter marker projects, coordinate exports, annotations, or measurement data. Compatibility and migration The fix is compatible with existing Video Marker projects and exported files. No user migration is required. Validation The Video Marker hidden GUI test covers the corrected initial preview path. The reusable declarative UI GUI test covers startup-failure detection in the shared helper. Both regressions were added in the focused startup repair commits below. Evidence Video Marker startup repair `48319203`. Focused GUI startup-lifecycle regression split `c3c6ce06`. Known limitations and follow-up Automated startup checks validate lifecycle completion and diagnostics; they do not replace manual review of video rendering or point-drag interaction."},{"title":"Video Marker visual skeleton setup and continuous marking","url":"history/records/2026/07/LK-20260714-video-marker-continuous-marking.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-continuous-marking 2026-07-14 57 feat compatible labkit.ui labkit_VideoMarker_app Video Marker visual skeleton setup and continuous marking","text":"Video Marker visual skeleton setup and continuous marking id: LK-20260714-video-marker-continuous-marking date: 2026-07-14 sequence: 57 type: feat compatibility: compatible component: `labkit.ui` | `5.1.0 -> 5.1.1` component: `labkit_VideoMarker_app` | `1.0.1 -> 1.1.0` scope: Video Marker visual skeleton setup and continuous marking Context Video Marker required comma-separated keypoint names, manually encoded edge text, a start/finish edit toggle, and a separate confirm action. Those controls made skeleton setup error-prone and added modes that did not match the direct click-and-drag interaction already supported by the point editor. Decision and rationale Make skeleton definition the first explicit step, represent keypoints and connections as structured controls, and keep point editing active for the whole video session. Frame navigation is the save boundary; complete frames confirm automatically and inherited frames remain drafts until edited. Changes Replaced long skeleton strings with an editable ordered-keypoint table, mutually filtered connection endpoint selectors, and add/remove/reorder actions. Added editable skeleton presets led by the legacy iliac-to-foot five-point leg chain, while keeping blank custom construction available. Added a connect-in-order shortcut that fills all adjacent point connections without removing other user-defined edges. Added the domain-neutral `labkit.ui.control.setItems` API for dynamically updating selectable control choices without firing app callbacks. Added `labkit.ui.plot.replaceOverlay` so image apps can replace one named overlay layer without clearing peer graphics, view limits, or callbacks. Added hidden-test-safe `labkit.ui.runtime.confirm` for app-owned two-choice recovery and confirmation workflows. Removed start/finish and confirm controls; blank clicks add points in order, dragging refines them, and frame changes save and inherit coordinates. Made point edits refresh only the skeleton overlay so zoom, wheel handling, and the active continuous-marking session survive each placed point. Preserved the current X/Y viewport across frame navigation so users can mark the same zoomed ROI through consecutive frames. Added draft-only marking assists for temporal interpolation between nearest confirmed frames and lightweight local block matching from the immediately previous confirmed frame. Added atomic change-driven project-compatible autosave in a visible subfolder beside the source video, plus matching-video restore/start-new choices. Added a new-setup action that explicitly clears the current session before a different skeleton is declared. User and data impact Skeleton setup is visible and validated before video loading. Existing marker CSV, coordinate CSV, and project MAT schemas remain unchanged. Point order is still the coordinate-column identity contract. Compatibility and migration Existing marker CSV and project files remain readable. The workflow changes are UI-only; users no longer press Apply skeleton, Start point edit, or Confirm frame. Video Marker now requires `labkit.ui >=5.1.1 <6`. Validation Unit tests cover skeleton presets, edits, and edge remapping. Hidden GUI workflow tests cover preset application, table-based setup, mutually filtered connection choices, automatic editor activation, consecutive point placement with preserved zoom/scroll callbacks, complete-frame confirmation, frame inheritance, and draft-state preservation. UI framework tests cover dynamic selectable items. Evidence Continuous-marking and skeleton workflow redesign `bcba1833`. Known limitations and follow-up Automated hidden-GUI tests verify callbacks and state transitions but cannot judge visual density or pointer ergonomics; those still need a short manual review with a representative video."},{"title":"v1.0: app-owned packages and a standard test platform","url":"history/records/2026/06/LK-20260606-v1-foundation.html","kind":"history","section":"history","keywords":"LK-20260606-v1-foundation 2026-06-06 5 refactor compatible historical project evolution","text":"v1.0: app-owned packages and a standard test platform id: LK-20260606-v1-foundation date: 2026-06-06 sequence: 5 type: refactor compatibility: compatible scope: historical project evolution Context The pre-release work had produced many useful apps and library functions, but the repository still carried large entry points, custom test routing, UI compatibility aliases, and helper ownership that was difficult to explain. Releasing that state as a stable foundation required more than assigning a version number. Decision and rationale Make each app package responsible for its runner, callbacks, presenters, and private workflow helpers. Use MATLAB's official test platform as the common runner, retain focused GUI gesture tests where automation was meaningful, and remove compatibility abstractions that no longer represented supported code. Changes Added the official MATLAB test-platform skeleton and migrated project guardrails and app suites to it. Decomposed image, DIC, electrochem, and ECG entry points into app-owned packages and private workflow helpers. Added GUI gesture coverage and CI test artifacts. Centralized the build-task catalog and separated scheduled coverage from faster routine checks. Added Batch Image Crop with a base-MATLAB rotation path. Completed DIC package migration and removed toolbox-only unit dependencies from its tested paths. Removed obsolete UI compatibility aliases and specialized dual-plot shell APIs in favor of structured form specifications and app-owned plot controls. User and data impact The v1.0 foundation provided a coherent set of named apps backed by repeatable tests. Batch Image Crop joined the image family, while existing electrochem, DIC, curvature, focus, and ECG workflows retained their app entry points. The release concentrated on ownership and validation. It did not introduce a single shared project-file format or combine the apps into one monolithic GUI. Compatibility and migration Repository code using removed UI aliases or dispatch routers needed to call the supported app and UI contracts. User-facing app entry points remained the intended launch mechanism. Validation MATLAB unit suites, architecture guardrails, GUI gesture tests, build-task checks, CI JUnit summaries, and scheduled coverage were present by the end of the stage. Tag `v1.0` points to `0bb83a6e`. Evidence Test-platform rewrite `7ad36886` through `83c8fcc1`. Build entry points and task catalog `12aa66af` through `7cc209ab`. Batch Image Crop `0731f6f0`, `5a76eb31`. App-owned package migrations `c4ae5074` through `76146c3d`. UI API cleanup `65a8c65d` through `0f428eb8`. v1.0 tag target `0bb83a6e`. Known limitations and follow-up v1.0 still required users to know individual app commands and used the first generation of the UI runtime. The next major release added a launcher and migrated apps to UI 2.0."},{"title":"v2.0: launcher, image apps, and UI 2.0","url":"history/records/2026/06/LK-20260615-v2-launcher-and-ui2.html","kind":"history","section":"history","keywords":"LK-20260615-v2-launcher-and-ui2 2026-06-15 6 feat compatible historical project evolution","text":"v2.0: launcher, image apps, and UI 2.0 id: LK-20260615-v2-launcher-and-ui2 date: 2026-06-15 sequence: 6 type: feat compatibility: compatible scope: historical project evolution Context The first release had stable app commands, but discovery was poor: users had to know which command to run. Apps also used a mixture of first-generation UI patterns, which made startup, resizing, debug launch, and preview behavior less consistent than the underlying package boundaries. Decision and rationale Add a launcher as the visible entrance to the workbench and migrate the app families to a second-generation UI contract. Preserve separate app entry points and app-owned workflows; the launcher would discover and start them rather than turn them into screens of one large application. Changes Added the LabKit launcher and localized MATLAB project metadata. Added Image Enhance and Image Match workflows and semantic tab resizing. Supplied a base-MATLAB path for image matching instead of requiring an Image Processing Toolbox implementation. Migrated LabKit apps to UI 2.0 and added a starter template for new apps. Stabilized responsive layout behavior and default text-panel sizing. Completed cold-start migration, simplified debug launch, and persisted debug artifacts for later inspection. Refined DIC Postprocess overlay export at the release boundary. User and data impact Users could browse and start supported apps from one launcher while retaining the option to call an app directly. Image users gained enhancement and matching workflows. Debug sessions left evidence that could be inspected after a startup or callback problem. Existing scientific file formats and app-specific saved data remained owned by their apps. Compatibility and migration App implementations moved to UI 2.0, but the release preserved the model of separate named app entry points. Code that depended on first-generation UI implementation details needed to adopt the new runtime contracts. Validation The release used the reorganized MATLAB suite, GUI layout regressions, cold- start checks, and focused DIC overlay tests. Tag `v2.0` points to `4bc7343f`. Evidence Launcher `7c825ef8`. Image apps and base-MATLAB matching `5cdc31e2`, `c838635c`. UI 2.0 migration `c2658605`. Starter app and layout stabilization `6c8b57ab`, `78ca39e0`. Startup and debug work `90c8869f` through `2153bd08`. v2.0 tag target `4bc7343f`. Known limitations and follow-up The launcher did not yet update itself, and large file panels and image tools still exposed inconsistent busy, zoom, and path-selection behavior. Those cross-app concerns became the focus of the 2.1 through 2.3 releases."},{"title":"v2.2 to v2.3.1: self-contained launch and image workflow refinement","url":"history/records/2026/06/LK-20260621-v2-2-v2-3-image-workflows.html","kind":"history","section":"history","keywords":"LK-20260621-v2-2-v2-3-image-workflows 2026-06-21 8 feat compatible historical project evolution","text":"v2.2 to v2.3.1: self-contained launch and image workflow refinement id: LK-20260621-v2-2-v2-3-image-workflows date: 2026-06-21 sequence: 8 type: feat compatibility: compatible scope: historical project evolution Context The launcher and updater existed, but installation still assumed repository context. At the same time, ordinary image work exposed small but costly interaction problems: selecting several files, preserving zoom while placing a marker, padding a crop without shifting its source coordinates, or returning to duplicate-crop mode after editing scale. Decision and rationale Make the launcher usable as a self-contained entry artifact and refine the high-frequency image workflows in place. These releases favored fewer steps and stable spatial state over adding more general-purpose dialogs or toolbars. Changes Made the launcher self-contained and added an update button backed by the release updater. Appended file-panel selections and added multi-file choose-and-load behavior. Improved Image Match speed and separated its reference input from moving images. Preserved Batch Crop source coordinates when padding, reduced padding seams, and refined the padding workflow. Added physical scale mode to Batch Crop and restored scale and duplicate-task interactions after edits. Preserved image-tool zoom while placing or editing overlays. Hardened image-tool ownership and changed the README to present the launcher as the primary user entry. User and data impact Users could begin from a compact launcher, select several files in one action, and edit image overlays without losing their current view. Batch Crop gained physical scaling while retaining the relationship between the visible crop and the source image. Padding and scale metadata affected exported crop interpretation, so regression work explicitly protected source coordinates and resumed interaction modes. Compatibility and migration The changes were compatible additions and fixes to the v2 workflows. Release tags identify the incremental baselines: `v2.2.0` at `c904baca`, `v2.3.0` at `f2ed23c2`, and `v2.3.1` at `83d03e7a`. Validation Focused tests covered path-list cleanup, Batch Crop scale and padding contracts, preview interactions, and image-tool ownership. The exact command for each historical commit was not recorded. Evidence Self-contained launcher and updater button `a1938db8`, `c904baca`. File selection and Image Match work `5aa10909`, `471b79c2`, `ebdbf9db`. Batch Crop padding `54c8fe43` through `f2ed23c2`. Zoom and physical scale `632191b9` through `2b9690fc`. Ownership hardening and primary launcher guidance `8328248a`, `83d03e7a`. Known limitations and follow-up Some previews still repeated expensive processing, EIS log-axis navigation was fragile, and the release process did not yet prove that downloadable artifacts matched their tags. v2.3.2 and v2.3.3 addressed those issues."},{"title":"v2.3.2 and v2.3.3: preview performance and release contracts","url":"history/records/2026/06/LK-20260623-v2-3-performance-and-release-contracts.html","kind":"history","section":"history","keywords":"LK-20260623-v2-3-performance-and-release-contracts 2026-06-23 9 fix compatible historical project evolution","text":"v2.3.2 and v2.3.3: preview performance and release contracts id: LK-20260623-v2-3-performance-and-release-contracts date: 2026-06-23 sequence: 9 type: fix compatibility: compatible scope: historical project evolution Context By v2.3.1, app discovery and image interaction were substantially better, but users could still experience redundant preview processing and unstable axes limits. Separately, a working release download was not sufficient evidence that the downloaded launcher came from the intended tag. Decision and rationale Separate preview computation from rendering so an interaction does not repeat scientific or image processing unnecessarily. Stabilize axes behavior at the app boundary, then make release assets reproducible and add facade contract checks that describe which library calls apps may rely on. Changes Stabilized EIS zoom on logarithmic axes and reset plot limits when coordinate choices changed. Reduced repeated image preview processing and separated preview preparation from display updates. Documented the boundary between preview state and exported results. Added pixel-radius controls for scale previews. Required release assets to be reproduced from their Git tag. Improved GitHub onboarding and issue templates. Added facade contract checks and hid windows created by automated GUI tests. User and data impact Image apps responded more quickly when controls changed without requiring new processing. EIS navigation became more predictable, and scale previews exposed their pixel radius directly. Release users gained a stronger link between the published tag and the launcher they downloaded. The preview/export separation clarified that a faster display refresh must not silently change exported numerical or spatial results. Compatibility and migration The changes corrected existing v2 behavior without introducing a new user data format. Tags `v2.3.2` and `v2.3.3` point to `29669ca6` and `a7e7dfb1`, respectively. Validation Focused EIS and image-preview tests accompanied the fixes. Release guidance added tag-derived asset checks, and facade tests checked supported app-to- library calls. Automated GUI windows were hidden to reduce test interference. Evidence EIS axes fixes `6c8e6540`, `579bc56a`. Preview processing separation `cc65e844`, `2369ffd3`. Preview/export contract and scale controls `d0d46f6e`, `29669ca6`. Reproducible release and onboarding `1832f46a` through `0c5243a9`. Facade and GUI-test contracts `a25b79f9`, `20ea41b8`. Known limitations and follow-up Component-specific version metadata was introduced only after these releases, so the early record relies on repository tags and commit evidence. Later history pages can name both the repository release and the affected component version."},{"title":"VT Resistance","url":"apps/electrochemistry/vt-resistance.html","kind":"app","section":"apps","keywords":"apps electrochemistry vt resistance VT Resistance","text":"VT Resistance VT Resistance estimates cathodic and anodic steady resistance from a biphasic voltage transient and reports the mean of their absolute values. Requirements And Launch The app requires a chrono DTA curve with valid time, voltage, and current columns. labkit_VTResistance_app Inputs And Batch Behavior Add one or more chrono `.DTA` files. The transient session decodes and analyzes the registered batch so shared setting changes update every result together. No electrode-area normalization is performed because the reported quantity is electrical resistance in ohms. Saved projects preserve source order and portable identity through removal, later additions, and reopen. Basic Workflow Add files and select a representative transient. Choose pulse detection, steady-window policy, and voltage definition. Inspect detected phase windows and current/voltage traces. Review cathodic, anodic, and average resistance. Export the batch result CSV. Analysis Parameters Parameter Default Alternatives Pulse detection Metadata first, then auto Metadata only; Auto from Im only Steady window Full pulse median Center 60% median Resistance voltage Baseline-corrected dV/I Raw Vf/I The center-60% policy uses the interval from 20% to 80% of each detected phase to reduce onset and offset transients. Plot marker and shading choices affect display only. Calculation Semantics After finite-sample filtering and pulse detection, the app calculates median current and voltage in the chosen cathodic and anodic steady windows. The cathodic baseline is the median pre-pulse voltage and the anodic baseline is the median post-pulse voltage, with explicit finite fallbacks. Baseline-corrected mode uses: R_cath = (V_cath,steady - V_cath,baseline) / I_cath,steady R_anod = (V_anod,steady - V_anod,baseline) / I_anod,steady R_average = mean(abs(R_cath), abs(R_anod)) Raw mode replaces each voltage difference with its steady absolute voltage. Division by a missing, non-finite, or effectively zero current returns `NaN`; the app does not invent a finite resistance. Output Schema The CSV contains source identity, steady currents and voltages, baseline voltages and windows, raw and baseline-corrected resistance fields, selected resistance values, absolute cathodic/anodic resistance, average resistance, pulse-detection method, and status. A result manifest records the common batch parameters and output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message); options = struct( ... \"pulseMode\", \"Metadata first, then auto\", ... \"windowMode\", \"Full pulse median\", ... \"voltageMode\", \"Baseline-corrected dV/I\"); result = vt_resistance.analysisRun.computeResistance(item, options); assert(result.ok, result.message); Errors And Limitations At least five finite `T`, `Vf`, and `Im` samples are required. Each selected steady window must contain at least two samples. Resistance is invalid when steady current is zero or non-finite. The result is a transient-derived estimate and does not replace an impedance model or compensate wiring/contact resistance automatically. Related Topics [Electrochemistry family](../README.md) [CIC](../cic/README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md) vt_resistance.analysisRun.computeResistance Estimate cathodic and anodic resistance from a VT pulse."},{"title":"Gait Apps","url":"apps/gait/index.html","kind":"app family","section":"apps","keywords":"apps gait Gait Apps","text":"Gait Apps Gait apps turn frame-ordered landmark coordinates into auditable kinematic tables. Annotation stays in the image or video marker workflow; this family owns role mapping, coordinate calibration, temporal interpretation, gait metrics, quality checks, and result export. Choose An App Task App Place and revise landmarks on video frames [Video Marker](../image-measurement/video-marker/README.md) Convert completed landmarks into gait metrics [Gait Analysis](gait-analysis/README.md) Data Flow The current Video Marker records one ordered set of named points per frame and saves coordinates, skeleton, timing, and calibration in its project. Gait Analysis validates that project and normalizes the saved payload to a frame-by-point-by-two coordinate array. The user maps point names to iliac, hip, knee, ankle, and foot roles. The app smooths coordinates, calculates joint angles and segment lengths, detects steps, applies quality rules, and builds export tables. Gait Analysis reads current Video Marker project and autosave files directly. It never rewrites the annotation project and does not accept generic MAT or coordinate-table substitutes that lack the required source metadata. Use The Calculation Without The App The app-owned calculation entry point is `gait_analysis.analysisRun.computeGait`. It accepts normalized pose data from `gait_analysis.sourceFiles.readPoseFile` and the same option structure used by the app. See the [Gait Analysis manual](gait-analysis/README.md) for the data shape, defaults, outputs, and example. Related Documentation [Image Measurement apps](../image-measurement/README.md) [App catalog](../README.md) [App Framework](../../framework/README.md) gait_analysis.analysisRun.computeGait Calculate joint, segment, swing, and step-quality measurements. gait_analysis.sourceFiles.readPoseFile Read one current Video Marker MAT project for gait analysis."},{"title":"Gait Analysis","url":"apps/gait/gait-analysis.html","kind":"app","section":"apps","keywords":"apps gait gait analysis Gait Analysis","text":"Gait Analysis Gait Analysis 2 converts a current Video Marker project into independently segmented treadmill swing steps, per-frame kinematics, per-step gait parameters, visual step reports, and reproducible CSV outputs. Loading and analysis are deliberately separate: loading first shows the complete tracked skeleton trajectory; analysis then enables one-step-at-a-time review. Open Gait Analysis From the LabKit launcher, select **Gait Analysis** and choose **Open**. From a source checkout, run: labkit_GaitAnalysis_app Input Contract The only file input is a current Video Marker `labkitProject` MAT document or autosave. The MAT file is the analysis source of truth and the cross-App data contract; Gait Analysis does not call the Video Marker package at runtime. The saved fields are described by [Video Marker project and session state](../../image-measurement/video-marker/README.md#project-and-session-state). Gait Analysis reads: frames-by-points-by-2 pixel coordinates; point IDs, point names, and skeleton edges; frame annotation status and source when present; video frame count, frame rate, duration, width, and height; scale calibration and physical unit when calibrated. The app does not reopen the original video to obtain scientific metadata. Video Marker payloads older than the embedded-video-metadata contract must be opened and saved with the current Video Marker before analysis. Gait Analysis rejects generic coordinate tables and arbitrary MAT variables because they do not jointly own timing, skeleton, calibration, and annotation provenance. `computeGait` still accepts an in-memory normalized pose for deterministic tests and programmatic calculations. The Video Marker document is stored as a portable project source. Older Gait Analysis projects are upgraded on load. Coordinates use image convention: the origin is at the upper left and Y increases downward. The skeleton preview preserves that convention. Angle and length time series use conventional plot axes. Project And Session State The pose source, analysis options, computed tables/events, and export record are saved. Decoded pose data and the currently selected step are reconstructed after load. Projects from the older stride-naming contract invalidate results whose scientific meaning changed, so rerun the analysis before export. Two-Stage Workflow 1. Load And Inspect All Trajectories Choose **Open Video Marker MAT**. Before any analysis runs, the workspace overlays every recorded skeleton and each named point trajectory. This view is intended to expose gross tracking failures, missing landmarks, unexpected skeleton order, and coordinate orientation before derived numbers are produced. The source summary reports frame count, point count, embedded frame rate, source format, and unit. Role fields are populated from matching source point names; `iliac_crest` is accepted for the iliac role. 2. Analyze And Review One Step Choose **Run analysis**. Select a row in the step table or use **Previous step** and **Next step**. The workspace then shows only that step: all skeleton poses from lift-off through landing, with point trajectories; hip, knee, and ankle angle traces; iliac-hip, hip-knee, knee-ankle, and ankle-foot length traces. The skeleton plot annotates swing duration, step length, iliac/hip/knee/ankle/ foot translations, and each joint's minimum, maximum, and range of motion. This is the app version of the earlier per-step gait figure; the figures no longer need to be generated as an intermediate image set merely to inspect each step. Step Segmentation The default treadmill detector operates on the smoothed foot X coordinate. A lift-off candidate is a local maximum whose prominence is at least the configured threshold and whose height is at least the configured mean-minus-sigma standard-deviation floor. Candidates closer than the minimum interval retain the higher peak. Every accepted lift-off is independently paired with the lowest subsequent foot X value before the next lift-off, or before the recording ends. That following minimum is the landing event. This pairing is important: a completed last swing remains a valid step even when the recording ends before another lift-off. Earlier contact-to-contact implementations lost that final step. These are image-kinematic treadmill events, not force-plate contact measurements. When a later lift-off exists, the app additionally derives cycle time, stance time, cadence, and duty factor. Those values are legitimately unavailable for the final independently complete swing if no next lift-off was recorded. Parameters Parameter Default Meaning Iliac, hip, knee, ankle, foot point Matching role name Exact, case-insensitive source point assigned to each anatomical role. Frame rate `0` until supplied by the source Used only when the source has no usable per-frame time vector. Current Video Marker projects populate it automatically. Pixels per unit `1` Pixel density for physical outputs. A valid Video Marker calibration replaces it automatically. Unit name `px` Physical unit label associated with pixels per unit. First-frame first point as origin Off Shifts scaled coordinate exports; raw pixel coordinates remain unchanged. Smooth window 5 frames Centered finite-value mean applied independently to each point and axis. Minimum foot-X prominence 20 source units Minimum lift-off peak prominence inherited from the legacy treadmill workflow. Peak-height sigma 2 Requires a lift-off peak to be at least mean foot X minus this many standard deviations, matching the legacy treadmill detector. Minimum interval 0.2 s Minimum time between lift-off peaks; converted to frames from source timing. Minimum swing 3 frames Minimum accepted inclusive lift-off-to-landing span and timing-free separation fallback. Maximum swing 300 frames Maximum accepted inclusive lift-off-to-landing span. Minimum step length 1 output unit Minimum two-dimensional foot endpoint displacement. Maximum hip translation 1,000,000 output units Upper endpoint-displacement QC rule; the default normally leaves it inactive. Changing any parameter invalidates the previous result. Repeating a run with the same source and parameters is skipped when its deterministic fingerprint is unchanged. Calculations For each frame the app calculates unsigned 0-180 degree hip, knee, and ankle angles from adjacent segment vectors and four Euclidean segment lengths. A nonfinite or zero-length vector produces `NaN`; the app does not invent an angle. For each lift-off-to-landing step it calculates: swing duration; two-dimensional foot endpoint displacement, reported as step length; two-dimensional endpoint translation for iliac, hip, knee, ankle, and foot; minimum, maximum, and range of motion for hip, knee, and ankle; cycle time, stance time, cadence per minute, and duty factor when the next lift-off exists; validity and a concrete rejection reason for duration, step length, or hip translation rules. The former scripts sometimes called foot displacement `stride_length` even though only one active swing was measured. Version 2 uses `step_length` so the column name matches the calculation. Outputs **Export CSV set** writes: `_frames.csv`: frame/time, step membership, event flags, three joint angles, four segment lengths, and scaled point coordinates; `_coordinates.csv`: raw pixel and scaled/origin-shifted coordinates; `_steps.csv`: event boundaries, timing, cadence/duty factor, step length, five point translations, joint extrema/ROM, validity and reason; `_summary.csv`: source geometry, counts, valid-step means, and global joint extrema; `_gait.labkit.json`: provenance manifest for inputs, parameters, outputs, and valid-step count. Use Without The GUI pose = gait_analysis.sourceFiles.readPoseFile(\"marker-project.mat\"); opts = gait_analysis.analysisRun.optionsForPose( ... pose, gait_analysis.analysisRun.defaultOptions()); result = gait_analysis.analysisRun.computeGait(pose, opts); writetable(result.stepTable, \"steps.csv\"); `computeGait` performs no UI or file writes. Its result contains `frameTable`, `coordinateTable`, `stepTable`, `summaryTable`, paired `events`, normalized `options`, `ok`, and `message`. Limitations Kinematic lift-off and landing are not substitutes for force or pressure measurements. The default detector reflects lateral treadmill motion and assumes the foot travels toward increasing X before returning toward decreasing X. Smoothing and prominence affect event boundaries and must be reported with scientific results. Tracking confidence and manual/predicted frame status are preserved from Video Marker but are not yet automatic step-exclusion rules. Group statistics and between-animal inference are outside this single- recording app; exported step tables are the boundary for that workflow. Related Functions And Apps `gait_analysis.sourceFiles.readPoseFile` `gait_analysis.analysisRun.defaultOptions` `gait_analysis.analysisRun.optionsForPose` `gait_analysis.analysisRun.computeGait` [Video Marker](../../image-measurement/video-marker/README.md) [Gait apps](../README.md) gait_analysis.analysisRun.computeGait Calculate joint, segment, swing, and step-quality measurements. gait_analysis.sourceFiles.readPoseFile Read one current Video Marker MAT project for gait analysis."},{"title":"Image Measurement Apps","url":"apps/image-measurement/index.html","kind":"app family","section":"apps","keywords":"apps image measurement Image Measurement Apps","text":"Image Measurement Apps The Image Measurement family turns image and video data into repeatable crops, calibrated measurements, thermal readings, fused images, appearance pipelines, and landmark coordinates. Scientific and workflow-specific choices remain app-owned; generic image IO and primitives come from `labkit.image` and `labkit.thermal`. Choose An App Goal App Main result Apply repeatable crop geometry to many images [Batch Image Crop](batch-crop/README.md) same-size crop images and manifest Measure a fitted circular arc and traced length [Curvature Measurement](curvature/README.md) radius, curvature, length, overlay Decode and measure radiometric FLIR images [FLIR Thermal](flir-thermal/README.md) Celsius matrix, readings, rendered image Fuse multiple focal planes [Focus Stack](focus-stack/README.md) all-in-focus image and depth index map Apply an ordered enhancement pipeline [Image Enhance](image-enhance/README.md) enhanced images and processing history Transfer appearance from a reference image [Image Match](image-match/README.md) matched images and match history Mark ordered landmarks across video [Video Marker](video-marker/README.md) recoverable project and coordinate tables Shared Interaction Conventions Managed points, rectangles, scale references, and overlays remain editable without resetting the current zoom. A new source or project may start at a home view; ordinary marker placement, dragging, ROI resizing, and annotation refresh preserve the user's viewport. Canvas subtitles and action labels describe the active interaction mode. Image coordinates use `[x y]` with `x` as column and `y` as row. Rectangle geometry uses `[x y width height]` unless a linked API states that it accepts two corners. Calibrated measurements always retain the pixel-domain source geometry needed for audit. Numeric And Display Boundaries Display palettes, axes zoom, overlay opacity, marker appearance, and preview downsampling do not change scientific arrays. Crop geometry, scale calibration, thermal conversion, fusion parameters, enhancement steps, and coordinate transforms do affect output and are stored in project state or result manifests. Related Modules [Image Library](../../libraries/image/README.md) [Thermal Library](../../libraries/thermal/README.md) [App Framework interactions](../../framework/README.md) [All Apps](../README.md)"},{"title":"Batch Image Crop","url":"apps/image-measurement/batch-crop.html","kind":"app","section":"apps","keywords":"apps image measurement batch crop Batch Image Crop","text":"Batch Image Crop Batch Image Crop defines one crop task per image, previews rotation and edge-continuous padding, and exports repeatable same-size crops in pixel or physical-scale mode. Requirements And Launch labkit_BatchImageCrop_app Inputs Use **Add images or folder** to load supported image files. A folder selection loads supported files from that folder. Each list row stores its own crop center, rotation, padding, and optional scale calibration. **Duplicate image** creates another task for the same source so multiple ROIs can be exported without loading duplicate files. Duplicate tasks remain aligned with their source and cached preview when the native file list supplies row- or column-shaped state. Basic Workflow Load images and select a task. Set crop width and height or switch to physical mode. Click anywhere in the preview to place the crop center. Drag the center marker or anywhere inside the highlighted ROI to move it, or enter center X/Y. Set rotation and padding. Calibrate physical scale when required. Duplicate tasks for additional ROIs. Choose format and output folder, then export. The preview rectangle remains draggable by either its center marker or its interior; a click without dragging sets its center at that preview location, including inside the rectangle. Change crop width and height with their controls. Editing the ROI updates the selected task immediately; switching tasks restores that task's geometry. ROI refreshes preserve axes zoom. Pixel Mode Default crop dimensions are 1024 by 1024 pixels. Rotation defaults to 0 degrees and padding to 0%. Center controls use source-image coordinates. When crop geometry crosses the rotated image boundary, edge-continuous padding supplies pixels so every exported crop retains the requested dimensions. Physical Mode And Scale Physical mode specifies width and height in the selected unit (default `um`). Each source image requires a valid pixels-per-unit calibration. Measure a known reference line, enter its physical length, and verify the displayed pixels/unit. `targetPixelsPerUnit=0` selects an automatic common output scale; a positive value requests an explicit scale. The max-upsample warning defaults to 15%. It warns when a task would require substantial interpolation; it does not silently change the requested physical geometry. Crop and calibration units are converted explicitly. Scale Bar The scale-bar length defaults to 100 selected units, at Bottom right, in Black. Measure the reference first, then place the bar. The bar is an export overlay; its placement does not change crop geometry or scale calculations. Outputs Exports support PNG, TIFF, and JPEG. Each task produces one image at the planned output dimensions. The crop manifest records source, task identity, center, rotation, padding, source/output scale, requested physical geometry, format, and output filename. A second LabKit result JSON records project-wide parameters and identifies each output file. Project And Recovery Saved projects preserve one independent task per list row, so duplicated tasks can resolve to the same image while retaining separate crop geometry and calibration. Crop dimensions, physical-scale settings, output format, output folder, and scale-bar choices are also saved. Image pixels and preview caches are reconstructed from the sources after load rather than embedded in the project. Older projects are upgraded on open while preserving each task's center, rotation, padding, and calibration. If a source moved, the standard relinking flow asks for its new location. Use Without The GUI calibration = labkit.app.interaction.scaleCalibration(20, 10, \"um\"); items = struct(\"scaleCalibration\", calibration); physicalOptions = struct( ... \"physicalWidth\", 5, \"physicalHeight\", 3, ... \"scaleUnit\", \"um\", \"targetPixelsPerUnit\", 4); plan = batch_crop.cropGeometry.scalePlan(items, physicalOptions); imageData = reshape(uint8(1:100), 10, 10); cropOptions = struct(\"centerXY\", [5 5]); cropped = batch_crop.cropGeometry.cropScaledImage( ... imageData, cropOptions, plan); For pixel-domain work use `batch_crop.cropGeometry.cropImage`. Open the linked API pages for exact plan fields, interpolation, and padding behavior. Function Reference The generated crop-geometry pages document pixel coordinates, rounding, padding, scaling plans, per-image option fields, defaults, output metadata, failure behavior, and related APIs. Start with [`cropScaledImage`](../../../reference/api/batch_crop/cropGeometry/cropScaledImage.html) when reproducing a physical-scale export outside the GUI. Errors And Limitations Physical export requires a finite positive calibration for every task. Large upsampling cannot restore information absent from the source. Rotation and scale resampling can soften high-frequency detail. JPEG is lossy; use PNG or TIFF for quantitative downstream image analysis. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) batch_crop.cropGeometry.cropImage Rotate and crop an image at a fixed pixel size. batch_crop.cropGeometry.cropScaledImage Crop one calibrated image to a common physical output scale. batch_crop.cropGeometry.scalePlan Plan equal-size physical crops across calibrated images."},{"title":"Curvature Measurement","url":"apps/image-measurement/curvature.html","kind":"app","section":"apps","keywords":"apps image measurement curvature Curvature Measurement","text":"Curvature Measurement Curvature Measurement fits a circle to an ordered image curve, reports radius and curvature, measures traced arc length, and supports pixel-to-physical scale calibration. Requirements And Launch labkit_CurvatureMeasurement_app Basic Workflow The **Files + Analysis** tab contains the complete workflow: Choose an image in the **Image** section. Use **Measure reference pixels**, or enter **Reference pixels** directly, then enter the known reference length and unit. Configure and place the optional display scale bar. Use **Start curve edit** and place ordered anchors along the feature. Drag anchors to refine the trace; undo or clear as needed, then finish curve editing. Choose the densification settings, fit the circle, and measure curve length. Export the result CSV and overlay PNG. The edit buttons change to **Finish curve edit** or **Finish reference edit** while their managed interaction is active. Curve and reference edits are mutually exclusive. Anchor edits and result overlays preserve the current axes zoom. The **Summary + Results** tab reports curve length, radius, curvature, RMSE, fit center, and pixels per selected unit. **Details** explains the next valid step before a result exists and reports the current measurement afterward. **Tools > Diagnostics > Open Session Log...** records file, edit, fit, calibration, export, and runtime actions without consuming a workflow tab. The chosen image is saved as a portable project source. Older projects are upgraded on load without changing the curve, calibration, or result meaning. Curve Editing Curve points are ordered by placement. **Undo last point** removes the newest anchor and **Clear curve** removes the complete trace. Neighboring duplicate points are removed before numeric fitting. At least three distinct points are required for a circle fit; length requires at least two. Fit Parameters And Semantics **Densify before circle fit** is enabled by default with 300 dense points. Densification interpolates along the ordered trace before the least-squares circle fit; it changes sampling density, not the manually defined path. **Show dense fit points** is presentation-only. For fitted radius `r`: curvature = 1 / r Pixel-domain radius and curvature are always available. With calibration, radius and traced length are converted to the selected physical unit and curvature uses its reciprocal unit. Curve length is the sum of distances along the ordered polyline/densified curve specified by the API options; it is not the circumference of the fitted circle. Scale Calibration And Bar Measure the pixel distance spanning a known reference, enter its physical length, and choose `m`, `cm`, `mm`, `um`, or `nm`. Calibration stores reference pixels, reference length, unit, and pixels per unit. Scale-bar length defaults to 100 units; position defaults to Bottom right and color to Black. Placing or moving the display bar does not modify the fit. Outputs The CSV records point count, fit settings, center, radius, curvature, traced length, calibration, units, and status. The overlay PNG contains the source image, ordered curve, optional dense samples, fit residuals, fitted circle, center, and configured scale bar when available. Each export writes its standard result manifest with source and parameter provenance. Use Without The GUI points = [100 80; 130 58; 165 52; 200 66; 225 95]; fit = curvature.analysisRun.computeCurvatureFit(points, ... struct(\"densify\", true, \"densePointCount\", 300)); lengthResult = curvature.analysisRun.computeCurveLength(points, struct()); Errors And Limitations Collinear or nearly collinear points do not define a stable finite circle. Calibration uncertainty propagates directly to physical radius and length. A circle fit summarizes a curved segment; it does not prove the feature is truly circular. Inspect residuals and the overlay. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) curvature.analysisRun.computeCurvatureFit Fit a circle and curvature to traced image points. curvature.analysisRun.computeCurveLength Measure the polyline length of traced image points."},{"title":"FLIR Thermal","url":"apps/image-measurement/flir-thermal.html","kind":"app","section":"apps","keywords":"apps image measurement flir thermal FLIR Thermal","text":"FLIR Thermal FLIR Thermal decodes radiometric FLIR JPEG/RJPEG files, displays calibrated temperature maps, measures rectangular ROI hot/cold/mean values, and exports rendered images with Celsius data. Requirements And Launch A `.jpg`, `.jpeg`, or `.rjpg` extension alone is insufficient; the file must contain a readable FLIR raw thermal record and calibration metadata. labkit_FLIRThermal_app Inputs And Navigation Use **Add FLIR files or folder** to load one or more radiometric images. The selected row is the current image; Previous/Next change selection without discarding per-image display range or measurement annotations. Source files are read-only. Basic Workflow Load radiometric files and inspect the decoded min/max/metadata summary. Choose a palette and color mapping. Set a range preset, per-image range, or shared group range. Choose ROI hot spot, ROI cold spot, or ROI mean, then drag its rectangle. Optionally click the image for an independent manual point reading. Review the numeric results and markers. Export the current image or the full batch. Placing or dragging a reading ROI and refreshing its marker preserves the current zoom. The reading is recalculated from the thermal matrix, not from screen colors. Temperature Conversion `labkit.thermal.readFile` extracts the raw sensor matrix and converts it to Celsius. Environment-corrected conversion uses Planck constants plus available emissivity, object distance, atmospheric/window transmission, reflected temperature, and related metadata. Missing environmental fields may use documented defaults and are listed in `record.metadata.temperatureConversion`; required Planck constants are never invented. Absolute temperature accuracy depends on camera calibration and environmental assumptions. Preserve conversion diagnostics with reported measurements. Display Controls Palettes are Turbo, Iron, Hot, Parula, and Gray. Mapping modes are Linear, Log, and Gamma; gamma defaults to 2.2 and is limited to 0.1-5. Display ranges are stored per image. Actions can set each image independently, apply one shared group range, fit the current image, or round configured bounds. Palette, mapping, gamma, and color limits affect only mapping into display colors. They do not transform the Celsius matrix or exported temperature values. ROI Readings The rectangular ROI includes both boundary pixels after its two corners are rounded and clamped to the thermal matrix. Hot and cold readings report value and `[x y]` location. Mean ignores non-finite pixels and also records ROI geometry and finite pixel count. The marker is a display annotation; moving it does not change source data. Outputs Current or batch export writes PNG, TIFF, or JPEG rendered thermal images, matching color scale graphics, Celsius CSV matrices, measurement values, a batch CSV summary, and the standard `flir_thermal.labkit.json` result manifest. The clean image export excludes interactive toolbar chrome. Numeric temperature outputs remain Celsius regardless of palette or mapping mode. Project And State Saved projects keep portable source references, display parameters, export settings, and lightweight per-image ranges and readings. Raw sensor matrices and decoded Celsius matrices are loaded again from the selected source when a project opens. Missing source files use the standard relinking flow rather than relying on a local absolute path. An existing source that is no longer a readable radiometric file aborts the restore and preserves the current document. Batch import may still report and skip rejected selections before they become project sources. An empty launch does not choose an output directory. Adding files establishes the source-adjacent default; **Choose folder** remains available before export. Use Without The GUI record = labkit.thermal.readFile(\"capture.rjpg\"); temperatureC = record.temperatureC; point = flir_thermal.analysisRun.pointTemperatureReading( ... temperatureC, [120 80]); [hot, cold, meanReading] = ... flir_thermal.analysisRun.roiTemperatureMeanReading( ... temperatureC, [90 60], [169 129]); [imageHot, imageCold] = ... flir_thermal.analysisRun.extremeTemperatureReadings(temperatureC); Function Reference The generated pages for [`pointTemperatureReading`](../../../reference/api/flir_thermal/analysisRun/pointTemperatureReading.html) and [`extremeTemperatureReadings`](../../../reference/api/flir_thermal/analysisRun/extremeTemperatureReadings.html) specify coordinate order, clamping, nonfinite/empty behavior, returned fields, conversion failures, and related measurement APIs. Errors And Limitations Non-radiometric JPEG files are rejected even when their extension is valid. Coordinates refer to the decoded thermal matrix, not a separately scaled visible-light image. An ROI containing no finite temperature pixels cannot return a finite mean. JPEG export is visual and lossy; keep Celsius data for quantitative work. Related Topics [Thermal Library](../../../libraries/thermal/README.md) [Image Measurement family](../README.md) [API Reference](../../../reference/README.md) flir_thermal.analysisRun.extremeTemperatureReadings Locate finite global thermal extrema. flir_thermal.analysisRun.pointTemperatureReading Read the nearest calibrated thermal pixel. flir_thermal.analysisRun.roiTemperatureMeanReading Measure an inclusive rectangular thermal ROI."},{"title":"Focus Stack","url":"apps/image-measurement/focus-stack.html","kind":"app","section":"apps","keywords":"apps image measurement focus stack Focus Stack","text":"Focus Stack Focus Stack fuses at least two focal planes into one all-in-focus image using multilevel Laplacian focus evidence and exports a focus-depth index map. Requirements And Launch Inputs should show the same field of view at different focus positions. labkit_FocusStack_app Inputs Add selected image files or choose a folder. Remove blurred, displaced, or otherwise invalid frames before running. The first image defines working geometry; differently sized inputs are resized and the result records how many images required resizing. Basic Workflow Load at least two focal planes. Remove frames that do not belong to the stack. Start with the Balanced preset. Enable auto-registration when the stack has small rigid camera motion. Tune detail scale, blend radius, and uncertain blend only when artifacts are visible. Run the stack and inspect both fused image and focus map. Export fused PNG, focus-map PNG, and summary CSV. Fusion Parameters Parameter Default Meaning Preset Balanced coordinated starting values; Crisp favors fine detail, Smooth favors seam suppression, Noisy favors grain tolerance Auto-register off register images to the middle image before fusion Detail scale 31 px odd local window for focus-energy evidence Blend radius 4 px smooth focus-selection weights across boundaries Uncertain blend 5% blend competing planes where focus confidence is low The public calculation API also accepts `minConfidence` (default 0.05) and `pyramidLevels` (default 4, bounded by image geometry). Algorithm Images are normalized to grayscale or RGB double data on common geometry. A Gaussian/Laplacian pyramid is built for each plane. Local squared Laplacian detail energy chooses the strongest plane at each level. Confidence is the relative separation between the best and second-best evidence. High-confidence regions use the winning plane. Low-confidence regions blend normalized evidence weights; zero-evidence regions use equal weights. The weights are spatially smoothed by the level-scaled blend radius and the fused pyramid is reconstructed and clamped to `[0,1]`. The focus index is the winning input plane at full resolution. Focus coverage is the fraction of pixels assigned to each plane. The algorithm does not correct parallax, deformation, moving objects, illumination changes, or severe registration error. Outputs fused all-in-focus image; integer focus-depth index map; confidence map and per-plane focus coverage in the run result; summary CSV with geometry, parameters, confidence, coverage, registration, and resize information; result provenance manifest. Use Without The GUI images = {imread(\"z01.png\"), imread(\"z02.png\"), imread(\"z03.png\")}; options = struct(\"focusWindow\", 31, \"smoothRadius\", 4, ... \"minConfidence\", 0.05, \"pyramidLevels\", 4); result = focus_stack.analysisRun.computeFocusStack(images, options); assert(result.ok, result.message); imwrite(result.fused, \"stacked.png\"); Errors And Limitations Fewer than two images is an error. Reopening a saved stack is strict: an existing image that cannot be decoded aborts restore and leaves the current document unchanged. `minConfidence` must be finite and between 0 and 1. Resizing changes sampling and should not substitute for consistent capture. Auto-registration should be reviewed; a wrong registration can create a sharp-looking but geometrically false composite. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) focus_stack.analysisRun.computeFocusStack Fuse a stack of images with different focus planes."},{"title":"Image Enhance","url":"apps/image-measurement/image-enhance.html","kind":"app","section":"apps","keywords":"apps image measurement image enhance Image Enhance","text":"Image Enhance Image Enhance builds an ordered, reversible processing history for one image or a batch and exports the resulting images with the exact step sequence. Requirements And Launch All processing uses MATLAB and repository-owned code. labkit_ImageEnhance_app Inputs And Batch Mode Add supported image files or a folder. In **Batch shared processing** mode, one shared step history is applied to every source. When batch mode is off, each image keeps a separate history and optional white ROI. Selecting another image updates the preview without recalculating unrelated files. Basic Workflow Load images and select a representative source. Decide whether steps are shared across the batch. Choose a tool and set its controls. For White ROI calibration, draw the white-background ROI first. Apply the tool to history and inspect Enhanced, Original, or Before | After. Undo or reset history as needed. Choose output format/folder and export. Each **Apply tool** action saves one processing step. Panner changes preview the pending tool without changing the saved history until **Apply tool** is chosen. ROI creation and overlay refresh preserve the current zoom. Tools And Defaults Tool Primary control Secondary control Brightness/contrast Brightness 0% Contrast 15% Local contrast Clarity 30% Radius 12 px Sharpen Sharpen 35% Radius 1.5 px Hue/saturation Hue 0 degrees Saturation 10% White balance Strength 100% Temperature 0% White ROI calibration Strength 100% White target 92% Subject-preserving enhance Strength 70% Background target 90% Processing converts input to RGB double in `[0,1]`, applies steps in table order, and clamps the final image. Brightness, local contrast, sharpening, hue/saturation, and gray-world white balance call the linked `labkit.image` primitives. White ROI calibration requires a valid rectangle for every affected image. It builds a softened background mask from the rectangle and adjusts luminance toward the target while limiting subject changes. Subject-preserving enhance estimates a low-saturation bright background automatically and uses a whole image fallback only when no usable background support is found. History And Reproducibility The history table records step kind and settings in execution order. **Undo history** removes the latest applied step; **Reset history** returns to the normalized original. Source images are not modified. Shared and per-image histories are stored separately in project state so switching batch mode does not ambiguously merge them. Outputs Export supports PNG, TIFF, and JPEG. Each output is rendered from the source plus its effective stored pipeline, not from a downsampled preview. The export manifest records source, ordered steps, ROI geometry, batch mode, format, and output filename. A LabKit result JSON records the complete output set and the project parameters used to create it. Use Without The GUI steps = [ ... image_enhance.analysisRun.makeStep(\"Brightness/contrast\", 0, 15), ... image_enhance.analysisRun.makeStep(\"Local contrast\", 30, 12), ... image_enhance.analysisRun.makeStep(\"Sharpen\", 35, 1.5)]; output = image_enhance.analysisRun.applyPipeline(imread(\"source.png\"), steps); imwrite(output{1}, \"enhanced.png\"); Project And State Saved projects keep portable source references, shared and per-image step histories, white-reference ROIs, export settings, and compact result metadata. Full-size pixels and downsampled previews are loaded again from the selected source when a project opens rather than embedded in the project. An empty launch does not choose an output directory. Adding images establishes the source-adjacent default; **Choose folder** remains available before export. Errors And Limitations Unknown step labels are rejected. Reopening a project with an existing unreadable image aborts restore and preserves the current document instead of displaying an empty preview. White ROI calibration fails when a required image has no valid ROI. Enhancement changes pixel values and can invalidate quantitative intensity analysis; retain the source and manifest. JPEG export adds lossy compression after processing. Related Topics [Image Match](../image-match/README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) image_enhance.analysisRun.applyPipeline Replay an ordered non-destructive image enhancement history."},{"title":"LabKit Apps","url":"apps/index.html","kind":"overview","section":"apps","keywords":"apps LabKit Apps","text":"LabKit Apps LabKit apps are independent MATLAB tools for complete laboratory workflows. Choose an app by the result you need, then use its app page for supported inputs, controls, interaction behavior, calculations, outputs, recovery, and programmatic APIs. Start An App Open `labkit_launcher`, select one app, and choose **Open**. The launcher's **Documentation and History** action opens the selected app page. Source-checkout users can also call an app command after adding LabKit to the MATLAB path: labkit_launcher % Or, after repository path setup: labkit_CIC_app See [Getting Started](../getting-started/README.md) for installation, updating, version selection, and source-checkout setup. The [LabKit Launcher manual](labkit-core/launcher/README.md) documents its complete interactive and programmatic surface. Choose An App Task App Input Principal output Discover, install, launch, diagnose, and package LabKit apps [LabKit Launcher](labkit-core/launcher/README.md) Installed or source LabKit tree Running app, maintenance report, or deployment ZIP Register, crop, and mask image pairs for DIC [DIC Preprocess](dic/dic-preprocess/README.md) Reference and moving images Aligned images, crops, mask Render and summarize Ncorr strain results [DIC Postprocess](dic/dic-postprocess/README.md) Ncorr MAT, reference image, mask Strain overlays and summary CSV Overlay chrono voltage and current traces [Chrono Overlay](electrochemistry/chrono-overlay/README.md) Chrono DTA Plot and CSV Measure charge-injection capacity [CIC](electrochemistry/cic/README.md) Chrono DTA CIC result table and CSV Compare cyclic and time-domain CSC [CSC](electrochemistry/csc/README.md) CV/CT DTA Per-cycle CSC and CV data Inspect impedance curves [EIS](electrochemistry/eis/README.md) EIS DTA Nyquist/Bode plots and CSV Estimate voltage-transient resistance [VT Resistance](electrochemistry/vt-resistance/README.md) Chrono DTA Resistance table and CSV Convert tracked points into gait metrics [Gait Analysis](gait/gait-analysis/README.md) Current Video Marker project MAT Frame, step, coordinate, and summary tables Crop image batches at repeatable geometry [Batch Image Crop](image-measurement/batch-crop/README.md) Image files Same-size crops and manifest Measure curve radius, curvature, and length [Curvature Measurement](image-measurement/curvature/README.md) Image Overlay and measurement CSV Decode and measure radiometric images [FLIR Thermal](image-measurement/flir-thermal/README.md) FLIR radiometric image Temperature data, measurements, rendered image Fuse focal planes [Focus Stack](image-measurement/focus-stack/README.md) Aligned image stack Fused image and focus map Apply a repeatable enhancement pipeline [Image Enhance](image-measurement/image-enhance/README.md) Image files Enhanced images and manifest Match appearance to a reference image [Image Match](image-measurement/image-match/README.md) Source and reference images Matched images and manifest Annotate ordered landmarks across video [Video Marker](image-measurement/video-marker/README.md) Video Project MAT and coordinate CSV Restyle figures and export visible plot data [Figure Studio](labkit-core/figure-studio/README.md) FIG or popout axes Styled figure and plot data Inspect RHS recordings and define channels [RHS Preview](neurophysiology/rhs-preview/README.md) RHS file or folder Preview, protocol JSON, filter record Detect trains and measure neural responses [Nerve Response Analysis](neurophysiology/nerve-response-analysis/README.md) Filter record and protocol JSON Analysis JSON and CAP metrics Review aligned responses and statistics [Response Review and Stats](neurophysiology/response-review-stats/README.md) Analysis JSON or segment CSV Metrics CSV and summary Compare multiple groups with the first using t-tests and one mean/SD plot [T-Test Wizard](statistics/ttest-wizard/README.md) CSV, TSV, workbook, or entered values Result family, CSVs, and comparison plot Inspect ECG and measure segment SNR [ECG Print](wearable/ecg-print/README.md) MAT or delimited table Segment SNR CSV and waveform image Browse By Family [DIC](dic/README.md) - preparation and postprocessing around a DIC solver. [Electrochemistry](electrochemistry/README.md) - DTA-based chrono, CV/CT, impedance, charge, and resistance workflows. [Gait](gait/README.md) - pose-coordinate analysis and gait metrics. [Image Measurement](image-measurement/README.md) - calibrated image, thermal, annotation, crop, fusion, and appearance workflows. [LabKit Core](labkit-core/README.md) - the launcher and general MATLAB graphics tools. [Neurophysiology](neurophysiology/README.md) - RHS inspection, response analysis, and review. [Statistics](statistics/README.md) - explicit first-versus-each t-tests and result-based group plotting. [Wearable](wearable/README.md) - wearable biosignal workflows. How To Read An App Page Concrete app pages use a common MATLAB-style order: purpose, requirements, and launch command; supported inputs and the shortest successful workflow; controls and interaction behavior; calculation or algorithm semantics, units, and assumptions; outputs, project files, autosave, and recovery behavior; GUI-free MATLAB examples and public app-owned APIs; errors, limitations, troubleshooting, related topics, and history. The app page documents stable user-visible behavior. Exact callable syntax and data shapes live on the linked API reference pages. Internal callbacks and private implementation helpers are intentionally omitted. Shared framework contracts are documented once in the [App Framework](../framework/README.md), not repeated in every App page. An App page mentions shared behavior only when that App changes it or when the behavior is necessary to complete the App's workflow. Common App Behavior The App Framework owns lifecycle, busy state, file selection, state snapshots, screenshot actions, plot tools, and managed interactions. Apps own scientific choices, workflow-specific defaults, result schemas, and exports. See the [App Framework](../framework/README.md) for behavior shared across apps. Every App opens as a clean project. Use **Tools > Diagnostics** to inspect the current session history, enable future trace capture, or export a diagnostic bundle after a problem. Apps with declared sample generation expose **Tools > Developer Tools > Generate Synthetic Inputs...**; generation writes anonymous inputs without loading them or changing the open project. The [runtime guide](../framework/guides/runtime.md) defines these shared contracts. Action and input-selection buttons provide concise hover help. The shared **Tools** menu contains plot, screenshot, and project-state actions when the corresponding capability is available. Input data and exported results should remain outside the replaceable LabKit runtime folder. Apps do not overwrite source files unless an app page states an explicit in-place operation. Programmatic Use Important scientific and deterministic app operations can be called without opening the GUI. Each app page identifies its supported app-owned functions and links them to the generated [API Reference](../reference/README.md). Reusable file parsers and generic processing functions live in the public `labkit.*` libraries. MATLAB must be able to see both the repository root and the owning app root. The launcher prepares these paths automatically. In a source checkout, add them explicitly before calling an app package: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(repoRoot) addpath(fullfile(repoRoot, \"apps\", \"electrochem\", \"cic\")) help cic.analysisRun.computeCIC For another app, replace the final app path with the directory that directly contains its `+package` folder. Do not add every repository subfolder with `genpath`; app roots are independent and may contain same-named private or development files that should not become global MATLAB commands. Related Documentation [Getting Started](../getting-started/README.md) [LabKit Launcher](labkit-core/launcher/README.md) [App Framework](../framework/README.md) [API Reference](../reference/README.md) [App Development](../development/build-apps/app-development.md) [Component History](../history/README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics."},{"title":"Image Match","url":"apps/image-measurement/image-match.html","kind":"app","section":"apps","keywords":"apps image measurement image match Image Match","text":"Image Match Image Match transfers tone and color statistics from one reference image to one or more source images while preserving each source image's geometry. Requirements And Launch The app performs appearance matching only; it does not geometrically register images. labkit_ImageMatch_app Inputs Choose one reference image and add source images or a source folder. The reference supplies appearance statistics and is not exported as a matched source. All images are normalized to RGB double data in `[0,1]`; output retains the source height and width. Basic Workflow Choose the reference and source images. Select a matching method. Set overall, tone, and color strength. Apply the match to history. Inspect Matched, Original, and Before | After views. Undo/reset history or add another step. Export matched images. Changing method or strength previews the pending match; **Apply match** saves the settings as the next step in the image's processing history. Matching Methods Method Behavior Balanced robust white-point match followed by Lab-style tone/color transfer White balance match robust bright-neutral RGB white points Tone only quantile-match Lab lightness only Protected tone constrained luminance/background adjustment with shadow, highlight, and saturation guards Lab style quantile-match lightness and covariance-match Lab chroma Histogram independently quantile-match all three Lab channels Overall strength blends the matched result with the original. Tone and color strength separately blend lightness and chroma behavior where the selected method supports them. All three default to 100%. Robust statistics and covariance regularization handle flat or nearly single-color images, but a visually dissimilar reference can still produce an unhelpful result. Matching reproduces distributions, not semantic lighting or camera calibration. History And Outputs Each source is recomputed from its original plus the ordered match history. Undo removes the latest step and reset removes all steps. Export supports PNG, TIFF, and JPEG and writes an image manifest containing reference/source identity, method, strengths, history, format, and output filenames. Source and reference files are never overwritten. Use Without The GUI source = imread(\"source.png\"); reference = imread(\"reference.png\"); step = image_match.analysisRun.makeStep(\"Balanced\", 100, 100, 100); matched = image_match.analysisRun.applyMatch(source, reference, step); % Apply a stored sequence: outputs = image_match.analysisRun.applyPipeline({source}, step, reference); output = outputs{1}; Function Reference The generated pages for [`applyMatch`](../../../reference/api/image_match/analysisRun/applyMatch.html) and [`applyPipeline`](../../../reference/api/image_match/analysisRun/applyPipeline.html) document supported step fields and defaults, normalization and output geometry, empty-input behavior, failures, examples, and related APIs. Errors And Limitations An empty reference returns normalized source data in the calculation API, but the app requires a reference before enabling match actions. Reopening a project with an existing unreadable reference or source image aborts restore and preserves the current document. Matching never corrects position, scale, rotation, perspective, or parallax. Histogram and Lab matching can shift scientifically meaningful pixel values; retain original images for quantitative analysis. JPEG export is lossy. Related Topics [Image Enhance](../image-enhance/README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) image_match.analysisRun.applyMatch Transfer reference appearance statistics to one image. image_match.analysisRun.applyPipeline Replay ordered reference-match steps over source images."},{"title":"Video Marker","url":"apps/image-measurement/video-marker.html","kind":"app","section":"apps","keywords":"apps image measurement video marker Video Marker","text":"Video Marker Video Marker defines an ordered landmark skeleton, records coordinates across video frames, predicts forward positions between manual anchors, and saves a portable project with an explicit source-adjacent autosave copy. Requirements And Launch The app declares compatibility with `labkit.app` 1.x and uses image functions shipped with the same workbench. Video decoding uses MATLAB's available video support. Predictive navigation is implemented in repository-owned MATLAB code; no model weights or third-party runtime package are downloaded. labkit_VideoMarker_app Start Or Open A Project The **Session** panel appears first in the Video tab. **Open MAT** uses the same loader as the window's top-level Load State action and accepts an explicit project or compatible autosave. **Save autosave** immediately updates `Video Marker Autosaves/.video_marker.autosave.mat` beside the source video without asking for a path and without turning that recovery copy into the named project file. **New setup** offers Cancel, Save and start new, or Discard and start new; it never silently clears the current session. For a new project, choose a preset or add keypoints, edit their names, reorder them, and define connections. **Connect in order** links every adjacent pair. At least one keypoint is required before opening a new annotation session. The skeleton is locked after the video opens so coordinate column meaning remains stable. Marking Workflow Define the ordered skeleton and optional scale calibration. Open the video. Click blank image locations to place the next missing keypoint in order. Drag an existing marker to refine it. Use Undo last point or Clear frame points when needed. Move to the next frame; the current frame is saved automatically. Correct predicted frames periodically and export or save the project. Point marking remains active while a video is open. A complete edited frame is a manual anchor. Moving forward predicts every ordered point using cropped multiscale patch matching. The tracker compares mean-centered local patches by normalized correlation, refines accepted matches to subpixel coordinates, and reports confidence for each point. A rejected point retains its bounded motion- prior estimate. Predicted frames remain editable drafts. Editing a complete frame makes it a new manual anchor for later prediction. Jumping forward propagates through intermediate frames and does not overwrite existing manual anchors. Frame navigation and marker refresh preserve the current zoom. Opening a new video or project starts at its own home view. Scale And Coordinate Export Scale calibration stores a measured pixel distance, known length, and unit. The coordinate export options are: units: `pixels` or `calibrated_physical`; origin: `top_left_pixel_center` or `first_point`; Y axis: `up` or `down`; inclusive start and end frame. Marker CSV is the round-trip editing format. Coordinate CSV is the plotting-oriented table after optional calibration, origin shift, and Y-axis conversion. Raw pixel coordinates remain available in the project. Autosave, Recovery, And Portability **Save autosave** writes atomically to `Video Marker Autosaves` beside the source video. It is an explicit action, not a background timer. Autosave and named project MAT files contain the same project data: frame count, frame rate, duration, image dimensions, skeleton edges, annotation status/source, calibration, and durable coordinates. The video itself remains an external portable source and can be relinked without discarding annotations. When a project tree moves between folders, users, or operating systems, the relative reference is tried first. If no candidate exists, the app asks the user to locate the video without discarding skeleton or annotations. A compatible old Video Marker project or autosave opens through **Open MAT** or **Tools > Project State > Load State...**. Project And Session State Durable state consists of video metadata, the portable video source, skeleton, frame annotations and provenance, calibration, export parameters, and output manifest paths. The video reader, decoded frame, current interaction, selection, preview graphics, and frame cache are transient session resources. Only the current frame number is saved as navigation convenience; annotations remain the authoritative scientific data. Older compatible project and autosave formats are upgraded when opened. Save the upgraded project before moving it into another workflow. Outputs explicit project MAT and visible autosave MAT; marker CSV for round-trip editing; coordinate CSV for analysis and plotting; output manifests recording coordinate options and file roles. CSV dialogs start in a source-adjacent `video_marker` output folder. Result manifests are written beside the chosen CSV. Diagnostics And Synthetic Inputs Use **Tools > Diagnostics** to inspect the live session log, enable trace capture, or export a diagnostic bundle after a problem occurs. Use **Tools > Developer Tools > Generate Synthetic Inputs...** to create a synthetic video, a valid marking project, and declared marker/coordinate output targets without including user filenames or laboratory data. Generation does not load those inputs into the running App. Use Without The GUI previousFrame = peaks(61); nextFrame = circshift(previousFrame, [-2 3]); [nextPoints, confidence] = video_marker.motionEstimate.trackPoints( ... previousFrame, nextFrame, [31 31]); skeleton = video_marker.skeletonDefinition.fromText( ... \"hip, knee\", \"hip-knee\"); annotations = video_marker.frameAnnotations.emptyAnnotations(2, 2); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 1, [11 21; 31 41], \"confirmed\"); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 2, [13 25; 35 45], \"confirmed\"); videoInfo = struct(\"frameRate\", 10); options = video_marker.coordinateExport.options( ... \"startFrame\", 1, \"endFrame\", 2, \"unitMode\", \"pixels\", ... \"originMode\", \"first_point\", \"yAxisMode\", \"up\"); coordinateTable = video_marker.coordinateExport.buildTable( ... annotations, skeleton, videoInfo, struct(), options); Function Reference Use the generated [`buildTable`](../../../reference/api/video_marker/coordinateExport/buildTable.html) page for exact frame-range option types, defaults, coordinate transforms, calibration requirements, output columns, failures, and related marker APIs. The [`trackPoints`](../../../reference/api/video_marker/motionEstimate/trackPoints.html) page documents the repository-owned prediction contract separately. Errors And Limitations Prediction is an editing aid, not unattended long-duration tracking. Occlusion, blur, repeated texture, large deformation, and leaving the frame can invalidate patch matches; inspect and correct drafts. Calibrated export requires a valid positive scale calibration. Changing skeleton order after annotation would change coordinate meaning and is therefore prevented once a video session begins. Related Topics [Gait Analysis](../../gait/gait-analysis/README.md) [Image Measurement family](../README.md) [API Reference](../../../reference/README.md) video_marker.coordinateExport.buildTable Convert confirmed video annotations to a coordinate table. video_marker.motionEstimate.trackPoints Track ordered image points between two consecutive video frames."},{"title":"LabKit Core Apps","url":"apps/labkit-core/index.html","kind":"app family","section":"apps","keywords":"apps labkit core LabKit Core Apps","text":"LabKit Core Apps Core apps provide the LabKit workbench entry point and cross-domain tools that are useful to many workflows but do not own a scientific measurement or experiment-specific formula. Choose An App Task App Discover, install, launch, diagnose, and package apps [LabKit Launcher](launcher/README.md) Restyle a MATLAB figure and export its visible graphics [Figure Studio](figure-studio/README.md) Workbench Entry Point The launcher is documented here because users operate it as the first LabKit application. Its implementation remains a self-contained root file so it can repair a damaged installation without depending on the app framework. Figure Handoff Figure Studio can open tracked `.fig` files or receive a plot sent from a LabKit plot context menu. The lightweight framework popout is intended for inspection; Figure Studio provides the complete workflow for controlled canvas and style settings, raster/vector export, and a data-plus-recreation-script package. Figure Studio processes one selected axes at a time. A mixed FIG exposes its subplots as separate panels; Studio does not turn the composite layout into a single plot. It does not rerun the calculation that produced the original figure and does not claim that displayed graphics are a lossless replacement for the original scientific dataset. Related Documentation [Figure Studio manual](figure-studio/README.md) [LabKit Launcher manual](launcher/README.md) [App Framework](../../framework/README.md) [Architecture](../../development/build-apps/architecture.md)"},{"title":"Figure Studio","url":"apps/labkit-core/figure-studio.html","kind":"app","section":"apps","keywords":"apps labkit core figure studio Figure Studio","text":"Figure Studio Figure Studio restyles MATLAB figures, exports presentation copies, and extracts supported visible graphics into a portable data package. It changes presentation properties, not the calculation that produced the plot. Open Figure Studio From the launcher, select **Figure Studio** and choose **Open**. From a source checkout, run: labkit_FigureStudio_app A LabKit plot can also send its current axes to Figure Studio through the plot context menu. That handoff embeds a serializable plot snapshot in the project and immediately applies the **LabKit figure** preset. The source style remains available through **FIG default**. Project And Handoff A plot-context-menu handoff creates the same editable project state as loading a FIG file. Saved projects retain portable FIG sources, embedded plot snapshots, the selected subplot panel, style settings, and canvas settings. Decoded source graphics are rebuilt after load, and the default output folder follows the restored source. Load And Select Figures On **Figures**, choose **Add FIG files or scan folder**. The source list accepts MATLAB `.fig` files and keeps one current selection. Selecting another source opens its native graphics without rerunning the analysis. A mixed FIG is listed as ordered **Subplot panel** choices (top-left to bottom-right, with a title where one exists). Select one panel to preview, restyle, recalculate, and export exactly that axes; Studio never combines multiple panels into one output. The plot-context-menu handoff already represents one axes, so it provides one panel. **Clear figures** removes all sources from the project. For a source using **FIG default**, Figure Studio adopts its font, semantic line widths, annotations, legend, grid, and axes appearance. Selecting **LabKit figure** applies the calibrated single-panel publication hierarchy while retaining the source legend placement. Switching a text preset never changes the selected plot-frame width, aspect, or export scale. Style And Canvas Controls Group Controls Text Style preset, all/title/axis-label/tick/annotation font size, X tick-label angle, grid alpha, grid visibility Lines + Boundaries data, uncertainty, main-graphic boundary, reference-line, and axes line widths Legend source/on/off display, location, font size, columns, and border Plot frame reference, 6:5, 4:3, 16:9, 1:1, 3:2, or source aspect; a fixed plot width or source size; export supersampling; top/right frame visibility Changing **All font** updates title, axis-label, tick, annotation, and legend sizes together. Individual controls then provide category-level refinement. **LabKit figure** uses a calibrated 900-by-725 px reference **plot frame**: 45 pt title, axis-label, tick, annotation, and legend text; 6.5 pt data strokes; 2 pt uncertainty strokes; and 1.5 pt boundary, reference, and axes strokes. The default uses a complete top/right frame with no grid and no legend box. Legend samples use the reference's long line tokens. These editable baselines come from normalized pixel measurements across all nine panels of the maintained 3-by-3 visual reference, not from a single imported FIG. The configured width and aspect always describe the inside of the axes frame. Figure Studio calculates the enclosing figure's outer margins from the current title, labels, ticks, legend, and visible annotations, so changing a long label cannot silently shrink the data region. Empty ruler text is ignored, including the zero-area placeholders MATLAB exposes on logarithmic axes. If a Windows desktop refuses the requested hidden figure size, Studio recomputes the plot frame from the accepted canvas after reserving measured label and tick insets plus a typography-derived minimum outer margin. Older Windows `print` releases reserve one additional text line because their pre-print screen extent can omit that line from the hardcopy bounds. Choose **Source size** or one of 640, 720, 900, 960, 1200, 1237, 1364, 1600, or 2400 px; an aspect choice sets the paired plot-frame height. The workbench preview is a real interactive axes, not a raster image. When its allotted screen area changes, Studio reflows only the display text and strokes; project settings and export proportions remain unchanged. This calibration is validated on both 72-PPI Linux renderers and 96-PPI desktop MATLAB so display-density differences do not restyle exports. **Export x** is raster supersampling only. **Top/right frame** only shows or hides those two axes edges; it never changes the plot-frame size. Font controls use 0.5 pt steps and line controls use 0.1 pt steps. The preset styles every existing category but does not create or move a legend by default, because either action can cover data. Use the Legend panel for those explicit layout changes. **X tick labels** retains the source angle, makes labels horizontal, or rotates them 45 degrees. PNG and JPG resolution is `300 * Export x`, with a minimum of 72 dpi. SVG uses vector content. On MATLAB releases before R2025a, Figure Studio uses MATLAB's native `print` exporter to retain the complete styled figure because `exportgraphics` does not yet support figure padding or SVG. **FIG default** records the source plot-frame ratio as its reference, so reopening a source does not rescale its original typography. If limits are stale after copying or editing, use **Recalculate X/Y limits** to fit the visible graphics, update the interactive viewport, and refresh the four limit inputs. The X/Y minimum and maximum controls accept values within the finite visible-data extent expanded by exactly 50% on both sides; they do not change the source calculation. Quick Exports The **Figures** tab provides: **Save FIG** for an editable MATLAB figure; **PNG** and **JPG** for raster output; **SVG** for vector output. Each quick export also writes a LabKit result manifest beside the selected file. The manifest records the source, style parameters, output identity, and summary metadata. Export A Data Package On **Export**, select an output folder and choose **Export data + script**. Figure Studio creates a package containing supported visible data, style and axes metadata, and a MATLAB recreation script. For preview, editable FIG export, and image export, Studio first copies the selected native axes hierarchy when MATLAB permits the parent transition. This preserves MATLAB graphics that can be copied into the target axes, including ordinary grouped charts such as `boxplot`. Native child stacking is retained, so lines, markers, and text drawn over an image remain above that image in both the interactive preview and exports. UIAxes sources are rebuilt into conventional export axes from the portable snapshot, retaining the displayed scientific axis exponents as well as visible data. The portable data package separately recognizes visible line, bar, error-bar, area, scatter, image, surface, patch, rectangle, text, and constant-line objects. Existing legend text, visibility, placement, orientation, column count, font, interpreter, and border are retained. Explicit axis ticks, tick labels, label rotations, axis locations, and tick geometry are retained as visible presentation metadata. Visible graphics with hidden handles, such as error bars or significance brackets intentionally excluded from legends, are also retained without adding legend entries. Portable-package unsupported graphics are skipped and reported as warnings; the original FIG remains the authoritative source for object types that cannot be represented in that portable package. The package represents displayed graphics in axes child order. It is intended for audit, handoff, and plot recreation, not for reconstructing hidden source data or rerunning the original analysis. Programmatic Data Extraction fig = openfig(\"result.fig\", \"invisible\"); cleanup = onCleanup(@() close(fig)); ax = findobj(fig, \"Type\", \"axes\", \"-depth\", 1); plotData = figure_studio.resultFiles.extractAxesData(ax(1)); `plotData.axes` contains titles, labels, ticks, tick labels, scales, directions, limits, grid and aspect settings, color order, font settings, and colormap when available. `plotData.objects` contains the graphics type, display name, coordinate/color data, style fields, and object metadata. `plotData.warnings` lists skipped objects. Errors And Limitations A FIG must contain a readable axes to enter the editing workflow. Reopening a project with an existing but damaged or unsupported FIG stops the restore and preserves the currently open document; it is not presented as an empty figure source. Invisible objects are not exported. A selected single axes is copied natively when MATLAB supports that parent transition. The portable data package remains deliberately narrower and is not a promise of pixel-identical reconstruction of every MATLAB chart class. Callbacks, application data, custom classes, and analysis provenance inside the source figure are not treated as portable scientific data. Some object-specific rendering semantics cannot be reproduced from a plain graphics snapshot. Related Functions And Documentation `figure_studio.resultFiles.extractAxesData` `figure_studio.resultFiles.exportAxesPackage` `figure_studio.resultFiles.createStyledFigure` [LabKit Core apps](../README.md) [Plotting framework](../../../framework/README.md)"},{"title":"LabKit Launcher","url":"apps/labkit-core/launcher/index.html","kind":"app manual","section":"apps","keywords":"apps labkit core launcher LabKit Launcher","text":"LabKit Launcher The LabKit Launcher is the installed workbench entry point. It discovers apps, prepares their MATLAB paths, checks requirements, starts App SDK sessions, manages installed versions, opens app documentation, and exposes source-checkout maintenance tools. It is intentionally self-contained so a single surviving `labkit_launcher.m` can repair an incomplete ZIP installation. Start The Launcher labkit_launcher The window paints before app discovery completes. Its status area first reports that the app list is loading, then shows the selected app, integrity problems, tool availability, or the active maintenance operation. Launcher Window Group Action Behavior Run Apps **Open Selected App** Checks the selected app requirements, adds the app root, and calls its App SDK entrypoint without retired runtime launch arguments. Run Apps **Refresh App List** Repeats public and configured private-app discovery without restarting the launcher. Run Apps **Documentation and History** Opens the generated manual for the selected app. Versions and Install **Latest** Installs the current `main` branch archive. Versions and Install **Release** Installs the latest stable GitHub release. Versions and Install **Versions** Opens the release, tag, and commit selector for deliberate upgrade or rollback. Development and Maintenance **Update Documentation** Regenerates `site/` with the repository-owned documentation compiler. Development and Maintenance **Run Code Analyzer** Scans the checkout and writes JSON and HTML Code Analyzer reports. Development and Maintenance **Profile Selected App** Starts the selected app under the MATLAB profiler and saves its report when the app closes. Development and Maintenance **Clean Artifacts** Removes ignored generated reports under `artifacts/`; it does not delete app projects or exported laboratory results. Package and Publish **Package Checked** Creates one source ZIP containing all checked apps and their runtime support. Package and Publish **Checked P-code** Creates a runtime-only ZIP with MATLAB source encoded as P-code. Double-clicking an app row is equivalent to selecting it and opening it normally. The checkbox column controls package membership; ordinary launch selection does not change the checked set. When startup begins, the launcher immediately disables its App table and actions, changes the open button to **Starting App...**, shows a wait pointer, and reports two explicit stages: preparing the selected App path, then initializing its window through the named entry point. This feedback is painted before the potentially slow App entry point runs. Duplicate clicks are ignored while startup is active. Completion restores the controls and reports the opened command; failure reports the failing identifier and message, with repair guidance only for structural installation failures. Every launch uses the same clean App path. Use the App's **Tools > Diagnostics** menu to inspect its live session log, enable trace capture, or export a diagnostic ZIP after a problem occurs. Apps that declare a synthetic input pack expose **Tools > Developer Tools > Generate Synthetic Inputs...**. Generation writes anonymous fixture files and a manifest into a new folder but does not load them or mutate the running project. Programmatic Calls The launcher exposes a small non-GUI surface: fig = labkit_launcher; apps = labkit_launcher(\"list\"); info = labkit_launcher(\"version\"); page = labkit_launcher(\"documentation\", \"labkit_CIC_app\"); Call Result `labkit_launcher` Opens the window; optionally returns its figure handle. `labkit_launcher(\"list\")` Returns the discovered app catalog as a table without opening the launcher window. `labkit_launcher(\"version\")` Returns launcher name, display name, semantic version, and update date. `labkit_launcher(\"documentation\", appCommand)` Returns the local generated HTML page for one discovered app command. Unsupported modes, empty documentation commands, extra inputs, and more than one requested GUI output raise `labkit_launcher:InvalidInput` or `labkit_launcher:TooManyOutputs` errors. Documentation lookup uses the discovered public App folder and the unique path-conventional manual at `docs/apps///README.md`. It does not require a separately maintained App catalog. App Discovery Public apps are discovered from `apps/**/labkit_*_app.m`. Each entry point delegates to one `definition.m`, which supplies display metadata, family, command, version, requirements, layout, and optional capabilities. The launcher reads version and update metadata from that definition without starting the App. Local private apps may be discovered from `private_apps/apps/` or paths named by `LABKIT_PRIVATE_APP_ROOTS`; their repositories and manuals remain private. The launcher adds only the paths needed for the selected app. It does not use `genpath` to expose every app helper globally. Refresh the app list after adding, removing, or updating an app while the launcher remains open. Installation And Recovery The downloaded `labkit_launcher.m` can begin by itself. When no installed Launcher is available, it opens a focused install/repair window with: an editable installation folder and native **Browse...** action; a clear new-installation, existing-installation, or unsafe-target result; **Latest stable release** (recommended), a GitHub-backed selector of published stable versions, and **Current main branch** (development); honest preparation, download, package-validation, installation, and outcome stages with current-action or error details; and confirmation before replacing an existing installation. The target is checked before any network request. A new install may use a nonexistent folder whose parent exists, an empty folder, or a folder containing only the standalone launcher and ordinary OS metadata. Repair accepts an existing LabKit installation. Filesystem roots, Git checkouts, files, missing parents, and non-LabKit folders containing unrelated content are rejected. After LabKit is installed, **Latest**, **Release**, and **Versions** in the full Launcher provide the richer source-checkout version workflow. Every downloaded archive is validated before replacement. Existing repairs preserve a recovery copy transactionally and retain known local workspace folders when required. Keep experimental data and exports outside the runtime folder because installed code is replaceable. Installed apps run offline unless their own inputs use a network location. Version discovery and update actions require network access. Source and P-code deployment packages have different contents; see [App Deployment Packages](../../../development/tools/deployment.md). Maintenance Tools Maintenance buttons are enabled only when their corresponding source-checkout tool is available. The same operations can be called directly from MATLAB: [Code Analyzer Reports](../../../development/tools/codecheck.md) [App Deployment Packages](../../../development/tools/deployment.md) [Documentation Build Tools](../../../development/tools/documentation.md) [Performance Profiling](../../../development/tools/profiling.md) These tools produce ignored artifacts. They are not app runtime APIs and do not alter scientific data. Development History The launcher began as the visible entry point for UI 2.0, then gained self-contained repair updates, version selection, private-app discovery, profiling and packaging actions, early startup feedback, and direct app documentation links. The generated **Change history** below lists every record whose metadata names `labkit_launcher` in linear sequence. Key milestones: [v2.0 launcher and UI 2.0](../../../history/records/2026/06/LK-20260615-v2-launcher-and-ui2.md) [Self-contained launch and image workflow refinement](../../../history/records/2026/06/LK-20260621-v2-2-v2-3-image-workflows.md) [Launcher manager and stale callback repair](../../../history/records/2026/06/LK-20260626-launcher-manager-and-stale-callback-fix.md) [Profiling and validation speedups](../../../history/records/2026/07/LK-20260702-profiling-and-validation-speedups.md) [Searchable documentation and launcher workflow groups](../../../history/records/2026/07/LK-20260715-documentation-site.md) Related Documentation [Getting Started](../../../getting-started/README.md) [LabKit Apps](../../README.md) [LabKit Core Apps](../README.md) [Architecture](../../../development/build-apps/architecture.md) [Private Apps](../../../development/maintain-and-release/private-apps.md) [Release Process](../../../development/maintain-and-release/release.md)"},{"title":"Neurophysiology Apps","url":"apps/neurophysiology/index.html","kind":"app family","section":"apps","keywords":"apps neurophysiology Neurophysiology Apps","text":"Neurophysiology Apps The neurophysiology apps cover three stages of an Intan RHS workflow: checking recordings, measuring stimulus-evoked responses, and reviewing the resulting measurements. Choose An App What you want to do App Check recording metadata and short waveform windows; assign channel roles [RHS Preview](rhs-preview/README.md) Find stimulation events and measure compound action potentials [Nerve Response Analysis](nerve-response-analysis/README.md) Recalculate or summarize measurements from an analysis or segment table [Response Review and Stats](response-review-stats/README.md) Recommended Workflow Open a representative recording in RHS Preview. Confirm the channels and inspect a short portion of the waveform. Save a protocol draft that records channel roles. If you are working with a set of recordings, label the files and save a filter record. Open the filter record in Nerve Response Analysis, add the protocol, and run the analysis. Export the analysis as JSON. This file contains the events, stimulus trains, response measurements, and any issues found while reading the recordings. Open that JSON in Response Review and Stats to inspect and export the measurement table. A compatible segment CSV can be used instead when you need to recalculate measurements from waveforms. Each stage saves its own parameters with the results. Keep those settings with any reported latency, amplitude, or signal-to-noise values. Use RHS Data In MATLAB Code The [RHS library](../../libraries/rhs/README.md) provides functions for reading recording metadata and selected waveform windows without opening an app. The individual app manuals show the calculation functions used for event detection, response measurement, alignment, and summary statistics. Related Documentation [RHS library](../../libraries/rhs/README.md) [Biosignal library](../../libraries/biosignal/README.md) [App catalog](../README.md)"},{"title":"Nerve Response Analysis","url":"apps/neurophysiology/nerve-response-analysis.html","kind":"app","section":"apps","keywords":"apps neurophysiology nerve response analysis Nerve Response Analysis","text":"Nerve Response Analysis Nerve Response Analysis reads the recording list prepared in RHS Preview, finds stimulation events, groups them into trains, and measures compound action potential responses in the assigned channels. Open Nerve Response Analysis From the launcher, select **Nerve Response Analysis** and choose **Open**. From a source checkout, run: labkit_NerveResponseAnalysis_app Analyze A Recording Set On **Setup**, choose the filter JSON created by RHS Preview. On **Protocol**, choose the protocol JSON that assigns channel roles. A protocol is optional, but strongly recommended. Set **Max recordings** or **Max duration** only when you want a shorter test run. A value of zero means no limit. Choose **Analyze Filtered Files**. On **Review**, inspect the counts and switch the plot between **Counts** and **Issues**. On **Export**, choose an output folder and then **Export Analysis**. Changing the filter, protocol, or limits clears the previous analysis so that an export cannot silently use outdated settings. The filter record and optional protocol are saved as distinct portable project sources. Older projects with separate source fields are upgraded on load. Project And Session State The durable project stores portable references for the filter record and optional protocol, the two run limits, and the last export paths. Parsed JSON, analysis tables, issue details, preview selection, log messages, and output-folder convenience are transient session state. Opening a project reparses its JSON sources but does not persist or silently reuse old analysis tables; choose **Analyze Filtered Files** to calculate them again. If an existing selected JSON file is malformed, project restore stops and preserves the current document. An absent optional protocol remains valid. What The Analysis Does For each readable recording, the app selects an event source from the protocol and recording metadata, reads the required waveform, and finds sharp changes using the absolute first difference. A robust noise estimate sets the default detection threshold. Nearby peaks are reduced according to the minimum event spacing, then grouped into trains by their time gaps and train rules. For every event and response channel, the app measures a pre-event baseline, baseline noise, positive and negative peaks, peak-to-peak amplitude, peak time, latency, and SNR. The response search starts after the pulse-blanking interval to avoid measuring the stimulation artifact itself. Detection and measurement settings are scientific choices. Confirm that the event source and timing shift match the physical stimulus before interpreting latency. Output Files **Export Analysis** writes: `nerve_response_analysis.json`, containing source information, the protocol, events, trains, response measurements, and issues; `nerve_response_analysis.labkit.json`, containing the selected inputs, run limits, output identity, and result counts. An unreadable recording or missing channel is recorded in the issues table so that other recordings can still complete. Detect Events In MATLAB Code sampleRate = 10000; timeSec = (0:1/sampleRate:0.12).'; eventSignal = zeros(size(timeSec)); pulseTimes = 0.03 + (0:4).' * 0.01; eventSignal(round(pulseTimes * sampleRate) + 1) = 5; eventOptions = struct( ... \"sourceId\", \"stim\", ... \"stimShiftSec\", 0, ... \"train\", struct(\"minDetectedPulses\", 5)); [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, eventSignal, eventOptions); `timeSec` and `eventSignal` must be equal-length numeric vectors. Options can set the threshold multiplier, minimum score, minimum peak spacing, train gap, minimum pulse count, maximum train duration, isolation rule, and reported stimulus-time shift. With no candidates, the function returns empty tables. Measure CAP Features In MATLAB Code metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, responseSignal, events.timeSec, metricOptions); `metricOptions` can set `baselineWindowSec`, `blankingAfterPulseSec`, and `searchEndAfterPulseSec`. The output contains one row per event. If the search window contains no samples, that row is kept with status `noSamples`. To process an RHS file or an entire filter record, use `nerve_response_analysis.analysisRun.analyzeRecording` or `nerve_response_analysis.analysisRun.analyzeSession`. Function Reference The generated pages for [`detectEventTrains`](../../../reference/api/nerve_response_analysis/analysisRun/detectEventTrains.html), [`measureCapMetrics`](../../../reference/api/nerve_response_analysis/analysisRun/measureCapMetrics.html), and [`analyzeSession`](../../../reference/api/nerve_response_analysis/analysisRun/analyzeSession.html) document protocol nesting, direct option defaults and legal values, table schemas, units, partial-recording failure policy, and related APIs. Errors And Limitations Time and signal vectors must have the same length. A detection peak marks an electrical transition; it is not automatically the physical onset of the delivered stimulus. Default timing windows are starting points, not universal physiological constants. Missing events produce empty results rather than fabricated measurements. Review the issues table before comparing counts across recordings. Related Functions And Apps `nerve_response_analysis.analysisRun.analyzeSession` `nerve_response_analysis.analysisRun.detectEventTrains` `nerve_response_analysis.analysisRun.measureCapMetrics` [RHS Preview](../rhs-preview/README.md) [Response Review and Stats](../response-review-stats/README.md) nerve_response_analysis.analysisRun.analyzeRecording Analyze one RHS recording according to a protocol. nerve_response_analysis.analysisRun.analyzeSession Analyze accepted RHS recordings and combine their results. nerve_response_analysis.analysisRun.detectEventTrains Detect pulse candidates and group them into trains. nerve_response_analysis.analysisRun.measureCapMetrics Measure event-locked CAP response features."},{"title":"Response Review And Stats","url":"apps/neurophysiology/response-review-stats.html","kind":"app","section":"apps","keywords":"apps neurophysiology response review stats Response Review And Stats","text":"Response Review And Stats Response Review and Stats opens a Nerve Response Analysis result or a segment table, displays the measurements, and exports a clean CSV for review or downstream statistics. Segment tables can also be aligned and measured again with new baseline and noise windows. Open Response Review And Stats From the launcher, select **Response Review Stats** and choose **Open**. From a source checkout, run: labkit_ResponseReviewStats_app Review An Analysis On **Setup**, choose an analysis JSON or segment CSV. The app loads the measurement rows immediately. If the input contains waveform segments, set the **Baseline** and **Noise** windows. Both ranges are expressed in seconds relative to alignment time. Choose **Refresh Metrics** when you want to reload the source explicitly. On **Review**, inspect the summary and details. Switch the plot between **Summary** and **Aligned**. On **Export**, choose a folder and select **Export Metrics**. Changing either time window recalculates a loaded segment input automatically. For an analysis JSON that already contains measurements, the app reads those measurements rather than recreating unavailable waveform data. Accepted Inputs JSON exported by Nerve Response Analysis. The app reads its `metrics` records. CSV that can be interpreted as waveform segments with time and values. When segments have different time grids, the app interpolates them onto a shared grid. If no interval is specified in code, it uses the median positive source interval. If the timestamps cannot provide one, it uses 0.0001 seconds. When no window is specified, the grid spans the intersection of the time ranges after each segment's alignment-time shift. Samples outside an aligned segment's original range remain `NaN`. The selected input is saved as a portable project source. Older projects are upgraded on load. Project And Session State The project saves the source, baseline/noise windows, and last export reference. Metric tables, aligned waveforms, and summaries are recalculated from that source after load. Measurements And Summary For each aligned segment, the calculation subtracts the baseline mean and reports positive and negative peaks, peak-to-peak amplitude, noise RMS, and SNR. The summary groups rows by `pairId` when available; otherwise it uses the segment name. Counts are included so exclusions and missing rows remain visible. Output Files **Export Metrics** writes: `response_review_metrics.csv`, one row per measurement; `response_review_metrics.labkit.json`, recording the input, time windows, output identity, row count, and group count. Align Segments In MATLAB Code localTime = (-0.003:0.001:0.012).'; firstValues = zeros(size(localTime)); firstValues(localTime == 0.004) = 2; firstValues(localTime == 0.008) = -1; secondValues = 0.8 * firstValues; segments = struct( ... \"timeSec\", {10 + localTime, 20 + localTime}, ... \"values\", {firstValues, secondValues}, ... \"name\", {\"first\", \"second\"}, ... \"alignTimeSec\", {10, 20}); options = struct( ... \"sampleIntervalSec\", 0.001, ... \"windowSec\", [-0.003 0.012], ... \"baselineWindowSec\", [-0.003 -0.001], ... \"noiseWindowSec\", [-0.003 -0.001], ... \"measurementWindowSec\", [0 0.010]); aligned = response_review_stats.analysisRun.alignSegments(segments, options); metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, options); summary = response_review_stats.analysisRun.summarizeMetrics(metrics); Each segment supplies `timeSec`, `values`, and `name`. An optional `alignTimeSec` shifts that segment to the common zero. `aligned.values` is a samples-by-segments matrix. Function Reference The generated pages for [`alignSegments`](../../../reference/api/response_review_stats/analysisRun/alignSegments.html) and [`measureAlignedSegments`](../../../reference/api/response_review_stats/analysisRun/measureAlignedSegments.html) define time-window option shapes and defaults, interpolation and empty-sample behavior, output table columns, failures, examples, and related APIs. Errors And Limitations Baseline and noise windows must overlap the available data to produce finite measurements. Interpolation makes grids comparable but cannot restore timing precision or bandwidth that is absent from the source. An empty input returns an empty result rather than a synthetic summary. Keep the chosen windows with any exported amplitude or SNR values. Related Functions And Apps `response_review_stats.analysisRun.loadMetrics` `response_review_stats.analysisRun.alignSegments` `response_review_stats.analysisRun.measureAlignedSegments` `response_review_stats.analysisRun.summarizeMetrics` [Nerve Response Analysis](../nerve-response-analysis/README.md) response_review_stats.analysisRun.alignSegments Interpolate segment traces to a shared time grid. response_review_stats.analysisRun.measureAlignedSegments Measure peak-to-peak, noise, and SNR per segment. response_review_stats.analysisRun.summarizeMetrics Compute grouped metric counts and finite means."},{"title":"RHS Preview","url":"apps/neurophysiology/rhs-preview.html","kind":"app","section":"apps","keywords":"apps neurophysiology rhs preview RHS Preview","text":"RHS Preview RHS Preview lets you inspect an Intan RHS recording without loading the entire waveform into memory. Use it to check channels, move through short waveform windows, choose the channels to plot, and prepare protocol or file-filter JSON for later analysis. Open RHS Preview From the LabKit launcher, select **RHS Preview** and choose **Open**. From a source checkout, run: labkit_RHSPreview_app Preview A Recording On **Setup**, choose one `.rhs` file. The app reads the header, lists the amplifier channels, selects up to eight channels, and displays a short waveform window. Drag **Pan** to move the window through the recording. Scroll over the plot to change the displayed duration around the pointer. Edit the plotted interval on the graph when you need to mark a smaller region. Choose **Zoom to ROI** to make that interval the current window. Choose **Refresh Preview** to reread the current window from disk. Only the displayed window is decoded. This keeps navigation practical for long recordings and does not change the source file. Choose Channels And Roles On **Protocol**, the channel table contains four columns: Column Purpose Plot Show or hide this channel in the stacked preview Role Assign its purpose in the experiment Label Enter a readable name for plots and later analysis Channel Intan channel name read from the recording You may open an existing protocol JSON before editing the table. Choose **Save Protocol Draft** to save the current assignments. A protocol is recommended for response analysis because it makes the stimulation, reference, and response channels explicit. Prepare A File Filter Use **Add RHS files or folder** to collect recordings for a later analysis. The file table lets you assign a label and comment to each recording. Remove individual entries or clear the list as needed, then choose **Save Filter Record**. The filter record identifies the selected files and their labels. It does not copy the RHS recordings or contain decoded waveforms. Saved Project Sources The preview recording, optional protocol, and filter recordings are saved as distinct portable project sources. Older projects that stored these sources separately are combined automatically on load. Project And Session State The durable project stores portable references for one preview recording, one optional protocol, and an ordered collection of filter recordings. It also stores preview settings, channel-role drafts, manual filter labels/comments, and compact export records. Header indices, decoded preview windows, table presentation state, current ROI and window position, status text, and log messages are transient session data. They are reconstructed from the project sources when a project is opened. Review Recording Information The **Review** tab shows the indexed duration, channel counts, current window, selected channels, and recent action. The preview plot uses a separate vertical band for each selected channel so traces do not obscure one another. Read An RHS Window Without The App [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); assert(status.ok, status.message) options = struct( ... \"family\", \"amplifier\", ... \"channels\", [\"A-000\", \"A-001\"], ... \"timeRangeSec\", [1.0 1.5]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", options); assert(status.ok, status.message) `indexFile` reads recording metadata. `readWindow` returns the selected time range together with the sample rate and channel information needed to interpret the waveform matrix. Errors And Limitations RHS Preview currently displays amplifier channels. A channel listed in a protocol must exist in the selected recording. A window is limited to the indexed recording duration; requests outside that range are clamped. Short reads reduce memory and waiting time, but repeated navigation still requires disk access. The app is for inspection and preparation. Use Nerve Response Analysis for stimulus detection and CAP measurements. Related Functions And Apps `labkit.rhs.indexFile` `labkit.rhs.readWindow` `rhs_preview.analysisRun.readPreviewWindow` [Nerve Response Analysis](../nerve-response-analysis/README.md) [RHS library](../../../libraries/rhs/README.md) rhs_preview.analysisRun.readPreviewWindow Read the requested RHS preview window from app state."},{"title":"Statistics Apps","url":"apps/statistics/index.html","kind":"app family","section":"apps","keywords":"apps statistics Statistics Apps","text":"Statistics Apps Statistics Apps start from explicit observations selected or entered by the user. They keep data preparation, hypothesis testing, completed results, and figure design as separate steps so a visual change cannot silently alter a test. Choose An App What you want to do App Main result Select two or more groups, compare each later group with the first, and draw a mean/SD plot [T-Test Wizard](ttest-wizard/README.md) ordered t-test family, group CSV, result CSV, and plot Table And CSV Boundary An input file does not need a LabKit schema. T-Test Wizard first shows CSV, TSV, XLSX, and XLS sources as spreadsheet cells, then lets the user choose the numeric ranges that have scientific meaning. The first captured group is the reference and every later group is tested against it. For exchange between Apps and scripts, prefer the [Simple Scientific CSV Exchange](../../development/data-and-designs/scientific-csv-interchange.md): one header row, one rectangular table, readable labels, and blank cells for missing values. This is a lowest-layer table contract, not a universal experiment schema. Related Documentation [T-Test Wizard manual](ttest-wizard/README.md) [Simple Scientific CSV Exchange](../../development/data-and-designs/scientific-csv-interchange.md) [T-Test Wizard design](../../development/data-and-designs/group-comparison-app-design.md) [All Apps](../README.md)"},{"title":"Digital Image Correlation Apps","url":"apps/dic/index.html","kind":"app family","section":"apps","keywords":"apps dic Digital Image Correlation Apps","text":"Digital Image Correlation Apps The DIC family prepares optical image pairs before correlation and turns Ncorr strain results into presentation images and summary measurements afterward. LabKit does not implement the correlation solver itself. Apps In This Family Stage App Use it to Main result Before correlation [DIC Preprocess](dic-preprocess/README.md) register a moving image to a reference, apply identical crop geometry, and build a mask prepared image pair and binary ROI mask After correlation [DIC Postprocess](dic-postprocess/README.md) map EXX/EYY strain fields onto the reference image and summarize the valid ROI overlay PNGs and strain summary CSV End-To-End Workflow Use DIC Preprocess to align each moving image with the reference. Apply a shared crop and save the prepared pair. Create and save the ROI mask. Run correlation in Ncorr using the prepared images and mask. Load the Ncorr MAT result, matching reference image, and mask into DIC Postprocess. Generate overlays, inspect valid coverage, and export the summary. The two apps deliberately exchange ordinary image and MAT files rather than a private in-memory object. This keeps the external solver boundary explicit and makes each stage independently repeatable. Coordinate And Image Conventions Interactive points and rectangles use MATLAB image coordinates: `x` is the column coordinate and `y` is the row coordinate. Registration and crop operations preserve array class and color channels where the operation permits. Masks are logical images aligned with the displayed reference domain. DIC Preprocess owns geometric edits. DIC Postprocess may resize strain and mask arrays to the reference-image domain for rendering, but it does not alter the original Ncorr MAT file. Related Modules [Image Library](../../libraries/image/README.md) [App Framework interactions](../../framework/README.md) [All Apps](../README.md)"},{"title":"T-Test Wizard","url":"apps/statistics/ttest-wizard.html","kind":"app","section":"apps","keywords":"apps statistics ttest wizard T-Test Wizard","text":"T-Test Wizard T-Test Wizard captures two or more numeric groups, compares every group after the first with the first group, and draws one publication-oriented mean and standard-deviation plot. The first group is always the reference group. Open T-Test Wizard From the LabKit launcher, select **T-Test Wizard** and choose **Open**. From a source checkout, run: labkit_TTestWizard_app The App requires Base MATLAB and the LabKit App Framework. It does not require Statistics and Machine Learning Toolbox. Workflow The left controls use three short pages: **Data** — open a table, choose where selected values go, or clear data. **Test & Plot** — choose one t-test, run all comparisons, review result rows, and adjust plot presentation. **Export** — save all group observations and result rows as separate CSVs. The right workspace has two user-selectable pages: **Data** keeps the opened source table above the editable analysis table, each using half of the workspace. **Plot** gives the completed statistical figure the full workspace. Changing data or test settings does not silently recalculate the plot. A compact status beside the plot controls reports **OUT OF DATE** until **Run / refresh comparisons** is used again. The short **What will run** and **Result family** summaries stay compact so the result table and plot controls receive most of the vertical space. Add Groups From A Table The Data step accepts `.csv`, `.tsv`, `.xlsx`, and `.xls`. Workbook worksheets can be selected from the dropdown. Headers and labels remain visible in the source grid. For each group: select its numeric cells; leave **Source selection to** at `(new group)`, or choose an existing group; choose **Add selected source cells**; repeat for every additional group. Finite numeric cells and numeric text are accepted. Blank and label cells are skipped with a visible count. `NaN` and infinities block that selection until they are excluded or corrected. Opening another file replaces the source grid but keeps all groups already captured. For a new group, the App proposes a name from the nearest useful header above the selection. It follows merged-style headings to the left and combines up to two useful levels, such as `Treatment A - Metric Y`. Dates, row numbers, and numeric summary cells are not used as names. The proposed label remains editable in the analysis table. The lower analysis table uses one row per observation: Column Meaning Group Editable category label; repeated labels belong to one group Value One finite numeric observation Type or paste directly into either column. Blank rows are available for manual entry without a source file. Group order follows the first appearance of each label in this table; the first group is the reference. To change the reference, reorder the corresponding rows and edit any cell so the new order is committed. The interface deliberately does not expose an unexplained `N` column. To relabel many observations at once, select their rows in the analysis table, choose an existing group under **Change selected rows to**, and use **Change selected rows**. Selection is committed when the completed table selection changes, so dragging across a range does not repeatedly rebuild the App while the pointer is still moving. Use **Delete selected rows** to remove only the selected observations; if every observation in a group is deleted, that empty group is removed. **Clear all data** remains the separate full-reset action. Choose The Tests One selected method is applied to every comparison: Choice Calculation and use Independent t-test — Welch Independent groups without an equal-variance assumption; default Independent t-test — equal variances Pooled-variance independent test Paired t-test Row-by-row differences between the first group and each later group The Question dropdown offers two-sided, first greater than comparison, or first less than comparison alternatives. Difference direction is always: first/reference group minus comparison group Alpha defaults to `0.05`. No multiple-comparison correction is applied. The App reports each requested t-test exactly as configured; whether correction, ANOVA, a mixed model, or another method is scientifically appropriate remains the user's responsibility. Every group needs at least two finite observations. Paired testing additionally requires each comparison group to have the same length as the first group. Pairing follows displayed row order and is never inferred from a filename, header, or equal length. Results **Run / refresh comparisons** creates `N - 1` immutable result snapshots for `N` groups. Each row reports: comparison and reference labels; sample counts; group means; reference-minus-comparison difference; t statistic, degrees of freedom, and exact p-value; `NS`, `*`, `**`, `***`, or `****`; stable success or failure status. The thresholds used for the shorthand plot labels are: Label Rule `NS` `p >= alpha` `*` `p < alpha` `**` `p < 0.01` `***` `p < 0.001` `****` `p < 0.0001` The exact p-value remains available in the result table and CSV. Editing groups or test settings afterward does not rewrite the completed family; rerun to create a new family. Plot Style The plot uses the copied completed-result observations, not live editor values. Choose **Mean bars with SD** or **Box plot** without recalculating the tests. Both styles use numeric y-axis ticks, ordinary horizontal x-axis tick labels, and the same annotation geometry. Figure Studio can adjust typography and label presentation after the plot is popped out. The bar visual contract is: one pastel bar per group; bar height equal to the group mean; dark, capped sample-standard-deviation error bars; a zero-anchored y-axis for all-positive or all-negative bars, including after wheel or toolbar zoom; black boxed axes on a white background; Helvetica labels and no grid; stacked brackets from the first bar to every later successful comparison; significance shorthand on each bracket. The first four colors are soft green, orange, blue, and yellow; the palette cycles for additional groups. The box style uses the same group colors with subtler fill, dark outlines, whiskers, and medians. When individual values are shown, the box chart's own outlier markers are suppressed so observations are not drawn twice. Optional controls overlay individual values, hide SD bars on bar plots, hide brackets, and edit the title or y-axis label. Initial limits include bars or boxes, visible individual values, error bars, and significance brackets. **Reset plot view** recomputes that fitted viewport without rerunning the tests or changing project data. Switching between bar and box styles also accepts the new style's fitted limits; ordinary redraws otherwise preserve the user's current zoom. Use **Plot > Pop out all plots** for a standalone MATLAB figure. The popout can be passed to [Figure Studio](../../labkit-core/figure-studio/README.md) for further visual styling without recalculating the tests. CSV Outputs **Export group data CSV** writes one group per column and pads unequal groups with blank cells: Row,Reference,Treatment 1,Treatment 2 1,1.2,1.8,1.1 2,1.4,1.7,1.2 **Export comparison results CSV** writes one row per later group. Columns are: Test, Alternative, Alpha, Reference Group, Comparison Group, N Reference, N Comparison, N Pairs, Mean Reference, SD Reference, Mean Comparison, SD Comparison, Difference Reference-Comparison, SE, T, DF, P, CI Lower, CI Upper, Significance, Status, Message The imported source is never modified or overwritten. Programmatic Use Run a complete first-versus-each family without opening the GUI: groups = struct( ... \"label\", {\"Reference\", \"Treatment 1\", \"Treatment 2\"}, ... \"values\", {[1.2 1.4 1.3], [1.8 1.7 2.0], [1.1 1.2 1.4]}); options = struct( ... \"method\", \"welch\", ... \"alternative\", \"two_sided\", ... \"alpha\", 0.05); results = ttest_wizard.testRun.runGroupTTests(groups, options); [`runGroupTTests`](../../../reference/api/ttest_wizard/testRun/runGroupTTests.html) returns one canonical [`runTTest`](../../../reference/api/ttest_wizard/testRun/runTTest.html) result per later group. Project Recovery Project schema version 2 stores ordered groups, test settings, plot settings, completed comparison results, and source references. Version-1 A/B projects migrate by preserving A as the first group and B as the second group. Assumptions And Limitations Observations must share a meaningful measurement unit. Independent tests assume independence within and between groups. Paired tests use the displayed row order for every first-versus-group pair. The App does not infer experimental units, exclusions, independence, biological replication, or the correct statistical model. No multiple-comparison correction, ANOVA, nonparametric test, regression, or mixed model is performed. Plot shorthand does not replace the exact p-value or establish scientific importance. Related Documentation [Statistics Apps](../README.md) [T-Test Wizard design](../../../development/data-and-designs/group-comparison-app-design.md) [Simple Scientific CSV Exchange](../../../development/data-and-designs/scientific-csv-interchange.md) [Figure Studio](../../labkit-core/figure-studio/README.md) [App Framework](../../../framework/README.md) ttest_wizard.testRun.runGroupTTests Compare every group after the first with the first group. ttest_wizard.testRun.runTTest Run one independent Welch, pooled, or paired t-test."},{"title":"Wearable Apps","url":"apps/wearable/index.html","kind":"app family","section":"apps","keywords":"apps wearable Wearable Apps","text":"Wearable Apps Wearable apps read physiological recordings, preserve their time and channel information, and provide focused analysis and reporting workflows. Choose An App What you want to do App Filter ECG, detect beats, inspect segment quality, and export a printable waveform [ECG Print](ecg-print/README.md) Use Biosignal Functions Directly The [Biosignal library](../../libraries/biosignal/README.md) provides the same building blocks used by ECG Print: recording import, channel selection, filtering, peak detection, event-centered segments, template construction, and segment measurements. Use those functions when you need a script or batch workflow rather than the app. Related Documentation [ECG Print manual](ecg-print/README.md) [Biosignal library](../../libraries/biosignal/README.md) [App catalog](../README.md)"},{"title":"ECG Print","url":"apps/wearable/ecg-print.html","kind":"app","section":"apps","keywords":"apps wearable ecg print ECG Print","text":"ECG Print ECG Print reads a wearable recording, filters one channel, detects beats, builds event-centered segments and a representative template, and reports signal quality over time. It can export the segment measurements and a printable waveform image. Open ECG Print From the LabKit launcher, select **ECG Print** and choose **Open**. From a source checkout, run: labkit_ECGPrint_app Supported Inputs Choose **Open recording** to load a MAT, CSV, TXT, or TSV biosignal file. MAT files can contain a supported timetable or recording structure. Delimited text files can be detected automatically or parsed with the controls under **Import Parsing**. For a difficult text file, choose **Preview file header**, then set: the line containing column names; whether a header is present; the time column and its unit; the signal columns; a fallback sample rate when time values are unavailable. Choose **Parse / refresh file** after changing import settings. The app reports the detected channels and lets you select one for analysis. The recording is saved as a portable project source. Older projects are upgraded on load. Project And Session State ECG Print stores only durable workflow state in its project file: the portable recording source; import, channel, ROI, filter, detector, segment, template, and view settings; the compact last-analysis summary and export records. Decoded recordings, signal arrays, events, segments, templates, measurements, header previews, and plot models are transient session data. They are rebuilt from the recording and durable parameters when a project is opened. This keeps saved projects portable and avoids duplicating large waveform caches. Analyze ECG Open and parse a recording. Select a channel and, if needed, set the start and end of the time region. When the end is not greater than the start, the full signal is used. Set the bandpass range and peak method. Set minimum peak distance, segment half-window, template count, and smoothing count. Choose **Analyze current ROI**. Review the four plots: waveform and peaks, template-noise RMS, template SNR, and the template with either a residual band or individual segments. The filter is applied to the full selected channel before the time region is cropped. This reduces boundary artifacts at the region edges. The **Files + Analysis** tab keeps the workflow in five ordered sections: **Recording**, **Import Parsing**, **Channel + ROI**, **Signal Processing + SNR**, and **Exports**. Bounded numeric settings use paired spinner-and-slider controls. **Summary + Results** contains the analysis summary and file-header preview. **Tools > Diagnostics > Open Session Log...** records the current workflow and earlier runtime context. The **ECG Preview** workspace keeps four vertically stacked time-series axes available on every tab. Analysis Parameters Parameter Default Meaning Fallback sample rate 2000 Hz Used only when import cannot derive time spacing Bandpass 0.5 to 40 Hz Applied before peak detection; the upper cutoff is kept below 45% of sample rate Peak method QRS streaming QRS streaming, Pan-Tompkins, or local peaks Peak distance 0.28 s Minimum interval between accepted detections Segment half-window 0.7 s Data retained before and after each event Template top N 30 Number of best segments used to build the template Smooth beats 15 Smoothing span used in exported per-segment trends Template plot Template + residual band Alternative view is template plus individual segments Peak polarity is selected automatically. The default detector threshold is 2.8 standard deviations inside the app calculation. Output Files **Export segment SNR CSV** writes `ecg_segment_snr.csv` and a matching `ecg_segment_snr.labkit.json` manifest. The CSV contains per-segment measurements and smoothed trends. **Export waveform PNG** writes `ecg_waveform.png` and `ecg_waveform.labkit.json`. The manifest records the source, import and analysis settings, output identity, event count, segment count, and summary. Analyze A Signal In MATLAB Code [recording, status] = labkit.biosignal.readRecording(\"ecg.csv\"); assert(status.ok, status.message) signal = labkit.biosignal.getChannel(recording, 1); cache = struct(\"signal\", signal); parameters = struct( ... \"lowCut\", 0.5, ... \"highCut\", 40, ... \"roiStart\", 0, ... \"roiEnd\", 0, ... \"peakMethod\", \"QRS streaming\", ... \"peakDistance\", 0.28, ... \"segmentWindow\", 0.7, ... \"templateTopN\", 30); cache = ecg_print.analysisRun.analyzeSignal(cache, parameters); The returned cache contains the working and filtered signals, detected events, segments, template, and measurements. For a more customized pipeline, call `labkit.biosignal.filterSignal`, `detectEcgPeaks`, `segmentByEvents`, `buildTemplate`, and `measureSegments` directly. Errors And Limitations Time or a valid sample rate is required to interpret filter frequencies and event spacing. Automatic parsing should be checked when a text file has long preambles, unusual headers, or mixed metadata rows. A failed **Parse / refresh file** keeps the selected source and header preview available so import settings can be corrected in place. Automatic peak polarity and thresholds still require visual review when morphology or noise changes within a recording. Filtering and detection settings must accompany any reported heart rate or signal-quality result. Related Functions And Documentation `ecg_print.analysisRun.analyzeSignal` `labkit.biosignal.readRecording` `labkit.biosignal.detectEcgPeaks` `labkit.biosignal.measureSegments` [Biosignal library](../../../libraries/biosignal/README.md) ecg_print.analysisRun.analyzeSignal Rebuild ECG analysis products from one decoded signal cache."},{"title":"Development","url":"development/index.html","kind":"overview","section":"development","keywords":"development Development","text":"Development Use these pages for source changes and maintenance. App users normally need only [Getting started](../getting-started/README.md) and the [app guide](../apps/README.md). Read By Task Task Page Understand repository and package ownership [Architecture](build-apps/architecture.md) Create or refactor an app [App development](build-apps/app-development.md) and [complete app tutorial](build-apps/complete-app.md) Exchange simple scientific tables or gradually improve an App CSV export [Simple scientific CSV exchange](data-and-designs/scientific-csv-interchange.md) Develop the proposed cell-selection-based t-test App [T-Test Wizard design](data-and-designs/group-comparison-app-design.md) Work with an ignored private app repository [Private apps](maintain-and-release/private-apps.md) Select tests, GUI checks, or profiling [Testing](maintain-and-release/testing.md) Call source-checkout packaging, profiling, codecheck, or documentation tools [Maintainer tools](tools/README.md) Maintain the documentation sources and generated site [Documentation system](maintain-and-release/documentation.md) Choose versions, tags, and release evidence [Release process](maintain-and-release/release.md) Reusable facade calls are documented separately in the [public API reference](../reference/README.md). Launcher behavior belongs with the [LabKit Core apps](../apps/labkit-core/README.md), not in maintainer-only development pages."},{"title":"App Development","url":"development/build-apps/app-development.html","kind":"guide","section":"development","keywords":"development build apps app development App Development","text":"App Development LabKit apps are first-class deliverables. Each app owns its scientific workflow, state, plots, result schema, and exports; the reusable framework owns lifecycle and domain-neutral UI mechanics. Create An App Start from the LabKit app template rather than copying a complete neighboring app. Use the smallest nearby app only as a workflow example. A static App begins with only the product entrypoint, definition, and layout it actually uses: apps///labkit__app.m apps///+/definition.m apps///+/+workbench/buildLayout.m `labkit.app.Definition` supplies empty project/session and default presentation behavior when optional components are omitted. Bind real business callbacks directly from the layout. Add `+workbench/present.m` for derived visible state, `createSession.m` for transient decoded/cache state, and `projectSpec.m` only when the App owns durable data. That single project file contains local create, validate, and migrate functions. Its migrate callback exists only after a saved project schema has actually changed and only for the older payload versions the App explicitly supports; Runtime owns the version loop. Current saves always write the current payload version. Use an exact declared `LegacyImports` variable adapter only for real pre-envelope user files, never as a shape-guessing fallback. Runtime and App architecture names remain versionless. Put facade/App compatibility in the existing version and requirement metadata, and put saved payload numbers only in `projectSpec` migration logic. Do not create version-named packages, files, functions, types, tests, or manual sections. The project validator owns only App-specific requirements: domain fields, legal choices and ranges, cross-field relationships, source roles, and scientific invariants. Runtime validates the five canonical project buckets and standard portable source records before the callback runs. Do not repeat those framework checks in each App. Create additional packages only for concrete workflows that need them, for example `+sourceFiles`, `+analysisRun`, `+resultFiles`, `+cropGeometry`, or `+thermalFrames`. Avoid generic buckets such as `+actions`, `+ops`, `+io`, `+helpers`, and `+utils`. Define The Runtime Contract `definition.m` returns one immutable `labkit.app.Definition`. It is the App's single product contract and names the public command, stable ID, display metadata, App version, compatible LabKit facades, and workbench. Project schema, session factory, presenter, post-layout start callback, and debug sample are opt-in capabilities. Callbacks and renderers are owned directly by their layout nodes. The complete field tables, callback signatures, canonical project/session buckets, presenter shape, and renderer contract are documented in [Runtime and Lifecycle](../../framework/guides/runtime.md#definition-component-contract). For a complete file-by-file implementation, follow [Build a Complete App](complete-app.md). The framework owns: startup, readiness, and busy state queued callback dispatch and rollback project save/load and recovery managed interactions and resources debug tracing and result manifests The app owns durable `state.project`, transient `state.session`, semantic callbacks, presentation models, and scientific behavior. Build The Workbench `+workbench/buildLayout.m` returns a data-only `labkit.app.layout.*` tree. Keep tabs, sections, and workspace pages in the same order users see them. For a complex App, compose capability-owned layout fragments instead of flattening every control into this file. It must not create graphics handles, read files, run calculations, mutate state, or schedule startup work. `+workbench/present.m` is the pure assembly bridge from canonical state to a complete `labkit.app.view.Snapshot`. Compose feature-owned fragments with `Snapshot.include`. Renderers live with the capability they draw, receive only axes and a prepared model, and do not own workflow decisions. Use the complete runtime value only at this assembly bridge and at callbacks referenced directly by layout signals. Name it `applicationState`, unpack `project` and `session` at the top, and call feature presenters with the exact values they display. A feature presenter should look like `present(results, selection, displayOptions)`, not `present(state)`. A direct callback is the transaction adapter. Its signature is `(applicationState, callbackContext)` for a button or `(applicationState, typedEventValue, callbackContext)` for a value-bearing signal. It may perform short, visible state mutation in workflow order, then delegate calculations and writes through explicit inputs. Do not pass the complete state or callback context into a generic second action layer. Name Workflow Code Use concrete verb-object names such as: +sourceFiles/readSourceFiles.m +analysisRun/collectAnalysisOptions.m +analysisRun/computeAnalysisResults.m +resultFiles/writeResultFiles.m Keep callback-local glue local when extracting it would hide state mutation or workflow order. Extract a helper when it owns a stable calculation, data shape, file boundary, state transition, or reusable interaction contract. File length alone is not an extraction reason. Data Loading And Task Lifecycle Register large file selections with the least data needed for the first useful preview. Decode the selected file on demand and defer full-batch work until the user runs or exports when the workflow permits it. Apps with preview, edit, run, or export stages should make dirty state and the last successful task explicit. Pure helpers build deterministic task snapshots and calculations; result writers receive explicit task data rather than reading UI handles. Cross-App Data Contracts Apps exchange saved, documented data contracts; production and debug code do not call a sibling App package. A consumer owns its parser and error language, so it can launch with only the framework and its own App root on the MATLAB path. A producer owns serialization and schema validation. Keep one producer-consumer integration test that invokes both Apps and proves the current saved format remains compatible; keep consumer unit and debug fixtures independent of the producer package so the shared test path cannot hide a runtime dependency. Ownership Check Keep these in the app: accepted formats, domain defaults, units, and thresholds formulas and scientific interpretation plots, labels, result columns, and export schemas workflow ordering, user messages, and logs Move code into `+labkit` only when it is domain-neutral, independent of app state, directly testable, used broadly, and makes the public API clearer. See [Architecture](architecture.md#reusable-extraction-rule). Version And Requirements `definition.m` owns supported LabKit facade ranges together with the App command, display name, family, semantic version, and last change date. There is no second metadata registry. App behavior changes update this definition, the App documentation, and component-owned history in the same coherent change. Validation Use focused app tests while editing, then follow the stable gates in [Testing](../maintain-and-release/testing.md). Automated GUI tests cover launch, layout, callbacks, debug plumbing, and synthetic workflows; visual quality and manual interaction feel still require a human MATLAB check. Related Reference [App catalog and workflows](../../apps/README.md) [App Framework](../../framework/README.md) [Architecture](architecture.md) [Testing](../maintain-and-release/testing.md) labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot."},{"title":"Architecture","url":"development/build-apps/architecture.html","kind":"guide","section":"development","keywords":"development build apps architecture Architecture","text":"Architecture [Development index](../README.md) | [Public API index](../../reference/README.md) | [LabKit Launcher](../../apps/labkit-core/launcher/README.md) | [Maintainer Tools](../tools/README.md) LabKit is an app-first MATLAB workbench. Apps are the deliverables; `+labkit` is the small reusable foundation they share. Project Model apps/ workflow-specific GUI apps and app-owned helpers +labkit/ reusable UI, image, thermal, DTA, RHS, and biosignal facades tests/ behavior tests, project contracts, GUI checks, shared helpers, and runner code docs/ human-facing usage, API, architecture, and validation docs tools/ maintainer diagnostics, deployment packagers, and report generators .github/scripts/ GitHub Actions-only helper scripts The repository root has no generic `scripts/` bucket. Keep automation beside its single consumer; promote it to a shared owner only when several stable consumers genuinely need the same operation. Apps should remain independently launchable. The reusable library should grow only when a helper is domain-neutral, app-facing, tested, and useful beyond one workflow. Runtime Dependency Boundary LabKit apps run from MATLAB and repository-owned code. Production apps and facades do not create Python or Conda environments, install third-party runtime packages, download model weights, or require a network connection on first use. This keeps source checkouts, offline packages, and restored lab systems reproducible. During rapid development, an app may temporarily accelerate a capability with a MathWorks Toolbox only when it also ships a repository-owned base-MATLAB implementation with comparable user-visible behavior. The app must remain usable without the product, and automated tests must exercise that fallback. When values feed scientific interpretation, branching, exports, or later calculations, identical inputs must be idempotent: pure calculations reproduce the same app-consumed values, while safely repeated stateful operations do not compound state or side effects. A parity test must compare app-consumed outputs against the Toolbox reference within a documented tolerance. Visual similarity is not sufficient evidence. Record each temporary Toolbox dependency with its source function, MathWorks product, responsible app, fallback tests, idempotency/parity evidence, and replacement plan. Dependency reports continue to show the Toolbox call while that debt is open. The debt closes when the repository implementation replaces the Toolbox branch and the branch is removed. Adding any third-party runtime remains an architecture and deployment decision requiring explicit approval; it is not an ordinary app-local implementation choice. Version Ownership Facade and App compatibility is expressed by the existing version metadata and dependency-requirement components. Saved-data schemas keep explicit numeric versions only inside their persistence and migration contracts. Release and history records preserve the versions needed as evidence. Architecture concepts themselves are versionless. Packages, folders, files, functions, classes, type tags, protocol names, tests, and current manuals use stable semantic names. An incompatible redesign replaces the owning contract and updates version metadata; it does not create a second version-named architecture. Runtime Entrypoints Users normally start with: labkit_launcher The launcher discovers public `apps/**/labkit_*_app.m` entry points. Public app command names are stable user entry points, for example `labkit_CIC_app`, `labkit_DICPreprocess_app`, `labkit_ECGPrint_app`, and `labkit_RHSPreview_app`. Source checkouts may also keep local private apps under an ignored `private_apps/apps/` workspace or roots named by `LABKIT_PRIVATE_APP_ROOTS`. Any developer can create that local workspace for their own private apps. The launcher can list and launch those apps with `Visibility` set to `private`, but public releases and CI checks cover only the public `apps/` tree. Keep each private workspace as a separate private Git repository rather than mixing private app files into the public repo history. The public structure guide is [private-apps.md](../maintain-and-release/private-apps.md); private app documentation belongs in the private workspace. The launcher keeps update, discovery, and repair logic self-contained: it uses native MATLAB UI and local helper functions so users can repair a damaged zip install even if packages, apps, docs, or scripts have been deleted. It configures the MATLAB path for app entry points. MATLAB desktop project metadata belongs to each developer's local workspace. Tools under `tools/` are source-checkout support utilities rather than app runtime APIs. The launcher may call a small, explicit subset for maintenance and deployment actions, such as profiling a selected app or packaging a single app for offline deployment. Single-app deployment packages include the launcher and only those launcher-needed tool folders, not the whole source checkout. Direct syntax, options, outputs, and artifact behavior are documented in [Maintainer Tools](../tools/README.md). Ownership Boundaries Area Owns App entry point Public launch name delegated to one runtime definition, including lightweight requirements/version/debug requests. App package App definition, workflow state, command handlers, presenters, calculations, summaries, exports, and app-local helpers. `labkit.app` App definition and launch, semantic layout and view snapshots, typed events, callback capabilities, projects, diagnostics, results, interactions, and private native runtime lifecycle. `labkit.image` GUI-free image file IO, display normalization, resizing, mean filtering, and basic enhancement primitives. `labkit.thermal` GUI-free thermal source-file parsing, raw thermal matrices, embedded calibration metadata, raw-to-temperature conversion, and thermal colormap rendering. `labkit.dta` GUI-free Gamry DTA discovery, loading, parsed curves, and pulse helpers. `labkit.biosignal` GUI-free recording import, channel extraction, filtering, events, segments, templates, and measurements. `labkit.rhs` GUI-free Intan RHS discovery, header parsing, block indexing, and lazy waveform window reads. Apps own experiment-specific vocabulary, thresholds, protocol roles, plots, result schemas, export formats, alerts, and log wording. Reusable facades own domain-neutral mechanics that multiple apps can share. App Package Shape The target app shape is workflow-first. Each app keeps one MATLAB package under its app folder: apps///labkit__app.m apps///+/definition.m apps///+/+workbench/buildLayout.m Those three files are a complete static App. Add only the capabilities the product needs: projectSpec.m durable create/validate/migrate contract createSession.m transient App-specific reconstruction +workbench/present.m dynamic snapshot assembly +/layoutSection.m feature-owned controls +/present.m feature-owned snapshot fragment +/draw.m feature-owned plot renderer `definition.m` is the single product contract. It owns identity, display metadata, App version, facade requirements, layout, and references to any optional capabilities. `projectSpec.m` is the one durable-schema entry and keeps its create, validate, and version-aware migrate functions local. Runtime owns the migration loop. Do not add separate `requirements.m`, `version.m`, generic `+appLifecycle` or `+appState` packages, or per-version migration files. Add workflow packages only when the App has that user-facing capability: +sourceFiles/ choosing, reading, validating, and previewing source data +analysisRun/ collecting options, computing results, and showing results +resultFiles/ choosing output folders, writing files, and summarizing exports +cropGeometry/ app-owned crop geometry operations +thermalFrames/ app-owned thermal frame queues and display choices +syntheticInputs/ app-owned clean-room synthetic input generation Create only the packages the app needs. Names should describe a workflow or domain capability that changes together, not a broad technical phase. Avoid generic `+actions`, `+renderers`, `+ops`, `+io`, `+ui`, `+userInterface`, `+view`, `+export`, `+helpers`, and `+utils` packages for new app code. Avoid fixed `+app` namespaces, family-level `private/` helpers, `*Workflow.m` string dispatchers, and `+core/dispatch.m` routers. `+state`, `+actions`, `+ui`, `+view`, `+ops`, `+io`, and `+export` packages were retired with the workflow-first migration. Current app work should follow the workflow-first shape. App SDK Boundary App GUIs use the explicit `labkit.app` SDK: Layer App-facing API Definition `labkit.app.Definition` owns identity, requirements, optional project/session boundaries, workbench, presentation, and launch. Layout `labkit.app.layout.*` owns semantic controls, containers, workspace pages, direct callbacks, bindings, and plot renderers. View `labkit.app.view.Snapshot` owns complete derived visible state and prepared renderer models. Callback boundary Typed `labkit.app.event.*` values and sealed, specifically named `labkit.app.CallbackContext` operations. Optional contracts `labkit.app.project.*`, `result.*`, and `dialog.*`. Reusable facades publish MATLAB-native contract versions through their `version()` APIs. Apps declare required facade ranges in the `Requirements` field of `definition.m`, and `labkit.contract` checks those ranges in tests and at launch. This is a same-repo maintenance guardrail; routine users still update LabKit as one repository. Apps publish `AppVersion` and `Updated` metadata from the same definition for the launcher and window title. App versions are not dependency constraints and do not belong in `labkit.contract`. Project guardrails check `X.Y.Z` format and require versioned code changes to increase the corresponding definition, launcher, or facade version. The [release guide](../maintain-and-release/release.md) explains how to select and publish the next version. Image workflows may use `labkit.image` for generic image file filters, source image reads, display-name normalization, RGB double conversion, preview-size fitting, mean filtering, basic enhancement primitives, and image writes. Apps still own processing step semantics, ROI/background policy, matching formulas, crop geometry, focus-stack algorithms, DIC behavior, export schemas, and user workflow text. Thermal workflows may use `labkit.thermal` for radiometric source reads, embedded calibration metadata, raw thermal matrices, Celsius conversion, and linear thermal palette rendering. Apps still own file queues, display-range defaults, log/gamma display-mapping controls, export manifests, colorbar placement, overlay-removal workflow wording, measurements, and user-facing decisions. Generic image IO and filters stay in `labkit.image`; thermal file parsing and raw-to-temperature mechanics stay in `labkit.thermal`. `definition.m` returns the app runtime contract. Layout nodes own concrete callbacks and renderers, so Apps maintain no parallel registries. The framework compiles the static graph, builds the shell, owns the transactional event queue, persistence, resources, diagnostics, and private native adapter. `+workbench/buildLayout.m` should read as the product's user workflow. Capability packages own their controls, state transitions, prepared view models, rendering, alerts, and wording. `+workbench/present.m` extracts exact state inputs and composes those feature fragments; it is not a second monolithic implementation of the App. The state funnel The runtime commits one canonical value containing `project` and `session`, but that value is an SDK transaction envelope, not the App's domain model. Keep it at the edge: layout signal -> capability action(applicationState, event, callbackContext) -> calculation(exact scientific inputs) -> writer(exact result and destination inputs) runtime presentation -> workbench.present(applicationState) -> capability.present(exact visible inputs) -> renderer(axes, prepared model) Only `createSession`, `workbench.present`, `OnStart`, and a function bound directly to a layout signal accept the complete envelope. Those functions name it `applicationState`, expose the typed event and `callbackContext` in their signatures, and unpack it immediately. Feature presenters, renderers, calculations, writers, and local helpers receive named narrow inputs. Do not introduce a second App object, service bag, callback registry, or feature-wide context type to hide these inputs. If a calculation needs five scientific values, list those five values. If several always travel together and form a stable app-owned concept, give that concept a semantic struct and validate it at its owner. Reusable Extraction Rule A helper may move into `+labkit` only when all of these are true: It can be named without experiment-specific vocabulary. It does not encode app-specific units, thresholds, plots, result columns, or export schemas. It does not read or mutate app state. It is independently testable. At least two real apps use it, or one facade clearly needs it. It makes the app-facing API clearer rather than broader and vaguer. If those conditions are not met, keep the helper app-local. Extraction Quality Line budgets are maintainability backstops, not architecture goals. A smaller file is only better when the responsibilities are clearer. Extracted helpers should own a coherent behavior contract: a stable data shape, an explicit side effect, a GUI-free calculation, an export boundary, a display model, or a reusable app-facing framework mechanism. Trivial label formatters, single boolean checks, constant lists, and one-call facade wrappers can remain local, nested, or inline when that makes the workflow easier to follow. There is no minimum useful helper length. Small public facades, factories, filters, defaults, and test-facing helpers are valid when the name protects a real contract. Conversely, a long extracted helper is not reusable merely because it moved out of `run.m`; it must improve ownership, testability, or the app-facing API. When reviewing helper organization, prefer evidence from the helper's boundary role, call sites, tests, side effects, and ownership over raw line count. Repository line budgets count effective MATLAB code lines: blank lines and comment-only documentation do not consume the budget. Physical line counts remain useful diagnostic context, but they must not discourage complete function help. Validation Boundary The default automated validation boundary is: buildtool headless This covers project contracts, reusable facade behavior, and non-GUI app helper behavior. GUI checks cover launch, layout, callback wiring, trace plumbing, reusable tool lifecycle, and hidden synthetic app workflows. Manual MATLAB review is still required for full interactive workflow feel. labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot."},{"title":"Build A Complete App","url":"development/build-apps/complete-app.html","kind":"guide","section":"development","keywords":"development build apps complete app Build A Complete App","text":"Build A Complete App This guide builds a small trace viewer with the production `labkit.app` contract. The example keeps durable settings in a project, rebuilds transient file data in a session, draws a plot, and exports a result. Each file has one visible responsibility. File Shape apps/examples/trace_viewer/ |-- labkit_TraceViewer_app.m `-- +trace_viewer/ |-- definition.m |-- projectSpec.m |-- createSession.m |-- +workbench/ | |-- buildLayout.m | `-- present.m |-- +sourceTrace/ | |-- readTrace.m | |-- layoutSection.m | |-- workspacePlot.m | |-- present.m | `-- draw.m `-- +resultFiles/ |-- layoutSection.m `-- exportTrace.m There is no handler table, renderer registry, or `+userInterface` bucket. `+workbench` is the product assembly boundary. Capability packages own their part of the user workflow. 1. Thin Entrypoint function varargout = labkit_TraceViewer_app(varargin) [varargout{1:nargout}] = trace_viewer.definition().launch(varargin{:}); end The entrypoint does not build state, controls, services, or figures. 2. One App Definition function app = definition() app = labkit.app.Definition( ... Entrypoint=\"labkit_TraceViewer_app\", ... AppId=\"examples.trace-viewer\", ... Title=\"Trace Viewer\", ... Family=\"Examples\", ... AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... ProjectSchema=trace_viewer.projectSpec(), ... CreateSession=@trace_viewer.createSession, ... Workbench=trace_viewer.workbench.buildLayout(), ... PresentWorkbench=@trace_viewer.workbench.present); end Definition is a readable inventory, not an execution script. It validates the static layout, direct callback signatures, plot renderers, target IDs, project schema, and presentation contract before native UI mutation. 3. Durable Project function schema = projectSpec() schema = labkit.app.project.Schema( ... Version=1, ... Create=@createProject, ... Validate=@validateProject); end function project = createProject() project = struct( ... \"inputs\", struct(\"sources\", struct([])), ... \"parameters\", struct(\"gain\", 1), ... \"results\", struct(\"lastExport\", [])); end function accepted = validateProject(project) accepted = isstruct(project) && isscalar(project) && ... isfield(project, \"inputs\") && isfield(project.inputs, \"sources\") && ... isfield(project, \"parameters\") && ... isnumeric(project.parameters.gain) && ... isscalar(project.parameters.gain) && ... isfinite(project.parameters.gain); end The project contains only durable App meaning. External files are portable source records owned by the runtime, not raw paths. 4. Transient Session function session = createSession(project, callbackContext) paths = callbackContext.resolveSourcePaths(project.inputs.sources); trace = struct(\"x\", [], \"y\", []); if ~isempty(paths) trace = trace_viewer.sourceTrace.readTrace(paths(1)); end session = struct(\"trace\", trace); end `createSession(project,callbackContext)` is the one reconstruction boundary. The runtime calls it after project restore and file-list source changes. Session data is reconstructible and is not written into the project envelope. 5. Product Layout function layout = buildLayout() controls = { ... trace_viewer.sourceTrace.layoutSection(), ... trace_viewer.resultFiles.layoutSection()}; workspace = labkit.app.layout.workspace( ... trace_viewer.sourceTrace.workspacePlot()); layout = labkit.app.layout.workbench( ... controls, Workspace=workspace); end The assembly reads in user order. A complex App can use tabs and workspace pages here without flattening every feature into one file. The source capability owns its controls: function section = layoutSection() section = labkit.app.layout.section(\"sourceTrace\", \"Trace\", { ... labkit.app.layout.fileList(\"traceFiles\", ... Label=\"Trace file\", ... SelectionMode=\"single\", ... Bind=\"project.inputs.sources\", ... SourceRole=\"trace\", ... SourceIdPrefix=\"trace\"), ... labkit.app.layout.slider(\"gain\", ... Label=\"Gain\", Limits=[0.1 10], ... Bind=\"project.parameters.gain\")}); end Standard fields and file lists need no App callback. Their bindings update canonical state transactionally. The result capability binds a real business action directly: function section = layoutSection() section = labkit.app.layout.section(\"resultFiles\", \"Results\", { ... labkit.app.layout.button(\"exportTrace\", ... \"Export trace\", @trace_viewer.resultFiles.exportTrace, ... Tooltip=\"Export the calibrated trace and its sampling metadata.\")}); end 6. Complete View Snapshot function view = present(applicationState) view = labkit.app.view.Snapshot(); view = view.include(trace_viewer.sourceTrace.present( ... applicationState.session.trace, ... applicationState.project.parameters.gain)); view = view.enabled(\"exportTrace\", ... ~isempty(applicationState.session.trace.x)); end `+workbench/present.m` is a short assembly boundary. It extracts exact inputs and composes feature fragments with `Snapshot.include`; it does not perform IO or calculation. function view = present(trace, gain) model = struct(\"x\", trace.x, \"y\", trace.y .* gain); view = labkit.app.view.Snapshot().renderPlot(\"tracePlot\", model); end 7. Feature-Owned Renderer function node = workspacePlot() node = labkit.app.layout.plotArea( ... \"tracePlot\", @trace_viewer.sourceTrace.draw); end function draw(axesById, model) ax = axesById.main; cla(ax); plot(ax, model.x, model.y); xlabel(ax, \"Time (s)\"); ylabel(ax, \"Signal\"); grid(ax, \"on\"); end The plot area references the concrete renderer. The model carries prepared App meaning; drawing does not read project state or dispatch workflow actions. 8. Direct Business Callback function applicationState = exportTrace( ... applicationState, callbackContext) trace = applicationState.session.trace; gain = applicationState.project.parameters.gain; choice = callbackContext.chooseOutputFile( ... {\"*.csv\", \"CSV files\"}, \"\"); if choice.Cancelled return; end tableValue = table(trace.x(:), trace.y(:) .* gain, ... VariableNames=[\"Time\", \"Signal\"]); writetable(tableValue, choice.Value); applicationState.project.results.lastExport = string(choice.Value); end The callback signature exposes its runtime boundary. For larger exports, delegate table construction and result packaging to functions that accept only the trace, gain, and destination they require. Validation Test readers, calculations, project validation, snapshot fragments, renderers, and exports directly with synthetic values. Construct `definition()` in a headless test to validate the whole static contract. Run the App's bounded hidden-GUI workflow after smaller tests are stable; native dialogs and visual quality still require developer-led interactive validation. Before merge, update the App version, owning manual, structured component history, generated documentation site, and the final branch validation gates. Related Documentation [App Development](app-development.md) [Architecture](architecture.md) [LabKit App SDK](../../framework/README.md) [Testing](../maintain-and-release/testing.md) labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.slider Add a bounded numeric slider. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.project.Schema Declare one durable App project contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"T-Test Wizard Design","url":"development/data-and-designs/group-comparison-app-design.html","kind":"guide","section":"development","keywords":"development data and designs group comparison app design T-Test Wizard Design","text":"T-Test Wizard Design [Development index](../README.md) | [App manual](../../apps/statistics/ttest-wizard/README.md) | [App development](../build-apps/app-development.md) | [Simple scientific CSV exchange](scientific-csv-interchange.md) Product Boundary T-Test Wizard answers: Given two or more ordered numeric groups, what are the configured t-test results for every later group versus the first, and how should that completed family be visualized? The App owns visible group selection, order, test settings, result rows, plot meaning, export schemas, and failure wording. It does not choose the scientifically correct test, infer independence or pairing, apply automatic multiple-comparison correction, or replace ANOVA and repeated-measures models. Field Value Display name T-Test Wizard Command `labkit_TTestWizard_app` App ID `ttest_wizard` Family Statistics Version 1.0.0 No new `+labkit` facade is involved. The multi-group policy stays App-local. Workflow Select or enter groups -> order groups with reference first -> choose one test specification -> run group 2..N versus group 1 -> review result family -> draw bar or box comparison plot -> export group and result CSVs The left controls are Data, Test & Plot, Export, and Log. The right workspace has native Data and Plot pages. Data stacks the opened source table and editable analysis table at equal height; Plot gives the figure the full workspace. A compact freshness field stays beside the plot controls. Result rows stay beside the test controls, removing a result-only navigation step. Data Boundary The source grid accepts readable CSV, TSV, XLSX, and XLS tables. Selection is schema-free: finite numeric cells and numeric text are copied in visible spreadsheet order; blanks and labels are skipped with counts; nonfinite numeric cells block the selected range. Each capture appends one durable group: group.label group.values group.sourceDisplayName group.sheet group.cellAddresses Only `label` and `values` participate in calculation identity. Source metadata supports traceability and remains App-owned. A source can be replaced without discarding captured groups. The ordered group editor has `Group` and `Value` columns with one observation per row. Repeated labels form one group, and first label appearance defines group order. The first group is the reference. Blank rows support manual entry. The capture dropdown contains `(new group)` plus current labels, so a source selection can create a group or append to an existing one without an unexplained count column. For new groups, the App scans upward from the selected numeric cells and combines at most two nonnumeric header levels. Looking left on the same header row supports merged-style spreadsheet headings. Dates, numeric summaries, and row numbers are excluded. The result is only an editable suggestion, not a calculation identity inferred from the source. The editor supports batch group changes and deletion of selected observation rows. Deleting the final observation removes the empty group; clearing all data remains a distinct action. Table selection is delivered after the completed selection changes so pointer dragging does not trigger repeated presentation commits. Statistical Contract `runTTest` remains the two-vector primitive. `runGroupTTests` calls it once for each later group: for k = 2:numel(groups) results(k - 1) = runTTest( ... groups(1).values, groups(k).values, optionsForTheseLabels); end Common rules: difference direction is reference minus comparison; result order matches group order after the reference; the same method, alternative, and alpha apply to the family; Welch uses Welch-Satterthwaite degrees of freedom; pooled testing uses the selected equal-variance model; paired testing analyzes displayed reference(k)-comparison(k) differences; paired length is checked independently for each later group; Student-t probabilities and confidence intervals use the Base MATLAB implementation already owned by the App; each comparison preserves stable failure status instead of fabricating a number; no p-value correction is applied. The completed result family stores copies of the reference vector and each comparison vector. Plot callbacks consume those copies and cannot recalculate the tests. Result And Plot Model Each result row includes the method, alternative, alpha, labels, sample counts, means, SDs, reference-minus-comparison estimate and interval, standard error, t statistic, degrees of freedom, exact p-value, status, and message. Significance shorthand is presentation metadata: p < 0.0001 -> **** p < 0.001 -> *** p < 0.01 -> ** p < alpha -> * otherwise -> NS The plot contract follows the supplied laboratory reference figure: white axes and canvas; black box and ticks, no grid; explicit numeric y ticks and ordinary horizontal x-axis tick labels; modest default typography intended for later Figure Studio adjustment; either one pastel mean bar with sample-SD error bar or one colored box per group; black edge, cap, and annotation strokes; stacked brackets from x=1 to x=2..N; shorthand label centered above each bracket; optional deterministic raw-value overlay. The first four colors are: #9DD39C #F5C38A #A9D8E8 #FFF89A They cycle deterministically for additional groups. Plot limits reserve space for every successful bracket. Failed comparisons remain visible as group bars but do not receive a misleading significance annotation. Export Contract The data CSV has `Row` followed by one uniquely named column per group. Shorter groups are padded with blank cells. The result CSV has one row per later group and columns for: method, alternative, alpha, reference label, comparison label, sample counts, means and SDs, difference and interval, SE, t, df, p, shorthand, status, message CSV remains the portable boundary. The project MAT file is for recovery, not the only recoverable copy. Persistence And Migration Project schema version 2 replaces: inputs.vectorA inputs.vectorB with: inputs.groups Migration from version 1 preserves vector A as group 1 and vector B as group 2, including labels, values, and source metadata. A not-run scalar result becomes an empty result family; an existing completed A/B result remains the one-row family for those migrated groups. Validation Unit evidence covers: table-cell extraction and source preservation; Welch, pooled, paired, and directional reference values; ordered first-versus-each result generation; result-family identity after group mutation; multi-column group CSV and multi-row result CSV; stable individual failure statuses. Hidden GUI evidence covers: capture of three selections; ordered group state; two comparisons against the first group; equal-height source/editor tables and native Data/Plot workspace pages; stale-plot messaging after a manual data edit; boxed, grid-free rendered axes; group and result exports. Manual validation remains required for: native dialogs; large and irregular workbooks; group-table editing feel; long label wrapping; visual bracket spacing with many groups; publication suitability and scientific interpretation. Private laboratory files may be used only as local evidence. No real paths, filenames, identifiers, timestamps, or recognizable values enter tracked fixtures or documentation."},{"title":"Simple Scientific CSV Exchange","url":"development/data-and-designs/scientific-csv-interchange.html","kind":"guide","section":"development","keywords":"development data and designs scientific csv interchange Simple Scientific CSV Exchange","text":"Simple Scientific CSV Exchange [Development index](../README.md) | [T-Test Wizard](../../apps/statistics/ttest-wizard/README.md) | [T-Test Wizard design](group-comparison-app-design.md) | [App development](../build-apps/app-development.md) | [Architecture](../build-apps/architecture.md) Status And Purpose This page defines the minimum CSV exchange contract for LabKit Apps. It guides new exports and gradual improvement of existing exports; adopting it does not change another released App contract by itself. The contract is intentionally a table format, not a universal scientific data model. It provides a dependable lowest layer when two Apps do not share a richer schema: a person can type or repair it in Excel or a text editor; a script can read it without LabKit; an App can display it without first understanding its scientific meaning; different experiments may use different column names and row meanings; consumers can fall back to explicit column or cell-range selection; an App may add useful columns without asking the common format for permission. There are no required `Schema`, `Dataset`, `SampleID`, `Group`, `PairID`, `Include`, or `Note` columns. There is no required filename pattern or JSON sidecar. Repeating such fields in every row would make the lowest layer harder to write without making an unfamiliar experiment unambiguous. The Boundary Between Source And Exchange Data A laboratory source does not need to comply with this contract before LabKit can open it. Source workbooks may contain repeated blocks, dates between sections, formulas, summary rows, blank separators, inconsistent filenames, and several tables on one sheet. Existing App CSVs may mix text identifiers with numeric result columns. A compatible reader first opens these files as cell grids. It then lets the user select the cells or columns that matter. Exchange data is the simple rectangular table saved after that selection or after an App has produced a well-defined table. This distinction allows a fast, human-friendly experimental worksheet to remain useful without forcing every downstream App to reverse-engineer its layout. Existing file -> open cell grid -> select data -> copy values -> simple CSV Minimum File Contract A conforming exchange CSV has: UTF-8 text; comma delimiters; exactly one nonempty header row; a unique, nonempty name for every column; one rectangular table and no second header or table below it; ordinary CSV quoting for text containing commas, quotes, or line breaks; `.` as the decimal separator and no thousands separators in numeric cells; empty cells for missing values; evaluated values rather than spreadsheet formulas; no merged cells, color-dependent meaning, preamble, or comment lines. Text and numeric columns may coexist. A file is not invalid merely because it contains text, blanks, status messages, or columns that a particular analysis does not use. Headers are user-facing labels. Writers should keep them short and stable. When a unit is useful, the recommended spelling is: Charge [mC] Time [s] Resistance [ohm] The bracketed unit is a readable convention, not a prerequisite for opening the file. A generic reader preserves headers exactly and does not silently infer scientific roles from them. Three Useful Table Shapes The following shapes are examples of the same contract, not separate schemas. Apps may use other rectangular tables when their row meaning is documented. Vector Table A vector table places one numeric vector in each column. It is the preferred exchange shape for t-tests and other small comparisons: Condition A,Condition B 1.2,1.7 1.4,1.8 1.3,2.0 Independent vectors may have different lengths. Shorter columns end with blank cells: Condition A,Condition B 1.2,1.7 1.4,1.8 ,2.0 For a paired analysis, values on the same visible row form a candidate pair only after the user selects a paired test. The CSV does not force one statistical interpretation. An optional first column may give readable row labels: Row,Before,After 1,2.1,2.5 2,2.0,2.4 3,2.2,2.6 The same file can still be analyzed as two independent vectors. `Row` is an ordinary column, not a reserved identity system. Record Table A record table places one result, sample, cycle, image, segment, or other producer-defined record on each row. Text columns describe the row and numeric columns contain measurements: Source,Cycle,Charge [mC],Duration [s],Status sample_a,1,2.4,10,ok sample_a,2,2.5,10,ok sample_b,1,2.9,10,ok This is already a useful cross-App table. A statistics App can display it and the user can select cells from `Charge [mC]`. It does not need a conversion to a repeated `Metric,Value` representation first. Series Table A time series or curve is also a normal table: Time [s],Signal A [mV],Signal B [mV] 0.00,0.12,0.09 0.01,0.18,0.13 0.02,0.14,0.11 The exchange layer preserves it. Whether curve points are valid observations for a t-test is a scientific decision made by the user or the consuming App, not by the CSV reader. Reading Unknown Files A LabKit table reader follows a compatibility ladder: Read the file without requiring known headers. Display the table with spreadsheet row and column coordinates. If an obvious numeric column or vector exists, offer it as a convenience. Always allow the user to select or revise the exact cells. Copy the confirmed values into the App's own state. Offer export as a simple vector or record table. Automatic recognition is optional assistance. It must never be the only route through the App. The reader must not show a file-level error such as “file contains nonnumeric text” merely because some cells are labels. When a selected range contains mixed cells, report the actual selection in plain language, for example: 12 cells selected: 10 numbers will be used; 1 blank and 1 label will be skipped. The numeric preview is authoritative. The user can change the selection before continuing. Cell Selection Rules For a numeric vector: a single row or single column is the clearest selection; disjoint cells may be accepted when their order is shown in the preview; finite numeric cells are copied in visible row-major order; blanks and text are counted and shown, not silently mistaken for zero; `NaN`, positive infinity, and negative infinity are rejected as observations; the source is never overwritten during extraction. A paired test displays the copied A and B vectors side by side and pairs them by their displayed order. Unequal lengths block the paired test but do not block an independent test. Standard Export From A Two-Vector Tool A two-vector App writes the data actually used in a compact table: Row,Condition A,Condition B 1,1.2,1.7 2,1.4,1.8 3,1.3,2.0 It may omit `Row` when no row labels are useful. It pads unequal independent vectors with blanks. It writes labels chosen by the user as headers and units in those headers when known. Statistical results belong in a separate CSV because a completed test and its input observations have different row meanings: Test,Alternative,N A,N B,Mean A,Mean B,Difference A-B,T,DF,P Welch independent,Two-sided,3,3,1.3,1.833333,-0.533333,-5.237,3.812,0.0077 The values above are synthetic. Repository examples and fixtures must not contain real laboratory filenames, paths, identifiers, timestamps, or recognizable measurements. Result writers may add effect sizes, confidence intervals, warnings, or App-specific fields. They document those columns as that App's export contract; the common CSV layer does not standardize every statistical result. Manual Experimental Recording When a simple table fits the experiment, use it directly: put one stable header in each column; put raw numeric observations in ordinary cells; use one vector per column or one record per row; use blank cells for values not recorded; keep mean, SD, SEM, p-values, and notes outside the raw numeric range or in a separate table; start a new file when that is easier than making one very wide table. When repeated visual blocks are faster during acquisition, keep the block layout. The user can later select the relevant raw cells and export a simple table. The exchange contract must not make experimental entry slower merely to simplify one future importer. Several same-shape CSV files are also valid. A consuming App may open them one at a time, combine them, or let the user select one vector from each. Meaning encoded in filenames can be offered as editable text, but a filename pattern is never required and never silently becomes a group or pair assignment. Existing App Exports An existing CSV is already compatible with the lowest layer when it has one header row and one rectangular table. Migration does not require renaming all columns or expanding each numeric cell into a long observation row. Use these migration rules: Document what one row represents. Keep released column names stable unless a normal compatibility change is justified. Split genuinely different tables—raw data, summaries, test results, and logs—into separate CSVs when practical. Use readable headers and include units where they are known. Avoid absolute local paths in portable output. A basename or user label is usually enough. Preserve useful text and status columns; do not delete them merely to make a file “numeric.” Add an optional companion vector table only when it materially simplifies a common downstream workflow. A generic consumer reads the table and exposes manual selection. A producer-specific adapter is justified only when it removes repeated work and its scientific meaning is stable. The adapter still shows its proposed selection before use. No public `+labkit` facade is justified by this design alone. Reading, selection, and writing remain App-owned until several Apps demonstrate a stable, domain-neutral implementation. What This Contract Deliberately Does Not Encode The lowest layer does not claim to determine: which rows are statistically independent; whether equal-length vectors are paired; whether cycles or segments are samples or repeated measurements; whether a numeric column is raw data, a feature, or a summary; which group is control; which hypothesis test is appropriate; whether a filename token is a subject, condition, channel, or date. Those decisions require experimental context. A richer App export may record them, but an unknown CSV remains usable because the fallback is visible cell selection rather than rejection. Validation Expectations Synthetic tests for a compatible reader and writer cover: vector tables with equal and unequal column lengths; record tables containing both text and numeric columns; series tables; quoted headers and text; blank cells and fully blank trailing rows; duplicate or empty headers; mixed selections with a visible accepted/skipped count; nonfinite numeric values; CSV write/read round trips preserving column order and displayed values; extraction from two different source files; paired preview order and unequal-length blocking; source files that do not match any known App profile. Manual checks use representative private laboratory files without copying their names, paths, identifiers, or values into the repository. Related Guidance [T-Test Wizard Design](group-comparison-app-design.md) [T-Test Wizard](../../apps/statistics/ttest-wizard/README.md) [App Development](../build-apps/app-development.md) [Architecture](../build-apps/architecture.md) [Testing](../maintain-and-release/testing.md) [Documentation System](../maintain-and-release/documentation.md)"},{"title":"Documentation Architecture","url":"development/maintain-and-release/documentation.html","kind":"guide","section":"development","keywords":"development maintain and release documentation Documentation Architecture","text":"Documentation Architecture This page defines how LabKit documentation is authored, organized, rendered, and validated. It is a maintainer reference; readers looking for an app or a MATLAB function should start at the [documentation home](../../README.md). Design Goals LabKit documentation must support four distinct reading tasks: learn a workflow from a complete example; complete a specific task without reading architecture material; understand why a framework or scientific operation behaves as it does; look up exact MATLAB syntax, argument rules, fields, units, and errors. The organization follows the successful division visible in MATLAB and Qt documentation: product and module landing pages orient the reader, task pages teach workflows, concept pages explain behavior, and one page per callable entity provides exact reference. A single long narrative must not try to serve all four purposes. Source Tree `docs/` contains only author-maintained, human-readable documentation data: docs/ README.md getting-started/ apps/ /README.md //README.md //.md /history/ framework/ README.md .md history/ libraries/ /README.md /.md /history/ reference/README.md development/ build-apps/ data-and-designs/ maintain-and-release/ tools/ README.md .md history/README.md Executable renderer code and synthetic-asset generators live under `tools/docs/`. Generated HTML, CSS, JavaScript, and search data live under `site/`. No executable `.m` file belongs under `docs/`, and no generated file under `site/` is edited by hand. Launcher documentation follows the user-facing app map under `docs/apps/labkit-core/launcher/`. Direct-call references for maintainer tools follow their development ownership under `docs/development/tools/`; executable implementations remain under the repository `tools/` tree. Page Types Product landing page The documentation home answers what LabKit is, who each major section is for, and where a reader should begin. It is a routing page, not a project history, launcher manual, or API dump. Tutorial A tutorial leads a new reader through one complete, successful result. It states prerequisites, sample/input expectations, ordered actions, the expected result, and the next relevant topic. Tutorials may teach one path and defer alternatives. How-to guide A how-to guide begins with a concrete goal and gives the shortest reliable procedure. It includes prerequisites, commands or actions, verification, and failure recovery. Background explanation is linked, not repeated. App family page A family page explains the shared experimental domain, compares sibling apps, shows how their files or outputs connect, and links the relevant reusable libraries. It must not contain detailed instructions for one app. App manual Every public app owns one manual at `docs/apps///README.md`. Its normal section order is: purpose and applicability; requirements and launch; supported inputs and file interpretation; a shortest successful workflow; controls and interaction modes; parameters, defaults, legal values, and units; calculations, algorithms, and scientific assumptions; outputs and result schemas; project state, autosave, recovery, and overwrite behavior; use without the GUI; errors, limitations, troubleshooting, related topics, and history. Sections that genuinely do not apply may be omitted. Applicable information must not be hidden in an app catalog, family overview, changelog, or source comment. Complex apps add focused topic pages beside the manual rather than turning the manual into an unstructured notebook. Framework module page The framework landing page presents ownership and module relationships. Topic pages then document lifecycle, definition, callbacks, state transactions, busy behavior, file selection, persistence, layout, plotting, interactions, debugging, and extension rules. Each topic identifies: the problem and owning package; the observable behavior guaranteed to apps; the normal call sequence; public types or functions grouped by role; examples, failure behavior, and related topics. Private runtime mechanics are explained only when necessary to understand a public behavior; private functions do not receive reference pages. Library module page Each `labkit.*` facade has a module landing page comparable to a MATLAB toolbox or Qt module page. It states scope, dependencies, data model, grouped public functions, common call sequences, numerical/scientific semantics, compatibility, errors, examples, and related apps. Exact per-function syntax is linked to generated reference pages. Function reference page Every public function has one generated page. The MATLAB help block immediately after its declaration is the only authoring source and documents, as applicable: all supported syntax forms; concise description and observable behavior; each input, name-value option, and struct field; defaults, legal values, units, shape, type, and empty-value behavior; each output and its fields; algorithms, scientific definitions, and numerical assumptions; thrown errors or status-return failure behavior; a copyable example; related functions and the owning app or module. Function reference must not promise an overload, field, or default that the current implementation and tests do not support. History record History is component-owned. A record explains what changed, why it matters, compatibility, affected components, and validation evidence. It is not an instruction manual and must link to the current page that describes the final behavior. The global history page is only an index. The [history record format](../../history/record-format.md) requires a global positive-integer `sequence`; values are unique and contiguous, and a new record normally takes the previous maximum plus one. The renderer orders the timeline by descending sequence rather than inferring same-day order from a title, filename, filesystem timestamp, or Git checkout. Each rendered record ends with adjacent-record navigation: **Previous change** uses `sequence - 1` and **Next change** uses `sequence + 1`. The first and last records show only the direction that exists. Discovered Sources The documentation model is assembled without a page registry or catalog JSON: Source Authority Markdown path and first heading Page identity, output route, title, navigation area, and reader-task branch. `labkit_launcher(\"list\")` plus `docs/apps///README.md` Public Apps, commands, owning folders, and App manuals. Complete public MATLAB help Library APIs and App-owned non-GUI APIs. History record metadata Change identity, order, components, compatibility, and history associations. Every non-history Markdown page under `docs/` is published. `README.md` maps to its directory index; App manuals retain their established sibling HTML route. Missing titles, duplicate ids, duplicate outputs, missing App manuals, or ambiguous App ownership stop the build. Navigation hierarchy The top navigation selects a documentation area. The left sidebar then shows the current area's own hierarchy instead of repeating one flat site-wide list. Where a page has descendants, the sidebar uses labeled branches: app pages are grouped by app family, with the family manual above its apps; function pages are grouped by library, and API pages list sibling functions under their MATLAB package; framework and development guides are grouped by their reader task; history uses its generated timeline and adjacent-record links rather than placing every historical record in the sidebar. The first path segment selects the documentation area. Under `framework/` and `development/`, the next directory names the sidebar branch, so moving a page also changes its ownership and navigation. App branches come from their family manual path, library branches come from facade directories, and history uses record sequence. A documentation area with no descendants remains a single page and does not render an empty artificial branch. Markdown keeps standard relative links so source browsing works on GitHub. After moving pages, run: maintainLabKitDocLinks(repoRoot, \"Update\", true) The maintainer rewrites only uniquely identifiable moved targets. Its default read-only mode fails on unresolved or ambiguous links. Authoring From Current Code Documentation changes begin from the current implementation, not from old prose. For an app manual, inspect at least its entrypoint, runtime definition, workbench layout, state/options owner, input loader, output writer, public app-owned APIs, and focused tests. For a library, inspect its complete public surface and tests. Existing documentation and history may identify questions to investigate, but they are not evidence that a behavior still exists. User-visible labels, defaults, supported file types, state fields, output columns, formulas, units, and error behavior must match their current code owners. If code and documentation disagree, resolve the product contract rather than documenting both as alternatives. App manuals are reader-oriented product pages. Keep App-specific requirements, the shortest successful workflow, scientific semantics, outputs, recovery, limitations, and supported GUI-free APIs. Do not append implementation inventories or repeat framework contracts such as callback ownership, runtime lifecycle, hover-help coverage, source serialization, or layout binding. Document those contracts once under `docs/framework/`; link there only when a reader needs the shared behavior to use the App. Public Function Help A public function help block is part of the tested API. It states public call syntax, behavior, inputs, outputs, option fields, defaults, legal values, and errors or side effects where they matter. The documentation guardrail compares the names in the function signature with the `Inputs` and `Outputs` sections and requires option-bearing APIs to describe named fields. Generated API pages use this same help block, so the command-line `help` view and HTML reference do not have separate prose owners. Code under an `Example:` heading is an executable contract. The test runner extracts that code from the function header and executes it in MATLAB. Use `Typical Call:` when a useful sketch depends on a user's data file, an existing app/session variable, or an interactive dialog. Such a sketch becomes an `Example:` only after it has self-contained synthetic setup and passes the example runner. A self-contained MATLAB block in an app manual may be preceded by ``. The documentation contract extracts and executes every marked block in a clean test setup. Mark only examples that use synthetic data, require no dialogs or user files, and avoid lasting file or UI side effects. Unmarked blocks may demonstrate a typical call with a user's own file, but every variable and placeholder must still be explained by the nearby text. Cross-References Use relative Markdown links between narrative sources. Link the first useful mention of an app, framework topic, library, or related guide. Function names use fully qualified MATLAB symbols so the renderer can link them to generated API pages. Every app manual links its family, related apps, relevant libraries, public app APIs, and component history when records exist. Every library landing links representative apps. Related links should help the reader continue a task; they are not a list of every page sharing a keyword. The renderer rejects unresolved local Markdown targets. External links remain ordinary HTTPS links and are not copied into the site. Rendering And Search `tools/docs/renderLabKitDocs.m` reads the structured model, renders all narrative and API pages, builds navigation and related links, and writes the static site to `site/`. The renderer uses MATLAB only and introduces no third-party runtime dependency. Search is a generated static index containing titles, headings, descriptions, keywords, components, MATLAB symbols, and help text. It works from GitHub Pages and when `site/index.html` is opened directly from disk; it must not depend on a local web server or a fetch request. Generated pages carry a generated-file marker. Regeneration builds a complete temporary tree, then synchronizes its files into `site/`: changed files are updated, obsolete files and empty directories are removed, and the canonical `site/` directory itself is preserved. Keeping that root stable avoids numbered conflict copies when the repository is stored in a synchronized folder. For direct MATLAB syntax, result fields, missing-output reconstruction, and consistency-check behavior, see [Documentation Build Tools](../tools/documentation.md). Build And Validate Use the documentation build and consistency tasks listed in [Testing](testing.md). The build regenerates the tracked site; the consistency check renders independently and compares that result with `site/`. Project documentation tests additionally check page/output uniqueness, source and output existence, API coverage, local links, direct-file search, generated markers, and prohibited retired paths. Before committing documentation work: review the Markdown diff for claims not backed by current code; regenerate `site/`; run the focused documentation guardrail; open representative app, module, API, history, and search pages; commit source and generated output together. Related Topics [Documentation Home](../README.md) [Architecture](../build-apps/architecture.md) [App Development](../build-apps/app-development.md) [Testing](testing.md) [Maintainer Tools](../tools/README.md) [Release Process](release.md)"},{"title":"DIC Postprocess","url":"apps/dic/dic-postprocess.html","kind":"app","section":"apps","keywords":"apps dic dic postprocess DIC Postprocess","text":"DIC Postprocess DIC Postprocess converts Ncorr strain fields into EXX and EYY overlays on an optical reference image and calculates descriptive strain statistics over a validated ROI. It is a rendering and summary tool; it does not rerun DIC. Requirements And Launch Input MAT files must contain the Ncorr result structure recognized by the app loader. labkit_DICPostprocess_app Inputs Three inputs define one analysis: an Ncorr MAT file containing readable EXX and EYY strain fields; the optical reference image used for the displayed specimen domain; a mask image whose nonzero pixels define the requested ROI. All three are required before **Generate overlays + summary** can produce a result. The loader validates the expected Ncorr structure and reports malformed or incomplete MAT files instead of guessing at unrelated arrays. Basic Workflow Choose the DIC MAT file, matching reference image, and mask image. Set the overlay range and opacity. Adjust oversampling, smoothing, and edge trimming when justified. Optionally tune optical-image brightness, contrast, gamma, saturation, and RGB gains. Generate the EXX/EYY overlays and review the summary table. Save clean overlay PNGs and export the summary CSV. Changing an option updates the reproducible project parameters. Generate again before export when inputs or processing parameters change. Overlay Parameters Parameter Default Meaning Alpha 0.60 opacity of strain color over the optical image Color min -0.15 lower strain value mapped into the color scale Color max 0.15 upper strain value mapped into the color scale Oversample 6 output-domain refinement factor used during preparation Smooth sigma 0.8 Gaussian smoothing scale applied to valid strain data Edge trim 1 boundary pixels removed from the valid strain support Color limits affect rendering, not the stored numeric strain values used for the summary. Alpha affects compositing only. Smoothing and edge trimming alter the processed field and therefore can affect the summary; record them with any reported measurements. Optical Image Parameters Brightness defaults to `0`; contrast, gamma, saturation, and red/green/blue gain default to `1`. These controls prepare the visual background. They do not modify EXX/EYY values. Extreme settings can hide features or imply contrast that is not present in the strain field, so exported figures should retain the processing manifest. Processing And Scientific Semantics The app constructs a valid-strain mask from finite Ncorr values, intersects it with the requested ROI, trims invalid edges, and maps the result to the optical image domain. Linear resizing is used for continuous image/strain data and nearest-neighbor resizing for masks. Invalid strain samples remain excluded rather than being converted to zero. Summary statistics are computed independently for EXX and EYY over the final valid ROI. The result table reports the metrics returned by `dic_postprocess.analysisRun.summarizeStrain`; open its API page for the exact table schema and non-finite-value behavior. Outputs a clean EXX overlay PNG; a clean EYY overlay PNG; a CSV table containing the ROI strain summary; a LabKit result manifest describing inputs, parameters, and output roles. The clean PNG exports contain only the composed image, not axes chrome or interactive toolbar controls. Input MAT, reference, and mask files are never modified. Use Without The GUI inputs = struct( ... \"referenceImage\", zeros(16, 16, 3), ... \"maskImage\", true(16), ... \"strain\", struct( ... \"exx\", zeros(8), ... \"eyy\", 0.01 .* ones(8), ... \"roiMask\", true(8))); parameters = struct( ... \"alpha\", 0.60, \"colorMin\", -0.15, \"colorMax\", 0.15, ... \"oversample\", 6, \"smoothSigma\", 0.8, \"edgeTrim\", 1, ... \"brightness\", 0, \"contrast\", 1, \"gamma\", 1, ... \"saturation\", 1, \"redGain\", 1, \"greenGain\", 1, \"blueGain\", 1); [summary, overlayExx, overlayEyy] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters); The source loader is app-owned; the generated API table below lists only operations intentionally supported as public GUI-free calculation APIs. Function Reference Use the generated pages for [`makeStrainOverlay`](../../../reference/api/dic_postprocess/analysisRun/makeStrainOverlay.html), [`prepareOutputs`](../../../reference/api/dic_postprocess/analysisRun/prepareOutputs.html), and [`summarizeStrain`](../../../reference/api/dic_postprocess/analysisRun/summarizeStrain.html) for exact required and optional fields, units, output shapes, empty selections, failure behavior, and related APIs. Errors And Limitations A MAT file without the recognized Ncorr strain structure is rejected. Reference image, mask, and strain arrays must describe the same specimen domain; resizing resolves array dimensions, not semantic misalignment. A mask with no valid finite strain pixels produces no meaningful summary. Smoothing can suppress local extrema. Choose it as an analysis parameter, not merely a visual preference. Related Topics [DIC Preprocess](../dic-preprocess/README.md) [DIC family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) dic_postprocess.analysisRun.makeStrainOverlay Blend a color-mapped strain field over a reference image. dic_postprocess.analysisRun.prepareOutputs Build DIC strain summaries and both display overlays. dic_postprocess.analysisRun.summarizeStrain Summarize finite EXX and EYY values inside a mask."},{"title":"Private Apps","url":"development/maintain-and-release/private-apps.html","kind":"guide","section":"development","keywords":"development maintain and release private apps Private Apps","text":"Private Apps [Development index](../README.md) | [App development](../build-apps/app-development.md) LabKit supports local private app workspaces for apps that should not be published in the public repository. This page documents only the public extension point, folder shape, and development contract. Private app names, workflow notes, data assumptions, validation details, and release notes belong in the private app repository itself. Repository And Mounting Model A private app workspace is its own Git repository. It owns its app source, tests, manuals, histories, and repository rules regardless of where it is stored. The public repository never owns or migrates those files. For convenient local development, the independent repository may be checked out at the public checkout's ignored `private_apps/` mount point: LabKit-MATLAB-Workbench/ apps/ public apps tracked by this repository +labkit/ shared LabKit facades private_apps/ independent private Git repository, ignored here README.md tests/ apps/ / / README.md HISTORY.md labkit__app.m +/ definition.m +workbench/buildLayout.m projectSpec.m optional createSession.m optional +workbench/present.m optional +sourceFiles/... as needed +analysisRun/... as needed +resultFiles/... as needed That physical nesting is only a launcher mount; it does not make the private repository a folder owned by the public repository. The private repository may instead live anywhere and be registered through `LABKIT_PRIVATE_APP_ROOTS`. Each entry points either to a private repository root containing `apps/` or directly to its `apps/` folder. Separate entries with the platform path separator. Launcher Discovery The launcher discovers: public app entry points under `apps/**/labkit_*_app.m` private app entry points under `private_apps/apps/**/labkit_*_app.m` private app entry points under `LABKIT_PRIVATE_APP_ROOTS` Private entries appear in the app catalog with `Visibility` set to `private`. They launch like ordinary LabKit apps after the launcher adds the app folder to the MATLAB path. The launcher can also package a selected private app for offline deployment as either source `.m` files or encoded `.p` files. The generated zip preserves the private app under `private_apps/apps/...` and includes the packaged launcher plus the deployment/profiling tool folders it uses. Private apps discovered through `LABKIT_PRIVATE_APP_ROOTS` are copied into that same `private_apps/apps/...` package shape so the zip does not depend on the source machine's environment variable or private workspace path. Git Ownership Create or clone the private repository independently, then choose either the ignored mount point or the environment-variable registration: cd LabKit-MATLAB-Workbench mkdir private_apps cd private_apps git init git remote add origin git@github.com:/.git The public LabKit repository ignores its `private_apps/` mount point, so a nested checkout remains a normal independent repository and is not added to public history, public release zips, or public CI. Do not use a submodule unless you intentionally want the public repository to record a private remote URL and commit pointer. Structure Rules Private apps should follow the same app-owned package shape as public apps: keep one public entrypoint named `labkit__app.m` keep app workflow code under the owning `+/` package use one `definition.m` for product identity, version, requirements, layout, and references to optional capabilities start with only the entrypoint, definition, and semantic workbench layout bind callbacks and plot renderers directly on their owning layout controls; add `+workbench/present.m` only for dynamic visible state add one `projectSpec.m` only for durable App-owned state; keep create, validate, and version-aware migrate functions local to that file add root `createSession.m` only for App-specific transient reconstruction group workflow code by concrete user capability, such as `+sourceFiles`, `+analysisRun`, `+resultFiles`, or another app-owned domain package use shared LabKit facades such as `labkit.app.*`, `labkit.image.*`, `labkit.thermal.*`, `labkit.dta.*`, `labkit.rhs.*`, and `labkit.biosignal.*` Do not add separate `requirements.m`, `version.m`, generic `+appLifecycle` or `+appState` packages, or per-version `migrateProjectVx.m` files. Supported legacy top-level MAT variables use explicit import functions declared by the project spec. See [Build a Complete App](../build-apps/complete-app.md) for a working file-by-file example and [Runtime and Lifecycle](../../framework/guides/runtime.md) for every definition, project, session, action, presenter, and renderer field. Do not put private app source under public `apps/`. Do not move private workflow formulas, private result schemas, private labels, or private data assumptions into `+labkit`. Private Documentation The public repository should contain only this generic private-app structure guide. Put private app catalogs, SOPs, validation commands, sample notes, deployment notes, and release history in the private app repository. Private app repositories own their own tests and validation runners. They do not need to add app-specific suites, fixtures, build tasks, or sample assets to the public LabKit repository. Private app internals may be looser than public apps when a local workflow needs it, but the launcher-facing surface should remain compatible with LabKit discovery and guardrails: keep launch commands, definition-backed `requirements` and `version` lightweight requests, path setup, and private sample hygiene valid when the private workspace is present next to a public checkout. The public launcher's Code Analyzer tool includes configured private app workspaces in local reports only after the private workspace opts in. Put an empty `.labkit-accept-main-guardrails` file at the private workspace root to accept the public repository's style and code-quality guardrails for that workspace. When `private_apps/apps/` exists or `LABKIT_PRIVATE_APP_ROOTS` points at an accepted private workspace, the generated `artifacts/code-check/matlab_code_issues_*.json` and `.html` files include those private app findings without adding private source to the public repo. For one-off local checks, `LABKIT_GUARD_PRIVATE_APPS=1` temporarily includes configured private roots even without the sentinel file. Because private workspaces are separate Git repositories, public changed-file tasks do not discover private diffs. For an accepted private workspace, run the private repository's own tests and the public `buildtool changedFast` guardrail before push when private source, tests, docs, component history, or version metadata changed; CI remains the complete validation gate."},{"title":"Release Policy","url":"development/maintain-and-release/release.html","kind":"guide","section":"development","keywords":"development maintain and release release Release Policy","text":"Release Policy [Development index](../README.md) | [Project history](../../history/README.md) | [Deployment tool](../tools/deployment.md) | [LabKit Launcher](../../apps/labkit-core/launcher/README.md) Use semantic versioning for public releases. Version Numbers Patch releases, such as `v2.1.1`, are for bug fixes that do not add user-visible capabilities. Minor releases, such as `v2.2.0`, are for user-visible features, workflow changes, or meaningful maintenance improvements. Major releases, such as `v3.0.0`, are for breaking changes or intentionally incompatible workflows. If a commit marked as breaking, such as `feat!`, changes user workflow or app compatibility, choose a major version. If the implementation changes a user workflow without intentionally breaking compatibility, describe it in the release notes so launcher users can choose or roll back versions deliberately. On a development branch, choose each component's final version directly from the merge base with `origin/main`, not from intermediate branch commits. A branch may edit version metadata while work is evolving, but its merge-ready state must be exactly one semantic-version step from the mainline baseline: the next patch, the next minor with patch zero, or the next major with minor and patch zero. The related component history record should describe that direct `main baseline -> branch final` transition. This prevents temporary branch versions from accumulating into artificial public version jumps. Maintain user documentation with the same squash-oriented view: it describes the final branch behavior and one net compatibility transition, not the sequence of intermediate commits used to develop it. Tags Use `vX.Y.Z` for new release tags, for example `v2.2.0`. Do not rename or delete historical published tags only to normalize naming. If an older release used a different tag style, keep it for compatibility with existing links and user checkouts. Future releases should use `vX.Y.Z`. Complete developer-led interactive App validation before starting a release. Then dispatch the `Release` workflow from `main`, provide the new version, and explicitly confirm that manual validation is complete. The exact main commit must already have a successful `Continuous Integration` push run, which proves the complete headless and hidden-GUI suites on Toolbox-free Linux, macOS, and Windows. The release workflow verifies that run instead of executing the same six jobs again, then creates the annotated tag. An invalid version, a non-`main` dispatch, a missing manual-validation confirmation, an existing tag or release, or the absence of a successful same-commit main CI run prevents tag creation. Ordinary push, pull-request, and documentation workflows never create release tags. gh workflow run release.yml --ref main \\ -f version=vX.Y.Z \\ -f manual_validation_confirmed=true gh run watch Use `buildtool coverage` locally when you need a coverage report without preparing a release. GitHub Releases Use the release title format: LabKit MATLAB Workbench vX.Y.Z Use this note structure: ## Highlights - User-facing summary bullets. ## Fixes - Bug fixes, if any. ## Upgrade Note - Compatibility or upgrade guidance, if any. Mention app entrypoint or app requirement changes, launcher or app version bumps, and any LabKit facade contract version or supported-range changes. ## Validation - Local and CI validation evidence. Omit an empty section when it does not apply. Keep validation factual: name the commands or CI workflow that passed and the commit used for the release. After it verifies the required CI run, the workflow exports `labkit_launcher.m` from the annotated tag blob, verifies its SHA-256 against that blob, pushes the tag, creates a draft GitHub Release, uploads the launcher, and verifies the remote asset. The tag points at the same `github.sha` recorded by that Base MATLAB CI run; a later advance of `main` does not move it. Automation deliberately stops at a draft. Before publishing, rewrite or complete the generated notes with the required sections above, confirm the version and tag target, inspect the launcher asset, and record the final manual and CI evidence. Publishing the draft is a developer release decision, not a side effect of ordinary CI. Attach `labkit_launcher.m` to each GitHub release. The root README download link points at the latest release asset so browsers download the launcher instead of opening the raw source text. The launcher version manager lists recent releases, tags, and main-branch commits. Keep release titles and upgrade notes clear enough for users to select an older release when the newest build is unsuitable for their workflow. Component History Release notes summarize one public release. Long-lived change explanations live with the framework, library, app, launcher, or project documentation they affect. Each record is one Markdown file under a relevant `history/` directory. The generated documentation site aggregates those files into the [Project History](../../history/README.md) page and automatically lists matching records on every related component page. When a change bumps `labkit_launcher.m`, a `+labkit/**/version.m` facade, or an App's `AppVersion` metadata in `apps/**/definition.m`: update the owning component documentation; add one history record using the authoritative [history record format](../../history/record-format.md), including the stable Change ID, next global history sequence, affected components, and direct version transition; complete every required narrative section in that format; rebuild the generated site and verify the record appears on each affected component page. Write a cross-component change once and list every affected component in its metadata. Do not copy its narrative into multiple app pages. Current versions come from launcher, facade, and app source metadata, so there is no separate lookup table to synchronize. Git branches, PRs, tags, and commits express delivery state; do not add a second pending/unreleased hierarchy. Before tagging a release that adds, renames, or removes release-blocking guardrail tests, verify that the buildfile CI tasks still discover the intended suite and tag coverage. The workflow should call those build tasks through `matlab-actions/run-build` rather than maintaining long-lived test class selectors. Launcher Asset Reproducibility Generate the launcher asset from the release tag, not from an editor buffer, copied file, or platform-dependent checkout. This keeps the release asset byte for byte identical to the tagged source file, including line endings. Use a tag-specific staging folder: mkdir -p artifacts/release/vX.Y.Z git show vX.Y.Z:labkit_launcher.m > artifacts/release/vX.Y.Z/labkit_launcher.m Before upload, compare the staged launcher against the tag blob: git show vX.Y.Z:labkit_launcher.m | shasum -a 256 shasum -a 256 artifacts/release/vX.Y.Z/labkit_launcher.m wc -c artifacts/release/vX.Y.Z/labkit_launcher.m After creating the GitHub release, verify that the uploaded asset reports the same byte count and SHA-256 digest: gh release view vX.Y.Z --json assets If the asset digest or size differs from the tag-exported file, delete only the incorrect asset and re-upload the tag-exported `labkit_launcher.m`. Do not move or recreate an already-published release tag to fix an asset upload mistake."},{"title":"Testing","url":"development/maintain-and-release/testing.html","kind":"guide","section":"development","keywords":"development maintain and release testing Testing","text":"Testing [Development index](../README.md) | [Architecture](../build-apps/architecture.md) | [Maintainer Tools](../tools/README.md) | [Documentation](documentation.md) LabKit tests specify observable contracts. They are not organized by an implementation-stage label, suite selector, tag query, or hand-maintained test catalog. Start from the production file whose behavior changed; the test catalog determines its required evidence and executes exact test identities. Everyday Workflow For a changed production source, ask the catalog where evidence belongs: addpath(\"tests\") labkittest.explain(\"apps/electrochem/cic/+cic/+analysisRun/computeCIC.m\") `explain` prints the exact specification folders, contracts, environments, selected identities, and the reason for every selection. Add a new behavioral spec without inventing a path, metadata, class shell, or runner command: labkittest.createSpec( ... \"apps/electrochem/cic/+cic/+analysisRun/computeCIC.m\", ... Contract=\"scientific\", Name=\"PulseWindow\", ... Reason=\"Regression: pulse-window bounds must remain stable.\") `Contract` is needed only when one source has more than one author-owned boundary. `Reason` starts with `Regression`, `Invariant`, or `Compatibility`; it records the durable behavior being protected, rather than a temporary implementation detail. The generated file contains the required `Contract:` and `Env:` tags plus an intentional failing placeholder. Replace that placeholder with a small behavioral proof. Remove or revise a specification when that stated behavior is intentionally retired. Never create a test by guessing a folder, a suite range, a test tag, or a wrapper class. During an edit, execute the narrow owner and contract that changed: labkittest.run(Owner=\"apps/electrochem/cic/analysisRun\", ... Contract=\"scientific\") Use `labkittest.run(File=...)` only when the source maps to one complete, known evidence closure. It refuses missing evidence instead of silently running a partial test. A changed analysis source normally requires its scientific behavior, result schema, and presentation consumer together. Test Model Runnable specifications are under `tests/specs/`. Their owner path mirrors a production capability, and every test declares exactly one contract and one execution environment. tests/specs/ ├── framework/ / ├── apps//// ├── apps/conformance/ └── system/ / `tests/+testfixtures/` contains only synthetic inputs reused by multiple specification owners. Keep an owner-specific fixture beside its specification; do not create a generic `shared`, `support`, or `helpers` test directory. Contracts describe evidence, not test cost: Contract Proves `scientific` calculations, numerical policies, and scientifically meaningful branches `source` source decoding, callbacks, and capability-local state transitions `state` durable/transient state transitions and reconstruction `persistence` project schema creation, validation, and migration `result` exports, schemas, manifests, and task identity `presentation` reader-facing snapshot data and declared layout semantics `definition` / `product` parameterized public App conformance `system` build, repository, CI, documentation, packaging, and release guardrails Environments are `headless`, `hidden-gui`, and `path-isolated`. Headless tests do not prove GUI behavior. Hidden-GUI conformance proves that an App can build its declared layout; it does not prove native dialogs, pointer feel, visual quality, real lab data, or scientific review. The path-isolated conformance probes every public App from a reset path boundary in the already running catalog executor. It retains the deployable path boundary and batches all App results without requiring a second concurrent MATLAB license. Build Tasks Use stable Build tasks for branch and CI gates: buildtool changedFast buildtool headless buildtool gui buildtool isolated buildtool coverage buildtool docs buildtool docsCheck Task Purpose `changedFast` Local final pre-commit/pre-push gate. Reads tracked and untracked working-tree paths; on a clean checkpoint it reads `HEAD^..HEAD`. `headless` Every headless catalog identity. `gui` Every hidden-GUI catalog identity. `isolated` Every path-isolated catalog identity. `coverage` Headless catalog with Cobertura XML and HTML coverage artifacts. `docs` / `docsCheck` Render or verify the generated documentation site. `changedFast` prints whether its plan is `focused-local` or `full-profile`, semantic reasons, exact identities, and any explicitly ignored paths. For ordinary App and facade source it runs only the required contract closure. Framework, Build, catalog, and repository-policy paths select explicit bounded system evidence. Documentation and generated-site paths are explicitly ignored because `docsCheck` owns their consistency. An unknown path is a planning error: declare its production role or an explicit no-test reason; do not hide missing ownership by widening the run. Use `labkittest.explainChanged` to inspect that decision without executing tests. It prints each changed path's classification, selected evidence, and any manual boundary. A focused-local result is rapid author feedback, not merge safety evidence; CI runs the full platform profiles. Run focused behavior during iteration. Run `changedFast` once when the branch is ready for review or direct-main integration. CI owns broad platform validation; do not repeatedly run broad tasks after each small edit. Artifacts and Failures Each run writes one folder beneath `artifacts/test-results//`: manifest.json run identity plan.json exact selected identities and reasons events.jsonl progress and heartbeat stream active-test.json latest machine-readable progress state junit.xml CI result exchange summary.json pass/fail summary coverage.xml coverage runs only coverage-html/ coverage runs only After a failure, copy its exact class/method identity from the output and run the smallest owner/contract or exact source that proves the repair. A zero selection or missing-contract error is a test-authoring defect, never passing evidence. When a mapped layout change leaves a non-automatable boundary, its plan can name a manual check. It is printed and recorded in `plan.json`, but it never makes an automated run pass. Manual checks are limited to native dialogs, pointer behavior, visual design, real-data suitability, and scientific interpretation; they cannot replace an automated calculation, state, export, migration, structural-GUI, or workflow proof. For deterministic rendering regressions, use `labkittest.visualEvidencePath(name, extension)` and write the production image there. Keep an automated assertion over that same file; the retained image supports human or visual-model review but is not itself a passing test. CI includes each profile's `visual-evidence/` folder in the platform artifact. CI and Manual Evidence Continuous Integration runs `headless`, `gui`, and `isolated` on Linux, macOS, and Windows against R2022b and the latest release available to `matlab-actions/setup-matlab`. R2022b is the LabKit minimum supported release and also the first release with MATLAB Build Tool. macOS runs only the latest release as an Apple Silicon and native-platform sentinel; Linux and Windows cover both release boundaries. This matrix is compatibility evidence for the supported product boundary. CI uses clean MATLAB runtimes without optional Toolboxes. The R2022b entries use the fixed Ubuntu 22.04 and Windows Server 2022 runner images supported by that MATLAB release; latest MATLAB uses the current runner images. Each platform-release job installs MATLAB once, then runs `headless`, `gui`, and `isolated` in separate batch sessions so the profiles share setup cost without sharing MATLAB session state. Linux jobs provide an X virtual framebuffer before running MATLAB so native graphics tests have a real display service instead of relying on release-specific no-display behavior. It runs `docsCheck` once on the latest release, then reports one aggregate `CI Gate` result that depends on every required profile. Configure repository branch protection to require `CI Gate`; the workflow does not silently replace repository protection policy. It uploads the catalog artifacts even after failure. Coverage is an explicit report, not a duplicate CI gate. Every platform-release job publishes one evidence-oriented job summary after all three independent MATLAB sessions finish. Its profile table states what `headless`, `gui`, and `isolated` prove instead of repeating only a raw pass/fail count. A successful summary records the compatibility claim, clean runtime assumptions, display configuration, slowest tests, artifact name, and the manual boundaries that automation does not prove. A failed summary preserves any profiles that still passed, separates a missing JUnit report from a reported test failure, names failed test identities, includes recorded MATLAB diagnostics, and collapses active-test and log-tail evidence below the primary failure. Build tasks define descriptions so the upstream MATLAB Build Results table is meaningful as well. The repository-owned summary helper has no third-party Python dependency and is regression-tested by the lightweight change-policy job. A cancelled or skipped profile makes the compatibility result `incomplete`, not `failed`; passing profiles remain valid evidence, but the unfinished job cannot establish the platform claim. CI classifies the exact pushed or pull-request diff before scheduling MATLAB. Source, test, build, workflow, and tool changes run the complete platform matrix. Human documentation-only changes run `docsCheck` without the platform matrix. Agent guidance and GitHub contribution-template-only changes run the lightweight change-policy check without starting MATLAB. Mixed changes run the union of their required profiles, and `CI Gate` verifies every profile selected by the classifier. Changing the classifier or workflow is itself a full-matrix change. Manual App validation remains required for native file dialogs, visual design, pointer interaction, real-data suitability, and scientific interpretation. Use synthetic, minimal fixtures in automated tests. Never add real lab files, local paths, subject/device identifiers, timestamps, or proprietary metadata. Maintainer Rules Keep specs beside the capability that owns their behavior; tests never own a parallel product API. Keep cross-owner synthetic inputs in `tests/+testfixtures/`; keep every other test helper beside the specification that owns its behavior. Prefer direct behavioral calls over full App workflows. Add a structural GUI proof only when layout or wiring itself is the contract. Do not add legacy suite folders, stage tags, selector registries, test wrappers, runner options, or Code Analyzer suppression pragmas. Add a new public framework test API only when it is a stable product boundary. Test infrastructure stays private under `tests/+labkittest/`. Follow `.agents/migration_guide.md` only while retiring compatibility debt; it is not an everyday authoring checklist."},{"title":"Maintainer Tools","url":"development/tools/index.html","kind":"overview","section":"development","keywords":"development tools Maintainer Tools","text":"Maintainer Tools LabKit keeps a small set of MATLAB tools for source-checkout maintenance. They can be launched from the LabKit Launcher or called directly after adding the owning tool directory to the MATLAB path. They are not dependencies of normal app calculations and write generated output under ignored `artifacts/` paths unless an explicit destination is supplied. Choose A Tool Goal Entry point Reference Scan MATLAB source and export reviewable issues `runCodecheckReport` [Code Analyzer Reports](codecheck.md) Package one or more apps for offline use `packageLabKitApp` [App Deployment Packages](deployment.md) Regenerate or verify the static documentation site `renderLabKitDocs`, `checkLabKitDocs` [Documentation Build Tools](documentation.md) Profile startup, callbacks, scripts, or functions `profileLabKitTarget` [Performance Profiling](profiling.md) Direct Invocation Pattern Add the narrow tool folder, not all of `tools/` recursively: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"profiling\")) profileLabKitTarget(\"labkit_launcher\") Each public tool file owns its private implementation folder. Adding the public folder makes those private helpers available only to that tool, without turning them into global commands. Launcher And Command-Line Use The launcher provides the common interactive defaults: it supplies the current checkout root, selected app commands, progress UI, and sensible report locations. Direct calls are preferable for automation, custom output paths, batch MATLAB, reusable profile targets, or packaging several named apps. Tool reports and ZIPs are generated artifacts, not source documentation or app project data. **Clean Artifacts** may remove them. Copy any evidence that must be retained outside `artifacts/` or attach it to the relevant release or issue. Related Documentation [LabKit Launcher](../../apps/labkit-core/launcher/README.md) [Testing](../maintain-and-release/testing.md) [Documentation System](../maintain-and-release/documentation.md) [Release Process](../maintain-and-release/release.md) [Architecture](../build-apps/architecture.md)"},{"title":"Code Analyzer Reports","url":"development/tools/codecheck.html","kind":"guide","section":"development","keywords":"development tools codecheck Code Analyzer Reports","text":"Code Analyzer Reports `runCodecheckReport` runs MATLAB `codeIssues` over the LabKit checkout and writes both the native JSON export and a browsable HTML report. Use it when you need the complete analyzer result; ordinary tests and CI own their own focused quality checks. Syntax report = runCodecheckReport(root) report = runCodecheckReport(root, \"OpenReport\", false) report = runCodecheckReport(root, \"ProgressFcn\", progressFcn) Add the tool folder first: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"codecheck\")) report = runCodecheckReport(repoRoot, \"OpenReport\", false); Inputs And Options Name Meaning `root` LabKit checkout root to scan. It is converted to a text scalar before file discovery. `OpenReport` Logical scalar. Default `true`; opens the generated HTML report in the system browser. `ProgressFcn` Empty or a function handle called as `fcn(message, value)`, where `value` progresses from 0 to 1. The scan includes MATLAB files beneath `root`. It excludes `.git`, `.github`, `.vscode`, `.codes`, `artifacts`, `node_modules`, and `photos`. Additional private app roots configured outside `root` are included when their workspace contains `.labkit-accept-main-guardrails`, or when `LABKIT_GUARD_PRIVATE_APPS` explicitly enables local inspection. Output The returned struct contains: Field Meaning `jsonFile` Native `codeIssues` JSON export path. `htmlFile` LabKit HTML report path. `fileCount` Number of unique MATLAB files scanned. `issueCount` Unsuppressed issue count. `suppressedIssueCount` Suppressed issue count reported by MATLAB. Default reports are timestamped beneath `artifacts/code-check/`. A second run within the same second receives a numeric suffix instead of overwriting the first report. Behavior And Limitations An analyzer finding is report data, not a thrown failure. File-access errors, invalid MATLAB analyzer inputs, or report-write failures still raise their underlying MATLAB error. LabKit source must not add Code Analyzer suppression pragmas merely to make this report empty; fix the source shape or document a real external limitation in project governance. Related Documentation [Maintainer Tools](README.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"App Deployment Packages","url":"development/tools/deployment.html","kind":"guide","section":"development","keywords":"development tools deployment App Deployment Packages","text":"App Deployment Packages `packageLabKitApp` creates one ZIP containing one or more selected LabKit apps, their assets, the shared `+labkit` runtime, standalone entry files, and a machine-readable package manifest. Source and P-code packages serve different deployment needs. Syntax result = packageLabKitApp(appSelector) result = packageLabKitApp(appSelector, zipFile) result = packageLabKitApp(appSelector, zipFile, Name, Value) Typical calls: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"deployment\")) one = packageLabKitApp(\"labkit_CIC_app\"); several = packageLabKitApp( ... [\"labkit_CIC_app\", \"labkit_EIS_app\"], [], ... \"OutputRoot\", fullfile(repoRoot, \"artifacts\", \"deployment\")); runtimeOnly = packageLabKitApp( ... \"labkit_CIC_app\", [], \"CodeFormat\", \"pcode\"); App Selector `appSelector` accepts: one app command such as `\"labkit_CIC_app\"`; a string array or cell collection of app commands; an app entry-file path; an app folder containing exactly one entry point; a launcher app metadata struct or struct array. Commands are resolved through the same public and configured private-app roots used by the launcher. Ambiguous commands, missing entry points, duplicate folder entries, or unavailable source files raise an error before packaging. Options Name Default Meaning `Root` Repository root Source/runtime root containing `+labkit`, apps, launcher, and tools. `OutputRoot` `artifacts/deployment/` Folder used when `zipFile` is empty. `ProgressFcn` Empty Function handle called as `fcn(message, value)` from 0 to 1. `CodeFormat` `\"source\"` `\"source\"`/`\"m\"` for M-files, or `\"pcode\"`/`\"p\"` for runtime-only P-code. Source And P-code Packages Property Source P-code App implementation `.m` files `.p` files Standalone entry `run_.m` `run_.p` `labkit_launcher.m` Included Omitted Deployment/profiling tools Included Omitted `+labkit` and selected app assets Included Included in encoded runtime form Both formats include `README.txt` and `packaged_app_manifest.json`. Neither format includes unrelated sibling apps. P-code is a deployment format, not a source backup; retain the corresponding private or public source repository. Result Structure The result identifies `zipFile`, `packageRootName`, `entryFiles`, `appCommands`, app-relative folders, visibility values, `codeFormat`, and `fileCount`. Legacy scalar fields `entryFile`, `appCommand`, `appRelativeFolder`, and `visibility` describe the first selected app. Packaging stages files in a temporary directory, replaces an existing target ZIP only after validation reaches archive creation, and removes the staging directory afterward. Related Documentation [Maintainer Tools](README.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md) [Private Apps](../maintain-and-release/private-apps.md) [Release Process](../maintain-and-release/release.md)"},{"title":"Documentation Build Tools","url":"development/tools/documentation.html","kind":"guide","section":"development","keywords":"development tools documentation Documentation Build Tools","text":"Documentation Build Tools LabKit uses three public MATLAB entry points for its documentation sources and generated site. `renderLabKitDocs` builds and synchronizes the site; `checkLabKitDocs` performs an independent build and verifies that the tracked site is current; `maintainLabKitDocLinks` checks or repairs standard relative Markdown links. Path-organized Markdown and MATLAB help blocks are the authored sources. Maintain Links result = maintainLabKitDocLinks result = maintainLabKitDocLinks(repoRoot) result = maintainLabKitDocLinks(repoRoot, \"Update\", true) The default is read-only. With `Update=true`, a missing Markdown target is rewritten only when filename and link label identify exactly one current page. Review the rewritten diff, then rerun the default check. Render The Site result = renderLabKitDocs result = renderLabKitDocs(sourceRoot) result = renderLabKitDocs(sourceRoot, outputRoot) repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"docs\")) result = renderLabKitDocs( ... fullfile(repoRoot, \"docs\"), fullfile(repoRoot, \"site\")); Empty or omitted roots default to `docs/` and `site/` beneath the current LabKit repository. The result contains `pageCount`, `apiCount`, `fileCount`, `sourceRoot`, and `outputRoot`. Rendering discovers every Markdown page, public App, history record, and complete public help contract. It first creates a complete temporary tree, then creates `outputRoot` when absent, copies new or changed files, deletes obsolete generated files and empty directories, and preserves the output root itself. Every eligible Markdown page is included automatically, and no duplicated navigation catalog exists in this model. Check The Tracked Site result = checkLabKitDocs result = checkLabKitDocs(sourceRoot, committedSiteRoot) The check renders to a temporary folder and compares generated file paths and bytes with the tracked site. A difference raises `LabKit:Docs:StaleGeneratedSite`; the result otherwise includes `comparedFileCount` in addition to the renderer result fields. Build Tasks The supported command-line wrappers are: buildtool docs buildtool docsCheck Use `docs` when sources changed and `docsCheck` when validation must prove the tracked HTML is already current. Never edit files under `site/` manually. Related Documentation [Maintainer Tools](README.md) [Documentation System](../maintain-and-release/documentation.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"Performance Profiling","url":"development/tools/profiling.html","kind":"guide","section":"development","keywords":"development tools profiling Performance Profiling","text":"Performance Profiling `profileLabKitTarget` runs a MATLAB target under the profiler and exports an interactive HTML report plus a complete JSON sidecar. It supports launcher and app startup, callbacks, scripts, functions, command strings, and already captured `profile(\"info\")` data. Syntax [htmlFile, artifacts] = profileLabKitTarget(target) [htmlFile, artifacts] = profileLabKitTarget(target, htmlFile) [htmlFile, artifacts] = profileLabKitTarget(target, htmlFile, Name, Value) Batch-friendly launcher profile: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"profiling\")) [htmlFile, artifacts] = profileLabKitTarget( ... \"labkit_launcher\", [], ... \"OpenReport\", false, ... \"WaitForGuiClose\", false, ... \"CloseFiguresAfterRun\", true, ... \"PrintSummary\", true); Calling the function with an empty target opens a MATLAB file picker. A target may also be a function handle, function name, command string, `.m` path, or a profile-info struct containing `FunctionTable`. Options Name Default Meaning `OpenReport` `false` Open the HTML report after writing it. `WaitForGuiClose` `true` Wait for newly opened figures before collecting the final profile. `CloseFiguresAfterRun` `false` Close figures opened by the target after capture. `ChangeFolder` `true` Temporarily use a resolved target file's folder as the working directory. `OutputRoot` `artifacts/profile/` Default report folder when `htmlFile` is empty. `InitialRows` `500` Initial number of function-table rows shown. Values below 20 become 20. `ChartTopN` `40` Number of top roots in the flame control. `SortBy` `\"SelfTime\"` Initial report-table sort field. `ProjectRoot` Repository root Root used to classify project-owned functions. `TargetFile` Empty Explicit target path when exporting supplied profile data. `JsonFile` Empty Explicit JSON sidecar path. `PrintSummary` `false` Print the structured summary to the MATLAB command window. `SummaryTopN` `20` Rows retained in each summary ranking. `RethrowError` `false` Rethrow the target error after preserving available profile data. Outputs `htmlFile` is the generated report path. `artifacts` contains `htmlFile`, `jsonFile`, and `functionCount`. The HTML embeds the complete profile and an `AGENT_SUMMARY` block; the JSON contains metadata, summary text and tables, MATLAB profiler metadata, and every captured function row. Reading A Report Start with `topProjectSelfTime` to find time spent directly in editable LabKit code. Use `topProjectTotalTime` to see inclusive project costs and `topCapturedTotalTime` to understand user actions, app launches, callbacks, network operations, or GUI close time captured below a parent. Inspect parent and child edges before optimizing an inclusive row. Rows carry source tags such as `project`, `matlab_internal`, `external`, and `profiler_tool`. Profiler-tool rows remain in the complete data but are omitted from the default captured rankings so report generation does not appear to be application cost. Profile one perceived operation at a time. Separate normal and debug startup, file registration and actual file reads, and startup and close latency. A profile identifies cost; it does not prove correctness or scientific parity. Related Documentation [Maintainer Tools](README.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"LabKit App SDK","url":"framework/index.html","kind":"explanation","section":"framework","keywords":"framework LabKit App SDK","text":"LabKit App SDK `labkit.app` is the MATLAB App SDK above LabKit's GUI-free domain libraries. Apps own scientific workflow, calculations, units, wording, plots, and exports. The SDK owns semantic layout, transactions, project documents, portable sources, dialogs, resources, results, and native component lifecycle. Start Here Goal Documentation Build or refactor an App [Build a Complete App](../development/build-apps/complete-app.md) Understand ownership boundaries [Architecture](../development/build-apps/architecture.md) Declare compatible LabKit modules [Compatibility contracts](compatibility/contracts.md) Look up exact MATLAB syntax [Public API reference](../reference/README.md) Browse the App SDK API by capability [App SDK API](app-sdk-api.md) Validate framework or GUI changes [Testing](../development/maintain-and-release/testing.md) SDK Map The required path has three concepts: API Purpose `labkit.app.Definition` App identity, lifecycle callbacks, layout, and launch `labkit.app.layout.*` Semantic inputs, displays, containers, and workbench structure `labkit.app.view.Snapshot` Derived visible state and App-owned rendering Read an App in this order: `definition.m` shows its complete contract, `+workbench/buildLayout.m` shows the user workflow, and its capability packages show the controls, state transitions, presentation, and rendering owned by each feature. Open `projectSpec.m` or `createSession.m` only when the definition names those optional capabilities. Layout controls bind directly to named App callbacks, and plot areas bind directly to their renderer. Apps do not maintain handler, renderer, or capability registries. Runtime-injected values are `labkit.app.CallbackContext` and typed `labkit.app.event.*` payloads. Callbacks name those boundary values explicitly and delegate domain work through narrow inputs. Optional capabilities are grouped by purpose: Package Purpose `labkit.app.event.*` Typed callback payloads `labkit.app.interaction.*` Managed plot gestures with direct callbacks `labkit.app.plot.*` Domain-neutral axes redraw, message, fit, and annotation mechanics `labkit.app.project.*` Durable project schema and migration `labkit.app.result.*` Exported files and result packages `labkit.app.dialog.*` Explicit dialog outcomes `labkit.app.CallbackContext` Runtime operations available inside callbacks Smallest App function app = definition() workbench = labkit.app.layout.workbench({ ... labkit.app.layout.field(\"gain\", Label=\"Gain\", ... Kind=\"numeric\", Bind=\"project.parameters.gain\"), ... labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the result from the current gain.\")}); app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", AppId=\"example\", ... Title=\"Example\", Family=\"Examples\", ... AppVersion=\"1.0.0\", Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... Workbench=workbench); end The entrypoint calls `definition().launch(...)`. Definition compiles the immutable semantic graph before creating a figure, validates direct callback and renderer signatures, and builds one private native platform plan. Paved Road Bind ordinary state with `Bind=\"project...\"` or `Bind=\"session...\"`. Use `labkit.app.layout.fileList` for portable file records and selection. Set `AllowDuplicatePaths=true` only when separate workflow tasks may share one resolved path, and present row-level workflow state with `Snapshot.fileItemStatuses`. Source changes rebuild the transient session; Apps do not mirror choose, remove, clear, or selection UI events. Give every scientific or workflow action an App-owned `Tooltip`. The framework guarantees a nonempty label-based fallback, while repository guardrails require tracked Apps to explain the action instead of repeating its visible label. File-list choose/folder/remove/clear controls expose dedicated tooltip options and domain-neutral label defaults. Rebuild transient data with `session = createSession(project,context)` and resolve opaque source records with `context.resolveSourcePaths`. Return only derived view state from `labkit.app.view.Snapshot`; runtime supplies layout defaults, bindings, file state, log text, and status text. Give short `statusPanel` summaries an explicit `Lines` hint so the native layout reserves detail height only for genuinely multiline content. Use `labkit.app.layout.dataTable` with `labkit.app.event.TableCellEdit` and `labkit.app.event.TableCellSelection`; Apps never decode native events. Use `labkit.app.layout.plotArea` and a fixed `renderer(axesById,model)` callback. `axesById` is always a named struct, even when the plot area declares only one axis. Pass a transient `ViewRevision` to `Snapshot.renderPlot` when an App exposes an explicit reset-view action. The adapter preserves user zoom while the revision is unchanged and accepts renderer-fitted limits once when it changes. Pass one workspace node or a row cell array of vertically arranged nodes to `workspace.page`; growable tables and plots share the available page height without an App-authored wrapper section. Use `layout.group(..., Title=\"...\")` for a nested reader-facing control boundary inside a section; leave `Title` blank for arrangement-only groups. A control tab containing one growable file list, table, log, status, or plot surface fills the available tab height. Tabs with longer mixed content remain scrollable. Declare editable overlays with `labkit.app.interaction.*` on the plot area; supply their current values with same-named Snapshot methods. For a managed rectangle with `OnBackgroundPressed`, an un-dragged click anywhere on its plot—including inside the rectangle—uses that point callback; dragging the rectangle still uses its change callback. Use `labkit.app.plot.clearAxes`, `showMessage`, and `fitAxesToGraphics` for renderer mechanics; `EqualDataUnits=true` makes a one-time fitted equal-scale view from the settled native axes allocation without dispatching pending UI callbacks, without changing the allocation or locking later zoom. Apps still decide message wording and viewport policy. Use `labkit.app.project.Schema`, `labkit.app.result.File`, and `labkit.app.result.Package` only when those optional capabilities exist. Use `labkit.app.project.sourceRecord` only in pure project creation or migration code that must turn a legacy path into a portable source value; runtime callbacks still resolve paths through `CallbackContext`. Runtime validates candidate state and the complete view snapshot before publishing either. The private MATLAB adapter maps semantic IDs to native components, skips unchanged native property writes, reuses direct associations between controls and their labels, preserves plot viewports, normalizes native event differences, and never exposes component registries to Apps. Normal App launches show the completed native window. Official GUI validation uses the same launch path with a framework-owned visibility policy: `hidden` keeps the final window off screen and `minimized` minimizes it after startup. Tests therefore exercise real controls without individual Apps or test methods having to hide the window after launch. Built-in App Tools The native runtime installs one top-level **Tools** menu so framework-owned utilities do not compete with the App's workflow controls: **Plots** opens, copies, or saves the App's plot surfaces. **Screenshot** copies the complete App surface to the system clipboard or saves it to an image file. **Project State** saves or loads the current project document when the App declares a project schema. These actions are framework-owned native behavior. Apps do not declare menu items, implement clipboard integration, or duplicate project persistence callbacks. Framework concepts and source names are versionless. Compatibility belongs to `labkit.app.version`; saved-data versions belong to `labkit.app.project.Schema`. Related Topics [App catalog](../apps/README.md) [App development](../development/build-apps/app-development.md) [Project history](../history/README.md) labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.dataTable Add a tabular data display with optional editing and selection. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.plot.clearAxes Prepare an axes for an app-owned redraw. labkit.app.project.Schema Declare one durable App project contract. labkit.app.project.sourceRecord Create a portable source value during project migration. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.version Return the LabKit App SDK facade contract version. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"App SDK API","url":"framework/app-sdk-api.html","kind":"guide","section":"framework","keywords":"framework app sdk api App SDK API","text":"App SDK API Use this page to move from the App Framework concepts to exact MATLAB contracts. These links stay within the Framework reading path; each function page provides syntax, inputs, outputs, options, errors, and related APIs. Start With The Core Contract [Definition](../reference/api/labkit/app/Definition.html) declares an App. [layout](../reference/api/labkit/app/layout/workbench.html) assembles the semantic workbench. [Snapshot](../reference/api/labkit/app/view/Snapshot.html) presents derived visible state. [CallbackContext](../reference/api/labkit/app/CallbackContext.html) exposes runtime capabilities to callbacks. Browse By Capability Capability API entry point Layout and controls [App SDK layout API](../reference/api/labkit/app/layout/workbench.html) View snapshots [App SDK view API](../reference/api/labkit/app/view/Snapshot.html) Typed events [App SDK event API](../reference/api/labkit/app/event/ListSelection.html) Projects and sources [App SDK project API](../reference/api/labkit/app/project/Schema.html) Results [App SDK result API](../reference/api/labkit/app/result/File.html) Managed interactions [App SDK interaction API](../reference/api/labkit/app/interaction/anchorPath.html) Plot mechanics [App SDK plot API](../reference/api/labkit/app/plot/clearAxes.html) Runtime diagnostics [Runtime diagnostics and session logging](guides/runtime.md#diagnostics-and-session-logging) Dialog results [App SDK dialog API](../reference/api/labkit/app/dialog/Choice.html) Related Topics [App Framework](README.md) [Runtime and Lifecycle](guides/runtime.md) [Build a Complete App](../development/build-apps/complete-app.md) labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.dataTable Add a tabular data display with optional editing and selection. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.group Arrange related child elements without a titled boundary. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.rangeField Add an input for a numeric lower and upper bound. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.slider Add a bounded numeric slider. labkit.app.layout.statusPanel Add a text display for current App status. labkit.app.layout.tab Add a named tab containing related child elements. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.app.event.IntervalScroll Describe one normalized interval scroll gesture. labkit.app.event.ListSelection Describe selected file or list item identities. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.interaction.anchorPath Declare an editable open or closed path on one plot axis. labkit.app.interaction.interpolateAnchorPath Build a visible path through image anchor points. labkit.app.interaction.interval Declare an editable one-dimensional plot interval. labkit.app.interaction.pairedAnchors Declare matching editable points across plot axes. labkit.app.interaction.pointSlots Declare a fixed set of editable labeled point positions. labkit.app.interaction.rectangle Declare an editable rectangular plot region. labkit.app.interaction.regionSelection Declare a transient click-or-drag region gesture. labkit.app.interaction.scaleBarGeometry Compute serializable image scale-bar overlay geometry. labkit.app.interaction.scaleCalibration Convert a known image distance into pixels per unit. labkit.app.interaction.scaleReference Declare an editable two-point scale reference. labkit.app.plot.clampPointToAxes Keep data coordinates inside the visible axes box. labkit.app.plot.clearAxes Prepare an axes for an app-owned redraw. labkit.app.plot.enablePopout Add a standalone-figure action to an axes context menu. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data. labkit.app.plot.fitCanvasToSource Fit a preview axes into a fixed-aspect pixel frame. labkit.app.plot.offsetPointByAxesFraction Move data coordinates by normalized axes fractions. labkit.app.plot.showMessage Show a centered empty-state message in an axes. labkit.app.project.Schema Declare one durable App project contract. labkit.app.project.emptySourceRecords Create an empty portable source collection. labkit.app.project.sourceRecord Create a portable source value during project migration. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.dialog.Choice Represent a typed dialog choice or cancellation."},{"title":"DIC Preprocess","url":"apps/dic/dic-preprocess.html","kind":"app","section":"apps","keywords":"apps dic dic preprocess DIC Preprocess","text":"DIC Preprocess DIC Preprocess registers a moving optical image to a reference image, applies repeatable crop operations to the pair, and creates a binary analysis mask. Use it when camera motion or framing differences must be removed before an external DIC solver is run. Requirements And Launch Image IO and registration use MATLAB and repository-owned code; no external registration package is installed. labkit_DICPreprocess_app Inputs Choose one reference image and one moving image. Supported extensions are the formats returned by `labkit.image.supportedExtensions`, including common PNG, JPEG, TIFF, and BMP files. The reference and moving images may initially have different dimensions; alignment produces a moving image on the reference domain. Choosing a new reference or moving file clears operations derived from the old pair. Source files are read-only and are not overwritten. Basic Workflow Choose the reference and moving images. Select **Current pair** or **False-color overlay** to assess the initial displacement. Run manual point matching or **Auto align current pair**. Inspect the false-color result and undo or reset if necessary. Start the crop ROI, drag or resize the square, and apply it to both images. Start ROI editing, place a boundary, and add or subtract it from the mask. Save the prepared images and ROI mask. Alignment and crop actions form an ordered edit history. **Undo align/crop** removes the most recent applied operation. **Reset to originals** discards the derived working pair and replays no edits. Preview Modes Mode Display Current pair current reference above the current moving image Current moving image moving image in the main comparison view False-color overlay red/green registration comparison of the current pair Original pair source images before applied edits ROI mask current binary mask over the image domain Changing preview mode does not change project data. Point placement, point dragging, crop editing, mask editing, and applying an operation preserve the current axes zoom. Use the plot **Fit** action when a full-image view is wanted. Manual Point Matching Press **Start point matching**. Click a feature in the reference image, then click the same feature in the moving image. Repeat this reference/moving order for at least two complete pairs. Numbered markers show correspondence and the preview subtitle states which image expects the next point. Drag an existing marker to refine it. **Undo point pair** removes the newest complete pair. **Cancel point matching** discards the pending point set without changing the current images. **Apply point alignment** estimates and applies a rigid two-dimensional transform. Manual alignment uses all pairs in a least-squares rotation-and-translation fit. It does not estimate scale or shear and prevents a reflected solution. Choose points that are visually unambiguous and spread across the specimen; clustered points provide weak rotational leverage. Automatic Alignment **Auto align current pair** runs the app-owned base-MATLAB registration path. It returns the same aligned image and transform fields as manual alignment. Automatic alignment is a starting estimate, not a guarantee of DIC-quality correspondence. Always inspect the false-color overlay and prefer manual points when the image has repeated texture, large occlusion, or weak contrast. Crop ROI **Start/reset crop ROI** creates a square rectangle constrained to the current reference image. Drag the rectangle to move it and use its resize handles to change its size. **Apply ROI crop** uses exactly the same integer image-domain rectangle on the reference and aligned moving image. **Cancel ROI** exits the editor without adding a crop step. Applied crops change the coordinate domain for later operations. Undo the crop before reusing point coordinates defined on the larger image. Mask ROI Start ROI editing and choose **Curve** or **Straight lines**. Place boundary anchors as instructed by the canvas subtitle; anchors remain draggable while editing. **Undo point** removes the most recent boundary anchor and **Clear boundary** removes the pending boundary only. **Preview ROI mask** shows the rasterized boundary. **Add to mask** unions the boundary interior with the current mask; **Subtract from mask** removes it. **Undo mask edit** restores the previous mask snapshot. **Clear mask** removes all included pixels. The mask is stored as a logical image in the current reference domain. Outputs **Save current images** writes two images to the selected folder using stable reference and moving result names. **Save ROI mask** writes a binary image to the selected file. LabKit result manifests record source references, parameters, output roles, and generated filenames; the manifest JSON is provenance metadata, not an additional scientific result. The saved project stores portable source references, preview choices, edit steps, crop and mask annotations, and result references. Decoded source images and intermediate working images are loaded or recomputed when the project is opened; they are not duplicated inside the project file. Use Without The GUI reference = imread(\"reference.png\"); moving = imread(\"moving.png\"); referencePoints = [120 80; 360 95; 230 310]; movingPoints = [128 88; 369 101; 239 318]; [aligned, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... reference, moving, referencePoints, movingPoints); overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... reference, aligned); cropped = dic_preprocess.analysisRun.cropImage(aligned, [80 60 300 300]); Use the linked function pages below for exact coordinate, rectangle, return shape, interpolation, and error contracts. Function Reference The generated pages for [`alignMovingToReference`](../../../reference/api/dic_preprocess/analysisRun/alignMovingToReference.html), [`autoAlignMovingToReference`](../../../reference/api/dic_preprocess/analysisRun/autoAlignMovingToReference.html), [`applyRigidTransform`](../../../reference/api/dic_preprocess/analysisRun/applyRigidTransform.html), and the other cataloged DIC functions document coordinate conventions, assumptions, output shape, limitations, failure behavior, and related APIs. Errors And Troubleshooting Point alignment remains unavailable until at least two complete pairs exist. A strong colored double edge in the overlay indicates residual geometric mismatch, not a palette defect. Automatic alignment can fail on nearly uniform images; use separated manual points or improve the source capture. Crop and mask geometry is constrained to the active image domain; an old project source that changed size cannot safely reuse its prior annotations. Related Topics [DIC Postprocess](../dic-postprocess/README.md) [DIC family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) dic_preprocess.analysisRun.alignMovingToReference Fit and apply a rigid transform from matched points. dic_preprocess.analysisRun.applyRigidTransform Resample a moving image onto a reference-sized canvas. dic_preprocess.analysisRun.autoAlignMovingToReference Estimate and apply an integer translation. dic_preprocess.analysisRun.cropImage Crop an image using base-MATLAB indexing. dic_preprocess.analysisRun.makeFalseColorOverlay Build a red/green registration preview."},{"title":"Framework Compatibility Contracts","url":"framework/compatibility/contracts.html","kind":"guide","section":"framework","keywords":"framework compatibility contracts Framework Compatibility Contracts","text":"Framework Compatibility Contracts [App Framework](../README.md) | [API reference](../../reference/README.md) | [App development](../../development/build-apps/app-development.md) `labkit.contract` lets an app state which LabKit API versions it supports and check those requirements before startup. It compares versions already present in this repository; it does not download, install, or update code. This is part of the documented App Framework surface. The MATLAB namespace remains `labkit.contract` because it checks UI and domain facades alike; it is not an App metadata registry or a UI-only helper. Typical Use An app normally defines its requirements in a small function: function req = requirements() req = labkit.contract.requirements( ... \"ui\", \">=7 <8\", ... \"image\", \">=4 <5\"); end The app entry point checks them before creating the interface: labkit.contract.assertRequirements( ... \"labkit_Example_app\", requirements()) If all requirements are compatible, `assertRequirements` returns without a value. Otherwise it throws `labkit_Example_app:IncompatibleLabKit` with one message for each incompatible module. Choose a Function Task Function Build a requirement structure [`labkit.contract.requirements`](../../reference/api/labkit/contract/requirements.html) Inspect compatibility without throwing [`labkit.contract.checkRequirements`](../../reference/api/labkit/contract/checkRequirements.html) Stop startup when requirements fail [`labkit.contract.assertRequirements`](../../reference/api/labkit/contract/assertRequirements.html) Create the structure returned by a module's `version` function [`labkit.contract.versionInfo`](../../reference/api/labkit/contract/versionInfo.html) Version Ranges A range contains one or more whitespace-separated constraints. Supported operators are `<`, `<=`, `>`, `>=`, `=`, and `==`: \">=7 <8\" % 7.x releases \">=2.1 <=2.4\" % from 2.1 through 2.4, inclusive \"=1.3.0\" % exactly 1.3.0 Versions may contain one, two, or three numeric components. Missing components are treated as zero during comparison, so `6`, `6.0`, and `6.0.0` compare as the same version. Facade names may be short, such as `\"thermal\"`, or include the prefix, such as `\"labkit.thermal\"`. Names are normalized to lowercase without the prefix. Listing the same facade twice in one requirement structure is an error. How Compatibility Is Decided Each module's `version` function returns a structure like this: info = labkit.thermal.version() Field Meaning `name` Full module name, such as `\"labkit.thermal\"` `facade` Short normalized name, such as `\"thermal\"` `current` Current API version `compatible` One or more requirement ranges implemented by this version `status` `\"stable\"`, `\"experimental\"`, or `\"deprecated\"` `notes` Short description of the module contract A requirement passes only when both conditions are true: The module's `current` version falls inside the app's requested range. The requested range overlaps at least one range in the module's `compatible` list. The second check prevents a current version number from appearing acceptable when the installed implementation does not advertise the requested contract. Inspecting a Compatibility Report Use `checkRequirements` when a caller should decide how to display or handle a failure: req = labkit.contract.requirements(\"thermal\", \">=1 <2\"); report = labkit.contract.checkRequirements(req); if ~report.ok disp(report.message) end The returned structure contains: Field Meaning `ok` True when every requirement passes `failures` One structure per failed requirement `message` Success text or newline-separated failure explanations Each failure records the normalized `facade`, the `required` range, the module's advertised `available` ranges, and a readable `message`. An unknown facade has an empty `available` array. Supplying Version Information Explicitly `checkRequirements` normally queries the installed UI, DTA, RHS, biosignal, image, and thermal modules. Diagnostic code can pass an explicit version structure instead: available = labkit.contract.versionInfo( ... \"image\", \"4.1.0\", \">=4 <5\", \"stable\", ... \"Image file IO and processing functions.\"); req = labkit.contract.requirements(\"image\", \">=4.0 <5\"); report = labkit.contract.checkRequirements(req, available); This form is useful for checking a proposed version declaration without changing the installed module. Related Topics [App development](../../development/build-apps/app-development.md) explains how one App definition owns facade requirements and product version metadata. [Architecture](../../development/build-apps/architecture.md) describes the public LabKit modules that can appear in a requirement list. [Project history](../../history/README.md) records API version and compatibility changes. labkit.contract.assertRequirements Throw an error when LabKit API requirements are not met. labkit.contract.checkRequirements Compare required API ranges with available LabKit versions. labkit.contract.requirements Describe the LabKit API versions required by a caller. labkit.contract.versionInfo Describe one LabKit API version and its compatibility range. labkit.thermal.version Return version information for the thermal API."},{"title":"Runtime And Lifecycle","url":"framework/guides/runtime.html","kind":"guide","section":"framework","keywords":"framework guides runtime Runtime And Lifecycle","text":"Runtime And Lifecycle `labkit.app` is the App-facing runtime contract. Apps declare product meaning; the private runtime owns native MATLAB components, event serialization, transactions, project documents, portable sources, resources, and cleanup. Definition And Launch Each App-owned `definition.m` is the single immutable product contract, and each entrypoint delegates to it: function varargout = labkit_Example_app(varargin) [varargout{1:nargout}] = example.definition().launch(varargin{:}); end function app = definition() app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", ... AppId=\"examples.example\", ... Title=\"Example\", ... Family=\"Examples\", ... AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... Workbench=example.workbench.buildLayout(), ... ProjectSchema=example.projectSpec(), ... CreateSession=@example.createSession, ... PresentWorkbench=@example.workbench.present); end Required Definition arguments are product metadata, requirements, and one `labkit.app.layout.workbench` value. Optional callbacks are: Argument Signature Purpose `CreateSession` `session = callback(project,callbackContext)` Rebuild transient App data from durable project state. `PresentWorkbench` `view = callback(applicationState)` Return the App-owned fragment of the complete visible snapshot. `OnStart` `applicationState = callback(applicationState,callbackContext)` Perform a real post-first-commit request or resource initialization. `BuildSyntheticSample` `sample = callback(callbackContext)` Build clean-room debug input when the App supports it. Ordinary default state needs no startup callback. Exact syntax and errors are in the generated [public API reference](../../reference/README.md). `launch(\"requirements\")` and `launch(\"version\")` answer metadata without creating a figure. `launch()` constructs the private native adapter and shows the App. Static Workbench Contract `+workbench/buildLayout.m` returns a tree composed from `labkit.app.layout.*` values. Layout IDs are stable semantic identifiers and must be globally unique. function layout = buildLayout() controls = { ... labkit.app.layout.section(\"parameters\", \"Parameters\", { ... labkit.app.layout.field(\"gain\", ... Label=\"Gain\", Kind=\"numeric\", ... Bind=\"project.parameters.gain\"), ... labkit.app.layout.button(\"exportResult\", ... \"Export\", @example.resultFiles.exportResult, ... Tooltip=\"Export the current analyzed result.\")})}; workspace = labkit.app.layout.workspace( ... labkit.app.layout.plotArea( ... \"previewPlot\", @example.previewPlot.draw)); layout = labkit.app.layout.workbench( ... controls, Workspace=workspace); end Layout controls own direct callbacks. Plot areas own direct renderers. There is no handler, renderer, command, or capability registry for Apps to maintain. Definition compiles the immutable graph once and validates callback roles, renderer roles, IDs, bindings, and view capabilities before UI mutation. Complex Apps keep the top-level workbench readable by composing capability-owned `layoutSection`, `workspaceTable`, or `workspacePlot` functions in user order. State And Transactions Runtime state always has two App-owned buckets: applicationState.project applicationState.session `project` is durable, validated meaning. `session` is transient and reconstructible. A callback receives the previous complete state and returns a candidate complete state. The runtime: serializes the event; invokes the direct callback; validates project and session shape; builds and validates the complete view snapshot; reconciles native components; publishes state and view together. Failure rolls back both state and presentation and clears event-scoped resources. Apps do not implement busy flags, callback queues, readiness timers, or figure close guards. Use direct `Bind=\"project....\"` or `Bind=\"session....\"` paths for ordinary fields, ranges, sliders, file sources, and selection. Bound controls need no callback or presenter operation unless the App has additional derived meaning. Portable Sources And Session Reconstruction `labkit.app.layout.fileList` owns file/folder selection, portable source records, removal, clearing, and optional selection binding: labkit.app.layout.fileList(\"sources\", ... Filters=[\"*.csv\", \"CSV files\"], ... Bind=\"project.inputs.sources\", ... SelectionBind=\"session.selection.sources\", ... SourceRole=\"measurement\", ... SourceIdPrefix=\"source\") The App does not mirror those UI lifecycle actions with callbacks. Runtime updates the bound source records and invokes `CreateSession` after source changes: function session = createSession(project, callbackContext) paths = callbackContext.resolveSourcePaths(project.inputs.sources); session = struct(\"measurements\", example.sourceFiles.read(paths)); end Portable source records are opaque. Resolve their paths only at IO boundaries. Saved projects store portable references and use runtime relinking. Typed Events Callbacks are attached only where an App owns real behavior: button: `state = callback(state,callbackContext)` field, range, slider, workspace page, or interaction change: `state = callback(state,value,callbackContext)` table edit: `state = callback(state,labkit.app.event.TableCellEdit,callbackContext)` table selection: `state = callback(state,labkit.app.event.TableCellSelection,callbackContext)` Name the boundary values explicitly and delegate domain work through narrow inputs: function applicationState = replaceGroupValue( ... applicationState, cellEdit, callbackContext) arguments applicationState (1,1) struct cellEdit (1,1) labkit.app.event.TableCellEdit callbackContext (1,1) labkit.app.CallbackContext end groups = applicationState.project.groups; applicationState.project.groups = ... example.groupData.replaceValue(groups, ... cellEdit.Row, cellEdit.Column, cellEdit.NewValue); end Do not pass the complete state or callback context into calculation code that only needs groups and one edited value. Complete View Snapshots Runtime starts from layout defaults, bindings, file state, log text, and status text. `PresentWorkbench` returns only derived App-owned operations: function view = present(applicationState) view = labkit.app.view.Snapshot(); view = view.include(example.previewPlot.present( ... applicationState.session.measurements, ... applicationState.project.parameters)); view = view.enabled(\"exportResult\", ... ~isempty(applicationState.session.measurements)); end The combined snapshot must cover every semantic target exactly as its layout capabilities require. `Snapshot.include` composes feature-owned fragments without opening a generic property-patch schema. Plot presentation passes a prepared model to the renderer declared by its plot area: view = view.renderPlot(\"previewPlot\", model); function draw(axesById, model) ax = axesById.main; cla(ax); plot(ax, model.x, model.y); end Renderers own drawing and viewport policy, not workflow decisions or project mutation. Display-only graphics disable hit testing. Managed interaction specs own editable gestures and event-scoped resources. Declare managed gestures statically on their plot area and provide their current value in the snapshot: crop = labkit.app.interaction.rectangle( ... \"cropRegion\", @example.cropGeometry.moveCrop); plot = labkit.app.layout.plotArea( ... \"previewPlot\", @example.previewPlot.draw, Interactions={crop}); view = view.rectangle( ... \"cropRegion\", project.annotations.crop, ImageSize=size(image)); Named contracts also cover anchor paths, paired anchors, fixed point slots, transient region selection, intervals, and scale references. Apps never author `Kind`, `Targets`, `Event`, or `Options` transport structs. Renderer mechanics such as complete clears, empty-state messages, fitting, fixed-aspect canvases, and axes-relative annotation placement live under `labkit.app.plot.*`. Apps own when those operations occur, user wording, and whether a semantic change should preserve or fit the viewport. CallbackContext `labkit.app.CallbackContext` is sealed and exposes specifically named runtime operations for dialogs, status and diagnostics, portable sources, project documents, result packages, render surfaces, and managed resources. It does not expose figures, component registries, queues, lifecycle handles, or a nested service bag. Use context methods only at a callback or reconstruction boundary. Pure readers, calculations, result builders, and render-model builders accept ordinary explicit values. `callbackContext.chooseOption(prompt, choices, ...)` owns ordinary native confirmation choices. `Title` controls the dialog title, `DefaultChoice` selects the Enter-key action, and `CancelChoice` is returned when the user dismisses the dialog. All three named choices must be members of the declared nonempty unique choice row. File and folder methods remain separate because they return paths and use platform file choosers. An App-specific project button may choose a MAT file and return `callbackContext.restoreProjectDocument(filepath)`. The context prepares the same migrated, relinked project/session candidate used by the framework Load State menu; the active callback transaction still owns validation, native presentation, rollback, document metadata, and title publication. `callbackContext.newProjectDocument()` similarly returns the schema's fresh project/session state and publishes a new unsaved document identity only when that callback transaction commits. Diagnostics And Session Logging Every ordinary App launch starts one sanitized session event stream and durable journal. Launch arguments do not select a debug mode, change startup behavior, or generate sample data. Runtime automatically records lifecycle, callback, transaction, dialog, project, source, result, and failure boundaries with correlated operation IDs. App callbacks add domain events through `callbackContext.log(severity,eventName,message,Name=Value)`. Use `info` for useful progress and completed user actions, `warning` for recoverable conditions, and `error` or `critical` for failures. Stable event names and structured allowlisted attributes support diagnosis; messages remain concise and safe for display. Pass caught exceptions through the dedicated `Exception` option instead of copying stack, path, identifier, or scientific data into free text. The App's **Tools > Diagnostics** menu opens the live session viewer, enables more detailed trace capture for future activity, and exports a diagnostic bundle from the same session history. Enabling trace does not restart the App or reconstruct earlier detail. Journal degradation is itself exposed in the surviving in-memory stream; logging failures never alter callback transaction semantics or scientific results. Native open and save dialogs normalize their file filters to MATLAB character-cell tables before calling the platform dialog, including the diagnostic ZIP destination on Windows releases. If the save dialog, staging, ZIP creation, or final publish step fails, Runtime writes the surviving privacy-safe records as one plain-text diagnostic fallback. It first uses the selected destination folder when that folder is available and otherwise uses MATLAB's user-writable temporary folder; the failure alert reports the complete fallback path. The viewer's **View** filter describes intended readers, not access control. **Useful** shows user-workflow events plus developer warnings and failures; **User workflow**, **Developer details**, and **Everything** select the two event audiences explicitly. The **Action** filter groups a top-level user or lifecycle action with its nested callback, presentation, dialog, resource, and transaction records. Its readable label includes time and a semantic message while retaining the stable `op-*` correlation identifier for exported diagnostics. TRACE capture is independent of ordinary failure capture. With TRACE off, Runtime still retains DEBUG lifecycle/callback start and completion boundaries and all INFO, WARNING, ERROR, and CRITICAL events. The default viewer hides normal developer DEBUG/INFO records; selecting **DEBUG+** and **Developer details** reveals earlier retained callback boundaries. A callback exception is recorded as an ERROR with `failed` operation result, rollback disposition, safe exception identifier, and sanitized function stack. Runtime close is also an instrumented lifecycle operation. Resource and native adapter cleanup continue independently; a cleanup exception is retained and persisted before the journal closes, then returned to the caller. Diagnostics cannot manufacture evidence for a native event that never entered Runtime, an exception swallowed by App code without logging, or a MATLAB process that hangs or terminates before a terminal event. In those cases the last retained DEBUG boundary and durable journal state are the available evidence. Persistence, Results, And Cleanup Synthetic Inputs An App that declares `BuildSyntheticSample` exposes **Tools > Developer Tools > Generate Synthetic Inputs...**. The action writes an anonymous, validated `labkit.app.synthetic.Pack` and `synthetic-input-pack.json` into a new folder beneath the selected destination. Generation does not load the pack, mutate the open project, or suppress `OnStart`; every App launch follows the same clean startup path. Users deliberately import the generated files through the App's ordinary controls. `labkit.app.project.Schema` owns current project creation, validation, and ordered version migration. Runtime owns the project envelope, atomic save, restore, recovery, and relinking loop. Saved-project compatibility boundary Every save writes exactly one `labkitProject` variable using the current App payload version. A restore accepts an older payload only when the App's current schema declares one `Migrate(project, fromVersion)` callback; Runtime invokes it once for each missing version in order and then validates the current payload. A payload newer than the running App is rejected rather than guessed at. An App may declare an exact legacy MAT variable name in `LegacyImports` when real user files still require a one-way import. That callback converts the legacy value directly to the current project and optional resume state. It is read-only: current saves never write the legacy variable, and Runtime contains no App ID, field-shape, or filename heuristics. These readers are supported data contracts while their Apps declare and test them. They are not an excuse for duplicate live state fields or old runtime APIs. Removing a supported payload migration or importer is an explicit breaking saved-data decision; adding one requires App-owned persistence evidence plus a runtime restore test for the framework mechanism. `labkit.app.result.File` and `labkit.app.result.Package` describe App-owned outputs. `CallbackContext.writeResultPackage` writes through the runtime so source and project provenance remain consistent. Resources have event, interaction, document, or application scope. Replacing the same scope and ID is idempotent; the runtime cleans every surviving resource on scope end or close. Validation Use focused contract tests for Definition, layout, callbacks, snapshots, project schema, and runtime transactions. Add downstream App tests for changed behavior and a bounded hidden-GUI test for native wiring. Automated hidden GUI tests do not prove dialog quality, pointer feel, scientific validity, or a complete interactive workflow. See [Testing](../../development/maintain-and-release/testing.md) and [Build A Complete App](../../development/build-apps/complete-app.md). labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.interaction.rectangle Declare an editable rectangular plot region. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.project.Schema Declare one durable App project contract. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.synthetic.Pack Describe one typed anonymous App reproduction scenario. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"Getting Started","url":"getting-started/index.html","kind":"tutorial","section":"getting-started","keywords":"getting started Getting Started","text":"Getting Started This guide covers the shortest path from an empty folder to a running LabKit app. For source development, continue with the [development guide](../development/README.md). Requirements A supported MATLAB installation. Internet access for the first launcher download or update. Installed source and P-code packages run offline unless an app's own input lives on a network drive. Any MathWorks products declared by the selected app. The launcher shows app requirements before launch. Production apps do not create Python or Conda environments, download model weights, or install third-party packages on first use. Install And Launch Download the latest [`labkit_launcher.m`](https://github.com/Pluze/LabKit-MATLAB-Workbench/releases/latest/download/labkit_launcher.m). Put it in a standalone folder such as `LabKit/`. Open MATLAB in that folder. Run: labkit_launcher The launcher can install or update the runtime, discover available apps, and start the selected app. Keep experimental data and exported results outside the LabKit runtime folder. See the [LabKit Launcher manual](../apps/labkit-core/launcher/README.md) for every button, programmatic mode, discovery rule, and maintenance action. Choose A Version **Release** installs the latest stable GitHub release. **Latest** installs the current `main` branch. **Versions** selects a recent release, tag, or commit for a deliberate upgrade or rollback. Before replacing an installed runtime, the launcher moves its current contents to a dated `LabKit-previous-*` folder. This provides a local recovery point but is not a substitute for backing up lab data. Open An App Select one row in the launcher and choose **Open**. The launcher prepares the MATLAB path, checks the app's LabKit version requirements, and displays startup progress until the app is ready. Use the [app catalog](../apps/README.md) to choose a workflow and confirm its input and output formats. Common App Commands Every current LabKit app exposes one top-level **Tools** menu: **Tools > Plots** contains plot-specific actions such as opening larger editable views, copying plots, and saving plots. **Tools > Screenshot > Copy to Clipboard** copies the complete active App surface as an image. **Tools > Screenshot > Save to File...** saves the complete App surface. **Tools > Project State > Save State...** writes the current project document. **Tools > Project State > Load State...** opens a compatible project document. State files preserve app projects. They are different from exported result files and from ignored diagnostic manifests under `artifacts/diagnostics/`. Source Checkout Clone the repository only when you need source development, tests, profiling, or review: git clone https://github.com/Pluze/LabKit-MATLAB-Workbench.git cd LabKit-MATLAB-Workbench buildtool headless See [Testing](../development/maintain-and-release/testing.md) before choosing a broader build task. Next Steps [App guide](../apps/README.md) [LabKit Launcher](../apps/labkit-core/launcher/README.md) [Public API reference](../reference/README.md) [Development guide](../development/README.md)"},{"title":"Project History","url":"history/index.html","kind":"history index","section":"history","keywords":"history Project History","text":"Project History This page lists the recorded changes to LabKit in reverse chronological order. Each change has its own page, so a reader can open the reason, effect, compatibility notes, and validation without searching through one very long changelog. Reading The Timeline Every recorded change, including work before v1.0, appears once in the same reverse-chronological timeline below. Early records describe how imported laboratory scripts became an app workbench; later records use the same page format to explain framework, library, App, validation, and release decisions. Version boundaries are milestones in this one timeline, not separate kinds of history. History source files have one predictable home: docs/history/records/// The folder is chronological, while the metadata inside each record names the apps, framework, or libraries affected by the change. Every record also has a global positive-integer `sequence`. Sequence values are unique and contiguous: the first record is `1`, and each later change takes the next value even when several changes share a date. The generated timeline sorts by descending sequence, so titles and filenames cannot reorder same-day versions. Dates must remain nondecreasing as sequence increases. The authoritative [history record format](record-format.md) defines every metadata field and body section. The documentation site uses that metadata to show a relevant **Change history** section on each component page. A change that affects several components is stored once and linked from all of them. Find A Change Use the site search for an app command, public function, Change ID, or feature name. You can also browse the complete timeline below by date. Current version numbers come from the launcher, facade `version.m` files, and App `definition.m` metadata rather than from a separate table in this history."},{"title":"History record format","url":"history/record-format.html","kind":"guide","section":"history","keywords":"history record format History record format","text":"History record format Each file under `docs/history/records///` records one durable project decision or user-visible evolution. This page is the sole authority for its authored structure. The documentation renderer validates every record, builds the timeline from its sequence, adds component links, and indexes its scopes for search. Canonical metadata The title is the first line. It is followed by one blank line and exactly one `labkit-change` block. Metadata fields use the shown order; no other metadata fields are legal. [Level-one Markdown title: concise change title] id: LK-YYYYMMDD-lowercase-change-slug date: YYYY-MM-DD sequence: N type: feat compatibility: compatible component: `component-id` | `old-version -> new-version` scope: Search phrase Field Required Value Used for `id` Yes, once `LK-` + ISO date without hyphens + lowercase hyphenated slug Stable change identity and search `date` Yes, once ISO date: `YYYY-MM-DD` Timeline display and ordering check `sequence` Yes, once Next positive integer; all records are contiguous from `1` Definitive timeline order `type` Yes, once `feat`, `fix`, `perf`, `refactor`, `test`, `docs`, `ci`, or `chore` Timeline classification and search `compatibility` Yes, once `compatible` or `breaking` Upgrade expectation and search `component` Optional, repeated `` `component-id` `` or `` `component-id` \\ `old-version -> new-version` ``; use ``new -> X.Y.Z`` for an introduced component Component-page history aggregation `scope` Yes, one or more A trimmed, human-readable search phrase of at most 96 characters Search keywords Use a component ID only when the record should appear on that component's documentation page. A record may have multiple components and scopes; list all components before all scopes. Do not add a `schema` field or encode release state in a record. Canonical narrative After the metadata block, every record contains these headings exactly once and in this order. Each heading has substantive content. ## Context ## Decision and rationale ## Changes ## User and data impact ## Compatibility and migration ## Validation ## Evidence ## Known limitations and follow-up State what changed and why in the first three sections; state affected users, data, compatibility, and validation in the next four; reserve the last section for remaining limits or explicitly state that none are known. Write one cross-component record rather than duplicating a narrative across components. When to write a record Write a record for a versioned App, launcher, or facade change, or for a meaningful project evolution decision. Do not create one solely for generated site output, a typo-only edit, or mechanical formatting churn."},{"title":"Biosignal API","url":"libraries/biosignal/index.html","kind":"reference","section":"reference","keywords":"libraries biosignal Biosignal API","text":"Biosignal API [Public API index](../../reference/README.md) | [App guide](../../apps/README.md) `labkit.biosignal.*` provides GUI-free import and processing functions for physiological and wearable time-series data. Use it to build a MATLAB script from the same recording, channel, event, segment, template, and measurement structures used by LabKit apps. `labkit.biosignal.version()` returns biosignal-library version and compatibility information used by `labkit.contract` requirement checks. ECG peak detection is the only modality-specific detector in the current module. Import, filtering, segmentation, template construction, measurement, and group comparison use generic biosignal structures. Public Surface Current app-facing functions: [recording, status] = labkit.biosignal.readRecording(filepath); names = labkit.biosignal.listChannels(recording); signal = labkit.biosignal.getChannel(recording, channel); cropped = labkit.biosignal.cropSignal(signal, [startSec endSec]); filtered = labkit.biosignal.filterSignal(signal, filterSpec); ecgPeakOptions = labkit.biosignal.defaultEcgPeakOptions(\"qrs-streaming\"); events = labkit.biosignal.detectEcgPeaks(signal, ecgPeakOptions); segments = labkit.biosignal.segmentByEvents(signal, events, windowSec); template = labkit.biosignal.buildTemplate(segments, templateOptions); measurements = labkit.biosignal.measureSegments(segments, template, measureOptions); comparison = labkit.biosignal.compareGroups(values, groups); `readRecording` accepts MAT files containing timetable variables and delimited text tables such as CSV and TSV. For delimited text tables, time handling is deliberately conservative. The reader does not treat an arbitrary monotonic numeric column as seconds. It uses a table column as time only when the column is `datetime`/`duration`, when the column name is time-like such as `time_s` or `time_ms`, or when the caller explicitly passes `timeColumn` and optionally `timeUnit`. Otherwise the recording uses a synthetic sample-index time axis and keeps numeric columns as signal channels. CSV and TXT files from wearable workflows can contain preamble rows, headerless numeric data, `I0/I1`-style Arduino columns, `sec`/channel tables with per-column count rows, epoch timestamps, footer metadata rows, gaps, duplicated rows, or time axes that jump backward. The table reader tries to handle the common cases automatically and records import choices in `recording.metadata`. Backward or duplicate time steps are stitched forward with a nominal sample interval; large positive gaps are preserved and counted in `metadata.timeRepair`. If auto-detection is ambiguous, the app or caller should pass explicit import options rather than relying on inference. Example explicit text-table time options: opts = struct('timeColumn', 'timestamp', 'timeUnit', 'milliseconds'); [recording, status] = labkit.biosignal.readRecording(filepath, opts); Useful delimited-table options include `headerLine`, `hasHeader`, `timeColumn`, `timeUnit`, `signalColumns`, `fallbackFs`, and `timeRepair`. `readRecording` Options Option Type Default Valid values / meaning `headerLine` positive integer auto Header line for text tables, or first data line when `hasHeader=false`. `hasHeader` logical auto Whether the detected/explicit line contains column names. `timeColumn` name or 1-based index auto Explicit time column. Use this when auto-detection is ambiguous. `timeUnit` string infer `seconds`, `milliseconds`, `microseconds`, `nanoseconds`, or sample/index aliases. `signalColumns` names or 1-based indices all numeric non-time columns Restricts imported signal channels. `fallbackFs` positive scalar Hz none Used for synthetic sample-index time or timestamp repair fallback. `timeRepair` string `auto` `auto` stitches duplicate/backward time steps; `none`/`off` disables repair. `gapFactor` positive scalar `20` Positive gaps larger than this multiple of nominal `dt` are counted as large gaps. `useFirstNumericColumnAsTime` logical `false` Opt-in fallback for ambiguous text tables. Data Structures Functions exchange ordinary MATLAB structures with these principal fields: recording.sourcePath, recording.name, recording.signals, recording.metadata signal.time, signal.values, signal.fs, signal.name, signal.displayName, signal.metadata events.index, events.time, events.amplitude, events.score, events.label segments.values, segments.timeOffset, segments.eventIndex, segments.eventTime, segments.fs template.values, template.timeOffset, template.keptSegmentIndex, template.score measurements.perSegment, measurements.summary Open an individual function page for required fields, row/column orientation, units, empty-result behavior, and errors. Processing Provided By This Module The public functions cover: file loading and timetable/table normalization channel listing and extraction time ROI cropping generic filtering ECG/QRS peak detection event-centered segmentation template building and template-residual SNR-style segment measurements generic group summaries and pairwise Welch-style comparisons `detectEcgPeaks` supports `qrs-streaming`, `pan-tompkins`, and `local` methods. Select the method through its options; no detector-specific helper call is required. `detectEcgPeaks` Options Start from `labkit.biosignal.defaultEcgPeakOptions(method)` and override only the fields the app exposes: opts = labkit.biosignal.defaultEcgPeakOptions(\"pan-tompkins\"); opts.polarity = \"positive\"; opts.minDistanceSec = 0.35; events = labkit.biosignal.detectEcgPeaks(signal, opts); Option Type Default Valid values / meaning `method` string `qrs-streaming` `qrs-streaming`, `pan-tompkins`, or `local`. `polarity` string `auto` `auto`, `positive`, `negative`, or `absolute`. `minDistanceSec` positive scalar `0.25` for ECG methods, `0.05` for local Minimum accepted peak spacing. `thresholdStd` scalar `3` Local method robust-threshold multiplier. `smoothSec` positive scalar `0.01` Local method score smoothing window. `integrationWindowSec` positive scalar `0.150` Pan-Tompkins moving-integration window. `refineSearchSec` positive scalar `0.120` Pan-Tompkins, `0.090` streaming Detector-trace peak snap search half-window. `rawRefineSearchSec` positive scalar `0.020` Final raw-signal peak snap half-window for Pan-Tompkins and streaming. `baselineWindowSec` positive scalar `0.600` Streaming causal baseline window. `envelopeWindowSec` positive scalar `0.080` Streaming slope-envelope window. `lookaheadSec` positive scalar `0.080` Streaming local-maximum lookahead. `minTemplateScore` scalar `0.45` Streaming rolling-template QC threshold. `medianPolarityCorrection` logical `true` Streaming post-pass for `auto`/`positive` polarity: reviews recent anchors against the signal median and re-snaps inverted anchors. `medianReviewPeakCount` positive integer `3` Number of latest streaming peaks considered by the median polarity review. Other Processing Options Function Options / parameters `filterSignal(signal, spec)` `spec.type`: `bandpass`, `lowpass`, `highpass`, `none`, `off`; `spec.cutoffHz`: scalar or `[low high]`; `spec.edgeMode`: `reflect` default or `none`; `spec.edgePadSec`: padding seconds, auto by default; `spec.edgeTaperSec`: padded-edge taper seconds, default `1`. `segmentByEvents(signal, events, windowSec)` `windowSec`: `[start end]` seconds relative to event, default `[-0.35 0.35]`. `buildTemplate(segments, opts)` `opts.topN`: positive integer, default `min(30, segmentCount)`. `measureSegments(segments, template, opts)` `opts.signalWindowSec`: `[start end]`, default `[-0.06 0.06]`; `opts.noiseWindowsSec`: N-by-2 matrix, default `[-0.30 -0.20; 0.40 0.50]`. Applications decide: ECG-specific labels and workflow wording which channel is treated as ECG GUI controls, previews, plot arrangement, and printing or export layout which measurements are displayed or exported class labels and interpretation of group comparisons These application choices are intentionally not parameters of the generic biosignal functions. Reference Contract The generated page for every `labkit.biosignal` function documents its exact input structure, implemented options, defaults, legal values, empty-result and error behavior, and related functions. The repository documentation contract discovers the whole public package rather than relying on a hand-maintained function list, and executes every block labeled `Example:`. labkit.biosignal.buildTemplate Build a representative segment template. labkit.biosignal.compareGroups Summarize groups and compute pairwise Welch comparisons. labkit.biosignal.cropSignal Return a signal clipped to a time range in seconds. labkit.biosignal.defaultEcgPeakOptions Return documented defaults for ECG/QRS peak detection. labkit.biosignal.detectEcgPeaks Detect ECG/QRS peaks as event anchors. labkit.biosignal.filterSignal Apply a zero-phase FFT-domain filter to a biosignal. labkit.biosignal.getChannel Return one signal from a recording by display name or index. labkit.biosignal.listChannels Return display names for all channels in a recording. labkit.biosignal.measureSegments Measure generic template-residual segment quality. labkit.biosignal.readRecording Read a biosignal file into a standard recording struct. labkit.biosignal.segmentByEvents Extract fixed windows around event anchors. labkit.biosignal.version Return the LabKit biosignal facade contract version."},{"title":"Gamry DTA Files","url":"libraries/dta/index.html","kind":"reference","section":"reference","keywords":"libraries dta Gamry DTA Files","text":"Gamry DTA Files [API reference](../../reference/README.md) | [Electrochemistry apps](../../apps/electrochemistry/README.md) The `labkit.dta` functions find and read Gamry DTA files without opening an app. They recognize chrono, electrochemical impedance spectroscopy (EIS), and cyclic-voltammetry/charge-time (CV/CT) data, and return ordinary MATLAB structures and numeric arrays for further analysis. Start Here Use `loadFile` when you know the path to one file: [item, status] = labkit.dta.loadFile(\"measurement.DTA\", \"auto\"); if ~status.ok error(\"Could not read DTA file: %s\", status.message) end switch item.type case \"chrono\" plot(item.t_s, item.Vf_V) xlabel(\"Time (s)\") ylabel(\"Voltage (V)\") case \"eis\" loglog(item.freq_Hz, item.Zmod_ohm) xlabel(\"Frequency (Hz)\") ylabel(\"|Z| (ohm)\") case \"cvct\" curve = item.curves(1); [x, y] = labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); plot(x, y) end The second argument restricts the accepted data family. Use `\"auto\"` to detect it, or use `\"chrono\"`, `\"eis\"`, or `\"cvct\"` when loading the wrong family should be reported as a failure. Values are case-insensitive and may contain surrounding whitespace. A blank value also means `\"auto\"`. Choose a Function Task Function Identify the data family in one file [`labkit.dta.detectType`](../../reference/api/labkit/dta/detectType.html) Read one file [`labkit.dta.loadFile`](../../reference/api/labkit/dta/loadFile.html) Read a known list of files [`labkit.dta.loadFiles`](../../reference/api/labkit/dta/loadFiles.html) Find and read every DTA file below a folder [`labkit.dta.loadFolder`](../../reference/api/labkit/dta/loadFolder.html) Find paths without reading their contents [`labkit.dta.findFiles`](../../reference/api/labkit/dta/findFiles.html) Select a chrono or EIS table [`getMainCurve`](../../reference/api/labkit/dta/getMainCurve.html), [`getZCurve`](../../reference/api/labkit/dta/getZCurve.html) Extract numeric columns from parsed tables [`getColumn`](../../reference/api/labkit/dta/getColumn.html), [`getCurveXY`](../../reference/api/labkit/dta/getCurveXY.html) Locate chrono pulse windows [`labkit.dta.detectPulses`](../../reference/api/labkit/dta/detectPulses.html) Check library version and compatibility [`labkit.dta.version`](../../reference/api/labkit/dta/version.html) Loading Several Files `loadFiles` keeps every successful item and records failures instead of stopping at the first unreadable file: files = [\"run-01.DTA\", \"run-02.DTA\", \"run-03.DTA\"]; [items, report] = labkit.dta.loadFiles(files, \"chrono\"); fprintf(\"Loaded %d of %d files.\\n\", ... report.nLoaded, report.nRequested) for k = 1:numel(report.failed) warning(\"%s: %s\", ... report.failed(k).filepath, report.failed(k).message) end `report.statuses` contains one status in the same order as the input paths. `items` contains successful items only, so use the status array when you need to match a result to every original position. Use `loadFolder` for recursive folder import. It adds `folder`, `filepaths`, and `nDiscovered` to the same report: [items, report] = labkit.dta.loadFolder(\"experiment\", \"eis\"); Chrono Data A chrono item contains the parsed metadata and tables, the selected transient curve, pulse information, and these unit-explicit vectors: Field Meaning `t_s` Time in seconds `Vf_V` Measured voltage in volts `Im_A` Measured current in amperes `pt` Point number from the file, or a generated zero-based sequence `controlMode` `\"current\"`, `\"voltage\"`, or `\"unknown\"`, inferred from step metadata `pulse` Cathodic, gap, and anodic windows returned by `detectPulses` `meta`, `tables`, `curve` Parsed metadata, all parsed tables, and the selected T/Vf/Im table Rows with nonfinite T, Vf, or Im values are removed. Duplicate time values keep their first occurrence. Loading fails when fewer than two valid samples remain. Pulse Detection The default mode, `\"Metadata first, then auto\"`, first looks for negative and positive ISTEP/TSTEP metadata. If current-step metadata is absent, it can use VSTEP/TSTEP timing. If metadata detection fails, it locates dominant negative and positive segments in the measured current. [pulse, message] = labkit.dta.detectPulses( ... item.t_s, item.Im_A, item.meta, \"Metadata first, then auto\"); Other modes are `\"Metadata only\"` and `\"Auto from Im only\"`. Programmatic aliases `\"metadata_first\"`, `\"metadata_only\"`, and `\"current_only\"` are also accepted. Current-based detection uses 25 percent of `max(abs(Im))` as its threshold, with a `1e-12 A` floor, then selects the longest cathodic segment and the longest later anodic segment. An unknown mode returns `pulse.ok=false` with an explanatory message; it is never silently treated as the default. On success, `pulse.pre`, `pulse.cath`, `pulse.gap`, `pulse.anod`, and `pulse.post` provide unit-explicit `start_s` and `end_s` windows. `pulse.cath.current_A` and `pulse.anod.current_A` contain nominal or measured phase currents, and `pulse.gap.center_s` contains the alignment center. `pulse.ok` is false and numeric fields are `NaN` when a valid cathodic/anodic pair cannot be found. Always check `pulse.ok` before using the windows in a calculation. Version 3 returns only this unit-explicit item and pulse model. Chrono items use `t_s`, `Vf_V`, and `Im_A`; EIS items use the fields listed below. Earlier unit-ambiguous aliases are not emitted. EIS Data An EIS item selects the `ZCURVE` table, or the first compatible table with Freq, Zreal, and Zimag columns. It exposes these unit-explicit vectors: Field Meaning `freq_Hz` Frequency in hertz `time_s` Elapsed time in seconds `point` Point number `Zreal_ohm` Real impedance in ohms `Zimag_ohm` Imaginary impedance in ohms `negZimag_ohm` Negative imaginary impedance in ohms `Zmod_ohm` Impedance magnitude in ohms `Zphz_deg` Phase angle in degrees `Idc_A` DC current in amperes `Vdc_V` DC voltage in volts Rows are retained when at least one primary impedance value is finite. Missing optional columns are returned as same-length `NaN` vectors. Loading fails when fewer than two usable ZCURVE rows remain. CV/CT Data A CV/CT item contains `curves` in file order and exposes the parsed scan rate as `scanRate_V_per_s`. The parser converts a Gamry `SCANRATE` value from mV/s to V/s. Each curve retains its headers, units, numeric data, and information about which values were parsed successfully. Use exact header names with `getCurveXY`: curve = item.curves(1); [voltageV, currentA, xname, yname] = ... labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); Rows where either selected column is `NaN` are removed as a pair. `getCurveXY` returns empty arrays and empty names when either header is absent. Parsed Table Structure Chrono and EIS parsers return `tables`, a structure array containing the table name, headers, units when available, and numeric data. Use `getMainCurve` or `getZCurve` to select a compatible table, then use `getColumn` for case-insensitive column lookup: [curve, ok, message] = labkit.dta.getMainCurve(item.tables); if ok timeSec = labkit.dta.getColumn(curve, \"T\"); currentA = labkit.dta.getColumn(curve, \"Im\"); end `getColumn` returns `[]` when the requested header is absent. It does not change units or remove `NaN` values. File Format and Limitations The reader treats DTA files as tab-delimited text. It scans metadata and named TABLE or CURVE sections, preserves headers and units when available, and converts numeric rows conservatively. Gamry software versions and experiment methods can produce additional sections or columns. Successful parsing means that a supported core curve was found; it does not imply that every vendor-specific field was interpreted. `detectType` and `loadFile` return readable status messages for missing files, unsupported content, parse failures, and expected-kind mismatches. Invalid argument types and invalid expected-kind strings throw documented LabKit errors. Reference Contract Open an individual DTA function page for exact accepted kinds, path forms, status fields, thrown errors, and related loaders or accessors. Public help is checked against implemented options and its executable examples are run in a clean MATLAB test process. Related Topics [CIC](../../apps/electrochemistry/cic/README.md), [CSC](../../apps/electrochemistry/csc/README.md), and [EIS](../../apps/electrochemistry/eis/README.md) use DTA records in interactive workflows. [Contract functions](../../framework/compatibility/contracts.md) explain how apps declare a compatible `labkit.dta` API version. [Project history](../../history/README.md) lists parser, schema, and compatibility changes. labkit.dta.detectPulses Locate cathodic and anodic pulse windows in chrono data. labkit.dta.detectType Identify the supported Gamry DTA data family in a file. labkit.dta.findFiles Find DTA files in a folder and its subfolders. labkit.dta.getColumn Extract a named column from a parsed DTA table. labkit.dta.getCurveXY Extract paired X and Y data from a parsed CV/CT curve. labkit.dta.getMainCurve Select the transient table containing T, Vf, and Im data. labkit.dta.loadFile Read one supported Gamry DTA file. labkit.dta.loadFiles Read a list of Gamry DTA files. labkit.dta.loadFolder Read every DTA file below a folder. labkit.dta.version Return version information for the DTA API."},{"title":"Image API","url":"libraries/image/index.html","kind":"reference","section":"reference","keywords":"libraries image Image API","text":"Image API [Public API index](../../reference/README.md) | [App guide](../../apps/README.md) `labkit.image.*` provides GUI-free image file IO, conversion, preview sizing, and common enhancement operations. App-specific registration, measurement, ROI, matching, and export workflows build on these functions. `labkit.image.version()` returns image-library version and compatibility information used by App `definition.m` requirement declarations. Common Calls filter = labkit.image.fileDialogFilter(\"IncludeAll\", true); paths = labkit.image.normalizePaths(rawPaths); records = labkit.image.readFiles(paths); source = labkit.image.im2double(records(1).image); preview = labkit.image.ensureRgb(source); preview = min(max(preview, 0), 1); luma = labkit.image.rgb2gray(preview); [preview, scale] = labkit.image.resizeToFit(preview, \"MaxHeight\", 1500); [preview, budget] = labkit.image.previewBudget(preview, \"MaxPixels\", appPreviewBudget); blurred = labkit.image.meanFilter2(preview(:, :, 1), 7); enhanced = labkit.image.adjustBrightnessContrast(preview, 10, 20); enhanced = labkit.image.localContrast(enhanced, 50, 3); enhanced = labkit.image.sharpen(enhanced, 30, 1.5); enhanced = labkit.image.grayWorldWhiteBalance(enhanced, 80, 0); labkit.image.writeFile(enhanced, outputPath); Normalization Helpers `labkit.image.im2double` follows the MATLAB `im2double` call contract for supported numeric image classes, including the optional `\"indexed\"` mode. `labkit.image.rgb2gray` follows the MATLAB `rgb2gray` call contract for RGB images and colormaps while using the documented Rec.601 luma transform. `labkit.image.ensureRgb` changes channel shape only. It expands grayscale data to three channels or drops channels after RGB without changing class or sample values. Callers that need display-ready RGB data explicitly compose `im2double`, `ensureRgb`, and `[0, 1]` clamping as shown above. `labkit.image.previewBudget` preserves native pixels by default. An App passes an explicit finite `\"MaxPixels\"` value only when its own workflow permits sampling; the resulting integer coordinate scale is returned with the preview. Provided Operations The module provides: supported source-image extension lists and file-dialog filters path normalization and display names `imread`/`imwrite` wrappers that normalize app-facing edge behavior MATLAB-compatible image conversion, explicit RGB shaping, preview-size fitting, and edge-normalized mean filtering caller-owned display-pixel budget helpers for responsive previews while preserving a documented integer coordinate scale generic image enhancement primitives such as brightness/contrast, HSV hue/saturation, gray-world white balance, local contrast, and sharpening Applications add: which filters or processing steps appear in the app UI parameter labels, defaults, validation ranges, and step history semantics reference matching, protected background/ROI behavior, scientific formulas, crop geometry, focus stacking, curvature, and DIC processing export manifests, result columns, filenames, failed-row policy, alerts, and log text Use `labkit.image.readFiles` when an app needs generic source-image records. Apps may copy the returned `path`, `name`, and `image` fields into their own item structures. Specialized formats and result structures remain documented by the app that uses them. Reference Contract The generated Image API pages linked from the [public API index](../../reference/README.md) document exact syntax, inputs, outputs, implemented options and defaults, legal values, failure behavior, examples, and related functions. In particular, [`resizeToFit`](../../reference/api/labkit/image/resizeToFit.html) accepts only the documented `\"bilinear\"` and `\"nearest\"` methods and rejects any other method instead of silently selecting an interpolation policy. labkit.image.adjustBrightnessContrast Apply simple brightness and contrast adjustment. labkit.image.ensureRgb Return image data with exactly three color channels. labkit.image.fileDialogFilter Return a file-chooser-compatible image filter. labkit.image.grayWorldWhiteBalance Apply generic gray-world white balance. labkit.image.im2double Convert image data to double using MATLAB's im2double contract. labkit.image.localContrast Enhance local value-channel contrast with a mean-filter mask. labkit.image.meanFilter2 Apply normalized 2-D mean filtering with edge correction. labkit.image.normalizePaths Normalize image file path inputs to a string column. labkit.image.previewBudget Downsample image data to fit a display pixel budget. labkit.image.readFiles Read image files into path/name/image records. labkit.image.resizeToFit Resize an image to fit within maximum row/column limits. labkit.image.rgb2gray Convert RGB data using MATLAB's rgb2gray call contract. labkit.image.sharpen Apply generic unsharp-mask sharpening. labkit.image.version Return the LabKit image facade contract version. labkit.image.writeFile Write one image file, creating the parent folder when needed."},{"title":"Intan RHS Recordings","url":"libraries/rhs/index.html","kind":"reference","section":"reference","keywords":"libraries rhs Intan RHS Recordings","text":"Intan RHS Recordings [API reference](../../reference/README.md) | [Neurophysiology apps](../../apps/neurophysiology/README.md) The `labkit.rhs` functions inspect Intan RHS recordings and read selected waveform windows without loading an entire file into memory. They expose file metadata, channel names, data-block layout, timestamps, and samples in physical units. The functions can be called directly from MATLAB without opening a LabKit app. Start Here Inspect a file first when you need to see its channels and duration: [info, status] = labkit.rhs.inspectFile(\"recording.rhs\"); if ~status.ok error(\"Could not inspect RHS file: %s\", status.message) end fprintf(\"%.2f s at %.0f Hz\\n\", info.durationSec, info.sampleRateHz) disp(info.channelTable(:, [\"family\", \"nativeName\", \"customName\"])) Then read only the channels and times required by the analysis: opts = struct( ... \"family\", \"amplifier\", ... \"channels\", [\"C-001\", \"C-002\"], ... \"timeRangeSec\", [0.100 0.150]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", opts); if status.ok plot(window.timeSec, window.values) xlabel(\"Time (s)\") ylabel(\"Amplitude (microvolts)\") legend(window.channels) end `window.values` is always samples-by-channels. `window.timeSec` contains the timestamps stored in the recording, converted to seconds. Choose a Function Task Function Find RHS paths recursively [`labkit.rhs.findFiles`](../../reference/api/labkit/rhs/findFiles.html) Read header, channel, and duration information [`labkit.rhs.inspectFile`](../../reference/api/labkit/rhs/inspectFile.html) Build the byte/sample layout for later reads [`labkit.rhs.indexFile`](../../reference/api/labkit/rhs/indexFile.html) Read one channel family and time interval [`labkit.rhs.readWindow`](../../reference/api/labkit/rhs/readWindow.html) Check library version and compatibility [`labkit.rhs.version`](../../reference/api/labkit/rhs/version.html) Header Information `inspectFile` reads the header and calculates how much complete waveform data is available. Important fields include: Field Meaning `fileVersion` Intan file-format major and minor version `sampleRateHz` Amplifier sample rate in hertz `durationSec` Duration represented by complete data blocks `channelFamilies` Channel structures grouped as amplifier, board ADC, board DAC, digital input, and digital output `channelTable` One table combining enabled stored channels and their native/custom names `frequencyParameters` Requested and actual filter and bandwidth settings `stimParameters` Stimulation step size and charge-recovery settings `dcAmplifierSaved` Whether DC-amplifier samples are stored `blockCount`, `sampleCount` Number of complete blocks and samples `exactBlocks` Whether the data section ends on a complete block boundary Header-only files can be inspected successfully and have a sample count of zero. A file with trailing partial bytes remains readable through its last complete block; `exactBlocks` is false and the status message reports the condition. Channel Families and Units `readWindow` reads one family at a time: Family Accepted examples Returned unit `\"amplifier\"` `\"amplifier\"`, `\"amp\"` microvolts `\"stim\"` `\"stim\"`, `\"stimulus\"`, `\"stimcurrent\"` microamps `\"dcAmplifier\"` `\"dc\"`, `\"dcamplifier\"`, `\"dcamp\"` volts `\"boardAdc\"` `\"boardadc\"`, `\"adc\"` volts `\"boardDac\"` `\"boarddac\"`, `\"dac\"` volts `\"boardDigIn\"` `\"boarddigin\"`, `\"digitalin\"`, `\"digin\"` logical 0 or 1 `\"boardDigOut\"` `\"boarddigout\"`, `\"digitalout\"`, `\"digout\"` logical 0 or 1 Family matching ignores case and punctuation. Selecting a family that has no stored channels returns `status.ok=false` with an empty window. Selecting Channels Set `channels=[]` to read every channel in the selected family. Otherwise use one-based numeric positions, native names, or custom names: opts.channels = [1 3]; opts.channels = [\"C-001\", \"C-003\"]; opts.channels = [\"Reference\", \"Recording\"]; Names are matched exactly first. If no exact match is found, comparison ignores case and punctuation, so `\"C001\"` can match the native name `\"C-001\"`. Returned `window.channels` always contains native channel names. Selecting a Time Window `timeRangeSec=[start end]` selects an inclusive sample interval. The start is clamped to zero, `Inf` reads through the last complete sample, and an end past the recording duration is clipped. For example: opts.timeRangeSec = [0 0.050]; % first 50 ms opts.timeRangeSec = [1.5 Inf]; % from 1.5 s to the end A valid request that begins after the available data returns `status.ok=true` with empty `timeSec` and `values`, allowing a caller to distinguish an empty interval from a file-read failure. Block Indexing and Lazy Reads RHS waveform data is stored in 128-sample blocks. `indexFile` calculates the header offset, bytes per block, complete block count, sample count, and duration without reading the waveform arrays. `readWindow` uses this index to seek directly to the blocks that overlap the requested interval. [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); if status.ok fprintf(\"%d samples in %d blocks\\n\", ... index.sampleCount, index.blockCount) end This makes short previews and event-centered analyses practical for recordings that are too large to load all at once. Physical-Unit Conversion The reader follows the Intan RHS block layout and applies the documented conversion gains and code offsets for amplifier, DC-amplifier, stimulation, board ADC, and board DAC data. Digital inputs and outputs are decoded from their stored bit positions. No filtering, baseline correction, event detection, channel-role interpretation, or response measurement is performed by these file-reading functions. Errors and Unsupported Files Missing files, malformed headers, unsupported magic numbers, absent channel families, and waveform read failures are returned through `status.ok=false` and `status.message`. Invalid function arguments, such as an unknown family, channel, or reversed time range, throw the documented `labkit:rhs:*` error. Successful parsing confirms that the supported RHS header and complete block layout were readable. It does not validate an experiment protocol, decide which channels form a differential signal, or assess recording quality. Reference Contract The generated pages for `findFiles`, `inspectFile`, `indexFile`, `readWindow`, and `version` state complete inputs, outputs, options, defaults, legal values, status/error behavior, and related APIs. Contract tests discover the complete public package and execute each documented `Example:` block. Related Topics [RHS Preview](../../apps/neurophysiology/rhs-preview/README.md) provides interactive waveform review and channel-role drafting. [Nerve Response Analysis](../../apps/neurophysiology/nerve-response-analysis/README.md) reads selected RHS channels for event and response calculations. [Biosignal functions](../biosignal/README.md) operate on imported recordings and generic waveforms after file IO. [Project history](../../history/README.md) lists RHS parser and app changes. labkit.rhs.findFiles Find Intan RHS files in a folder and its subfolders. labkit.rhs.indexFile Build a block index for reading selected RHS time windows. labkit.rhs.inspectFile Read metadata and data-layout information from an RHS file. labkit.rhs.readWindow Read selected channels and times from an Intan RHS file. labkit.rhs.version Return version information for the RHS API."},{"title":"Thermal Images","url":"libraries/thermal/index.html","kind":"reference","section":"reference","keywords":"libraries thermal Thermal Images","text":"Thermal Images [API reference](../../reference/README.md) | [FLIR Thermal app](../../apps/image-measurement/flir-thermal/README.md) The `labkit.thermal` functions read FLIR radiometric JPEG files, expose their raw sensor data and calibration metadata, convert sensor values to degrees Celsius, and create display-ready RGB images. They can be called from MATLAB without opening a LabKit app. Start Here Use this sequence when you want to analyze thermal files in a script: files = [\"capture-01.rjpg\", \"capture-02.rjpg\"]; [records, report] = labkit.thermal.readFiles(files, ... struct(\"SkipInvalid\", true)); for k = 1:numel(records) temperatureC = records(k).temperatureC; fprintf(\"%s: %.1f to %.1f C\\n\", records(k).name, ... min(temperatureC, [], \"all\"), max(temperatureC, [], \"all\")); end fprintf(\"Loaded %d files; skipped %d.\\n\", ... report.loaded, report.skipped) `readFiles` preserves the input order. With `SkipInvalid` set to `true`, it returns every readable record and describes unreadable files in `report.failures`. Without that option, the first read error stops the call. Choose a Function Task Function Read one radiometric image [`labkit.thermal.readFile`](../../reference/api/labkit/thermal/readFile.html) Read a list of images [`labkit.thermal.readFiles`](../../reference/api/labkit/thermal/readFiles.html) Check a file without throwing a read error [`labkit.thermal.inspectFile`](../../reference/api/labkit/thermal/inspectFile.html) Convert raw counts with supplied calibration [`labkit.thermal.rawToTemperatureC`](../../reference/api/labkit/thermal/rawToTemperatureC.html) Convert a numeric matrix to RGB colours [`labkit.thermal.renderImage`](../../reference/api/labkit/thermal/renderImage.html) Build a file-dialog filter [`labkit.thermal.fileDialogFilter`](../../reference/api/labkit/thermal/fileDialogFilter.html) Test or list recognized extensions [`isSupportedPath`](../../reference/api/labkit/thermal/isSupportedPath.html), [`supportedExtensions`](../../reference/api/labkit/thermal/supportedExtensions.html) Check library version and compatibility [`labkit.thermal.version`](../../reference/api/labkit/thermal/version.html) Thermal Records `readFile` returns one structure with these fields: Field Meaning `path`, `name` Source location and filename `format` Detected radiometric container format `raw` Decoded sensor-count matrix `temperatureC` Same-size double matrix in degrees Celsius; invalid conversion pixels are `NaN` `units` `\"C\"` when temperature conversion succeeded, otherwise `\"raw\"` `metadata` Parsed FLIR tags, calibration values, record information, and conversion diagnostics `message` Short description of the conversion result An ordinary JPEG preview is not a temperature matrix. The reader looks for an embedded FLIR FFF `RawThermalImage` record and uses that sensor data for the conversion. A `.jpg` or `.rjpg` extension therefore indicates only a candidate file. Call `inspectFile` when you need to know whether the contents are actually readable as thermal data. Temperature Correction The default `\"environment\"` correction uses the camera's Planck constants and, when available, emissivity, object distance, reflected apparent temperature, atmospheric temperature, humidity, infrared-window temperature, and infrared-window transmission. Missing environmental values receive documented defaults so that conversion can continue. The five Planck constants never receive fallback values. Use two outputs from `rawToTemperatureC` to see exactly what happened: [temperatureC, diagnostics] = labkit.thermal.rawToTemperatureC( ... raw, calibration, struct(\"Correction\", \"environment\")); if diagnostics.usedDefaults fprintf(\"Defaults used for: %s\\n\", ... strjoin(diagnostics.defaultedFields, \", \")) end `diagnostics.parameterSources` records `\"calibration\"` or `\"default\"` for each environmental input. A conversion that used defaults may be useful for previewing data, but the result is not evidence that the assumed emissivity, distance, humidity, or temperatures match the experiment. Check those values before using the temperatures in quantitative analysis. Choose `\"planck-basic\"` only when you intentionally want the conversion based on the five Planck constants alone: temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration, ... struct(\"Correction\", \"planck-basic\")); Rendering a Temperature Matrix `renderImage` maps a numeric matrix onto a colour palette. It does not change the temperatures and does not perform calibration. rgb = labkit.thermal.renderImage(temperatureC, ... struct(\"Limits\", [20 40], \"Palette\", \"iron\", \"Levels\", 256)); image(rgb) axis image off Values outside `Limits` are clipped to the end colours. Supported palettes are `\"turbo\"`, `\"parula\"`, `\"hot\"`, `\"gray\"`, and `\"iron\"`. The FLIR Thermal app also offers display-only log and gamma controls; those controls are app features and do not alter exported temperature values. Supported Files and Limitations The current reader supports FLIR radiometric JPEG and RJPEG files with an embedded FFF raw-thermal record. Camera models and firmware revisions can encode metadata differently. Use `inspectFile` or `readFiles` with `SkipInvalid=true` for mixed or uncertain inputs, and retain the returned failure messages when reporting an unsupported file. Do not infer measurement accuracy from successful parsing alone. Quantitative temperature accuracy still depends on the camera calibration and on correct environmental inputs for the photographed material and scene. Reference Contract Each generated Thermal API page distinguishes returned status from thrown errors and documents every implemented option, default, legal value, output field, and related function. Executable examples and package-wide public-help discovery prevent the manual and callable surface from silently diverging. Related Topics [Image functions](../image/README.md) cover ordinary image IO and image processing that does not use radiometric calibration. [FLIR Thermal app](../../apps/image-measurement/flir-thermal/README.md) provides interactive file review, measurements, display controls, and exports. [Project history](../../history/README.md) lists changes to thermal file support and calibration behavior. labkit.thermal.fileDialogFilter Create a file-selection filter for thermal images. labkit.thermal.inspectFile Check whether a file contains supported radiometric data. labkit.thermal.rawToTemperatureC Convert FLIR raw sensor values to degrees Celsius. labkit.thermal.readFile Read radiometric image data and convert it to degrees Celsius. labkit.thermal.readFiles Read several radiometric image files. labkit.thermal.renderImage Map a thermal matrix to an RGB image. labkit.thermal.version Return version information for the thermal API."},{"title":"Electrochemistry Apps","url":"apps/electrochemistry/index.html","kind":"app family","section":"apps","keywords":"apps electrochemistry Electrochemistry Apps","text":"Electrochemistry Apps The Electrochemistry family reads Gamry DTA files through `labkit.dta` and keeps experiment-specific analysis, plotting, defaults, and export schemas in the owning app. Source DTA files are never modified. Choose An App Measurement or task App Required DTA content Main result Compare voltage and current transients across files [Chrono Overlay](chrono-overlay/README.md) chrono curve with `T`, `Vf`, `Im` aligned overlay and wide CSV Charge-injection capacity and polarization voltage [CIC](cic/README.md) biphasic chrono transient per-file CIC, charge, and voltage metrics Charge-storage capacity by time and CV integration [CSC](csc/README.md) CV/CT cycles with `T`, `Vf`, `Im` per-cycle CT/CV CSC comparison Impedance inspection and export [EIS](eis/README.md) `ZCURVE` configurable Nyquist/Bode-style overlay Steady pulse resistance [VT Resistance](vt-resistance/README.md) biphasic chrono transient cathodic, anodic, and mean resistance Shared File Behavior File controls accept one or more `.DTA` files from one folder in a single selection. A canceled chooser leaves the current project unchanged. CIC, CSC, and VT Resistance use the selected row as the active preview while retaining the loaded source list for batch export. Invalid items are reported per file; one failed item does not silently replace another result. The DTA library returns structured items, curve tables, headers, units, metadata, parser messages, and status. Apps use exact required columns for scientific calculations and do not infer missing physical quantities from a plot label. The Apps require the version 3 unit-explicit DTA contract rather than branching between duplicate legacy and canonical item fields. Units And Traceability Time is seconds, voltage is volts, current is amperes, impedance is ohms, charge is coulombs, electrode area is square centimetres, and normalized CIC or CSC is reported in the unit shown by the app. UI display conversions do not change stored base-unit calculations. Export tables include the source identity and analysis settings needed to interpret the result. Runtime-generated `.labkit.json` files are provenance manifests describing inputs, parameters, and output roles; they are not a second numerical result and can be kept with the corresponding CSV. Use Without The GUI Reusable parsing and curve access live in `labkit.dta`. App-specific numeric operations such as `cic.analysisRun.computeCIC`, `csc.analysisRun.computeCSC`, and `vt_resistance.analysisRun.computeResistance` are documented on the owning app page and in the generated API reference. Related Modules [DTA Library](../../libraries/dta/README.md) [API Reference](../../reference/README.md) [All Apps](../README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics. csc.analysisRun.computeCSC Compare time-integrated and scan-rate-derived electrode charge. vt_resistance.analysisRun.computeResistance Estimate cathodic and anodic resistance from a VT pulse."},{"title":"API Reference","url":"reference/index.html","kind":"reference","section":"reference","keywords":"reference API Reference","text":"API Reference This index covers domain libraries and GUI-free App APIs. The App SDK itself is documented under [Framework: App SDK API](../framework/app-sdk-api.md), so its contracts stay with the lifecycle and architecture guidance they support. Use this reference to look up supported MATLAB functions. Every function name in the generated module tables is a direct link to an individual page with syntax, arguments, outputs, behavior, source, and related APIs. Find The Right Layer Need Module guide App lifecycle, layout, plotting, interaction, state, debugging, and compatibility requirements [App Framework](../framework/README.md) Image IO and generic image operations [Image](../libraries/image/README.md) Radiometric image decoding and temperature conversion [Thermal](../libraries/thermal/README.md) Gamry DTA discovery, parsing, curves, and pulses [DTA](../libraries/dta/README.md) Intan RHS inspection and waveform windows [RHS](../libraries/rhs/README.md) Generic biosignal import and processing [Biosignal](../libraries/biosignal/README.md) Scientific operations owned by one app [Apps](../apps/README.md) Module guides explain scope, data models, normal call sequences, algorithms, and representative examples. Function pages are the source for exact callable contracts. Files in `private/` directories and App helpers without a complete public help contract are not supported entry points. Compatibility Each reusable library publishes a `version()` result. Apps declare compatible library ranges in the `Requirements` field of their single `definition.m` contract. A function documented here is public within that library version; compatibility across a breaking version is not implied. The tables below are generated from current MATLAB source. Each entry links to the current function help. labkit.biosignal Recording import, channel access, filtering, events, templates, and measurements. Framework Compatibility Version and MathWorks-product requirement contracts. labkit.dta Gamry DTA discovery, parsing, curve access, and pulse detection. labkit.image Generic image file IO, normalization, resizing, filtering, and enhancement primitives. labkit.rhs Intan RHS discovery, indexing, inspection, and bounded waveform reads. labkit.thermal Radiometric image inspection, conversion, reading, and rendering. Batch Image Crop Supported GUI-free operations owned by the Batch Crop app in the Image Measurement family. Charge-Injection Capacity Supported GUI-free operations owned by the Cic app in the Electrochemistry family. Charge-Storage Capacity Supported GUI-free operations owned by the Csc app in the Electrochemistry family. Curvature Measurement Supported GUI-free operations owned by the Curvature app in the Image Measurement family. DIC Postprocess Supported GUI-free operations owned by the Dic Postprocess app in the Digital Image Correlation family. DIC Preprocess Supported GUI-free operations owned by the Dic Preprocess app in the Digital Image Correlation family. ECG Print Supported GUI-free operations owned by the Ecg Print app in the Wearable family. FLIR Thermal Supported GUI-free operations owned by the Flir Thermal app in the Image Measurement family. Focus Stack Supported GUI-free operations owned by the Focus Stack app in the Image Measurement family. Gait Analysis Supported GUI-free operations owned by the Gait Analysis app in the Gait family. Image Enhance Supported GUI-free operations owned by the Image Enhance app in the Image Measurement family. Image Match Supported GUI-free operations owned by the Image Match app in the Image Measurement family. Nerve Response Analysis Supported GUI-free operations owned by the Nerve Response Analysis app in the Neurophysiology family. Response Review And Stats Supported GUI-free operations owned by the Response Review Stats app in the Neurophysiology family. RHS Preview Supported GUI-free operations owned by the Rhs Preview app in the Neurophysiology family. T-Test Wizard Supported GUI-free operations owned by the Ttest Wizard app in the Statistics family. Video Marker Supported GUI-free operations owned by the Video Marker app in the Image Measurement family. VT Resistance Supported GUI-free operations owned by the Vt Resistance app in the Electrochemistry family."},{"title":"Chrono Overlay","url":"apps/electrochemistry/chrono-overlay.html","kind":"app","section":"apps","keywords":"apps electrochemistry chrono overlay Chrono Overlay","text":"Chrono Overlay Chrono Overlay compares voltage and current transients from multiple Gamry DTA files on a common pulse-centered time axis and exports the aligned curves. Requirements And Launch Each source must contain a readable chrono curve with time, voltage, and current data. labkit_ChronoOverlay_app Inputs Use **Add DTA files** to select one or more `.DTA` files from one directory. The app parses each file as chrono data and reports unreadable items. The file list controls curve order, legend labels, and removal; selection does not discard other loaded curves. Saved projects preserve the ordered file list and reopen it through portable source references. Basic Workflow Add the DTA files to compare. Inspect the overlaid voltage and current plots. Choose seconds, milliseconds, or sample number for the X axis. Set line width, legend visibility, and grid visibility. Export the aligned curves CSV. Plot styling does not alter exported values. Removing a file removes its curves from both plots and the next export. Alignment Algorithm For each valid transient, the app uses the detected cathodic/anodic blank interval and defines time zero at its center. If pulse detection or the gap center is unavailable, it explicitly falls back to the first recorded sample and reports that fallback in the app log. If files use different sample times, export constructs a merged aligned-time axis and interpolates each voltage/current series onto that axis. Missing coverage remains missing; the app does not extrapolate a transient beyond its recorded time range. Parameters Parameter Default Effect X axis Time (s) plot coordinate only; alternatives are Time (ms) and Sample # Line width 1.3 rendered curve width Show file-name legend on legend visibility Show grid on axes grid visibility Output **Export curves CSV** writes `TimeGapCenterAligned_s` followed by paired voltage and current columns for each source. Column names retain a sanitized source identity. The app also writes a result manifest containing source references, current parameters, and the CSV output role. Use Without The GUI [items, status] = labkit.dta.loadFiles([\"run01.DTA\", \"run02.DTA\"], ... \"chrono\"); assert(all([status.ok]), \"One or more DTA files could not be read.\"); aligned = arrayfun(@(item) ... chrono_overlay.sourceFiles.alignByPulseGap(item), items); tableOut = chrono_overlay.resultFiles.buildOverlayExportTable(aligned); These app-owned helpers expose the workflow to scripts but are not currently listed as stable public APIs; reusable DTA reading is supported through `labkit.dta`. Errors And Limitations A source without a detectable biphasic gap is aligned to its first sample; review the log before comparing it with gap-centered files. Interpolation enables a common export table but does not make differently sampled instruments scientifically identical. Compare only experiments whose control mode, scale, and units are compatible. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [CIC](../cic/README.md) [VT Resistance](../vt-resistance/README.md)"},{"title":"Charge-Injection Capacity","url":"apps/electrochemistry/cic.html","kind":"app","section":"apps","keywords":"apps electrochemistry cic Charge-Injection Capacity","text":"Charge-Injection Capacity The CIC app measures charge delivered by a biphasic current pulse, normalizes charge by electrode area, and reports voltage-transient polarization metrics at a controlled delay after each pulse phase. Requirements And Launch The app requires a chrono DTA curve with valid `T`, `Vf`, and `Im` columns. labkit_CIC_app Inputs And Batch Behavior Add one or more chrono `.DTA` files. The selected row is decoded for immediate preview; batch calculation is performed with the same analysis settings when results are exported. This avoids repeatedly decoding every large file while the user is only switching previews. Saved projects preserve file order and portable source identity through removal, later additions, and reopen. Electrode area comes from a positive UI override when supplied, otherwise from the parsed DTA metadata. Without a valid positive area, charge in coulombs can still be calculated but area-normalized CIC fields are `NaN`. Basic Workflow Add chrono DTA files and select a representative file. Choose a water-window preset or set custom cathodic/anodic limits. Choose pulse detection, post-pulse delay, electrode area, phase mode, and output unit. Inspect detected phases, sampling markers, and voltage/current plots. Review the current-file summary. Export the batch result CSV. Analysis Parameters Parameter Default Legal choices or unit Preset Pt (-0.6 to 0.8 V) Pt, PEDOT:PSS, or Custom Cathodic / anodic limits -0.6 / 0.8 volts Delay 10 microseconds after each phase end Pulse detection Metadata first, then auto metadata fallback, metadata only, or current-only auto CIC phase Total biphasic Cathodic phase, Anodic phase, Total biphasic CIC unit mC/cm^2 mC/cm^2 or uC/cm^2 Use measured current on integrate measured `Im`; otherwise use detected pulse estimates Plot markers, limit lines, shading, axes, and grid choices affect presentation only. Analysis controls apply to the entire exported batch. Calculation Semantics The app removes samples where time, voltage, or current is not finite and requires at least five valid points. `labkit.dta.detectPulses` identifies the cathodic phase, interpulse gap, anodic phase, and surrounding baseline windows. Measured-current mode integrates each detected current phase over time. The phase charges `Qc_C` and `Qa_C` are positive magnitudes; total charge is their sum. With area `A` in cm^2: CIC_mC_cm2 = 1000 * Q_C / A_cm2 Maximum cathodic and anodic polarization potentials are interpolated at `phase end + delay`. The app does not extrapolate: if either requested time is outside the recorded range, that file fails with an explicit delay message. Baseline candidates are selected from pre-pulse, interpulse, and post-pulse windows, with documented fallbacks recorded in the result struct. Water-window status compares the calculated polarization potentials with the selected cathodic and anodic limits. The limits are an app policy and do not change the integrated charge. Output Schema The batch CSV contains source identity, area, delay, pulse-detection method, phase timing, measured/estimated current information, charge fields, area-normalized CIC fields, polarization and baseline values, water-window status, and a per-file result message. `Area_cm2` and `Delay_us` are exported so normalization and sample timing remain auditable. A `.labkit.json` result manifest accompanies the CSV and records source references, parameters, and the output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message); options = struct( ... \"delay_s\", 10e-6, ... \"cathLimit\", -0.6, ... \"anodLimit\", 0.8, ... \"area_cm2\", 0.05, ... \"pulseMode\", \"Metadata first, then auto\", ... \"usedMeasuredCurrent\", true); result = cic.analysisRun.computeCIC(item, options); assert(result.ok, result.message); Errors And Limitations Missing `T`, `Vf`, or `Im`, fewer than five valid samples, or failed pulse detection returns `ok=false` with a message. A delay outside the recorded time range fails instead of extrapolating. Area-normalized values require a positive area; invalid area is not replaced by an arbitrary default. CIC results depend on pulse detection, baseline selection, sampling rate, electrode area, and whether measured or estimated current is used. Related Topics [Electrochemistry family](../README.md) [DTA pulse detection](../../../libraries/dta/README.md) [VT Resistance](../vt-resistance/README.md) [API Reference](../../../reference/README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics."},{"title":"Charge-Storage Capacity","url":"apps/electrochemistry/csc.html","kind":"app","section":"apps","keywords":"apps electrochemistry csc Charge-Storage Capacity","text":"Charge-Storage Capacity The CSC app compares charge obtained from time-domain current integration with charge obtained from cyclic-voltammetry integration for every readable CV/CT cycle in one or more Gamry DTA files. Requirements And Launch Each analyzed cycle must expose exact `T`, `Vf`, and `Im` columns and a positive scan rate. labkit_CSC_app Inputs And Selection Add one or more CV/CT `.DTA` files. The selected file determines the current curve list, readout, and plots. Selecting another file resets the curve selection and default plot quantities to that file; it does not silently keep a cycle from the previous source. Saved projects preserve the successfully decoded file order through removal, later additions, and reopen. The default curve selection is **All cycles**. Individual cycle selection updates the comparison readout for that cycle. Basic Workflow Add CV/CT files and select the active source. Inspect all cycles for incomplete or anomalous edge cycles. Choose Full, Cathodic, or Anodic comparison mode. Enter electrode area when area-normalized CSC is required. Optionally ignore the first and last cycle. Configure the two plot panes and review CT/CV agreement. Export all-cycle results and, when needed, column-oriented CV data. Plot Behavior All-cycle plots use a distinct color for each cycle; cathodic and anodic current branches use related dark/light variants. Time is shifted so each cycle begins at zero. Selecting new X/Y quantities refits the axes. Toggling trim overlays preserves the user's current view because it changes an overlay, not the underlying coordinate selection. **Ignore first/last cycle** applies consistently to the all-cycle plot, result table, CSC export, and CV-data export. It is intended for known incomplete edge cycles and must not be used to hide arbitrary outliers. Calculation Semantics Time-domain charge integrates current against time. CV-domain charge integrates current against potential and divides by positive scan rate. Each line segment is split at a current zero crossing so cathodic and anodic contributions are assigned without smearing a sign change across one trapezoid. Q_CT,cath = integral |I(t)| dt over cathodic current Q_CT,anod = integral I(t) dt over anodic current Q_CV = integral I(V) dV / scanRate, split by current sign CSC = 1000 * Q_C / area_cm2 Full mode sums positive cathodic and anodic charge magnitudes. Relative difference is `100 * abs(Q_CT - Q_CV) / max(abs(Q_CT), abs(Q_CV))`, with zero reported when both charges are zero. `dtErr` reports the maximum discrepancy between measured time increments and `abs(dV)/scanRate` over valid segments. Without a valid positive area, charge comparison remains available but CSC fields in mC/cm^2 are `NaN`. Outputs **Export all cycles CSV** writes one row per included file cycle with source, cycle, mode, scan rate, area, CT and CV cathodic/anodic/full charges, selected charge and CSC values, absolute/relative difference, time-step consistency, and status fields. **Export CV data CSV** writes potential plus paired current and scan-rate columns for replotting and independent calculation. When all exported cycles share the same potential vector, they occupy one file. When vectors differ, the app writes one CSV per source item using the selected filename as a stem. Each export action has its own `.labkit.json` provenance manifest. Use Without The GUI [item, status] = labkit.dta.loadFile(\"cv.DTA\", \"cvct\"); assert(status.ok, status.message); curve = item.curves(1); options = struct(\"scanRate\", 0.05, ... \"area_cm2\", 0.05, \"mode\", \"Full\"); result = csc.analysisRun.computeCSC(curve, options); assert(result.ok, result.message); Function Reference The generated pages for [`computeCSC`](../../../reference/api/csc/analysisRun/computeCSC.html) and [`chargeDensity`](../../../reference/api/csc/analysisRun/chargeDensity.html) state the exact numeric inputs, units, sign conventions, outputs, invalid-area behavior, executable examples, and related APIs used outside the GUI. Errors And Limitations Scan rate must be finite and positive. Required column names are exact; a visually similar arbitrary table is not accepted as a CV/CT cycle. Area normalization is omitted rather than guessed when area is invalid. Agreement between CT and CV integration is a consistency measurement, not proof that the electrochemical experiment is otherwise valid. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md) csc.analysisRun.chargeDensity Convert charge in coulombs to density in mC/cm^2. csc.analysisRun.computeCSC Compare time-integrated and scan-rate-derived electrode charge."},{"title":"batch_crop.cropGeometry.cropImage","url":"reference/api/batch_crop/cropGeometry/cropImage.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.cropImage","text":"CROPIMAGE Rotate and crop an image at a fixed pixel size. CROPIMAGE Rotate and crop an image at a fixed pixel size. Usage: result = batch_crop.cropGeometry.cropImage(imageData, opts) Description: Adds optional padding, rotates the resulting canvas, and extracts a crop centered at a point expressed in the original image coordinate system. The crop always has the requested pixel dimensions. Areas outside the transformed canvas use the selected fill value. This function does not resize the crop, display graphics, or write files. Inputs: imageData - Nonempty numeric or logical image. Accepted sizes are M-by-N and M-by-N-by-C. The output preserves its class and channel count. opts - Scalar structure containing the crop options below. Options: cropWidth - Required positive pixel count. Noninteger values are rounded. cropHeight - Required positive pixel count. Noninteger values are rounded. angleDeg - Counterclockwise canvas rotation in degrees. Default: 0. centerXY - Crop center [x y] in original-image pixel coordinates, where x is the column and y is the row. Invalid or empty input selects the geometric image center. The center is limited so the requested crop remains representable on the prepared canvas. Default: []. paddingPercent - Padding added around each source edge as a percentage of the corresponding source dimension. Negative values are treated as zero by the canvas preparation code. Default: 0. fillValue - Scalar or channel-compatible value used outside the rotated image. Empty input selects the app's white value for imageData. Default: []. Outputs: result - Scalar crop result structure. result.image is exactly cropHeight-by-cropWidth-by-C. result.centerX and centerY retain the effective center in original-image coordinates. rotationDeg, paddingPercent, sourceWidth, sourceHeight, cropWidth, cropHeight, nativeCropWidth, nativeCropHeight, outputWidth, and outputHeight describe the operation. scaleMode is \"Pixels\", ok is true, status is \"cropped\", and message is \"OK\". Errors: labkit_BatchImageCrop_app:InvalidImage - imageData is empty, nonnumeric, nonlogical, or has more than three dimensions. labkit_BatchImageCrop_app:InvalidCropSize - cropWidth or cropHeight is absent, nonfinite, nonscalar, or smaller than one. Example: imageData = reshape(uint8(1:100), 10, 10); opts = struct(\"cropWidth\", 4, \"cropHeight\", 3, \"centerXY\", [5 6]); result = batch_crop.cropGeometry.cropImage(imageData, opts); assert(result.ok && isequal(size(result.image), [3 4])) See also batch_crop.cropGeometry.cropScaledImage, batch_crop.cropGeometry.scalePlan"},{"title":"batch_crop.cropGeometry.cropScaledImage","url":"reference/api/batch_crop/cropGeometry/cropScaledImage.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.cropScaledImage","text":"CROPSCALEDIMAGE Crop one calibrated image to a common physical output scale. CROPSCALEDIMAGE Crop one calibrated image to a common physical output scale. Usage: result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan) result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan, itemIndex) Description: Uses the selected item's native crop dimensions from scalePlan, delegates rotation and cropping to batch_crop.cropGeometry.cropImage, then resamples the crop to the plan's common output dimensions. Resampling uses bilinear interpolation independently for every channel and preserves image class. The function has no graphics or file side effects. Inputs: imageData - Nonempty numeric or logical M-by-N or M-by-N-by-C image. opts - Crop option structure accepted by cropImage. cropWidth and cropHeight are replaced by the native dimensions selected by plan. plan - Scalar structure returned by batch_crop.cropGeometry.scalePlan. itemIndex - Positive index selecting nativeCropWidth, nativeCropHeight, sourcePixelsPerUnit, resampleFactor, and warnings for imageData. Default: 1. Options: angleDeg - Counterclockwise canvas rotation in degrees. Default: 0. centerXY - Crop center [x y] in original-image pixel coordinates. Empty or invalid input selects the image center. Default: []. paddingPercent - Padding around the source before rotation, as a percentage of each source dimension. Default: 0. fillValue - Value used outside the rotated image. Empty selects the app's white value for imageData. Default: []. Outputs: result - cropImage result updated for physical mode. result.image has plan.outputHeight rows and plan.outputWidth columns. cropWidth and cropHeight describe that output; nativeCropWidth and nativeCropHeight describe the pre-resampling crop. scaleUnit, sourcePixelsPerUnit, targetPixelsPerUnit, resampleFactor, and scaleWarning come from plan. Failure Behavior: itemIndex must select an element from every per-image plan field, and plan output dimensions must be positive. Missing fields, out-of-range indices, invalid image/crop options, or unsupported interpolation classes propagate the originating cropGeometry or MATLAB error. Example: imageData = reshape(uint8(1:100), 10, 10); opts = struct(\"centerXY\", [5 5]); plan = struct(\"nativeCropWidth\", 4, \"nativeCropHeight\", 4, ... \"outputWidth\", 8, \"outputHeight\", 6, \"unit\", \"um\", ... \"sourcePixelsPerUnit\", 2, \"targetPixelsPerUnit\", 4, ... \"resampleFactor\", 2, \"warnings\", \"\"); result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan); assert(isequal(size(result.image), [6 8])) See also batch_crop.cropGeometry.cropImage, batch_crop.cropGeometry.scalePlan"},{"title":"batch_crop.cropGeometry.scalePlan","url":"reference/api/batch_crop/cropGeometry/scalePlan.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.scalePlan","text":"SCALEPLAN Plan equal-size physical crops across calibrated images. SCALEPLAN Plan equal-size physical crops across calibrated images. Usage: plan = batch_crop.cropGeometry.scalePlan(items, opts) Description: Converts every image calibration to a requested physical unit, determines a common output pixel density, and calculates native crop and common output dimensions. Automatic density is the median converted source density. Large scale differences produce warnings but do not reject the plan. Inputs: items - Nonempty structure array. Each element must have a valid scaleCalibration accepted by batch_crop.scaleCalibration.isSet, including a positive pixelsPerUnit value and its source unit. opts - Scalar structure containing the physical crop options below. Options: physicalWidth - Required positive crop width in scaleUnit. physicalHeight - Required positive crop height in scaleUnit. scaleUnit - Unit used for physical dimensions and pixel densities. Legal values are \"m\", \"cm\", \"mm\", \"um\", and \"nm\". Default: \"um\". targetPixelsPerUnit - Positive common output density in pixels per scaleUnit. Zero, negative, or nonfinite input selects the median source density automatically. Default: 0. maxUpsamplePercent - Largest accepted upsampling above native density before a warning is recorded. Negative input is treated as zero. Default: 15. Outputs: plan - Scalar structure with these fields: Plan Fields: mode - \"Physical\". unit - Effective scaleUnit. targetSource - \"Manual\" for a positive targetPixelsPerUnit, otherwise \"Auto\". physicalWidth, physicalHeight - Requested dimensions in unit. sourcePixelsPerUnit - Converted density for each input item. targetPixelsPerUnit - Common output density. resampleFactor - targetPixelsPerUnit ./ sourcePixelsPerUnit. nativeCropWidth, nativeCropHeight - Per-item native crop sizes in pixels, rounded to integers with a minimum of one. outputWidth, outputHeight - Common output size in pixels, rounded to integers with a minimum of one. warnings - Per-item text. Upsampling beyond maxUpsamplePercent reports \"upsample ...x\"; resample factors below 0.5 report \"downsample ...x\". Errors: labkit_BatchImageCrop_app:ScaleCalibrationMissing - Any item lacks a valid calibration convertible to scaleUnit. labkit_BatchImageCrop_app:InvalidPhysicalSize - physicalWidth or physicalHeight is missing, nonscalar, nonfinite, or nonpositive. labkit_BatchImageCrop_app:InvalidScaleUnit - scaleUnit is unsupported. Example: cal = labkit.app.interaction.scaleCalibration(20, 10, \"um\"); items = struct(\"scaleCalibration\", cal); opts = struct(\"physicalWidth\", 5, \"physicalHeight\", 3, ... \"scaleUnit\", \"um\", \"targetPixelsPerUnit\", 4); plan = batch_crop.cropGeometry.scalePlan(items, opts); assert(plan.outputWidth == 20 && plan.outputHeight == 12) See also batch_crop.cropGeometry.cropScaledImage, labkit.app.interaction.scaleCalibration"},{"title":"cic.analysisRun.computeCIC","url":"reference/api/cic/analysisRun/computeCIC.html","kind":"reference","section":"reference","keywords":"cic.analysisRun.computeCIC","text":"COMPUTECIC Calculate charge-injection and voltage-transient metrics. COMPUTECIC Calculate charge-injection and voltage-transient metrics. Usage: A = cic.analysisRun.computeCIC(item) A = cic.analysisRun.computeCIC(item, opts) Description: Analyzes one biphasic chronopotentiometry transient. The function detects the cathodic and anodic pulse windows, samples the polarization voltage after each phase, integrates injected charge, normalizes charge by electrode area, and checks the measured voltages against the selected cathodic and anodic limits. It performs no plotting or file export. Inputs: item - Scalar DTA item structure. item.curve, or the main curve selected from item.tables, must provide T in seconds, Vf in volts, and Im in amperes. Pt is optional; sample numbers are generated when it is absent. item.meta may contain pulse metadata and area_cm2. opts - Optional scalar structure. See Options. Options: delay_s - Time in seconds from the end of each pulse phase to the Emc or Ema sample. The voltage is linearly interpolated at cath_end+delay_s and anod_end+delay_s. Default: 10e-6 seconds. cathLimit - Cathodic water-window limit in volts. Emc passes when Emc >= cathLimit. Default: -0.6 V. anodLimit - Anodic water-window limit in volts. Ema passes when Ema <= anodLimit. Default: 0.8 V. areaOverride - Positive electrode area in square centimetres, supplied as a numeric scalar or numeric text. A valid value takes precedence over area_cm2 and item.meta.area_cm2. Default: \"\". area_cm2 - Positive numeric electrode area in square centimetres used when areaOverride is empty or invalid. If neither option supplies an area, item.meta.area_cm2 is used. Without a valid area, charge is still calculated but CIC density fields are NaN. Default: NaN. pulseMode - Pulse-detection policy. Allowed display values are \"Metadata first, then auto\", \"Metadata only\", and \"Auto from Im only\". The equivalent values \"metadata_first\", \"metadata_only\", and \"current_only\" are also accepted by labkit.dta.detectPulses. Default: \"Metadata first, then auto\". usedMeasuredCurrent - Logical scalar. When true, Qc and Qa are the absolute trapezoidal integrals of measured Im over the detected phase windows. When false, charge is the absolute nominal phase current multiplied by phase duration. Default: true. Calculations: Samples with NaN T, Vf, or Im are removed together. At least five remaining samples are required. Emc is Vf at cath_end+delay_s and Ema is Vf at anod_end+delay_s. The cathodic baseline Eipp uses the first finite value in this order: pre-pulse median, interpulse median, post-pulse median, zero. The anodic baseline Eipp_gap prefers the interpulse median, followed by the pre-pulse median, post-pulse median, and Eipp. Qc_C and Qa_C are stored as positive charge magnitudes in coulombs; Qt_C = Qc_C + Qa_C. For a valid area, CIC density is 1000*Q/area_cm2 in mC/cm^2. CICc_mCcm2, CICa_mCcm2, and CICt_mCcm2 refer to the cathodic, anodic, and summed biphasic charge respectively. Outputs: A - Scalar result structure. A.ok is true only when curve selection, pulse detection, delayed voltage sampling, and both charge integrations succeed. A.message is \"OK\" on success and explains the first failed stage otherwise. Output Fields: ok - Logical success flag. message - \"OK\" or a description of the first failed stage. delay_s - Effective delayed-voltage sampling interval in seconds. cathLimit - Effective cathodic voltage limit in volts. anodLimit - Effective anodic voltage limit in volts. area_cm2 - Selected positive electrode area in cm^2, or NaN when missing. usedMeasuredCurrent - Effective charge-integration policy. logOnFailure - True for failures that the app should copy into its visible diagnostic log; false for ordinary incomplete-input results. Emc - Delayed cathodic polarization voltage in volts. Ema - Delayed anodic polarization voltage in volts. t_emc - Requested Emc sample time in seconds. t_ema - Requested Ema sample time in seconds. emc_idx - Index of the recorded sample nearest t_emc. ema_idx - Index of the recorded sample nearest t_ema. Epre - Median pre-pulse voltage in volts. Ebetween - Median interpulse voltage in volts. Epost - Median post-pulse voltage in volts. Eipp - Baseline selected for the cathodic phase, in volts. Eipp_gap - Baseline selected for the anodic phase, in volts. baselineCathSource - Text naming the cathodic baseline source. baselineAnodSource - Text naming the anodic baseline source. baselineCathWindow - Two-element time window used for Eipp, in seconds. baselineAnodWindow - Two-element time window used for Eipp_gap, in seconds. Vc_on - Voltage sampled delay_s after cathodic phase onset, in volts. Va_on - Voltage sampled delay_s after anodic phase onset, in volts. t_conset - Requested cathodic onset sample time in seconds. t_aonset - Requested anodic onset sample time in seconds. Va_cath_mag - Absolute Eipp-to-Vc_on voltage change, in volts. Va_anod_mag - Absolute Eipp_gap-to-Va_on voltage change, in volts. tc_s - Cathodic phase duration in seconds. ta_s - Anodic phase duration in seconds. tip_s - Interpulse gap duration in seconds. Ic_est_A - Median measured cathodic current in amperes. Ia_est_A - Median measured anodic current in amperes. cathMask - Logical vector selecting cathodic phase samples. anodMask - Logical vector selecting anodic phase samples. Qc_C - Cathodic charge magnitude in coulombs. Qa_C - Anodic charge magnitude in coulombs. Qt_C - Qc_C + Qa_C in coulombs. CICc_mCcm2 - Cathodic charge density in mC/cm^2. CICa_mCcm2 - Anodic charge density in mC/cm^2. CICt_mCcm2 - Total biphasic charge density in mC/cm^2. cathOK - True when Emc is not below cathLimit. anodOK - True when Ema is not above anodLimit. safe - True when both cathOK and anodOK are true. limitSide - \"safe\", \"cathodic exceeded\", \"anodic exceeded\", or \"both exceeded\". pulse - Pulse geometry returned by labkit.dta.detectPulses. detectMode - Pulse-detection method that produced pulse. detectMsg - Pulse-detection status message. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. pt - Filtered source point numbers or generated zero-based sample numbers. sample_dt - Median difference between consecutive filtered times, in seconds. sample_dt_report contains the same value for exports. ampEstimate_A - Largest absolute filtered current in amperes. Failure Behavior: Missing curves, too few valid samples, failed pulse detection, an out-of-range delay, or a pulse window with fewer than two samples returns A.ok=false. These data failures normally return a message instead of throwing. Invalid MATLAB types or malformed structures can still raise an error from the DTA access or numeric functions. Typical Call: [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message) opts = struct( ... \"delay_s\", 10e-6, ... \"cathLimit\", -0.6, ... \"anodLimit\", 0.8, ... \"area_cm2\", 0.03, ... \"pulseMode\", \"Metadata first, then auto\", ... \"usedMeasuredCurrent\", true); A = cic.analysisRun.computeCIC(item, opts); assert(A.ok, A.message) fprintf(\"Cathodic CIC: %.4g mC/cm^2\\n\", A.CICc_mCcm2) See also labkit.dta.detectPulses, vt_resistance.analysisRun.computeResistance"},{"title":"csc.analysisRun.chargeDensity","url":"reference/api/csc/analysisRun/chargeDensity.html","kind":"reference","section":"reference","keywords":"csc.analysisRun.chargeDensity","text":"CHARGEDENSITY Convert charge in coulombs to density in mC/cm^2. CHARGEDENSITY Convert charge in coulombs to density in mC/cm^2. Usage: value = csc.analysisRun.chargeDensity(chargeC, areaCm2) Inputs: chargeC - Numeric scalar or array of charge values in coulombs. Signed values remain signed. areaCm2 - Positive finite scalar electrode area in square centimetres. Outputs: value - Charge density with the same size as chargeC, in mC/cm^2. When areaCm2 is nonfinite or nonpositive, value is scalar NaN. Description: The conversion is 1000*chargeC/areaCm2 because one coulomb equals 1000 millicoulombs. This unit helper performs no integration and does not take the absolute value of cathodic charge. Failure Behavior: A nonfinite or nonpositive scalar area returns scalar NaN. areaCm2 must be scalar and chargeC must support numeric division; incompatible MATLAB values or nonscalar area conditions raise the originating logical or arithmetic error. Example: density = csc.analysisRun.chargeDensity([0.002 -0.001], 0.5); assert(isequal(density, [4 -2])) See also csc.analysisRun.computeCSC"},{"title":"csc.analysisRun.computeCSC","url":"reference/api/csc/analysisRun/computeCSC.html","kind":"reference","section":"reference","keywords":"csc.analysisRun.computeCSC","text":"COMPUTECSC Compare time-integrated and scan-rate-derived electrode charge. COMPUTECSC Compare time-integrated and scan-rate-derived electrode charge. Usage: A = csc.analysisRun.computeCSC(curve) A = csc.analysisRun.computeCSC(curve, opts) Inputs: curve - Scalar DTA curve struct. curve.headers must contain the exact names T, Vf, and Im, and curve.data must hold the corresponding numeric columns. T is in seconds, Vf in volts, and Im in amperes. opts - Optional scalar struct described below. Default: struct(). Options: scanRate - Positive finite voltage scan rate in V/s. This value is required for the CV-derived charge. Default: NaN, which returns ok=false. mode - Charge branch reported in Qct and Qcv: \"Full\", \"Cathodic\", or \"Anodic\". Unrecognized values use \"Full\". Default: \"Full\". area_cm2 - Positive electrode area in cm^2 as a numeric scalar or numeric text. Invalid or missing values leave density fields NaN while charge calculations still succeed. Default: NaN. Outputs: A - Scalar result struct. On success, it contains every field described below. Validation failures return ok=false, a stable message and logMessage, plus the option fields established before the failure. Result Fields: ok - Logical success flag. message - \"OK\", \"scan rate missing\", \"Need T, Vf, Im\", or \"Not enough points\". logMessage - User-facing explanation for a failed comparison; blank on success. mode - Effective branch used for Qct and Qcv. scanRate - Effective scan rate in V/s. area_cm2 - Parsed positive electrode area, or NaN. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. IcathDisp - Im with zero and anodic samples replaced by NaN. IanodDisp - Im with zero and cathodic samples replaced by NaN. QctCath - Absolute cathodic integral of I dt in coulombs. QctAnod - Anodic integral of I dt in coulombs. QctFull - QctCath + QctAnod in coulombs. QcvCath - Absolute cathodic scan-rate-derived charge in coulombs. QcvAnod - Anodic scan-rate-derived charge in coulombs. QcvFull - QcvCath + QcvAnod in coulombs. Qct - QctCath, QctAnod, or QctFull selected by mode. Qcv - QcvCath, QcvAnod, or QcvFull selected by mode. diff_C - Signed difference Qct-Qcv in coulombs. rel_pct - 100*abs(diff_C)/max(abs(Qct),abs(Qcv)); zero when both selected charges are zero. dtErr - Largest absolute difference, in seconds, between each measured time interval and abs(dV)/scanRate after zero-crossing subdivision. Qct_mC_cm2 - Selected CT charge density in mC/cm^2, or NaN without area. Qcv_mC_cm2 - Selected CV charge density in mC/cm^2, or NaN without area. diff_mC_cm2 - Signed density difference in mC/cm^2, or NaN without area. Description: computeCSC evaluates one current-voltage cycle two ways. The CT result uses recorded time, while the CV result reconstructs interval duration from the commanded scan rate. Reporting both values makes timing inconsistencies and charge-density differences visible without requiring the CSC GUI. Calculations: Rows containing NaN in T, Vf, or Im are removed together. Each adjacent sample interval is split where linearly interpolated current crosses zero. CT charge integrates current over measured time. CV charge uses dt=abs(dV)/scanRate for the same current endpoints. Cathodic contributions are stored as positive magnitudes, so each full charge is the sum of two nonnegative phase charges. Failure Behavior: A missing scan rate, missing exact column name, or fewer than two remaining samples returns ok=false instead of throwing. Malformed MATLAB types or inconsistent curve dimensions may still raise an indexing or numeric error. Example: curve = struct(\"headers\", {{'T','Vf','Im'}}, ... \"data\", [0 0 -1; 1 1 1; 2 2 1]); A = csc.analysisRun.computeCSC(curve, ... struct(\"scanRate\", 2, \"mode\", \"Full\")); assert(A.ok && abs(A.QctFull - 1.5) < 1e-12) See also csc.analysisRun.chargeDensity"},{"title":"curvature.analysisRun.computeCurvatureFit","url":"reference/api/curvature/analysisRun/computeCurvatureFit.html","kind":"reference","section":"reference","keywords":"curvature.analysisRun.computeCurvatureFit","text":"COMPUTECURVATUREFIT Fit a circle and curvature to traced image points. COMPUTECURVATUREFIT Fit a circle and curvature to traced image points. Usage: fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix) fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix, calibration) fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix, ... calibration, doDensify, denseN, fitPathX, fitPathY) Description: Removes consecutive duplicate points, optionally resamples a traced path at equal arc-length intervals, obtains an algebraic Kasa circle estimate, and minimizes radial squared error with base-MATLAB fminsearch. The result includes pixel-space values and, when calibration is valid, physical radius, curvature, residuals, RMSE, and traced length. No GUI or file is required. Inputs: xPix - Numeric x-coordinate vector in image pixels (image columns). At least three unique consecutive point pairs must remain. yPix - Numeric y-coordinate vector in image pixels (image rows), with the same number of elements as xPix. calibration - Scale structure accepted by curvature.analysisRun.normalizeScaleCalibration. Important fields are referencePixels, referenceLength, unit, pixelsPerUnit, isCalibrated, and referenceLine. Empty or omitted input selects uncalibrated pixels. doDensify - Logical-like scalar controlling equal-arc-length resampling. Default: true. denseN - Number of resampled fitting points, rounded to an integer with a minimum of three. Default: 300. fitPathX - Optional displayed-path x-coordinate vector. fitPathY - Optional displayed-path y-coordinate vector. When fitPathX and fitPathY have equal length and at least three unique points, this path supplies curve length and, when doDensify is true, the circle fit. Otherwise the anchor vectors supply both calculations. Calculations: R_px is the fitted radius and kappa_per_px = 1/R_px. rmse_px is the root mean square radial residual of the points used by the optimizer. When calibration.isCalibrated is true, displayed lengths are divided by pixelsPerUnit and displayed curvature is the reciprocal physical radius. residuals_show always corresponds to the original unique anchor points. Outputs: fit - Scalar structure describing the successful fit. Fit Fields: ok, message - true and empty text after a successful calculation. xc_px, yc_px, R_px - Circle center and radius in pixels. kappa_per_px, rmse_px - Pixel curvature and radial RMSE. R_show, kappa_show, rmse_show - Values in unitLen/unitK when calibrated, otherwise the corresponding pixel values. unitLen, unitK - Length and curvature units, for example \"um\" and \"1/um\", or \"px\" and \"1/px\" without calibration. residuals_show - Anchor-point radial residual column vector in unitLen. xPix, yPix - Unique anchor coordinates used for reported residuals. xFit, yFit - Points passed to the circle optimizer. curveLength_px, curveLength_show, curveLengthUnit, curvePointCount - Polyline length result for the accepted fit path. referencePx, referenceLength, scaleUnit, px_per_unit, usePhysicalScale - Effective scale metadata. Errors: labkit_CurvatureMeasurement_app:NotEnoughPoints - Fewer than three unique anchor points remain. labkit_CurvatureMeasurement_app:InvalidFit - Optimization returns a nonfinite or nonpositive radius. Example: theta = (0:11).' * (2*pi/12); x = 30 + 10*cos(theta); y = 40 + 10*sin(theta); fit = curvature.analysisRun.computeCurvatureFit(x, y, [], false); assert(fit.ok && abs(fit.R_px - 10) < 1e-3) See also curvature.analysisRun.computeCurveLength, curvature.analysisRun.normalizeScaleCalibration"},{"title":"curvature.analysisRun.computeCurveLength","url":"reference/api/curvature/analysisRun/computeCurveLength.html","kind":"reference","section":"reference","keywords":"curvature.analysisRun.computeCurveLength","text":"COMPUTECURVELENGTH Measure the polyline length of traced image points. COMPUTECURVELENGTH Measure the polyline length of traced image points. Usage: lengthResult = curvature.analysisRun.computeCurveLength(xPix, yPix) lengthResult = curvature.analysisRun.computeCurveLength( ... xPix, yPix, calibration) Description: Removes consecutive points closer than the app's duplicate tolerance and sums the Euclidean distance between the remaining adjacent points. A valid calibration converts the pixel length to the requested physical unit. The function does not smooth, interpolate, close the path, plot, or write files. Inputs: xPix - Numeric x-coordinate vector in image pixels (image columns). At least two unique consecutive point pairs are required. yPix - Numeric y-coordinate vector in image pixels (image rows), with the same number of elements as xPix. calibration - Scale structure accepted by curvature.analysisRun.normalizeScaleCalibration. Empty or omitted input reports length in pixels. Outputs: lengthResult - Scalar structure with the following fields. Result Fields: ok, message - true and empty text after a successful calculation. length_px - Sum of adjacent segment lengths in pixels. length_show - length_px divided by pixelsPerUnit when calibrated; otherwise length_px. unitLen - Calibration unit when calibrated, otherwise \"px\". referencePx, referenceLength, scaleUnit, px_per_unit, usePhysicalScale - Effective scale metadata. pointCount - Number of points remaining after duplicate removal. Errors: labkit_CurvatureMeasurement_app:NotEnoughLengthPoints - Fewer than two unique points remain. Example: x = [0 3 3]; y = [0 0 4]; result = curvature.analysisRun.computeCurveLength(x, y); assert(result.length_px == 7 && result.unitLen == \"px\") See also curvature.analysisRun.computeCurvatureFit, curvature.analysisRun.normalizeScaleCalibration"},{"title":"dic_postprocess.analysisRun.makeStrainOverlay","url":"reference/api/dic_postprocess/analysisRun/makeStrainOverlay.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.makeStrainOverlay","text":"MAKESTRAINOVERLAY Blend a color-mapped strain field over a reference image. MAKESTRAINOVERLAY Blend a color-mapped strain field over a reference image. Usage: overlay = dic_postprocess.analysisRun.makeStrainOverlay( ... referenceImage, strainMap, mask, roiMask, opts) Inputs: referenceImage - Numeric grayscale or RGB image used as the background. strainMap - Two-dimensional numeric strain field on the DIC grid. mask - Logical display mask. It is resized to referenceImage by nearest neighbor before blending. roiMask - Optional logical mask on the strain grid. When nonempty, it determines valid strain samples together with finite strainMap values; otherwise a resized form of mask is used. opts - Scalar overlay option struct described below. Required Options: alpha - Strain-color blend fraction; 0 shows the reference and 1 shows the color map inside the overlay mask. colorRange - Two-element [minimum maximum] strain range mapped to the first and last colormap rows. Values outside the range are clipped. colormap - N-by-3 RGB colormap. oversample - Positive strain-grid enlargement factor, rounded to an integer before linear resize. Values below 1 use 1. sigmaSmooth - Gaussian smoothing sigma on the extended strain grid. Values at or below 0 disable smoothing. brightness - Additive reference-image brightness adjustment. contrast - Multiplicative contrast around normalized intensity 0.5. gamma - Exponent applied after brightness and contrast adjustment. saturation - Multiplier for the HSV saturation channel. rgbGain - Three-element red, green, and blue gain vector. Optional Options: edgeTrim - Nonnegative number of valid-mask edge pixels removed before interpolation. Default: 1 when the field is absent. Outputs: overlay - Double RGB image with the reference height and width. Reference pixels remain unchanged outside the intersection of mask and valid resized strain data. Description: The reference is normalized and enhanced first. Valid strain values are extended into their ROI, optionally smoothed, linearly resized, clipped to colorRange, and blended only where both masks permit. The function creates image data only and performs no plotting or file output. Failure Behavior: referenceImage, strainMap, masks, and every required option must have compatible numeric shapes. Missing fields, an invalid color range or colormap, and incompatible mask/image sizes propagate the originating App helper or MATLAB indexing/conversion error. Typical Call: opts = struct(\"alpha\", 0.55, \"colorRange\", [-0.02 0.02], ... \"colormap\", jet(256), \"oversample\", 2, \"sigmaSmooth\", 1, ... \"edgeTrim\", 1, \"brightness\", 0, \"contrast\", 1, ... \"gamma\", 1, \"saturation\", 1, \"rgbGain\", [1 1 1]); overlay = dic_postprocess.analysisRun.makeStrainOverlay( ... referenceImage, strainMap, displayMask, roiMask, opts); See also dic_postprocess.analysisRun.prepareOutputs, dic_postprocess.analysisRun.summarizeStrain"},{"title":"dic_postprocess.analysisRun.prepareOutputs","url":"reference/api/dic_postprocess/analysisRun/prepareOutputs.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.prepareOutputs","text":"PREPAREOUTPUTS Build DIC strain summaries and both display overlays. PREPAREOUTPUTS Build DIC strain summaries and both display overlays. Usage: [summaryTable, overlayExx, overlayEyy] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters) Inputs: inputs - Scalar source struct with referenceImage, maskImage, and strain. strain must contain exx, eyy, and roiMask arrays on a common DIC grid. parameters - Scalar display struct described below. Parameters: alpha - Overlay blend fraction. colorMin - Lower shared EXX/EYY strain color limit. colorMax - Upper shared EXX/EYY strain color limit. oversample - Strain-grid interpolation enlargement factor. smoothSigma - Gaussian smoothing sigma. edgeTrim - Valid strain edge pixels removed before interpolation. brightness - Additive normalized reference brightness adjustment. contrast - Reference contrast multiplier around 0.5. gamma - Reference-image intensity exponent. saturation - Reference HSV saturation multiplier. redGain - Reference red-channel multiplier. greenGain - Reference green-channel multiplier. blueGain - Reference blue-channel multiplier. Outputs: summaryTable - Five-row table of Mean, Std, Median, Min, and Max with EXX and EYY columns. overlayExx - Double RGB EXX overlay at referenceImage size. overlayEyy - Double RGB EYY overlay at referenceImage size. Description: prepareOutputs is the GUI-free composition used by the app. It converts the mask image to a logical display mask, uses a fixed 256-color jet map for both components, renders EXX and EYY with identical display parameters, and summarizes finite strain samples inside roiMask when supplied. Failure Behavior: Missing input/parameter fields, incompatible EXX/EYY/mask geometry, or invalid display values propagate the originating field-access, overlay, or summary error. The function does not catch partial-output failures. Typical Call: [summary, exxImage, eyyImage] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters); See also dic_postprocess.analysisRun.makeStrainOverlay, dic_postprocess.analysisRun.summarizeStrain"},{"title":"dic_postprocess.analysisRun.summarizeStrain","url":"reference/api/dic_postprocess/analysisRun/summarizeStrain.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.summarizeStrain","text":"SUMMARIZESTRAIN Summarize finite EXX and EYY values inside a mask. SUMMARIZESTRAIN Summarize finite EXX and EYY values inside a mask. Usage: T = dic_postprocess.analysisRun.summarizeStrain(strain, mask) Inputs: strain - Scalar struct with numeric exx and eyy arrays of the same size. mask - Logical array matching exx and eyy. true entries select samples for both components. Outputs: T - Five-row table. Metric contains \"Mean\", \"Std\", \"Median\", \"Min\", and \"Max\"; EXX and EYY contain the corresponding statistics. Description: Nonfinite values are removed independently from each selected component. Std uses MATLAB's default sample standard deviation. When a component has no finite selected values, all five values for that component are NaN. Failure Behavior: An empty finite selection returns NaN statistics as documented. Missing exx or eyy fields, incompatible mask geometry, or values unsupported by MATLAB numeric statistics propagate the originating field/indexing error. Example: strain = struct(\"exx\", [1 2; NaN 4], \"eyy\", [2 4; 6 Inf]); T = dic_postprocess.analysisRun.summarizeStrain( ... strain, true(2)); assert(T.EXX(T.Metric == \"Mean\") == 7/3) assert(T.EYY(T.Metric == \"Max\") == 6) See also dic_postprocess.analysisRun.prepareOutputs"},{"title":"dic_preprocess.analysisRun.alignMovingToReference","url":"reference/api/dic_preprocess/analysisRun/alignMovingToReference.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.alignMovingToReference","text":"ALIGNMOVINGTOREFERENCE Fit and apply a rigid transform from matched points. ALIGNMOVINGTOREFERENCE Fit and apply a rigid transform from matched points. Usage: [alignedImage, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... referenceImage, movingImage, fixedPoints, movingPoints) Inputs: referenceImage - Reference image whose height and width define the output canvas. movingImage - Numeric grayscale or multichannel image to transform. fixedPoints - N-by-2 [x y] coordinates in referenceImage. movingPoints - N-by-2 corresponding [x y] coordinates in movingImage. Use the same number and order of points as fixedPoints. At least two distinct pairs are needed to constrain a 2-D rigid transform. Outputs: alignedImage - movingImage resampled onto the reference canvas. It has the moving image class and channel count; pixels outside the source are 0. tformRigid - Three-by-three row-vector homogeneous transform, shown as transform in the usage syntax. A moving coordinate maps to reference coordinates as [x y 1]*tformRigid. Description: The function uses the least-squares orthogonal Procrustes solution for one rotation and translation; reflection is explicitly rejected. It does not estimate scale or shear. Resampling is delegated to applyRigidTransform. Failure Behavior: The caller is responsible for supplying corresponding finite N-by-2 point arrays with enough nondegenerate geometry to constrain the fit. The function does not validate pair count or fit quality; insufficient or degenerate data can therefore return an arbitrary transform. Shapes that cannot participate in the matrix operations, unsupported image classes, and SVD/interpolation failures propagate the originating MATLAB error. Example: reference = zeros(12, 12); moving = zeros(12, 12); moving(4:6, 3:5) = 1; movingPoints = [3 4; 5 4; 3 6]; fixedPoints = movingPoints + [2 1]; [aligned, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... reference, moving, fixedPoints, movingPoints); assert(isequal(size(aligned), size(reference))) assert(norm([movingPoints ones(3,1)]*transform - ... [fixedPoints ones(3,1)], \"fro\") < 1e-10) See also dic_preprocess.analysisRun.applyRigidTransform, dic_preprocess.analysisRun.autoAlignMovingToReference"},{"title":"dic_preprocess.analysisRun.applyRigidTransform","url":"reference/api/dic_preprocess/analysisRun/applyRigidTransform.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.applyRigidTransform","text":"APPLYRIGIDTRANSFORM Resample a moving image onto a reference-sized canvas. APPLYRIGIDTRANSFORM Resample a moving image onto a reference-sized canvas. Usage: alignedImage = dic_preprocess.analysisRun.applyRigidTransform( ... referenceImage, movingImage, transform) Inputs: referenceImage - Image whose height and width define the output canvas. Its pixel values and channel count are not used. movingImage - Numeric two-dimensional or multichannel source image. transform - Three-by-three row-vector homogeneous rigid transform. Source points map forward as [x y 1]*transform. The upper-left two-by-two block is rotation and row 3, columns 1:2 are [x y] translation. Outputs: alignedImage - Linearly interpolated moving image on the reference height and width, preserving movingImage class and channels. Samples outside movingImage are 0; integer outputs are rounded and clipped to range. Description: The function performs inverse mapping for every output pixel using base MATLAB interp2. It assumes the supplied two-by-two block is an orthogonal rotation; scale or shear matrices are not validated. Failure Behavior: transform is expected to be a finite 3-by-3 numeric matrix and images must be compatible with interp2 and output casting. The function does not validate finiteness, rigidity, or invertibility; nonfinite or nonrigid values can therefore produce an unusable image without a dedicated error. Invalid dimensions and unsupported classes propagate the originating indexing, interpolation, or cast error. Example: moving = uint8(reshape(1:25, 5, 5)); transform = [1 0 0; 0 1 0; 1 0 1]; aligned = dic_preprocess.analysisRun.applyRigidTransform( ... zeros(5), moving, transform); assert(isa(aligned, \"uint8\") && aligned(1,1) == 0) assert(aligned(1,2) == moving(1,1)) See also dic_preprocess.analysisRun.alignMovingToReference"},{"title":"dic_preprocess.analysisRun.autoAlignMovingToReference","url":"reference/api/dic_preprocess/analysisRun/autoAlignMovingToReference.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.autoAlignMovingToReference","text":"AUTOALIGNMOVINGTOREFERENCE Estimate and apply an integer translation. AUTOALIGNMOVINGTOREFERENCE Estimate and apply an integer translation. Usage: [alignedImage, transform, method] = ... dic_preprocess.analysisRun.autoAlignMovingToReference( ... referenceImage, movingImage) Inputs: referenceImage - Numeric grayscale or RGB reference image. Its first two dimensions define the output canvas and correlation size. movingImage - Numeric grayscale or RGB image to translate. Outputs: alignedImage - Original movingImage translated onto the reference canvas, with linear interpolation and zero fill. tformRigid - Three-by-three row-vector homogeneous translation transform, shown as transform in the usage syntax. method - Character vector identifying the fixed phase-correlation method. Description: Each image is converted to normalized grayscale independently. For shift estimation only, moving grayscale data is resized to the reference size by nearest-neighbor sampling. Phase correlation returns a whole-pixel circular shift. Rotation, scale, deformation, repeated texture, and large nonoverlap can produce a poor fit. Failure Behavior: The function does not assign a confidence score or reject an ambiguous phase-correlation peak; low-texture or repeated-pattern inputs can return a numerically valid but poor translation. Empty arrays, unsupported image classes, or invalid channel shapes propagate image conversion/interpolation errors. Example: reference = zeros(16); reference(5:8, 6:9) = 1; moving = circshift(reference, [2 -3]); [aligned, transform, method] = ... dic_preprocess.analysisRun.autoAlignMovingToReference( ... reference, moving); assert(isequal(size(aligned), size(reference))) assert(isequal(size(transform), [3 3]) && contains(method, \"phase-correlation\")) See also dic_preprocess.analysisRun.alignMovingToReference, dic_preprocess.analysisRun.applyRigidTransform"},{"title":"dic_preprocess.analysisRun.cropImage","url":"reference/api/dic_preprocess/analysisRun/cropImage.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.cropImage","text":"CROPIMAGE Crop an image using base-MATLAB indexing. CROPIMAGE Crop an image using base-MATLAB indexing. Usage: cropped = dic_preprocess.analysisRun.cropImage(imageData, rect) Inputs: imageData - Numeric two-dimensional or multichannel image. rect - Four finite values [x y width height] in image-pixel coordinates. Values are rounded to pixel indices and limited to image bounds. Outputs: cropped - Inclusive pixel crop preserving input class and channels. Description: Width and height follow MATLAB imcrop's inclusive-endpoint convention: [x y 10 10] selects 11-by-11 pixels when fully inside the image. A partially overlapping rectangle is clipped; a rectangle with no overlap errors. Errors: labkit_DICPreprocess_app:InvalidCropRectangle - rect does not contain four finite numeric values. labkit_DICPreprocess_app:EmptyCropRectangle - The rounded rectangle does not overlap imageData. Example: imageData = reshape(1:100, 10, 10); cropped = dic_preprocess.analysisRun.cropImage(imageData, [2 3 4 2]); assert(isequal(size(cropped), [3 5])) See also dic_preprocess.analysisRun.applyRigidTransform"},{"title":"dic_preprocess.analysisRun.makeFalseColorOverlay","url":"reference/api/dic_preprocess/analysisRun/makeFalseColorOverlay.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.makeFalseColorOverlay","text":"MAKEFALSECOLOROVERLAY Build a red/green registration preview. MAKEFALSECOLOROVERLAY Build a red/green registration preview. Usage: overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... referenceImage, alignedImage) Inputs: referenceImage - Numeric grayscale or RGB reference image. alignedImage - Numeric grayscale or RGB moving image. A different height or width is resized to the reference dimensions by nearest neighbor. Outputs: overlay - Double RGB image the size of referenceImage. Red contains the independently normalized moving image, green contains the independently normalized reference, and blue is zero. Description: Coincident structure appears yellow, moving-only structure red, and reference-only structure green. Independent min/max grayscale normalization emphasizes geometry rather than preserving absolute intensity calibration. Failure Behavior: A constant image retains its normalized intensity instead of being stretched. An all-NaN image remains NaN. Empty arrays, unsupported image classes, or invalid channel shapes propagate errors from LabKit image conversion or nearest-neighbor resizing. Example: reference = eye(5); moving = circshift(reference, [0 1]); overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... reference, moving); assert(isequal(size(overlay), [5 5 3])) assert(all(overlay(:,:,3) == 0, \"all\")) See also dic_preprocess.analysisRun.autoAlignMovingToReference"},{"title":"ecg_print.analysisRun.analyzeSignal","url":"reference/api/ecg_print/analysisRun/analyzeSignal.html","kind":"reference","section":"reference","keywords":"ecg_print.analysisRun.analyzeSignal","text":"ANALYZESIGNAL Rebuild ECG analysis products from one decoded signal cache. ANALYZESIGNAL Rebuild ECG analysis products from one decoded signal cache. Usage: cache = ecg_print.analysisRun.analyzeSignal(cache, parameters) Description: Runs the numerical pipeline used by ECG Print without opening the app. It filters the complete decoded channel, optionally crops the raw and filtered signals to a time interval, detects ECG peaks, extracts event-centered segments, builds a representative template, and measures each segment against that template. Filtering before cropping reduces FFT boundary artifacts at the requested interval edges. The input structure is returned with six rebuildable analysis fields replaced. Other cache fields are preserved, so callers can keep source and import metadata beside the derived results. The function creates no graphics and reads or writes no files. Inputs: cache - Scalar structure containing cache.signal. The signal must have corresponding time and values vectors, a positive sample rate fs in hertz, a displayName, and a metadata structure. These are the fields used by the biosignal functions in this pipeline. parameters - Scalar structure containing every field listed under Parameter Fields. Unlike the ECG Print controls, this direct API does not fill missing fields or sanitize invalid values. Parameter Fields: lowCut - Lower band-pass cutoff in hertz. Supply a finite nonnegative scalar below highCut and below 45% of the sample rate. highCut - Requested upper band-pass cutoff in hertz. For a conventional lowCut, the effective value is min(highCut, 0.45*fs). If lowCut is at or above that limit, the cap becomes lowCut+eps so the two cutoffs can remain ordered. Callers should avoid that case because it leaves no useful safety margin below Nyquist. roiStart - Start time in seconds on cache.signal.time. roiEnd - End time in seconds on cache.signal.time. When roiEnd is greater than roiStart, samples at both endpoints are retained and the cropped time vectors restart at zero. Otherwise the full signal is analyzed. peakMethod - ECG Print method label: \"QRS streaming\", \"Pan-Tompkins\", or \"Local peaks\". Any other value currently falls back to QRS streaming. peakDistance - Minimum accepted peak spacing in seconds. Use a positive finite scalar. segmentWindow - Positive half-width in seconds for the symmetric interval [-segmentWindow, segmentWindow] around every peak. Peaks too close to either signal boundary are omitted. Values below 0.5 seconds may not cover all default noise-measurement windows and can yield NaN metrics. templateTopN - Number of highest-correlation segments requested for the template. buildTemplate rounds the value and limits it to the available segment count, with a minimum of one for nonempty input. Fixed Analysis Settings: The detector uses automatic polarity and a 2.8 robust-standard-deviation threshold setting. Segment measurements use signal window [-0.06 0.06] seconds and noise windows [-0.30 -0.20] and [0.40 0.50] seconds. Call the individual labkit.biosignal functions when those settings must be changed. Outputs: cache - Updated analysis cache with the fields below. Cache Fields: signal - Original decoded signal, unchanged. workingSignal - Raw full signal or selected time crop. filteredSignal - Corresponding band-pass filtered signal. events - Peak anchors from labkit.biosignal.detectEcgPeaks. segments - Retained event-centered columns from segmentByEvents. template - Representative waveform and segment ranking from buildTemplate. measurements - Per-segment and summary signal-quality tables from measureSegments. Empty detections lead to empty downstream results instead of invented measurements. Errors: Missing cache or parameter fields raise normal MATLAB field-reference errors. Invalid signal shapes, time ranges, filter settings, or detector choices raise the documented errors from the corresponding labkit.biosignal function; this function does not catch them. Example: fs = 100; time = (0:1/fs:6)'; values = 0.02*sin(2*pi*1.5*time); values(101:100:501) = values(101:100:501) + 1; signal = struct('time', time, 'values', values, 'fs', fs, ... 'displayName', \"Synthetic ECG\", 'metadata', struct()); cache = struct('signal', signal); parameters = struct('lowCut', 0.5, 'highCut', 40, ... 'roiStart', 0, 'roiEnd', 0, 'peakMethod', \"Local peaks\", ... 'peakDistance', 0.5, 'segmentWindow', 0.7, 'templateTopN', 5); cache = ecg_print.analysisRun.analyzeSignal(cache, parameters); assert(isfield(cache, 'measurements')) See also labkit.biosignal.filterSignal, labkit.biosignal.cropSignal, labkit.biosignal.detectEcgPeaks, labkit.biosignal.segmentByEvents, labkit.biosignal.buildTemplate, labkit.biosignal.measureSegments"},{"title":"flir_thermal.analysisRun.extremeTemperatureReadings","url":"reference/api/flir_thermal/analysisRun/extremeTemperatureReadings.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.extremeTemperatureReadings","text":"EXTREMETEMPERATUREREADINGS Locate finite global thermal extrema. EXTREMETEMPERATUREREADINGS Locate finite global thermal extrema. Usage: [hotSpot, coldSpot] = ... flir_thermal.analysisRun.extremeTemperatureReadings(temperatureC) Description: Finds the highest and lowest finite values in a calibrated temperature image. NaN, Inf, and -Inf pixels are ignored. Coordinates are one-based image coordinates: x is the column and y is the row. If an extreme occurs more than once, MATLAB column-major order selects the first occurrence. Display palettes and color limits do not affect this calculation. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before calculation. Outputs: hotSpot - Scalar structure containing the finite maximum's x, y, and temperatureC fields. coldSpot - Scalar structure containing the finite minimum's x, y, and temperatureC fields. Empty, nonmatrix, or all-nonfinite input returns NaN in every field of both outputs. Failure Behavior: Empty, nonmatrix, or all-nonfinite input returns NaN readings. Values must otherwise be convertible to double; unsupported MATLAB types propagate the originating conversion error. Example: T = [NaN 24; 18 31]; [hot, cold] = flir_thermal.analysisRun.extremeTemperatureReadings(T); assert(hot.x == 2 && hot.y == 2 && hot.temperatureC == 31) assert(cold.x == 1 && cold.y == 2 && cold.temperatureC == 18) See also labkit.thermal.readFile, flir_thermal.analysisRun.roiTemperatureMeanReading"},{"title":"flir_thermal.analysisRun.pointTemperatureReading","url":"reference/api/flir_thermal/analysisRun/pointTemperatureReading.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.pointTemperatureReading","text":"POINTTEMPERATUREREADING Read the nearest calibrated thermal pixel. POINTTEMPERATUREREADING Read the nearest calibrated thermal pixel. Usage: reading = flir_thermal.analysisRun.pointTemperatureReading( ... temperatureC, pointXY) Description: Samples the nearest pixel in a calibrated temperature image. Coordinates are rounded to integers and limited to the image bounds, so a point outside the image samples the nearest edge pixel. The function does not change the image, draw a marker, or apply display color limits. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before sampling. pointXY - Numeric image coordinate [x y], where x is the column and y is the row. Additional values are ignored. Both selected values must be finite. Outputs: reading - Scalar structure with x, y, and temperatureC fields. x and y are the effective one-based integer pixel coordinates. An empty or nonmatrix image, or a coordinate without two finite values, returns NaN in all fields. A nonfinite sampled pixel remains nonfinite in the result. Failure Behavior: Invalid image geometry or fewer than two finite coordinates returns a NaN reading. Inputs must otherwise be convertible to double; unsupported MATLAB types propagate the originating conversion error. Example: T = [10 20 30; 40 50 60]; reading = flir_thermal.analysisRun.pointTemperatureReading(T, [2.2 1.7]); assert(reading.x == 2 && reading.y == 2 && reading.temperatureC == 50) See also flir_thermal.analysisRun.roiTemperatureMeanReading, flir_thermal.analysisRun.extremeTemperatureReadings"},{"title":"flir_thermal.analysisRun.roiTemperatureMeanReading","url":"reference/api/flir_thermal/analysisRun/roiTemperatureMeanReading.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.roiTemperatureMeanReading","text":"ROITEMPERATUREMEANREADING Measure an inclusive rectangular thermal ROI. ROITEMPERATUREMEANREADING Measure an inclusive rectangular thermal ROI. Usage: [hotSpot, coldSpot, meanReading] = ... flir_thermal.analysisRun.roiTemperatureMeanReading( ... temperatureC, startXY, endXY) Description: Interprets two points as opposite corners of an inclusive rectangular ROI. Bounds are rounded, ordered, and limited to the image. NaN, Inf, and -Inf pixels are excluded from the extrema and arithmetic mean. Coordinates are one-based: x is the column and y is the row. No graphics are created. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before calculation. startXY - Numeric [x y] image coordinate at one ROI corner. endXY - Numeric [x y] image coordinate at the opposite corner. Corner order is irrelevant. Additional values are ignored; the first two values of each corner must be finite. Outputs: hotSpot - Scalar structure with x, y, and temperatureC for the finite ROI maximum. coldSpot - Scalar structure with x, y, and temperatureC for the finite ROI minimum. Ties select the first value in column-major order. meanReading - Scalar structure with x and y at the clamped upper-left ROI corner, inclusive width and height, mean temperatureC, and pixelCount. pixelCount counts only finite pixels, whereas width and height describe the entire clamped rectangle. Failure Behavior: Empty or nonmatrix temperature data, invalid corners, or an ROI with no finite pixels returns NaN point fields. meanReading contains NaN geometry and temperature and a pixelCount of zero. Example: T = [10 NaN 30; 40 50 60; 70 80 90]; [hot, cold, avg] = ... flir_thermal.analysisRun.roiTemperatureMeanReading(T, [1 1], [2 2]); assert(hot.temperatureC == 50 && cold.temperatureC == 10) assert(avg.temperatureC == 100/3 && avg.pixelCount == 3) See also flir_thermal.analysisRun.pointTemperatureReading, flir_thermal.analysisRun.extremeTemperatureReadings"},{"title":"focus_stack.analysisRun.computeFocusStack","url":"reference/api/focus_stack/analysisRun/computeFocusStack.html","kind":"reference","section":"reference","keywords":"focus_stack.analysisRun.computeFocusStack","text":"COMPUTEFOCUSSTACK Fuse a stack of images with different focus planes. COMPUTEFOCUSSTACK Fuse a stack of images with different focus planes. Usage: result = focus_stack.analysisRun.computeFocusStack(images) result = focus_stack.analysisRun.computeFocusStack(images, opts) Description: Builds a Laplacian pyramid for each input image, estimates local detail energy at every pyramid level, and blends pixels using the resulting focus weights. Low-confidence regions use normalized soft weights instead of a hard source selection. The first image defines the output height and width; other images are resized to that geometry. Grayscale and RGB inputs may be mixed, in which case the working stack and output are RGB. This function fuses focus only. It does not align images, compensate for parallax, or correct moving subjects. Use the app's registration step before calling it when the camera or specimen moved between frames. Inputs: images - At least two numeric images supplied as a cell array or numeric stack. A four-dimensional array uses its fourth dimension as the image index. A three-dimensional numeric array is interpreted as a stack of two-dimensional grayscale images, not as one RGB image; use a cell array for multiple RGB images. Values are converted with labkit.image.im2double. opts - Optional scalar structure containing the fusion options below. Options: focusWindow - Local detail-energy window in pixels. The value must be a positive finite scalar; it is rounded up to an odd integer of at least three. Default: 31. smoothRadius - Radius in pixels used to smooth source weights at the finest pyramid level. It is rounded to a nonnegative integer and reduced at coarser levels. Default: 4. minConfidence - Threshold in [0,1] below which fusion uses relative detail scores rather than only the best source image. Default: 0.05. pyramidLevels - Requested number of Laplacian levels. It is rounded and limited to the range supported by the image geometry, from one through five. Default: 4. Outputs: result - Scalar structure containing the fused image and diagnostics. Result Fields: ok, message - true and empty text after successful fusion. fused - Grayscale or RGB double image in [0,1]. focusIndex - M-by-N uint16 map naming the source with the largest finest- level detail score at each pixel. Values run from 1 to inputCount. confidence - M-by-N double map in [0,1] measuring separation between the best and second-best finest-level detail scores. focusCoverage - One fraction per source image, calculated from focusIndex; the fractions sum to one. inputCount, imageHeight, imageWidth, channelCount - Working stack geometry. focusWindow, smoothRadius, minConfidence, pyramidLevels - Effective options. meanConfidence - Mean of the confidence map. method - \"Laplacian pyramid focus fusion\". resizedCount - Number of input images resized to the first image's geometry. Errors: labkit_FocusStack_app:NotEnoughImages - Fewer than two images are supplied. labkit_FocusStack_app:InvalidImages - The input container or one of its elements is not a supported numeric image. MATLAB validation errors - focusWindow or minConfidence is outside its documented numeric domain. Example: [x, y] = meshgrid(1:24, 1:20); first = double(mod(x, 4) < 2); second = double(mod(y, 4) < 2); result = focus_stack.analysisRun.computeFocusStack( ... {first, second}, struct(\"focusWindow\", 5, \"smoothRadius\", 1)); assert(result.ok && isequal(size(result.fused), [20 24])) assert(abs(sum(result.focusCoverage) - 1) < 1e-12) See also labkit.image.im2double, labkit.image.meanFilter2"},{"title":"gait_analysis.analysisRun.computeGait","url":"reference/api/gait_analysis/analysisRun/computeGait.html","kind":"reference","section":"reference","keywords":"gait_analysis.analysisRun.computeGait","text":"COMPUTEGAIT Calculate joint, segment, swing, and step-quality measurements. COMPUTEGAIT Calculate joint, segment, swing, and step-quality measurements. Usage: result = gait_analysis.analysisRun.computeGait(pose, opts) Description: Analyzes named two-dimensional pose coordinates without opening the app. The function maps five anatomical roles to source points, smooths their coordinates, calculates joint angles and segment lengths, detects repeated active swings, evaluates each swing against quality thresholds, and returns the same four tables used by app preview and CSV export. Inputs: pose - Scalar normalized pose structure, normally returned by gait_analysis.sourceFiles.readPoseFile. Required fields are coords, an F-by-P-by-2 numeric array of [x y] image coordinates, and pointNames, a P-element text vector. Optional frameIndex and time vectors must have F elements. unitName labels coordinates that are already physical. sourceFormat is copied into the summary table. opts - Scalar option structure. Start with gait_analysis.analysisRun.defaultOptions; missing fields use those defaults. Options: iliacPoint - Case-insensitive point name assigned to the iliac role. Default: \"iliac\". hipPoint - Point name assigned to the hip role. Default: \"hip\". kneePoint - Point name assigned to the knee role. Default: \"knee\". anklePoint - Point name assigned to the ankle role. Default: \"ankle\". footPoint - Point name used for event detection and step length. Default: \"foot\". frameRate - Frames per second used only when pose.time has no finite values. Current Video Marker projects supply this value from embedded video metadata. Zero leaves time_s as NaN. Default: 0. pixelsPerUnit - Positive pixel density for physical output. Distances and scaled coordinates are divided by this value. When invalid, pose.unitName is retained at scale 1 when present; otherwise output uses pixels. Default: 1. unitName - Label used with a valid pixelsPerUnit. Default: \"px\". originAtFirstFrameFirstPoint - When true, coordinateTable subtracts the first point's first-frame coordinate from every scaled point. Raw pixel columns are unchanged. Default: false. smoothWindow - Requested centered smoothing span in frames, rounded to an integer of at least one. The effective odd span is 2*floor((smoothWindow-1)/2)+1. Finite values are averaged separately for each point and axis. Default: 5. detectionProminence - Minimum foot-X peak prominence in source coordinate units. Default: 20, preserving the legacy treadmill workflow threshold. detectionMinHeightSigma - A lift-off peak must be at least mean(foot X)-value*std(foot X). Default: 2, preserving the legacy treadmill peak-height floor. minLiftOffIntervalSeconds - Minimum separation between lift-off peaks. It is converted to frames from source time/frame rate. Default: 0.2 seconds. minSwingFrames - Minimum accepted lift-off-to-landing span. It is also the event separation fallback when source timing is unavailable. Default: 3. maxSwingFrames - Maximum accepted inclusive lift-off-to-landing span. Default: 300. minStepLength - Minimum accepted two-dimensional foot endpoint displacement. Default: 1. maxHipTranslation - Maximum accepted hip endpoint displacement in output units. Default: 1000000, which normally leaves this check inactive. Event Detection: The detection trace is smoothed foot X. A lift-off candidate is a local maximum meeting detectionProminence; nearby candidates retain the higher peak. Each lift-off is independently paired with the following foot-X minimum before the next lift-off, or before the recording ends. Therefore a completed final swing is retained even without a later lift-off. These are kinematic treadmill events, not force-plate contact measurements. Measurements: Hip, knee, and ankle angles are the unsigned angles from 0 to 180 degrees between the two adjacent segment vectors. A zero-length or nonfinite segment produces NaN. Segment lengths are Euclidean distances. For each cycle, Step length and each point translation are Euclidean endpoint displacements from lift-off to landing. Range of motion is the finite maximum-minus-minimum joint angle within that swing. When a next lift-off exists, cycle time, stance time, cadence, and duty factor are also reported; the final complete swing may legitimately lack them. Outputs: result - Scalar structure containing status, normalized options, events, and four tables. Result Fields: ok, message - true and \"Analysis complete\" after successful calculation. options - Effective options after defaults, scalar cleanup, and rounding. events - Structure with paired liftOffFrames and landingFrames, peak prominence, the foot-X detectionSignal, and footRelativeX diagnostic. frameTable - One row per frame. It contains frame/time/step membership, landing and lift-off flags, coordinate unit, three joint angles, four segment lengths, and scaled _x/_y columns. coordinateTable - One row per frame with frame/time and origin metadata, raw __x_px/__y_px columns, and scaled, optionally shifted __x/__y columns. The unshifted origin is [0 0] in pixel coordinates rather than the center of pixel [1 1]. stepTable - One row per paired lift-off-to-landing swing. It contains validity and reason, event frames, swing/cycle/stance timing, cadence, duty factor, two-dimensional step length and point translations, three joint extrema/ranges of motion, and recording-wide joint extrema. invalid_reason is \"ok\", \"duration_out_of_range\", \"step_length_too_small\", or \"hip_translation_too_large\". summaryTable - Metric/value text table reporting source geometry, detected and valid step counts, mean valid-swing time and step length, and global finite joint-angle extrema. Errors: labkit_GaitAnalysis_app:NoPoseData - pose has no nonempty coords field. labkit_GaitAnalysis_app:MissingRolePoint - A configured role name is absent from pose.pointNames. Example: frame = (1:12).'; footX = [-2; -3; -1; 2; 4; -3; -1; 2; 4; -3; -1; 1]; pose = struct(\"coords\", NaN(12, 5, 2), ... \"pointNames\", [\"iliac\"; \"hip\"; \"knee\"; \"ankle\"; \"foot\"], ... \"frameIndex\", frame, \"time\", (frame-1)/30, ... \"unitName\", \"px\", \"sourceFormat\", \"synthetic\"); pose.coords(:, :, 1) = [-2*ones(12,1), zeros(12,1), ... ones(12,1), 2*ones(12,1), footX]; pose.coords(:, :, 2) = repmat([8 6 4 2 0], 12, 1); opts = gait_analysis.analysisRun.defaultOptions(); opts.smoothWindow = 1; opts.detectionProminence = 2; opts.minLiftOffIntervalSeconds = 0.1; result = gait_analysis.analysisRun.computeGait(pose, opts); assert(result.ok && height(result.stepTable) == 2) See also gait_analysis.sourceFiles.readPoseFile, gait_analysis.analysisRun.defaultOptions, video_marker.coordinateExport.buildTable"},{"title":"gait_analysis.sourceFiles.readPoseFile","url":"reference/api/gait_analysis/sourceFiles/readPoseFile.html","kind":"reference","section":"reference","keywords":"gait_analysis.sourceFiles.readPoseFile","text":"READPOSEFILE Read one current Video Marker MAT project for gait analysis. READPOSEFILE Read one current Video Marker MAT project for gait analysis. Usage: pose = gait_analysis.sourceFiles.readPoseFile(filepath) Inputs: filepath - Scalar path to a MAT file containing one current `labkitProject` envelope produced by Video Marker payload version 2. The reader loads only that named variable and never reopens the video. Outputs: pose - Normalized scalar structure. coords is F-by-P-by-2 [x y] image coordinates; pointNames and skeleton preserve the Video Marker order; frameIndex is 1:F; time is derived from embedded frameRate; annotation status/source, calibration, video metadata, and units are preserved. Errors: labkit_GaitAnalysis_app:PoseFileNotFound - filepath does not exist. labkit_GaitAnalysis_app:UnsupportedPoseFile - filepath is not MAT. labkit_GaitAnalysis_app:InvalidMarkerProject - The file is not a current Video Marker project or its coordinate/skeleton shapes disagree. labkit_GaitAnalysis_app:MissingVideoMetadata - Embedded timing metadata is absent, invalid, or has a nonpositive frame rate. Description: Video Marker MAT is the sole file-input contract for Gait Analysis. CSV, arbitrary workspace MAT variables, and legacy marker payloads are rejected because they do not jointly own timing, skeleton, calibration, and frame provenance. `computeGait` remains callable with an in-memory normalized pose for deterministic tests and GUI-free calculations. Typical Call: pose = gait_analysis.sourceFiles.readPoseFile(\"walk.video_marker.autosave.mat\"); assert(pose.ok && pose.frameRate > 0) See also gait_analysis.analysisRun.computeGait, video_marker.videoSource.metadataFromInfo"},{"title":"image_enhance.analysisRun.applyPipeline","url":"reference/api/image_enhance/analysisRun/applyPipeline.html","kind":"reference","section":"reference","keywords":"image_enhance.analysisRun.applyPipeline","text":"APPLYPIPELINE Replay an ordered non-destructive image enhancement history. APPLYPIPELINE Replay an ordered non-destructive image enhancement history. Usage: processed = image_enhance.analysisRun.applyPipeline(images, steps) processed = image_enhance.analysisRun.applyPipeline(images, steps, contexts) Description: Converts every input image to RGB double data, then applies each history step in order. The same step sequence is applied independently to every image. The input arrays and step records are not modified, so the function can be used outside the GUI for reproducible preview or export processing. Inputs: images - One numeric image or a cell array of numeric images. Grayscale and multichannel inputs are converted to M-by-N-by-3 RGB double images and limited to [0,1]. The returned cell order matches the input order. steps - Structure array created by image_enhance.analysisRun.makeStep. Steps are reshaped to a column and executed in array order. An empty array performs normalization only. contexts - Optional cell array with one entry per image. Context is used by \"White ROI calibration\" to find whiteRoi, either directly as context.whiteRoi or as context.item.whiteRoi. Other step kinds ignore it. Default: one empty context per image. Step Fields: kind - One label returned by image_enhance.imagePreview.presentationData.toolKinds: \"Brightness/contrast\", \"Local contrast\", \"Sharpen\", \"Hue/saturation\", \"White balance\", \"White ROI calibration\", or \"Subject-preserving enhance\". amount - Primary numeric control. It is brightness percentage, local- contrast strength, sharpening strength, hue rotation in degrees, white-balance strength, or protected-enhancement blend strength, depending on kind. secondary - Secondary numeric control. It is contrast percentage, radius in pixels, saturation percentage, warm/cool percentage, or target background white level in percent, depending on kind. referenceIndex - Preserved history metadata; applyPipeline does not use it. label - Human-readable history text; applyPipeline does not branch on it. Outputs: processed - Column cell array of M-by-N-by-3 double images in [0,1]. Each output preserves its corresponding source height and width. Errors: labkit_ImageEnhance_app:UnknownEnhancementStep - A step kind is not one of the supported labels. labkit_ImageEnhance_app:MissingWhiteRoi - \"White ROI calibration\" is used without a valid [x y width height] ROI in that image's context. MATLAB indexing errors - A nonempty contexts array has fewer entries than images. Example: imageData = repmat(linspace(0.1, 0.8, 20), 12, 1); steps = [ ... image_enhance.analysisRun.makeStep(\"Brightness/contrast\", 10, 20, 0); ... image_enhance.analysisRun.makeStep(\"Sharpen\", 30, 1, 0)]; processed = image_enhance.analysisRun.applyPipeline(imageData, steps); assert(iscell(processed) && isequal(size(processed{1}), [12 20 3])) See also image_enhance.analysisRun.applyStep, image_enhance.analysisRun.makeStep, labkit.image.adjustBrightnessContrast"},{"title":"image_match.analysisRun.applyMatch","url":"reference/api/image_match/analysisRun/applyMatch.html","kind":"reference","section":"reference","keywords":"image_match.analysisRun.applyMatch","text":"APPLYMATCH Transfer reference appearance statistics to one image. APPLYMATCH Transfer reference appearance statistics to one image. Usage: outputImage = image_match.analysisRun.applyMatch( ... inputImage, referenceImage, step) Description: Converts the source and reference to RGB double images, transfers the selected tone and color statistics from the reference, and blends the matched result with the source. Matching never registers, crops, or resizes either image; only appearance statistics are transferred. The source geometry therefore determines the output geometry. Inputs: inputImage - Numeric or logical grayscale, RGB, or multichannel source image. It is converted to M-by-N-by-3 double data in [0,1]. referenceImage - Numeric or logical reference image. Its dimensions may differ from inputImage because matching uses image-wide statistics. Empty input returns the normalized source unchanged. step - Scalar structure created by image_match.analysisRun.makeStep. Step Fields: matchMethod - One label returned by image_match.imagePreview.presentationData.matchMethods. Punctuation and spaces are ignored when matching labels. An unknown or empty value uses \"Balanced\". amount - Overall blend percentage. Zero returns the source; 100 returns the selected matched result. Values are limited to [0,100]. secondary - Tone-match percentage, limited to [0,100]. colorStrength - Color-match percentage, limited to [0,100]. Match Methods: Balanced - Applies robust white-balance gains, then Lab tone quantile and a/b covariance matching. White balance - Matches robust bright neutral RGB channel ratios only. Tone only - Quantile-matches Lab lightness; colorStrength is unused. Protected tone - Moves background lightness and color toward the reference while limiting changes in saturated subject regions. Lab style - Quantile-matches Lab lightness and covariance-matches the Lab a/b color channels. Histogram - Quantile-matches all three Lab channels independently. Outputs: outputImage - M-by-N-by-3 double image in [0,1], with the same height and width as inputImage. Failure Behavior: Empty referenceImage returns normalized inputImage unchanged. Missing step fields use the App's match defaults; unsupported image classes/channel shapes or malformed numeric step values propagate the originating LabKit image or MATLAB calculation error. Example: source = cat(3, 0.3*ones(8), 0.5*ones(8), 0.8*ones(8)); reference = cat(3, 0.8*ones(6), 0.5*ones(6), 0.3*ones(6)); step = image_match.analysisRun.makeStep(\"White balance\", 100, 100, 100); outputImage = image_match.analysisRun.applyMatch(source, reference, step); assert(isequal(size(outputImage), [8 8 3])) See also image_match.analysisRun.applyPipeline, image_match.analysisRun.makeStep, image_match.imagePreview.presentationData.matchMethods"},{"title":"image_match.analysisRun.applyPipeline","url":"reference/api/image_match/analysisRun/applyPipeline.html","kind":"reference","section":"reference","keywords":"image_match.analysisRun.applyPipeline","text":"APPLYPIPELINE Replay ordered reference-match steps over source images. APPLYPIPELINE Replay ordered reference-match steps over source images. Usage: processed = image_match.analysisRun.applyPipeline(images, steps) processed = image_match.analysisRun.applyPipeline( ... images, steps, referenceImage) Description: Normalizes each source image and applies the ordered reference-match history independently to every source. A step sees the output of the preceding step. The reference image is normalized once, remains separate from the source batch, and is never included in the returned cell array. Inputs: images - One numeric image or a cell array of source images. Each source is converted to RGB double data in [0,1]. Cell order is preserved. steps - Structure array created by image_match.analysisRun.makeStep. Steps are reshaped to a column and executed in array order. An empty array performs source normalization only. referenceImage - Optional numeric reference image. Its dimensions may differ from the sources. Empty or omitted input makes every match step a no-op after source normalization. Default: []. Step Fields: kind - History category; makeStep sets it to \"Reference match\". matchMethod - \"Balanced\", \"White balance\", \"Tone only\", \"Protected tone\", \"Lab style\", or \"Histogram\". amount - Overall match blend percentage. secondary - Tone-match percentage. colorStrength - Color-match percentage. label - Display text retained for history and export; it does not control the calculation. Outputs: processed - Column cell array containing only processed source images. Each entry is an M-by-N-by-3 double image in [0,1] and keeps its own source height and width. Failure Behavior: Empty images returns an empty cell column; empty steps or referenceImage performs normalization only. An invalid image container, unsupported image value, or malformed step propagates the originating normalization or applyMatch error and no partial output is returned. Example: sourceA = cat(3, 0.2*ones(6), 0.4*ones(6), 0.7*ones(6)); sourceB = 0.6*ones(4, 5); reference = cat(3, 0.7*ones(8), 0.5*ones(8), 0.3*ones(8)); step = image_match.analysisRun.makeStep(\"Balanced\", 75, 100, 80); processed = image_match.analysisRun.applyPipeline( ... {sourceA, sourceB}, step, reference); assert(numel(processed) == 2 && isequal(size(processed{2}), [4 5 3])) See also image_match.analysisRun.applyMatch, image_match.analysisRun.makeStep, image_match.imagePreview.presentationData.matchMethods"},{"title":"labkit.app.CallbackContext","url":"reference/api/labkit/app/CallbackContext.html","kind":"reference","section":"reference","keywords":"labkit.app.CallbackContext","text":"CALLBACKCONTEXT Provide declared App-neutral runtime capabilities. CALLBACKCONTEXT Provide declared App-neutral runtime capabilities. Usage: context.log(severity, eventName, message, Name=Value) context.alert(message, title) result = context.chooseOption(prompt, choices, Name=Value) result = context.chooseInputFile(filters, startPath) result = context.chooseInputFolder(startPath) result = context.chooseOutputFile(filters, startPath) result = context.chooseOutputFolder(startPath) result = context.saveProjectDocument(state, filepath) state = context.restoreProjectDocument(filepath) state = context.newProjectDocument() context.saveRecoveryDocument(state, filepath) paths = context.resolveSourcePaths(sources) paths = context.resolveSourcePaths(sources, ids) context.setResource(scope, id, value, cleanup) value = context.getResource(scope, id) context.removeResource(scope, id) context.clearResourceScope(scope) result = context.writeResultPackage(folder, result) Description: CallbackContext is the sealed callback capability boundary. Each specifically named method invokes one private runtime operation. Apps receive this value only as a callback argument and never construct it. It exposes no figure, registry, component, launch request, debug object, or nested service bag. Inputs: state - Complete App-owned state value. message - Scalar reader-facing text. severity - Log severity from \"trace\" through \"critical\". eventName - Stable semantic event identifier. Category - Semantic App capability category. Default: \"workflow\". Audience - \"user\" or \"developer\"; default: \"user\". Attributes - Scalar privacy-safe structured details. Default: struct(). Exception - Scalar MException associated with the event. Default: []. id - Stable semantic diagnostic or resource identifier. count - Nonnegative integer diagnostic count. title - Scalar reader-facing dialog title. prompt - Scalar reader-facing choice prompt. choices - Row string or cellstr array. Title - Reader-facing choice-dialog title. Default: \"Choose an option\". DefaultChoice - Choice selected by pressing Enter. Default: the first choice. CancelChoice - Choice returned when the dialog is dismissed. Default: the first choice. filters - Runtime-supported file-dialog filter value. startPath - Scalar starting file or folder path. filepath - Scalar project or recovery source or destination path. sources - Runtime-owned portable source collection. ids - Optional source identifiers to resolve. scope - \"event\", \"interaction\", \"document\", or \"application\". value - App-neutral resource value. cleanup - Empty or fixed callback cleanup(value). folder - Scalar result-package folder. result - labkit.app.result.Package value for writeResultPackage. Outputs: state - Complete restored App state prepared for the current runtime transaction. result - labkit.app.dialog.Choice for dialogs, project saves, and result writing. paths - Column string array of resolved source paths. value - Stored resource value. Errors: labkit:app:contract:InvalidValue - A public argument is malformed. labkit:app:runtime:InvariantFailure - A private backend operation is unavailable. Typical Call: function state = runAnalysis(state,event,callbackContext) arguments state (1,1) struct event callbackContext (1,1) labkit.app.CallbackContext end callbackContext.log(\"info\", \"analysis.started\", ... \"Analysis started.\"); end See also labkit.app.Definition, labkit.app.dialog.Choice, labkit.app.result.Package"},{"title":"labkit.app.Definition","url":"reference/api/labkit/app/Definition.html","kind":"reference","section":"reference","keywords":"labkit.app.Definition","text":"DEFINITION Compile and launch one immutable App SDK contract. DEFINITION Compile and launch one immutable App SDK contract. Usage: app = labkit.app.Definition(Entrypoint=entrypoint, AppId=appId, ... Title=title, ... Family=family, AppVersion=version, Updated=date, ... Requirements=requirements, Workbench=workbench, Name=Value) fig = app.launch() fig = app.launch(InitialProject=project) requirements = app.launch(\"requirements\") version = app.launch(\"version\") Description: Definition validates product metadata, layout-owned callbacks and renderers, global IDs, callback roles, and references in one atomic constructor. The static target graph is cached once. validateViewSnapshot checks a complete view snapshot against that graph without rebuilding the layout. Required Name-Value Arguments: Entrypoint - Public MATLAB launch function name as a scalar identifier. AppId - Stable App identifier beginning with an ASCII letter and containing letters, digits, underscore, hyphen, or period. Title - Nonempty reader-facing scalar text. Family - Nonempty reader-facing scalar text. AppVersion - Semantic version in X.Y.Z form. Updated - Product date in YYYY-MM-DD form. Requirements - Empty value or labkit.contract.requirements result. Workbench - Root value returned by labkit.app.layout.workbench. Optional Name-Value Arguments: DisplayName - Nonempty scalar text. Default: Title. ProjectSchema - labkit.app.project.Schema or empty for a static App. Default: empty. CreateSession - Fixed callback session = callback(project,context). Portable project sources remain opaque; use context.resolveSourcePaths while rebuilding transient session data. Default: empty. PresentWorkbench - Fixed callback view = callback(state). Default: empty. OnStart - Fixed callback state = callback(state,context), invoked after the first view commit. Default: empty. BuildSyntheticSample - Fixed callback pack = callback(context). Default: empty. Outputs: app - Immutable compiled labkit.app.Definition value. Definition Methods: launch() - Build and show the native MATLAB App figure. launch(\"requirements\") - Return declared facade requirements without creating a figure. launch(\"version\") - Return product version metadata without creating a figure. validateViewSnapshot(view) - Validate target references, target capabilities and complete target coverage. Returns true or throws before any runtime UI mutation. Errors: labkit:app:contract:UnknownArgument - A required argument is missing or an argument is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Metadata, requirements, workbench, callbacks, or renderers are malformed. labkit:app:contract:DuplicateId - A layout ID is duplicated. labkit:app:contract:UnknownReference - A view target is undeclared. labkit:app:contract:UnsupportedOperation - A view operation is not legal for its target. labkit:app:contract:InvalidValue - A launch request or output count is unsupported. Typical Call: workbench = labkit.app.layout.workbench({ ... labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the current analysis.\")}); app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", AppId=\"example.app\", ... Title=\"Example\", Family=\"Examples\", AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", Requirements=[], Workbench=workbench); See also labkit.app.layout.workbench, labkit.app.view.Snapshot, labkit.contract.requirements"},{"title":"labkit.app.dialog.Choice","url":"reference/api/labkit/app/dialog/Choice.html","kind":"reference","section":"reference","keywords":"labkit.app.dialog.Choice","text":"DIALOGRESULT Represent a typed dialog choice or cancellation. DIALOGRESULT Represent a typed dialog choice or cancellation. Usage: result = labkit.app.dialog.Choice(value) result = labkit.app.dialog.Choice(value, Cancelled=cancelled) Description: Choice separates cancellation from the chosen value so an empty string, zero, false, or empty App value is not interpreted as cancel. Inputs: value - App-facing value returned by the dialog. Name-Value Arguments: Cancelled - Logical scalar. Default: false. Outputs: result - Immutable labkit.app.dialog.Choice value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Cancelled is not logical scalar. Example: result = labkit.app.dialog.Choice(\"\", Cancelled=false); assert(~result.Cancelled) See also labkit.app.CallbackContext"},{"title":"labkit.app.event.IntervalScroll","url":"reference/api/labkit/app/event/IntervalScroll.html","kind":"reference","section":"reference","keywords":"labkit.app.event.IntervalScroll","text":"INTERVALSCROLL Describe one normalized interval scroll gesture. INTERVALSCROLL Describe one normalized interval scroll gesture. Usage: event = labkit.app.event.IntervalScroll(Anchor=anchor, Count=count) Description: IntervalScroll replaces native MATLAB scroll events and untyped structs with the horizontal data coordinate under the pointer and the signed vertical scroll count used by interval interactions. Required Name-Value Arguments: Anchor - Finite scalar horizontal data coordinate. Count - Finite nonzero scalar vertical scroll count. Outputs: event - Immutable labkit.app.event.IntervalScroll value. Errors: labkit:app:contract:UnknownArgument - An argument is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Anchor or Count is malformed. Example: event = labkit.app.event.IntervalScroll(Anchor=0.25, Count=-1); assert(event.Anchor == 0.25) See also labkit.app.interaction.interval"},{"title":"labkit.app.event.ListSelection","url":"reference/api/labkit/app/event/ListSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.event.ListSelection","text":"LISTSELECTION Describe selected file or list item identities. LISTSELECTION Describe selected file or list item identities. Usage: selection = labkit.app.event.ListSelection(Name=Value) Description: ListSelection carries stable item IDs, positive display indices, or both for file panels and list controls. When both are supplied they have equal lengths and matching order. Optional Name-Value Arguments: Ids - Unique row string or cellstr array. Default: empty. Indices - Unique positive integer numeric row. Default: empty. Outputs: selection - Immutable labkit.app.event.ListSelection value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - IDs or indices are malformed or have inconsistent lengths. Example: selection = labkit.app.event.ListSelection( ... Ids=[\"sample-a\", \"sample-b\"], Indices=[1 3]); assert(isequal(selection.Indices, [1 3])) See also labkit.app.event.TableCellSelection, labkit.app.layout.fileList"},{"title":"labkit.app.event.TableCellEdit","url":"reference/api/labkit/app/event/TableCellEdit.html","kind":"reference","section":"reference","keywords":"labkit.app.event.TableCellEdit","text":"TABLEEDIT Describe one validated table-cell edit signal. TABLEEDIT Describe one validated table-cell edit signal. Usage: edit = labkit.app.event.TableCellEdit(Name=Value) Description: TableEdit replaces raw MATLAB CellEditData and event metadata with stable row/column identity, the previous and proposed values, and the complete proposed table data when the callback must interpret pasted or related rows atomically. Required Name-Value Arguments: RowIndex - Positive integer row index. ColumnIndex - Positive integer column index. PreviousValue - App-owned value before the edit. NewValue - App-owned proposed value. Optional Name-Value Arguments: RowId - Empty or nonempty scalar text stable across sorting. Default: empty. ColumnId - Empty or nonempty scalar text stable across display-name changes. Default: empty. Data - Complete proposed table data after the edit. Default: empty. Outputs: edit - Immutable labkit.app.event.TableCellEdit value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - An index or ID is malformed. Example: edit = labkit.app.event.TableCellEdit(RowIndex=2, ColumnIndex=3, ... ColumnId=\"group\", PreviousValue=\"A\", NewValue=\"B\"); assert(edit.ColumnId == \"group\") See also labkit.app.layout.dataTable, labkit.app.event.ListSelection"},{"title":"labkit.app.event.TableCellSelection","url":"reference/api/labkit/app/event/TableCellSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.event.TableCellSelection","text":"TABLECELLSELECTION Describe selected cells in a semantic data table. TABLECELLSELECTION Describe selected cells in a semantic data table. Usage: selection = labkit.app.event.TableCellSelection(cellIndices) Description: TableCellSelection carries unique N-by-2 row/column index pairs. It replaces native MATLAB table-selection event shapes at the App callback boundary. Inputs: cellIndices - Unique positive integer N-by-2 matrix. An empty selection is zeros(0,2). Outputs: selection - Immutable TableCellSelection value. Errors: labkit:app:contract:InvalidValue - cellIndices is not a unique positive integer N-by-2 matrix. Example: selection = labkit.app.event.TableCellSelection([1 2; 3 1]); assert(isequal(selection.CellIndices, [1 2; 3 1])) See also labkit.app.event.TableCellEdit, labkit.app.layout.dataTable"},{"title":"labkit.app.interaction.anchorPath","url":"reference/api/labkit/app/interaction/anchorPath.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.anchorPath","text":"ANCHORPATH Declare an editable open or closed path on one plot axis. ANCHORPATH Declare an editable open or closed path on one plot axis. Usage: spec = labkit.app.interaction.anchorPath(id, onChanged, Name=Value) Description: Creates the semantic declaration for a managed multi-anchor path editor; the runtime owns native graphics, viewport preservation, and dispatch. Inputs: id - Unique MATLAB identifier for this interaction. onChanged - Callback state = callback(state,points,context). Options: Axis - Axis ID within the owning plotArea. Default: \"main\". Style - Scalar struct of anchor editor visual options. Default: struct(). Instruction - Scalar user guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration accepted by layout.plotArea. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: spec = labkit.app.interaction.anchorPath( ... \"curve\", @changeCurve, Style=struct(\"closed\", false)); See also labkit.app.layout.plotArea, labkit.app.view.Snapshot"},{"title":"labkit.app.interaction.interpolateAnchorPath","url":"reference/api/labkit/app/interaction/interpolateAnchorPath.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.interpolateAnchorPath","text":"INTERPOLATEANCHORPATH Build a visible path through image anchor points. INTERPOLATEANCHORPATH Build a visible path through image anchor points. Usage: curve = labkit.app.interaction.interpolateAnchorPath(points, imageSize) curve = labkit.app.interaction.interpolateAnchorPath(points, imageSize, Name=Value) [curve, owners] = labkit.app.interaction.interpolateAnchorPath(...) Inputs: points - N-by-2 numeric matrix of [x y] anchor coordinates in image-pixel coordinates. imageSize - Numeric vector whose first two elements are the positive finite image height and width. options - Name-value argument container for Style and Closed. Supply these values by name; do not pass an options struct. Name-Value Arguments: Style - \"Curve\" for a Catmull-Rom path or \"Straight lines\" for line segments joining the anchors. Default: \"Curve\". Closed - Logical scalar. true joins the last anchor to the first and requires at least three anchors. false requires at least two. Default: false. Outputs: curve - M-by-2 path samples. Coordinates are limited to pixel-edge bounds [0.5, width+0.5] and [0.5, height+0.5]. The result is empty until the selected open or closed path has enough anchors. owners - (M-1)-by-1 anchor-segment indices used to associate each visible curve segment with its starting anchor. It is empty with curve. Description: anchorPath contains the deterministic geometry shared by managed anchor editors and app previews. Curved paths pass through the supplied anchors; two-point open curves reduce to a straight segment. The function creates no graphics and can be used independently of a LabKit app. Errors: MATLAB argument-validation or assertion errors are raised when points is not N-by-2, imageSize lacks positive finite height and width, Style is unsupported, or a named argument has an incompatible type or shape. Example: points = [10 30; 30 10; 50 30]; curve = labkit.app.interaction.interpolateAnchorPath( ... points, [40 60], \"Style\", \"Straight lines\"); assert(isequal(curve, points)) See also labkit.app.interaction.anchorPath"},{"title":"labkit.app.interaction.interval","url":"reference/api/labkit/app/interaction/interval.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.interval","text":"INTERVAL Declare an editable one-dimensional plot interval. INTERVAL Declare an editable one-dimensional plot interval. Usage: spec = labkit.app.interaction.interval(id,onChanged,Name=Value) Description: Creates the semantic declaration for a managed interval editor and its optional typed scroll callback on one plot axis. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,range,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnScrolled - Optional callback state = callback(state,event,context), where event is labkit.app.event.IntervalScroll. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.interval(\"window\",@changeWindow); See also labkit.app.layout.plotArea, labkit.app.event.IntervalScroll"},{"title":"labkit.app.interaction.pairedAnchors","url":"reference/api/labkit/app/interaction/pairedAnchors.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.pairedAnchors","text":"PAIREDANCHORS Declare matching editable points across plot axes. PAIREDANCHORS Declare matching editable points across plot axes. Usage: spec = labkit.app.interaction.pairedAnchors(id,onChanged,Name=Value) Description: Creates the semantic declaration for corresponding editable points across two or more axes while the runtime owns their native editors. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,pointSets,context). Options: Axes - Two or more axis IDs within the owning plotArea. Required. Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.pairedAnchors(\"matches\",@changeMatches); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.pointSlots","url":"reference/api/labkit/app/interaction/pointSlots.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.pointSlots","text":"POINTSLOTS Declare a fixed set of editable labeled point positions. POINTSLOTS Declare a fixed set of editable labeled point positions. Usage: spec = labkit.app.interaction.pointSlots(id,onChanged,Name=Value) Description: Creates the semantic declaration for a fixed, labeled set of editable points whose structured value is committed by the runtime. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,value,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.pointSlots(\"markers\",@changeMarkers); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.rectangle","url":"reference/api/labkit/app/interaction/rectangle.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.rectangle","text":"RECTANGLE Declare an editable rectangular plot region. RECTANGLE Declare an editable rectangular plot region. Usage: spec = labkit.app.interaction.rectangle(id,onChanged,Name=Value) Description: Creates the semantic declaration for a persistent editable rectangle, including an optional background-point callback on the same axis. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,position,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnBackgroundPressed - Optional point callback state = callback(state,point,context). It receives clicks on blank plot space and clicks on the rectangle that do not move it. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.rectangle(\"crop\",@moveCrop); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.regionSelection","url":"reference/api/labkit/app/interaction/regionSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.regionSelection","text":"REGIONSELECTION Declare a transient click-or-drag region gesture. REGIONSELECTION Declare a transient click-or-drag region gesture. Usage: spec = labkit.app.interaction.regionSelection(id,onSelected,Name=Value) Description: Creates the semantic declaration for a transient click-or-drag selection whose result is delivered without exposing native interaction objects. Inputs: id - Unique MATLAB identifier. onSelected - Callback state = callback(state,position,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnBackgroundPressed - Optional point callback. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.regionSelection( ... \"temperatureRegion\",@measureRegion, ... OnBackgroundPressed=@measurePoint); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.scaleBarGeometry","url":"reference/api/labkit/app/interaction/scaleBarGeometry.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleBarGeometry","text":"SCALEBARGEOMETRY Compute serializable image scale-bar overlay geometry. SCALEBARGEOMETRY Compute serializable image scale-bar overlay geometry. Usage: geometry = labkit.app.interaction.scaleBarGeometry(imageSize, ... calibration, barLength, position, colorName) Inputs: imageSize - Numeric vector whose first two elements are positive finite image height and width in pixels. calibration - Struct with a positive finite pixelsPerUnit field and a unit field, normally returned by scaleBarCalibration. barLength - Positive finite physical length expressed in calibration.unit. position - Text containing \"top\" or \"bottom\" and optionally \"left\", \"center\", or \"right\". Unrecognized vertical text uses bottom; unrecognized horizontal text uses center. colorName - \"White\" selects [1 1 1]. Every other value selects black. Outputs: geometry - Scalar data struct with the fields described below. Geometry Fields: line - Two-by-two [x y] endpoints in image-pixel coordinates. label - Display text containing barLength and calibration.unit. color - RGB triplet selected by colorName. labelPosition - One-by-two centered label position in image pixels. verticalAlignment - \"top\" for a top bar or \"bottom\" for a bottom bar. pixelsPerUnit - Calibration value copied into the geometry. unit - Unit label copied into the geometry as a string scalar. barLength - Requested physical length. position - Supplied position text as a string scalar. colorName - Supplied color text as a string scalar. Description: The helper places the line with an eight-percent image-edge margin and a five-pixel minimum inset. It errors when the requested bar cannot fit in the available horizontal span. Apps and renderers own drawing; this function creates no graphics and is suitable for saved project state. Errors: labkit:app:interaction:InvalidImageSize - imageSize lacks positive finite height and width. labkit:app:interaction:InvalidScaleBar - calibration or barLength is not a positive finite scalar. labkit:app:interaction:ScaleBarTooLong - The requested bar does not fit inside the horizontal margins. Example: cal = labkit.app.interaction.scaleCalibration(80, 20, \"mm\"); geometry = labkit.app.interaction.scaleBarGeometry( ... [600 800], cal, 10, \"Bottom right\", \"White\"); assert(abs(diff(geometry.line(:,1))) == 40) See also labkit.app.interaction.scaleCalibration"},{"title":"labkit.app.interaction.scaleCalibration","url":"reference/api/labkit/app/interaction/scaleCalibration.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleCalibration","text":"SCALECALIBRATION Convert a known image distance into pixels per unit. SCALECALIBRATION Convert a known image distance into pixels per unit. Usage: cal = labkit.app.interaction.scaleCalibration(referencePixels, ... referenceLength, unitName) cal = labkit.app.interaction.scaleCalibration(..., opts) Inputs: referencePixels - Measured reference distance in image pixels. Empty, nonnumeric, nonfinite, or nonpositive values are treated as missing. referenceLength - Physical reference distance expressed in unitName. Missing, nonfinite, or negative values become 0. unitName - Unit label. With default options, legal values are \"m\", \"cm\", \"mm\", \"um\", and \"nm\". An unsupported value uses defaultUnit. opts - Optional scalar struct described below. Default: struct(). Options: units - Allowed unit labels. Default: {'m','cm','mm','um','nm'}. defaultUnit - Fallback unit. Default: the first entry in units. referenceLine - N-by-2 numeric reference points stored with the result. When it contains exactly two rows and referencePixels is missing, their Euclidean distance supplies referencePixels. Default: zeros(0,2). Outputs: cal - Scalar struct with the fields described below. Calibration Fields: referencePixels - Positive measured pixel distance, or NaN when missing. referenceLength - Nonnegative physical reference distance. unit - Normalized unit label as a character vector. pixelsPerUnit - referencePixels/referenceLength, or 0 when calibration is incomplete. isCalibrated - true when pixelsPerUnit is positive. referenceLine - Normalized N-by-2 numeric reference coordinates. Description: This function builds a serializable calibration value; it does not read an image or draw a scale bar. Repeating the call with identical inputs returns identical numeric fields. Divide a pixel distance by pixelsPerUnit to obtain a distance in cal.unit. Failure Behavior: Missing or invalid measurement values are normalized into an uncalibrated result instead of throwing. opts must be a scalar structure whose units can be converted to text and whose referenceLine can be converted to an N-by-2 numeric array; incompatible MATLAB values propagate conversion errors. Example: cal = labkit.app.interaction.scaleCalibration(80, 20, \"mm\"); physicalLength = 40 / cal.pixelsPerUnit; assert(cal.isCalibrated && physicalLength == 10) See also labkit.app.interaction.scaleBarGeometry"},{"title":"labkit.app.interaction.scaleReference","url":"reference/api/labkit/app/interaction/scaleReference.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleReference","text":"SCALEREFERENCE Declare an editable two-point scale reference. SCALEREFERENCE Declare an editable two-point scale reference. Usage: spec = labkit.app.interaction.scaleReference(id,onChanged,Name=Value) Description: Creates the semantic declaration for an editable two-endpoint physical scale reference while the runtime owns its native editor lifecycle. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,endpoints,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.scaleReference(\"scale\",@changeScale); See also labkit.app.layout.plotArea"},{"title":"labkit.app.layout.button","url":"reference/api/labkit/app/layout/button.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.button","text":"BUTTON Add a push button with one explicit pressed callback. BUTTON Add a push button with one explicit pressed callback. Usage: node = labkit.app.layout.button(id, label, onPressed, Name=Value) Description: Declares a semantic push button without creating a native component. Inputs: id - Unique MATLAB identifier for the layout target. label - Nonempty text displayed on the button. onPressed - Scalar function handle with the fixed callback state = onPressed(state,context). Options: BusyMessage - Status text while the action runs. Default: \"\". Enabled - Initial logical enabled state. Default: true. Tooltip - Nonempty hover text explaining the action's scientific or workflow effect. Default: label. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the current analysis from the selected inputs.\"); See also labkit.app.layout.workbench, labkit.app.CallbackContext"},{"title":"labkit.app.layout.dataTable","url":"reference/api/labkit/app/layout/dataTable.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.dataTable","text":"DATATABLE Add a tabular data display with optional editing and selection. DATATABLE Add a tabular data display with optional editing and selection. Usage: node = labkit.app.layout.dataTable(id, Name=Value) Description: Declares a semantic data table and typed cell callbacks. Inputs: id - Unique MATLAB identifier for the layout target. Options: Title - Reader-facing panel title or blank. A single-table section supplies its title when this is blank. Default: blank. Columns - Column-label text row. Default: strings(1,0). RowNames - Row-label text row. Default: strings(1,0). ColumnEditable - Logical scalar or row matching Columns. Default: false. OnCellEdited - Scalar callback state = callback(state,edit,context), where edit is a labkit.app.event.TableCellEdit. Default: []. OnCellSelectionChanged - Scalar callback state = callback(state,selection,context), where selection is a labkit.app.event.TableCellSelection. Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid options or callback signatures. Typical Call: node = labkit.app.layout.dataTable(\"results\", Columns=[\"Name\" \"Value\"]); See also labkit.app.event.TableCellEdit, labkit.app.event.TableCellSelection"},{"title":"labkit.app.layout.field","url":"reference/api/labkit/app/layout/field.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.field","text":"FIELD Add a text, numeric, choice, or logical input field. FIELD Add a text, numeric, choice, or logical input field. Usage: node = labkit.app.layout.field(id, Name=Value) Description: Declares one bound or event-driven scalar input field. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Kind - \"text\", \"numeric\", \"choice\", \"logical\", or \"readonly\". Readonly fields display one labeled result value. Default: \"text\". Value - Initial value. Default: []. Choices - Text row for choice fields. Default: strings(1,0). Limits - Increasing finite numeric 1-by-2 row. Default: []. Step - Positive numeric scalar. Default: []. Bind - Project or session field path. Default: \"\". ValueDisplayFormat - MATLAB numeric display format. Default: \"\". ShowTicks - Logical slider-tick preference. Default: false. Enabled - Initial logical enabled state. Default: true. OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.field(\"gain\", Kind=\"numeric\", ... Bind=\"project.parameters.gain\"); See also labkit.app.layout.rangeField, labkit.app.layout.slider"},{"title":"labkit.app.layout.fileList","url":"reference/api/labkit/app/layout/fileList.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.fileList","text":"FILELIST Add file selection and portable-source controls. FILELIST Add file selection and portable-source controls. Usage: node = labkit.app.layout.fileList(id, Name=Value) Description: Declares framework-owned file choosing, removal, selection, and portable source binding. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Reader-facing collection label. Default: id. Mode - \"files\" or \"folder\". Default: \"files\". Filters - File-dialog filter text row. Default: strings(1,0). SelectionMode - \"single\" or \"multiple\". Default: \"multiple\". MaxFiles - Positive scalar or Inf. Default: Inf. FolderWarningThreshold - Positive scalar or Inf. Default: 500. ShowStatus - Logical status visibility. Default: true. StartPath - Initial folder text. Default: \"\". ChooseLabel - File button text. Default: \"Choose\". FolderLabel - Folder button text. Default: \"Choose Folder\". RecursiveFolderLabel - Recursive button text. Default: \"Choose Folder Recursively\". RemoveLabel - Remove button text. Default: \"Remove\". ClearLabel - Clear button text. Default: \"Clear\". ChooseTooltip - File button hover text. Default: ChooseLabel. FolderTooltip - Folder button hover text. Default: FolderLabel. RecursiveFolderTooltip - Recursive-folder button hover text. Default: RecursiveFolderLabel. RemoveTooltip - Remove button hover text. Default: RemoveLabel. ClearTooltip - Clear button hover text. Default: ClearLabel. EmptyText - Empty-list text. Default: \"No files selected\". AllowDuplicatePaths - Preserve separate portable source records that resolve to the same path. Use this when each list row is a distinct workflow task. Default: false. Bind - Project source-record field path. Default: \"\". SelectionBind - ListSelection field path. Default: \"\". OnSelectionChanged - Optional callback applicationState = callback(applicationState,selection,callbackContext) for business effects such as lazily decoding the selected source. selection is labkit.app.event.ListSelection. Ordinary selection state needs only SelectionBind. Default: empty. SourceRole - Portable source role. Default: id. SourceIdPrefix - Portable source ID prefix. Default: id. Required - Logical relinking requirement. Default: true. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid options, paths, or callbacks. Typical Call: node = labkit.app.layout.fileList(\"files\", ... Bind=\"project.inputs.sources\", ... ChooseTooltip=\"Choose calibrated source images for this analysis.\"); See also labkit.app.event.ListSelection, labkit.app.CallbackContext, labkit.app.view.Snapshot"},{"title":"labkit.app.layout.group","url":"reference/api/labkit/app/layout/group.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.group","text":"GROUP Arrange related child elements without a titled boundary. GROUP Arrange related child elements without a titled boundary. Usage: node = labkit.app.layout.group(id, children, Name=Value) Description: Groups compatible control nodes into one semantic arrangement. A titled group draws a nested reader-facing boundary inside its owning section. Inputs: id - Unique MATLAB identifier for the layout target. children - Row cell array of layout nodes. Options: Layout - \"auto\", \"vertical\", or \"horizontal\". Default: \"auto\". Title - Reader-facing nested-group title or blank. Default: blank. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:* for invalid IDs, children, or options. Typical Call: node = labkit.app.layout.group(\"inputs\", {gainField}); See also labkit.app.layout.section, labkit.app.layout.workbench"},{"title":"labkit.app.layout.plotArea","url":"reference/api/labkit/app/layout/plotArea.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.plotArea","text":"PLOTAREA Add one or more axes rendered by an App-owned renderer. PLOTAREA Add one or more axes rendered by an App-owned renderer. Usage: node = labkit.app.layout.plotArea(id, renderer, Name=Value) Description: Declares axes and their one App-owned renderer. The renderer is declared here once; a view snapshot supplies only its current model. Inputs: id - Unique MATLAB identifier for the layout target. renderer - Scalar function handle renderer(axesById,model) with no output. axesById is always a scalar struct with one graphics axes field per declared AxisIds value, including single-axis plots. Options: Title - Reader-facing panel title or blank. Default: blank. Layout - Axes arrangement: \"single\", \"pair\", or \"stack\". Default: \"single\" for one axis and \"stack\" for multiple axes. AxisIds - Unique MATLAB identifier row. Default: \"main\". AxisTitles - One title per axis. Default: axis IDs. XLabels - One x-axis label per axis. Default: blank. YLabels - One y-axis label per axis. Default: blank. ColumnWidths - One positive pixel, \"fit\", or \"1x\" value per axis for pair layout. Default: equal flexible widths. RowHeights - One positive pixel, \"fit\", or \"1x\" value per axis for stack layout. Default: equal flexible heights. ScrollZoomAxes - One \"xy\", \"x\", or \"y\" value per axis. Default: \"xy\". ViewModes - App-owned mode labels. Default: strings(1,0). OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Interactions - Row cell array returned by named labkit.app.interaction.* declarations. Default: {}. Outputs: node - Immutable internal layout node accepted by workspace. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.plotArea(\"preview\", @drawTrace, ... AxisIds=\"trace\"); See also labkit.app.view.Snapshot, labkit.app.layout.workspace, labkit.app.interaction.anchorPath"},{"title":"labkit.app.layout.rangeField","url":"reference/api/labkit/app/layout/rangeField.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.rangeField","text":"RANGEFIELD Add an input for a numeric lower and upper bound. RANGEFIELD Add an input for a numeric lower and upper bound. Usage: node = labkit.app.layout.rangeField(id, Name=Value) Description: Declares one two-value numeric range input. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Value - Finite numeric 1-by-2 row. Default: []. Limits - Increasing finite numeric 1-by-2 row. Default: []. Enabled - Initial logical enabled state. Default: true. Bind - Project or session field path. Default: \"\". OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: node = labkit.app.layout.rangeField(\"window\", Limits=[0 10]); See also labkit.app.layout.field, labkit.app.layout.slider"},{"title":"labkit.app.layout.section","url":"reference/api/labkit/app/layout/section.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.section","text":"SECTION Arrange related child elements under a visible title. SECTION Arrange related child elements under a visible title. Usage: node = labkit.app.layout.section(id, title, children, Name=Value) Description: Groups compatible controls under a reader-facing title. Inputs: id - Unique MATLAB identifier for the layout target. title - Nonempty section title. children - Row cell array of layout nodes. Options: Collapsible - Logical collapse support. Default: false. Expanded - Initial logical expansion state. Default: true. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:* for invalid IDs, children, or options. Typical Call: node = labkit.app.layout.section(\"inputs\", \"Inputs\", {gainField}); See also labkit.app.layout.group, labkit.app.layout.tab"},{"title":"labkit.app.layout.slider","url":"reference/api/labkit/app/layout/slider.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.slider","text":"SLIDER Add a bounded numeric slider. SLIDER Add a bounded numeric slider. Usage: node = labkit.app.layout.slider(id, Name=Value) Description: Declares one bounded scalar slider. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Value - Finite numeric scalar. Default: lower Limits value. Limits - Increasing finite numeric 1-by-2 row. Default: [0 1]. Step - Positive numeric scalar. Default: []. ValueDisplayFormat - Optional spinner numeric format such as \"%.6g\". Default: \"\". ShowTicks - Logical tick visibility. Default: false. Bind - Project or session field path. Default: \"\". Enabled - Initial logical enabled state. Default: true. OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: node = labkit.app.layout.slider(\"frame\", Limits=[1 100], Step=1); See also labkit.app.layout.field, labkit.app.layout.rangeField"},{"title":"labkit.app.layout.statusPanel","url":"reference/api/labkit/app/layout/statusPanel.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.statusPanel","text":"STATUSPANEL Add a text display for current App status. STATUSPANEL Add a text display for current App status. Usage: node = labkit.app.layout.statusPanel(id, Name=Value) Description: Declares a current-status or static instruction display. Inputs: id - Unique MATLAB identifier for the layout target. Options: Title - Visible panel title. Default: \"Status\". Text - Static text lines. When omitted, the runtime's latest status is displayed. Default: strings(1,0). Lines - Approximate visible text lines. The native layout uses this hint to size short summaries without consuming detail-panel height. Integer from 1 through 12. Default: 5. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:InvalidValue for an invalid ID. Typical Call: node = labkit.app.layout.statusPanel(\"status\"); See also labkit.app.CallbackContext"},{"title":"labkit.app.layout.tab","url":"reference/api/labkit/app/layout/tab.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.tab","text":"TAB Add a named tab containing related child elements. TAB Add a named tab containing related child elements. Usage: node = labkit.app.layout.tab(id, title, children) Description: Declares one named control-side tab. Inputs: id - Unique MATLAB identifier for the layout target. title - Nonempty tab title. children - Row cell array of compatible layout nodes. Outputs: node - Immutable internal layout node accepted by workbench. Errors: Throws labkit:app:contract:* for invalid IDs, titles, or children. Typical Call: node = labkit.app.layout.tab(\"settings\", \"Settings\", {gainField}); See also labkit.app.layout.section, labkit.app.layout.workbench"},{"title":"labkit.app.layout.workbench","url":"reference/api/labkit/app/layout/workbench.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.workbench","text":"WORKBENCH Assemble controls and a central workspace into the root layout. WORKBENCH Assemble controls and a central workspace into the root layout. Usage: node = labkit.app.layout.workbench(children, Name=Value) Description: Produces the root semantic layout required by labkit.app.Definition. Inputs: children - Row cell array of controls, groups, sections, or tabs. Options: Workspace - Value returned by layout.workspace. Default: []. Usage - Static workflow instruction lines appended consistently to the first control tab. Default: strings(1,0). UsageTitle - Title for generated workflow instructions. Default: \"Usage\". Outputs: node - Immutable root layout node. Errors: Throws labkit:app:contract:* for invalid children or workspace values. Typical Call: node = labkit.app.layout.workbench({runButton}); See also labkit.app.Definition, labkit.app.layout.workspace"},{"title":"labkit.app.layout.workspace","url":"reference/api/labkit/app/layout/workspace.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.workspace","text":"WORKSPACE Define the App's central working area and optional pages. WORKSPACE Define the App's central working area and optional pages. Usage: node = labkit.app.layout.workspace() node = labkit.app.layout.workspace(content, Name=Value) Description: Declares a single-content workspace or a workspace extended with node.page(id,title,content) and node.initialPage(id). A named page accepts one workspace node or a nonempty row cell array of nodes arranged vertically; growable tables and plots share the available page height. Inputs: content - Optional plotArea, dataTable, group, or section node. For node.page, content may also be a nonempty row cell array of workspace nodes. Options: Title - Reader-facing workspace title. Default: \"Workspace\". OnPageChanged - Callback state = callback(state,pageId,context). Default: []. Outputs: node - Immutable workspace node accepted by layout.workbench. Errors: Throws labkit:app:contract:* for invalid content, pages, or callbacks. Typical Call: node = labkit.app.layout.workspace(plotArea); See also labkit.app.layout.workbench, labkit.app.layout.plotArea"},{"title":"labkit.app.plot.clampPointToAxes","url":"reference/api/labkit/app/plot/clampPointToAxes.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.clampPointToAxes","text":"CLAMPPOINTTOAXES Keep data coordinates inside the visible axes box. CLAMPPOINTTOAXES Keep data coordinates inside the visible axes box. Usage: xyOut = labkit.app.plot.clampPointToAxes(ax, xy) xyOut = labkit.app.plot.clampPointToAxes(ax, xy, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. Its XLim, YLim, XScale, YScale, XDir, and YDir properties define the visible box. xy - N-by-2 numeric matrix of [x y] data coordinates. Name-Value Arguments: Padding - Minimum distance from each axes edge, expressed as a fraction of the visible width or height. Values are limited to [0, 0.49]. Default: 0.04. Outputs: xy - N-by-2 data coordinates, shown as xyOut in the usage syntax. Each point is moved only as far as needed to satisfy Padding. Description: clampData is useful for labels and annotations that must remain readable near an axes boundary. Conversion through normalized axes coordinates keeps the result visually consistent on logarithmic or reversed axes. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidPointPairs - xy is not an N-by-2 numeric array. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig, \"XLim\", [0 10], \"YLim\", [0 20]); xy = labkit.app.plot.clampPointToAxes(ax, [-2 25], \"Padding\", 0.1); assert(isequal(xy, [1 18])) See also labkit.app.plot.offsetPointByAxesFraction"},{"title":"labkit.app.plot.clearAxes","url":"reference/api/labkit/app/plot/clearAxes.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.clearAxes","text":"CLEARAXES Prepare an axes for an app-owned redraw. CLEARAXES Prepare an axes for an app-owned redraw. Usage: labkit.app.plot.clearAxes(ax) labkit.app.plot.clearAxes(ax, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle to clear. Name-Value Arguments: ResetScale - Logical value. true restores linear X/Y scales and automatic X/Y tick modes. false preserves those four properties. Default: false. ClearLegend - Logical value. true turns the legend off. Default: true. Outputs: None. Description: clear deletes plotted children, clears LabKit's cached image home view, releases hold, and returns XLim, YLim, ZLim, and CLim to automatic mode. Labels and other axes decorations follow MATLAB cla behavior. Call it once at the start of a complete redraw, not when adding an overlay that should preserve the current zoom. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. MATLAB graphics errors raised while clearing a valid axes propagate to the caller. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); plot(ax, 1:3, [2 1 3]); labkit.app.plot.clearAxes(ax, \"ResetScale\", true); assert(isempty(ax.Children)) See also labkit.app.plot.fitAxesToGraphics, labkit.app.plot.showMessage"},{"title":"labkit.app.plot.enablePopout","url":"reference/api/labkit/app/plot/enablePopout.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.enablePopout","text":"ENABLEPOPOUT Add a standalone-figure action to an axes context menu. ENABLEPOPOUT Add a standalone-figure action to an axes context menu. Usage: labkit.app.plot.enablePopout(ax) Inputs: ax - MATLAB axes or uiaxes handle that receives an \"Open axes in new figure\" context-menu item. Empty or invalid handles are ignored. Outputs: None. Description: The menu copies the current axes content, limits, labels, colors, and visible legend entries into a normal MATLAB figure with publication and export tools. Existing axes context-menu items are preserved. Children without their own context menu inherit the axes menu, including children added after this call. Repeated calls do not add duplicate menu items. Failure Behavior: Empty or invalid handles are ignored. Errors raised while MATLAB creates the context menu, copies graphics, or opens the standalone figure are not caught and propagate from the originating graphics operation. Typical Call: plot(ax, time, signal); labkit.app.plot.enablePopout(ax); See also labkit.app.layout.plotArea"},{"title":"labkit.app.plot.fitAxesToGraphics","url":"reference/api/labkit/app/plot/fitAxesToGraphics.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.fitAxesToGraphics","text":"FITAXESTOGRAPHICS Fit axes limits to finite plotted X/Y data. FITAXESTOGRAPHICS Fit axes limits to finite plotted X/Y data. Usage: limits = labkit.app.plot.fitAxesToGraphics(ax) limits = labkit.app.plot.fitAxesToGraphics(ax, graphicsHandles) limits = labkit.app.plot.fitAxesToGraphics(..., Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle whose limits are changed. graphicsHandles - Optional graphics handle array. Objects with numeric XData and YData contribute to the fitted range. When omitted, all current axes children are examined. Name-Value Arguments: Padding - Nonnegative fractional padding added on each side of the data range. Default: 0.02. For logarithmic axes, padding is computed in base-10 logarithmic space. EqualDataUnits - Logical value. true expands one fitted dimension so one data unit occupies the same screen distance on X and Y without changing the axes position. For logarithmic dimensions, equality is evaluated in base-10 logarithmic space. Default: false. Outputs: limits - Scalar struct with x and y fields. Each field contains the applied two-element limit, or [] when that dimension had no usable data and was returned to automatic limit mode. Description: fit ignores nonfinite XData and YData. Nonpositive values do not contribute to a logarithmic dimension. Supplying graphicsHandles lets an app exclude annotations such as reference lines from the fitted range. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Invalid supplied graphics handles are ignored when they do not expose numeric XData/YData. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); h = plot(ax, [1 2 3], [10 20 15]); xline(ax, 100); limits = labkit.app.plot.fitAxesToGraphics(ax, h, \"Padding\", 0); assert(isequal(limits.x, [1 3])) See also labkit.app.plot.clearAxes"},{"title":"labkit.app.plot.fitCanvasToSource","url":"reference/api/labkit/app/plot/fitCanvasToSource.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.fitCanvasToSource","text":"FITCANVASTOSOURCE Fit a preview axes into a fixed-aspect pixel frame. FITCANVASTOSOURCE Fit a preview axes into a fixed-aspect pixel frame. Usage: [applied, frame] = labkit.app.plot.fitCanvasToSource(ax, width, height) [applied, frame] = labkit.app.plot.fitCanvasToSource(..., Name=Value) Inputs: ax - UI axes whose parent is a uigridlayout created for a LabKit preview. width - Positive finite source-canvas width in pixels. height - Positive finite source-canvas height in pixels. Name-Value Arguments: margin - Preferred empty margin around the frame in pixels. Default: 24. maxScale - Largest allowed ratio between displayed and source dimensions. Default: 1, so the helper does not enlarge the source canvas. Outputs: applied - true when the grid and axes positions were updated; false when the axes, parent grid, dimensions, or available space were unsuitable. frame - Scalar struct with width, height, ratio, position, scale, and pixelPosition fields. It is an empty struct when applied is false. Description: fitCanvas centers the axes in the middle row and column of a three-by-three flexible grid and preserves the requested aspect ratio. The helper keeps one managed parent-position listener with the axes and reapplies the most recent canvas request after host resizing. It returns false instead of throwing when the host has not been laid out yet or is smaller than the minimum usable preview area. Failure Behavior: Invalid axes, host grids, source dimensions, or insufficient available space return applied=false and frame=struct(). Malformed or unsupported name-value arguments throw labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption. Typical Call: [ok, frame] = labkit.app.plot.fitCanvasToSource(ax, 720, 540); See also labkit.app.layout.plotArea"},{"title":"labkit.app.plot.offsetPointByAxesFraction","url":"reference/api/labkit/app/plot/offsetPointByAxesFraction.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.offsetPointByAxesFraction","text":"OFFSETPOINTBYAXESFRACTION Move data coordinates by normalized axes fractions. OFFSETPOINTBYAXESFRACTION Move data coordinates by normalized axes fractions. Usage: xyOut = labkit.app.plot.offsetPointByAxesFraction(ax, xy, offsetFraction) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. Its current limits, scales, and directions define the coordinate conversion. xy - N-by-2 numeric matrix of [x y] data coordinates. offsetFraction - 1-by-2 or N-by-2 axes-fraction offsets. One row is reused for every point; otherwise the row count must match xy. For example, [0.03 -0.04] moves a label slightly right and down in visual axes space, including on log or reversed axes. Outputs: xy - N-by-2 offset coordinates in data units, shown as xyOut in the usage syntax. Values are not clamped to the visible axes box. Description: offsetData expresses annotation spacing in visual axes fractions instead of data units. This keeps the apparent offset stable as data ranges change and correctly handles logarithmic and reversed axes. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidPointPairs - xy is not an N-by-2 numeric array. labkit:app:plot:InvalidPointOffsets - offsetFraction is neither one row nor one row per point. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig, \"XLim\", [0 10], \"YLim\", [0 20]); xy = labkit.app.plot.offsetPointByAxesFraction(ax, [5 10], [0.1 -0.1]); assert(isequal(xy, [6 8])) See also labkit.app.plot.clampPointToAxes"},{"title":"labkit.app.plot.showMessage","url":"reference/api/labkit/app/plot/showMessage.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.showMessage","text":"SHOWMESSAGE Show a centered empty-state message in an axes. SHOWMESSAGE Show a centered empty-state message in an axes. Usage: hText = labkit.app.plot.showMessage(ax, message) hText = labkit.app.plot.showMessage(ax, message, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. message - User-visible text scalar displayed at the axes center. Name-Value Arguments: Title - Axes title text. Default: \"\". Color - MATLAB color value for the message text. Default: [0.30 0.30 0.30]. Outputs: hText - MATLAB text object containing the message. Description: message clears the axes, resets it to linear unit limits [0 1], removes ticks, and draws non-pickable text. Use it for an empty preview, loading prompt, or unavailable result. Because it performs a complete clear, it is not an overlay operation and does not preserve the previous zoom. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Invalid MATLAB text or color values propagate their originating graphics error. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); h = labkit.app.plot.showMessage(ax, \"No data\", \"Title\", \"Preview\"); assert(string(h.String) == \"No data\") See also labkit.app.plot.clearAxes"},{"title":"labkit.app.project.Schema","url":"reference/api/labkit/app/project/Schema.html","kind":"reference","section":"reference","keywords":"labkit.app.project.Schema","text":"PROJECTCONTRACT Declare one durable App project contract. PROJECTCONTRACT Declare one durable App project contract. Usage: contract = labkit.app.project.Schema() contract = labkit.app.project.Schema(Name=Value) Description: Project Schema owns payload creation, validation, sequential migration, optional legacy import, resume, and source-relink callback signatures. App-specific fields and scientific meaning remain inside the payload and are not interpreted by this value. Default Contract: With no arguments, Version is 1, Create returns struct(), and Validate accepts any scalar struct. This is the standard path for a simple App with no migration or specialized project invariants. Required Name-Value Arguments (custom contract): Version - Positive integer payload version. Create - Fixed callback project = create(). Validate - Fixed callback accepted = validate(project). Optional Name-Value Arguments: Migrate - Fixed callback project = migrate(project,fromVersion). Required when Version is greater than 1. Default: empty. LegacyImports - Scalar struct mapping legacy MAT variable names to fixed callbacks accepting one value and returning project or [project,resume]. Default: struct(). CreateResume - Fixed callback resume = createResume(session,project). Default: empty. ApplyResume - Fixed callback session = applyResume(session,resume,project). Default: empty. RelinkSources - Fixed callback project = relink(project,unresolved,projectFile). Returning empty cancels the load. Default: empty. Outputs: contract - Immutable labkit.app.project.Schema value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Version or LegacyImports is invalid. labkit:app:contract:CallbackRoleMismatch - A callback does not have its documented fixed input and output count. Typical Call: contract = labkit.app.project.Schema(Version=1, ... Create=@createProject, Validate=@validateProject); See also labkit.app.Definition, labkit.app.result.Package"},{"title":"labkit.app.project.emptySourceRecords","url":"reference/api/labkit/app/project/emptySourceRecords.html","kind":"reference","section":"reference","keywords":"labkit.app.project.emptySourceRecords","text":"EMPTYSOURCERECORDS Create an empty portable source collection. EMPTYSOURCERECORDS Create an empty portable source collection. Usage: records = labkit.app.project.emptySourceRecords() Description: Returns a zero-row struct collection with the same durable field shape as labkit.app.project.sourceRecord. App project factories use this value so the first file selection can assign a portable source without exposing or duplicating its field schema. Outputs: records - Empty column collection of portable source records. Failure Behavior: None - The function accepts no inputs and returns the canonical empty collection deterministically. Example: project.inputs.sources = labkit.app.project.emptySourceRecords(); assert(isempty(project.inputs.sources)) See also labkit.app.project.sourceRecord, labkit.app.project.Schema"},{"title":"labkit.app.project.sourceRecord","url":"reference/api/labkit/app/project/sourceRecord.html","kind":"reference","section":"reference","keywords":"labkit.app.project.sourceRecord","text":"SOURCERECORD Create a portable source value during project migration. SOURCERECORD Create a portable source value during project migration. Usage: record = labkit.app.project.sourceRecord(id,role,filepath) record = labkit.app.project.sourceRecord(id,role,filepath,required) Description: Creates the opaque durable source value accepted by App project schemas. Use this pure constructor only while creating or migrating payloads. Runtime callbacks resolve paths through CallbackContext. Inputs: id - Nonempty scalar text stable within the project. role - Nonempty scalar semantic source role. filepath - Nonempty scalar source path, or an existing scalar opaque reference struct during legacy migration. required - Optional logical scalar relinking requirement. Default: true. Outputs: record - Scalar portable source struct owned by the App framework. Errors: labkit:app:contract:InvalidValue - Text or required is malformed. Example: source = labkit.app.project.sourceRecord( ... \"image1\",\"cropSource\",\"image.png\"); assert(source.id == \"image1\") See also labkit.app.CallbackContext, labkit.app.project.Schema"},{"title":"labkit.app.result.File","url":"reference/api/labkit/app/result/File.html","kind":"reference","section":"reference","keywords":"labkit.app.result.File","text":"RESULTOUTPUT Declare one validated result-package output. RESULTOUTPUT Declare one validated result-package output. Usage: output = labkit.app.result.File(id, role, relativePath, Name=Value) Description: File records App-owned output identity and media semantics. Runtime result writing later resolves the relative path, file size, checksum, and aggregate manifest status. Inputs: id - Nonempty scalar text unique within one Result. role - Nonempty scalar text describing output purpose. relativePath - Nonempty package-relative path without traversal. Optional Name-Value Arguments: MediaType - Nonempty media-type scalar text. Default: \"application/octet-stream\". Status - \"success\", \"failed\", or \"skipped\". Default: \"success\". Message - Scalar reader-facing status text. Default: empty. Warnings - Row string or cellstr array. Default: empty. Outputs: output - Immutable labkit.app.result.File value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Text, path, status, or warnings are malformed. Example: output = labkit.app.result.File( ... \"summary\", \"primary\", \"summary.csv\", MediaType=\"text/csv\"); assert(output.Status == \"success\") See also labkit.app.result.Package, labkit.app.CallbackContext"},{"title":"labkit.app.result.Package","url":"reference/api/labkit/app/result/Package.html","kind":"reference","section":"reference","keywords":"labkit.app.result.Package","text":"RESULT Declare one App-owned result manifest request. RESULT Declare one App-owned result manifest request. Usage: result = labkit.app.result.Package(Name=Value) Description: Result contains validated output declarations and App-owned input, parameter, and summary data. Runtime result writing owns provenance, App and facade versions, document identity, file bytes, checksums, creation identity/time, aggregate status, and atomic replacement. Required Name-Value Arguments: Outputs - Nonempty row cell array of labkit.app.result.File values with unique IDs and relative paths and at least one success. Inputs - Scalar App-owned struct describing result inputs. Parameters - Scalar App-owned struct describing result parameters. Summary - Scalar App-owned struct describing result summary. Optional Name-Value Arguments: Warnings - Row string or cellstr array. Default: empty. ManifestName - Nonempty filename without folders. Default: \"labkit_result.json\". Outputs: result - Immutable labkit.app.result.Package value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - A value or manifest name is invalid. labkit:app:contract:DuplicateId - Output IDs or paths repeat. Example: output = labkit.app.result.File( ... \"summary\", \"primary\", \"summary.csv\", MediaType=\"text/csv\"); result = labkit.app.result.Package(Outputs={output}, Inputs=struct(), ... Parameters=struct(), Summary=struct(\"rows\", 1)); assert(result.ManifestName == \"labkit_result.json\") See also labkit.app.result.File, labkit.app.CallbackContext"},{"title":"labkit.app.synthetic.Artifact","url":"reference/api/labkit/app/synthetic/Artifact.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Artifact","text":"ARTIFACT Describe one anonymous synthetic-input artifact. ARTIFACT Describe one anonymous synthetic-input artifact. Usage: artifact = labkit.app.synthetic.Artifact( ... id,role,relativePath,Name=Value) Description: Identifies one synthetic input, expected export, or support file beneath a synthetic-input root without exposing a user file path. Inputs: id - Nonempty semantic identifier unique within a Pack. role - Nonempty App-owned artifact purpose. relativePath - Nonempty synthetic-root-relative path without traversal. Optional Name-Value Arguments: Expectation - \"loads\", \"rejects\", \"exports\", or \"support\". Default: \"loads\". Outputs: artifact - Immutable synthetic-input artifact value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Text, path, or Expectation is malformed. Example: artifact = labkit.app.synthetic.Artifact( ... \"input\",\"source\",\"samples/input.csv\"); assert(artifact.Expectation == \"loads\") See also labkit.app.synthetic.Context, labkit.app.synthetic.Pack"},{"title":"labkit.app.synthetic.Context","url":"reference/api/labkit/app/synthetic/Context.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Context","text":"CONTEXT Provide bounded folders for anonymous synthetic inputs. CONTEXT Provide bounded folders for anonymous synthetic inputs. Usage: context = labkit.app.synthetic.Context(rootFolder) filepath = context.samplePath(relativePath) filepath = context.outputPath(relativePath) record = context.sourceRecord(id,role,filepath,required) artifact = context.artifact(id,role,filepath,Name=Value) Description: Context creates one synthetic-input root with samples and outputs children. App-owned BuildSyntheticSample callbacks may write only anonymous synthetic files beneath these folders. The value does not expose a runtime, recorder, project store, or UI object. Inputs: rootFolder - Nonempty scalar folder dedicated to one generated pack. relativePath - Nonempty child path without an absolute root, empty segment, current segment, or parent traversal. id - Stable portable-source identifier. role - Stable portable-source role. filepath - Path returned by samplePath. required - Logical scalar source requirement. Outputs: context - Immutable synthetic-input context. filepath - Absolute path bounded by SampleFolder or OutputFolder. record - Portable source value from labkit.app.project.sourceRecord. artifact - Typed synthetic artifact whose relative path is derived from a filepath beneath RootFolder. Errors: labkit:app:contract:InvalidValue - A folder, relative path, or source argument is malformed. MATLAB filesystem errors propagate when the synthetic folders cannot be created. Typical Call: context = labkit.app.synthetic.Context(tempname); filepath = context.samplePath(\"input.csv\"); See also labkit.app.synthetic.Artifact, labkit.app.synthetic.Pack, labkit.app.project.sourceRecord"},{"title":"labkit.app.synthetic.Pack","url":"reference/api/labkit/app/synthetic/Pack.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Pack","text":"PACK Describe one typed anonymous App reproduction scenario. PACK Describe one typed anonymous App reproduction scenario. Usage: pack = labkit.app.synthetic.Pack( ... Scenario=scenario,InitialProject=project,Artifacts=artifacts) Description: Couples one App-authored synthetic project with its anonymous artifact declarations so users and tests can reproduce a named scenario through the App's ordinary import workflow. Required Name-Value Arguments: Scenario - Nonempty stable scenario identifier. InitialProject - Scalar current App project struct. Artifacts - Row cell array of labkit.app.synthetic.Artifact values. Empty is legal for an App whose scenario needs no files. Outputs: pack - Immutable synthetic-input pack. Errors: labkit:app:contract:UnknownArgument - An argument is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Scenario, project, or Artifacts is malformed. labkit:app:contract:DuplicateId - Artifact IDs or relative paths are duplicated. Example: artifact = labkit.app.synthetic.Artifact( ... \"input\",\"source\",\"samples/input.csv\"); pack = labkit.app.synthetic.Pack( ... Scenario=\"representative\",InitialProject=struct(), ... Artifacts={artifact}); assert(pack.Scenario == \"representative\") See also labkit.app.synthetic.Context, labkit.app.synthetic.Artifact, labkit.app.Definition"},{"title":"labkit.app.version","url":"reference/api/labkit/app/version.html","kind":"reference","section":"reference","keywords":"labkit.app.version","text":"VERSION Return the LabKit App SDK facade contract version. VERSION Return the LabKit App SDK facade contract version. Usage: info = labkit.app.version() Description: Reports the semantic version and compatibility range of the public LabKit App SDK. The SDK owns App definitions, semantic layout, transactions, projects, portable sources, resources, results, dialogs, and native MATLAB presentation. It is independent of App product and saved-project schema versions. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo with name, current, compatible, status, and notes fields. Errors: labkit:contract:InvalidVersionInfo - Embedded facade metadata is invalid. Example: info = labkit.app.version(); assert(startsWith(info.current, \"2.\")) See also labkit.contract.versionInfo, labkit.contract.requirements, labkit.app.Definition"},{"title":"labkit.app.view.Snapshot","url":"reference/api/labkit/app/view/Snapshot.html","kind":"reference","section":"reference","keywords":"labkit.app.view.Snapshot","text":"SNAPSHOT Build one immutable complete visible-state snapshot. SNAPSHOT Build one immutable complete visible-state snapshot. Usage: view = labkit.app.view.Snapshot() view = view.value(target, value) view = view.choices(target, choices) view = view.limits(target, limits) view = view.enabled(target, enabled) view = view.visible(target, visible) view = view.text(target, text) view = view.filePaths(target, paths) view = view.fileItemStatuses(target, statuses) view = view.listSelection(target, selection) view = view.tableCellSelection(target, selection) view = view.tableData(target, data, Name=Value) view = view.renderPlot(target, model, Name=Value) view = view.workspacePage(target, Name=Value) view = view.anchorPath(interaction, points, Name=Value) view = view.pairedAnchors(interaction, pointSets, Name=Value) view = view.pointSlots(interaction, value, Name=Value) view = view.rectangle(interaction, position, Name=Value) view = view.regionSelection(interaction, Name=Value) view = view.interval(interaction, range, Name=Value) view = view.scaleReference(interaction, endpoints, Name=Value) view = view.include(fragment) Description: View snapshot is a closed immutable operation vocabulary. One value describes the complete current visible state; Definition validates targets, capabilities, renderer references, and completeness before a runtime may reconcile it. Apps do not author patches or generic target/property setters. Inputs: target - Nonempty Layout target ID. value - Target value owned by the App state. choices - Text array or cellstr of legal choices. limits - Increasing finite two-element numeric row. enabled - Logical scalar availability. visible - Logical scalar visibility. text - Scalar text. paths - String or cell array of file paths. statuses - Empty or one reader-facing status per file-list row. selection - Selection value accepted by the target. data - App-owned table, numeric array, or cell array. model - App-owned model passed to the renderer declared by plotArea. Options: ViewRevision - Nonnegative integer identifying the requested initial viewport. The adapter preserves user zoom while the value is unchanged and accepts renderer limits once when it changes. Default: 0. Outputs: view - New immutable labkit.app.view.Snapshot snapshot. fragment - Another Snapshot whose non-duplicate operations should be included in this snapshot. Errors: labkit:app:contract:InvalidValue - An operation argument is malformed. labkit:app:contract:DuplicateId - The same operation is supplied twice for one target. Example: view = labkit.app.view.Snapshot(); view = view.choices(\"group\", [\"A\", \"B\"]); view = view.value(\"group\", \"A\"); assert(isa(view, \"labkit.app.view.Snapshot\")) See also labkit.app.Definition, labkit.app.layout.workbench"},{"title":"labkit.biosignal.buildTemplate","url":"reference/api/labkit/biosignal/buildTemplate.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.buildTemplate","text":"BUILDTEMPLATE Build a representative segment template. BUILDTEMPLATE Build a representative segment template. Usage: template = labkit.biosignal.buildTemplate(segments) template = labkit.biosignal.buildTemplate(segments, opts) Description: Forms a first-pass template by averaging every segment, then ranks each segment by its correlation with that average. The returned waveform is the sample-by-sample mean of the highest-ranked segments. This two-pass procedure reduces the influence of beats or trials whose shape differs from the majority. Correlations are computed after subtracting each waveform's mean. Missing samples are omitted from means and correlations. A constant waveform has a NaN score and sorts behind finite scores. If segments contains no waveforms, the function returns an empty template with the same timeOffset vector. Inputs: segments - Segment struct returned by labkit.biosignal.segmentByEvents. segments.values is an M-by-N matrix, with one segment per column; segments.timeOffset contains the M relative times. opts - Optional scalar struct containing the fields listed below. Options: topN - Number of ranked segments to average. The default is the smaller of 30 and the number of available segments. The value is rounded to an integer and limited to the range 1:N. Outputs: template - Structure containing the representative waveform and the ranking information used to construct it. Output Fields: type - String scalar \"biosignalTemplate\". values - M-by-1 template waveform, or an M-by-0 array when no segments are available. timeOffset - M-by-1 relative time vector copied from segments. keptSegmentIndex - Indices of the segment columns included in values, ordered from highest to lowest correlation. score - N-by-1 correlation score for every input segment. metadata.topN - Number of segments included in a nonempty template. metadata.sourceName - Source signal name copied from segments for a nonempty template. metadata is empty when no segment waveforms are available. Errors: labkit:biosignal:InvalidSegments - segments is not a structure with values and timeOffset fields. Invalid numeric values or an unusable topN value may also raise the originating MATLAB conversion error. Example: segments = struct('values', [0 0; 1 0.9; 0 0], ... 'timeOffset', [-1; 0; 1], 'sourceName', \"ECG\"); template = labkit.biosignal.buildTemplate(segments, struct('topN', 1)); See also labkit.biosignal.segmentByEvents, labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.compareGroups","url":"reference/api/labkit/biosignal/compareGroups.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.compareGroups","text":"COMPAREGROUPS Summarize groups and compute pairwise Welch comparisons. COMPAREGROUPS Summarize groups and compute pairwise Welch comparisons. Usage: result = labkit.biosignal.compareGroups(values, groups) Description: Calculates descriptive statistics for each group and a two-sided Welch t-test for every pair of groups. Welch's test does not assume equal variances. Group order follows the first occurrence of each label in groups. Rows with a nonfinite measurement or a blank group label are discarded before calculation. A pairwise row is still returned when either group has fewer than two observations or the estimated standard error is zero; its test statistic, degrees of freedom, and p-value are NaN. Inputs: values - Numeric vector of measurements. groups - Vector of labels accepted by string, such as a string array, cell array of character vectors, or categorical array. It must contain one label for each element of values. Outputs: result - Structure with summary and pairwise tables. Output Fields: summary - One row per group. Columns are Group, N, Mean, Std, Median, Min, and Max. pairwise - One row per unique group pair. Columns are GroupA, GroupB, MeanDifference, T, DF, and P. MeanDifference is mean(GroupA) minus mean(GroupB); P is the two-sided Welch-test p-value. Failure Behavior: Groups with fewer than two finite observations, or with zero estimated standard error, retain a pairwise row with NaN test statistics. values and groups must contain the same number of elements; incompatible input shapes or values that cannot be converted to numeric/text raise the originating MATLAB error. Example: values = [4.8 5.1 5.0 6.2 6.0 6.4]; groups = [\"control\" \"control\" \"control\" \"treated\" \"treated\" \"treated\"]; result = labkit.biosignal.compareGroups(values, groups); See also labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.cropSignal","url":"reference/api/labkit/biosignal/cropSignal.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.cropSignal","text":"CROPSIGNAL Return a signal clipped to a time range in seconds. CROPSIGNAL Return a signal clipped to a time range in seconds. Usage: cropped = labkit.biosignal.cropSignal(signal, timeRangeSec) cropped = labkit.biosignal.cropSignal(signal) Description: Keeps samples whose timestamps lie between the requested start and end times, including both endpoints. For a nonempty result, time is shifted so that the first retained sample occurs at zero. The sample rate is recalculated from the median positive time step when at least two usable timestamps remain; otherwise the input sample rate is preserved. Omitting timeRangeSec or passing [] returns signal unchanged. A valid range that contains no samples returns empty time and values vectors. Inputs: signal - Biosignal structure with time, values, fs, and metadata fields. time and values must describe corresponding samples. timeRangeSec - Two-element numeric vector [startSec endSec], expressed on signal.time, with startSec less than endSec. The value may be omitted or empty to skip cropping. Outputs: cropped - Copy of signal containing only the selected samples. The original range is recorded in metadata.cropTimeRangeSec. Errors: labkit:biosignal:InvalidSignal - signal does not contain time, values, and fs fields. labkit:biosignal:InvalidTimeRange - timeRangeSec is not an increasing two-element vector. Example: signal = struct('time', (0:0.5:3)', 'values', (10:16)', ... 'fs', 2, 'metadata', struct()); cropped = labkit.biosignal.cropSignal(signal, [0.75 2.25]); See also labkit.biosignal.filterSignal, labkit.biosignal.getChannel"},{"title":"labkit.biosignal.defaultEcgPeakOptions","url":"reference/api/labkit/biosignal/defaultEcgPeakOptions.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.defaultEcgPeakOptions","text":"DEFAULTECGPEAKOPTIONS Return documented defaults for ECG/QRS peak detection. DEFAULTECGPEAKOPTIONS Return documented defaults for ECG/QRS peak detection. Usage: opts = labkit.biosignal.defaultEcgPeakOptions() opts = labkit.biosignal.defaultEcgPeakOptions(method) Description: Returns a complete starting options structure for one ECG peak detector. Modify only the fields needed for a recording, then pass the structure to labkit.biosignal.detectEcgPeaks. Fields that do not apply to the selected method are omitted, which makes saved settings easier to interpret. Inputs: method - Optional character vector or scalar string. Canonical values are \"qrs-streaming\" (default), \"pan-tompkins\", and \"local\". Common spellings such as \"streaming\", \"pantompkin\", and \"simple\" are accepted and normalized to a canonical value. Outputs: opts - Scalar structure containing the selected method and its defaults. Common Options: method - Canonical detector name. polarity - Peak direction: \"auto\", \"positive\", \"negative\", or \"absolute\". The default is \"auto\". minDistanceSec - Minimum interval between accepted peaks. The default is 0.25 seconds for qrs-streaming and pan-tompkins, and 0.05 seconds for local. Local Options: thresholdStd - Number of robust standard deviations above the median score used as the automatic threshold. Default 3. smoothSec - Moving-average duration applied to the peak score. Default 0.01 seconds. Pan-Tompkins Options: integrationWindowSec - Moving integration duration for squared QRS-band slope energy. Default 0.150 seconds. refineSearchSec - Half-width used to move an energy candidate to the nearest peak in the detector trace. Default 0.120 seconds. rawRefineSearchSec - Half-width for the final move to a peak in the raw signal. Default 0.020 seconds. Streaming QRS Options: baselineWindowSec - Causal moving-baseline duration. Default 0.600 seconds. envelopeWindowSec - Causal slope-envelope duration. Default 0.080 seconds. lookaheadSec - Forward interval used to confirm a local envelope maximum. Default 0.080 seconds. refineSearchSec - Half-width used to move a candidate from the envelope to the baseline-corrected signal. Default 0.090 seconds. rawRefineSearchSec - Half-width for the final move to a raw-signal peak. Default 0.020 seconds. minTemplateScore - Minimum correlation with the rolling QRS template once at least four templates are available. Default 0.45. medianPolarityCorrection - Logical value that enables a final correction of isolated opposite-polarity anchors. Default true. medianReviewPeakCount - Number of accepted peaks used by the polarity review. Default 3. Errors: labkit:biosignal:UnsupportedPeakMethod - method cannot be normalized to a supported detector. Example: opts = labkit.biosignal.defaultEcgPeakOptions(\"pan-tompkins\"); opts.minDistanceSec = 0.35; See also labkit.biosignal.detectEcgPeaks"},{"title":"labkit.biosignal.detectEcgPeaks","url":"reference/api/labkit/biosignal/detectEcgPeaks.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.detectEcgPeaks","text":"DETECTECGPEAKS Detect ECG/QRS peaks as event anchors. DETECTECGPEAKS Detect ECG/QRS peaks as event anchors. Usage: events = labkit.biosignal.detectEcgPeaks(signal) events = labkit.biosignal.detectEcgPeaks(signal, opts) Description: Detects beat anchors from one ECG-like signal. The function first obtains the documented defaults for opts.method, then replaces them with fields supplied in opts. Input time, sample indices, and raw amplitudes are preserved in the returned events so the anchors can be used directly by labkit.biosignal.segmentByEvents. qrs-streaming uses a causal moving baseline and slope envelope, adaptive signal and noise levels, a rolling morphology template, and optional median-polarity review. pan-tompkins uses a QRS-band signal, squared derivative energy, moving integration, adaptive thresholding, and search back. local applies a robust amplitude threshold followed by nonmaximum suppression. Pan-Tompkins and streaming candidates are finally moved to nearby peaks in the raw signal. Empty signals, invalid sample rates, and signals too short for the chosen detector return an empty events structure rather than an error. Inputs: signal - Biosignal structure with time, values, and fs fields. time and values must have corresponding samples; fs is in hertz. opts - Optional scalar struct. Start with labkit.biosignal.defaultEcgPeakOptions and change the fields that need tuning for the recording. Options: method - \"qrs-streaming\" (default), \"pan-tompkins\", or \"local\". polarity - \"auto\" (default), \"positive\", \"negative\", or \"absolute\". auto chooses the stronger direction; absolute scores either direction by magnitude. minDistanceSec - Minimum accepted peak spacing in seconds. Defaults are method-specific; see defaultEcgPeakOptions. threshold - Local method only. Explicit score threshold. The default [] derives a threshold from thresholdStd. thresholdStd - Local method only. Robust standard-deviation multiplier; default 3. smoothSec - Local method only. Score smoothing duration; default 0.01 seconds. integrationWindowSec - Pan-Tompkins integration duration; default 0.150 seconds. refineSearchSec - Detector-trace search half-width. Defaults are 0.120 seconds for pan-tompkins and 0.090 seconds for qrs-streaming. rawRefineSearchSec - Final raw-signal search half-width for pan-tompkins and qrs-streaming. Default 0.020 seconds. baselineWindowSec - qrs-streaming baseline duration; default 0.600 seconds. envelopeWindowSec - qrs-streaming slope-envelope duration; default 0.080 seconds. lookaheadSec - qrs-streaming local-maximum lookahead; default 0.080 seconds. minTemplateScore - qrs-streaming rolling-template correlation threshold; default 0.45. medianPolarityCorrection - qrs-streaming logical switch for final anchor polarity correction; default true. medianReviewPeakCount - qrs-streaming review length; default 3 peaks. Outputs: events - Structure containing one row-aligned entry per detected peak. Output Fields: type - String scalar \"biosignalEvents\". index - 1-based sample indices in signal.values. time - Event times copied from signal.time at index. amplitude - Raw signal amplitudes at the accepted indices. score - Method-specific detector score at each event. label - \"qrs\" for pan-tompkins and qrs-streaming, or \"peak\" for local. threshold - Final score threshold used by the detector. metadata - Selected method, polarity, minimum spacing, and other method-specific settings used during detection. Errors: labkit:biosignal:InvalidSignal - signal lacks time, values, or fs. labkit:biosignal:UnsupportedPeakMethod - opts.method is unsupported. labkit:biosignal:UnsupportedPolarity - opts.polarity is unsupported. Example: fs = 100; time = (0:1/fs:4)'; values = zeros(size(time)); values(101:100:401) = 1; signal = struct('time', time, 'values', values, 'fs', fs); opts = struct('method', 'local', 'polarity', 'positive', ... 'minDistanceSec', 0.5, 'thresholdStd', 1); events = labkit.biosignal.detectEcgPeaks(signal, opts); See also labkit.biosignal.defaultEcgPeakOptions, labkit.biosignal.segmentByEvents"},{"title":"labkit.biosignal.filterSignal","url":"reference/api/labkit/biosignal/filterSignal.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.filterSignal","text":"FILTERSIGNAL Apply a zero-phase FFT-domain filter to a biosignal. FILTERSIGNAL Apply a zero-phase FFT-domain filter to a biosignal. Usage: filtered = labkit.biosignal.filterSignal(signal) filtered = labkit.biosignal.filterSignal(signal, spec) Description: Applies an ideal frequency mask in the discrete Fourier domain and uses an inverse transform to return a zero-phase filtered waveform. Low-pass and band-pass filtering restore the input mean after filtering; high-pass filtering does not. Missing samples are filled before the transform, so filtered.values is finite when the input contains enough finite data for interpolation. Reflect edge mode extrapolates both ends of the waveform before applying the FFT and removes the padding afterward. A cosine taper is applied only to the outer padded samples. This reduces wraparound artifacts without changing the length or time axis of the signal. Use edgeMode \"none\" when the unpadded periodic FFT behavior is required. Inputs: signal - Biosignal structure with values, fs, and metadata fields. fs is the sample rate in hertz. spec - Optional scalar struct containing the fields listed below. Options: type - \"bandpass\" (default), \"lowpass\", \"highpass\", \"none\", or \"off\". The latter two skip frequency masking but still fill missing data. cutoffHz - Scalar cutoff for low-pass or high-pass filtering, or a two-element [low high] band for band-pass filtering. The default is [0.5 40] Hz. Empty or nonfinite values skip filtering and return the filled waveform. edgeMode - \"reflect\" (default) or \"none\". The aliases \"reflection\", \"symmetric\", \"pad\", and \"padded\" select reflection; \"off\" and \"raw\" select no padding. edgePadSec - Nonnegative padding duration in seconds. By default, band-pass and high-pass filters use three periods of the lowest cutoff, limited to 0.5 through 5 seconds. Other filters use 1 second. Padding cannot exceed half the available signal minus one sample. edgeTaperSec - Nonnegative duration of the outer padding taper. The default is 1 second. Outputs: filtered - Copy of signal with filtered values. All other signal fields are preserved, and metadata.filter records spec exactly as supplied by the caller. Errors: labkit:biosignal:InvalidSignal - signal lacks values or fs. labkit:biosignal:UnsupportedFilter - type is not supported. labkit:biosignal:UnsupportedFilterEdgeMode - edgeMode is not supported. Example: fs = 100; time = (0:1/fs:2)'; values = sin(2*pi*2*time) + 0.2*sin(2*pi*30*time); signal = struct('time', time, 'values', values, 'fs', fs, ... 'metadata', struct()); filtered = labkit.biosignal.filterSignal(signal, ... struct('type', 'lowpass', 'cutoffHz', 10)); See also labkit.biosignal.cropSignal, labkit.biosignal.detectEcgPeaks"},{"title":"labkit.biosignal.getChannel","url":"reference/api/labkit/biosignal/getChannel.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.getChannel","text":"GETCHANNEL Return one signal from a recording by display name or index. GETCHANNEL Return one signal from a recording by display name or index. Usage: signal = labkit.biosignal.getChannel(recording, channel) Description: Selects one channel from a recording created by labkit.biosignal.readRecording. A numeric channel is interpreted as a 1-based index. Text is matched exactly against displayName first and then against the source name. Matching is case-sensitive. Use labkit.biosignal.listChannels to obtain the display names presented by the recording. Display names are unique even when source channel names are repeated. Inputs: recording - Recording structure returned by labkit.biosignal.readRecording. channel - Positive integer index, character vector, or scalar string containing a channel name or display name. Outputs: signal - Selected signal structure. Its principal fields are time in seconds, values, fs in hertz, name, displayName, unit, and metadata. Errors: labkit:biosignal:InvalidRecording - recording has no signals field. labkit:biosignal:NoChannels - recording contains no signals. labkit:biosignal:InvalidChannelIndex - numeric index is not an integer within the available range. labkit:biosignal:InvalidChannel - channel has an unsupported type. labkit:biosignal:UnknownChannel - no exact name matches channel. Example: channel = struct('name', \"ECG\", 'displayName', \"ECG\", ... 'time', [0; 0.01], 'values', [0; 1], 'fs', 100, ... 'unit', \"mV\", 'metadata', struct()); recording = struct('signals', channel); signal = labkit.biosignal.getChannel(recording, \"ECG\"); See also labkit.biosignal.listChannels, labkit.biosignal.readRecording"},{"title":"labkit.biosignal.listChannels","url":"reference/api/labkit/biosignal/listChannels.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.listChannels","text":"LISTCHANNELS Return display names for all channels in a recording. LISTCHANNELS Return display names for all channels in a recording. Usage: names = labkit.biosignal.listChannels(recording) Description: Returns channel display names in recording order. These are the same names accepted by labkit.biosignal.getChannel and are suitable for a list box or drop-down menu. A recording with no signals returns a 1-by-0 cell array. Inputs: recording - Recording structure returned by labkit.biosignal.readRecording. Outputs: names - 1-by-N cell array of display-name character vectors. Errors: labkit:biosignal:InvalidRecording - recording is not a structure with a signals field. Example: signals(1).displayName = \"ECG\"; signals(2).displayName = \"Respiration\"; recording = struct('signals', signals); names = labkit.biosignal.listChannels(recording); See also labkit.biosignal.getChannel, labkit.biosignal.readRecording"},{"title":"labkit.biosignal.measureSegments","url":"reference/api/labkit/biosignal/measureSegments.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.measureSegments","text":"MEASURESEGMENTS Measure generic template-residual segment quality. MEASURESEGMENTS Measure generic template-residual segment quality. Usage: result = labkit.biosignal.measureSegments(segments, template) result = labkit.biosignal.measureSegments(segments, template, opts) Description: Measures each segment against a representative template. SignalP2P is the peak-to-peak amplitude inside the signal window. NoiseRMS is the root mean square of segment minus template inside the selected noise windows. SNRdB is 20*log10(SignalP2P/NoiseRMS), and TemplateCorrelation is the mean-centered correlation between the complete segment and template. The function also summarizes the per-segment measurements with means and sample standard deviations. If either the segment matrix or template is empty, both returned tables and metadata are empty. Inputs: segments - Segment structure returned by labkit.biosignal.segmentByEvents. Its values matrix must have the same number of rows as template.values. template - Template structure returned by labkit.biosignal.buildTemplate. opts - Optional scalar struct containing the fields listed below. Options: signalWindowSec - Two-element interval used for SignalP2P. The default is [-0.06 0.06] seconds relative to the event. The interval must include at least one timeOffset sample. noiseWindowsSec - N-by-2 matrix of intervals used for NoiseRMS. The default is [-0.30 -0.20; 0.40 0.50] seconds. Their union must include at least one timeOffset sample. Outputs: result - Structure containing per-segment measurements, aggregate statistics, and the windows used in the calculation. Output Fields: type - String scalar \"biosignalSegmentMeasurements\". perSegment - Table with Segment, EventIndex, EventTime, SignalP2P, NoiseRMS, SNRdB, and TemplateCorrelation columns. summary - One-row table with SegmentCount and the mean and standard deviation of SignalP2P, NoiseRMS, and SNRdB, plus the mean TemplateCorrelation. metadata.signalWindowSec - Signal interval used for the calculation. metadata.noiseWindowsSec - Noise intervals used for the calculation. Errors: labkit:biosignal:InvalidSegments - segments lacks values or timeOffset. labkit:biosignal:InvalidTemplate - template lacks values. Example: segments = struct('values', [0 0; 2 1.8; 0 0], ... 'timeOffset', [-0.1; 0; 0.1], 'eventIndex', [10; 20], ... 'eventTime', [1; 2]); template = struct('values', [0; 1.9; 0]); opts = struct('signalWindowSec', [-0.05 0.05], ... 'noiseWindowsSec', [-0.1 -0.05; 0.05 0.1]); result = labkit.biosignal.measureSegments(segments, template, opts); See also labkit.biosignal.buildTemplate, labkit.biosignal.segmentByEvents"},{"title":"labkit.biosignal.readRecording","url":"reference/api/labkit/biosignal/readRecording.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.readRecording","text":"READRECORDING Read a biosignal file into a standard recording struct. READRECORDING Read a biosignal file into a standard recording struct. Usage: [recording, status] = labkit.biosignal.readRecording(filepath) [recording, status] = labkit.biosignal.readRecording(filepath, opts) Description: Imports supported biosignal files into one recording format used by the rest of labkit.biosignal. MAT files contribute each numeric or logical vector stored in a timetable. CSV, TSV, and TXT files contribute each selected numeric column other than the time column. For delimited text, the importer detects a header after an optional preamble and recognizes numeric, datetime, or duration time columns. An explicit timeColumn takes precedence. Otherwise, names that look like time are preferred; a headerless first numeric column and the common I0 device column are also recognized. If no time column can be identified, the importer creates a sample-index time axis. All returned signal times are measured in seconds from zero. Import and format errors are reported in status instead of being thrown. The returned recording is then an empty recording carrying the requested source path. An invalid filepath value is a programming error and is thrown before import begins. Inputs: filepath - Character vector or scalar string naming a MAT, CSV, TSV, or TXT file. opts - Optional scalar struct containing the fields listed below. Options: headerLine - Positive integer giving the header line, or the first data line when hasHeader is false. By default the importer scans the first 120 lines. hasHeader - Logical scalar indicating whether headerLine contains column names. The default [] selects automatic detection. timeColumn - Column name or 1-based column index to use as time. The default [] enables the inference described above. timeUnit - Unit for a numeric time column: \"seconds\", \"milliseconds\", \"microseconds\", \"nanoseconds\", or the abbreviations s, sec, ms, us, and ns. \"sample\", \"samples\", \"index\", and \"sample_index\" leave the numeric scale unchanged. The default infers SI-prefixed units from the column name and otherwise uses seconds. signalColumns - Column names or 1-based indices to import from a delimited file. The default [] imports every numeric non-time vector. fallbackFs - Positive sample rate in hertz. It sets the spacing of a synthetic time axis and supplies a nominal interval when timestamps cannot provide one. Without it, synthetic sample-index time advances by one second per row. timeRepair - \"auto\" (default) replaces duplicate or backward timestamp steps with the nominal positive step. \"none\" and \"off\" preserve the converted intervals after shifting to zero. gapFactor - Positive scalar used only to report unusually large positive gaps. A gap larger than gapFactor times the median positive interval is counted. The default is 20. useFirstNumericColumnAsTime - Logical scalar. true permits the first numeric column to be used when no other time rule matches. The default is false. Outputs: recording - Scalar recording structure. signals is a structure array with one element per imported channel. status - Scalar structure describing whether import succeeded. Recording Fields: type - String scalar \"biosignalRecording\". version - Recording schema version, currently 1. sourcePath - Source path supplied by filepath. name - Source file name including its extension. signals - Channel structures with time, values, fs, name, displayName, sourceName, unit, and metadata fields. metadata - Import information. Text imports report sourceKind, timeColumn, timeUnit, timeSource, timeRepair, importHeaderLine, and importHasHeader. Status Fields: ok - Logical true when import completed. message - Empty string on success; otherwise the import error message. kind - \"mat\" or \"table\" on success. Failed requests report \"unknown\" or the attempted file extension. filepath - Normalized source path. Failure Behavior: A missing, unsupported, unreadable, or malformed source returns status.ok=false, preserves the normalized source path, and returns an empty recording. Only an invalid filepath MATLAB value is thrown as labkit:biosignal:InvalidFilepath before import begins. Example: filepath = [tempname '.csv']; sampleTime = (0:0.01:0.03)'; ECG = [0; 1; 0; -1]; writetable(table(sampleTime, ECG), filepath); cleanup = onCleanup(@() delete(filepath)); [recording, status] = labkit.biosignal.readRecording(filepath, ... struct('timeColumn', 'sampleTime', 'timeUnit', 'seconds')); signal = labkit.biosignal.getChannel(recording, 'ECG'); See also labkit.biosignal.listChannels, labkit.biosignal.getChannel"},{"title":"labkit.biosignal.segmentByEvents","url":"reference/api/labkit/biosignal/segmentByEvents.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.segmentByEvents","text":"SEGMENTBYEVENTS Extract fixed windows around event anchors. SEGMENTBYEVENTS Extract fixed windows around event anchors. Usage: segments = labkit.biosignal.segmentByEvents(signal, events) segments = labkit.biosignal.segmentByEvents(signal, events, windowSec) Description: Extracts one fixed-length signal segment around each event index. Events whose requested window would extend beyond the signal are omitted rather than padded. The returned columns therefore correspond only to interior events, and eventIndex and eventTime identify which events were retained. Window boundaries are converted to sample counts by rounding after multiplication by signal.fs. The default window is 0.35 seconds before through 0.35 seconds after each event. Inputs: signal - Biosignal structure with values, fs, and displayName fields. events - Event structure returned by labkit.biosignal.detectEcgPeaks. index contains 1-based sample positions and time contains the corresponding event times. windowSec - Optional two-element vector [startSec endSec] relative to an event. Use a negative start and nonnegative end to span the event. The default is [-0.35 0.35] seconds. Outputs: segments - Structure containing the retained waveform segments. Output Fields: type - String scalar \"biosignalSegments\". values - M-by-N matrix with one retained event segment per column. timeOffset - M-by-1 vector of sample times relative to the event. eventIndex - N-by-1 source sample indices of retained events. eventTime - N-by-1 source times of retained events. fs - Sample rate in hertz copied from signal. sourceName - signal.displayName. metadata.windowSec - Requested window in seconds. Errors: labkit:biosignal:InvalidSignal - signal lacks values or fs. labkit:biosignal:InvalidEvents - events lacks index or time. labkit:biosignal:InvalidWindow - windowSec is not an increasing two-element vector. Example: signal = struct('values', (1:21)', 'fs', 10, 'displayName', \"ECG\"); events = struct('index', [6; 16], 'time', [0.5; 1.5]); segments = labkit.biosignal.segmentByEvents(signal, events, [-0.2 0.2]); See also labkit.biosignal.detectEcgPeaks, labkit.biosignal.buildTemplate, labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.version","url":"reference/api/labkit/biosignal/version.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.version","text":"VERSION Return the LabKit biosignal facade contract version. VERSION Return the LabKit biosignal facade contract version. Usage: info = labkit.biosignal.version() Description: Reports the semantic version and compatibility range of the public labkit.biosignal API. Use this information when an app or saved project needs to check whether the installed biosignal facade satisfies a known contract range. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. It identifies the biosignal component, current version, compatible contract range, stability status, and a short description. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.biosignal.version(); currentVersion = info.current; See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.contract.assertRequirements","url":"reference/api/labkit/contract/assertRequirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.assertRequirements","text":"ASSERTREQUIREMENTS Throw an error when LabKit API requirements are not met. ASSERTREQUIREMENTS Throw an error when LabKit API requirements are not met. Usage: labkit.contract.assertRequirements(appName, req) labkit.contract.assertRequirements(appName, req, versions) Description: Calls checkRequirements and returns normally when every requirement is compatible. On failure, combines the individual report messages into one error. Use checkRequirements instead when incompatibility should be shown without throwing. Inputs: appName - Text scalar used as the error-identifier prefix. It should be a valid MATLAB identifier, for example \"labkit_Example_app\". req - Scalar structure returned by labkit.contract.requirements. versions - Optional version structure array accepted by checkRequirements. Outputs: None - Returns no value. Errors: Throws :IncompatibleLabKit when compatibility checks fail. Input and range validation errors from checkRequirements are passed through. Example: req = labkit.contract.requirements(\"app\", \">=1 <2\"); labkit.contract.assertRequirements(\"labkit_Example_app\", req) See also labkit.contract.checkRequirements, labkit.contract.requirements"},{"title":"labkit.contract.checkRequirements","url":"reference/api/labkit/contract/checkRequirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.checkRequirements","text":"CHECKREQUIREMENTS Compare required API ranges with available LabKit versions. CHECKREQUIREMENTS Compare required API ranges with available LabKit versions. Usage: report = labkit.contract.checkRequirements(req) report = labkit.contract.checkRequirements(req, versions) Description: Checks each requested facade in two ways: its current version must satisfy the caller's range, and that range must overlap at least one compatibility range advertised by the facade. When versions is omitted, the current app, dta, rhs, biosignal, image, and thermal version functions are queried. Inputs: req - Scalar structure returned by labkit.contract.requirements. versions - Optional structure array returned by facade version functions or versionInfo. Every element must contain name, current, compatible, status, and notes. An optional facade field overrides the name used for matching. This argument is mainly useful for diagnostics and tests. Outputs: report - Scalar structure with ok, failures, and message fields. ok is true when every requirement passes. message is a success sentence or newline-separated failure text. Output Fields: failures - Structure array with one element per failed requirement. failures.facade - Normalized facade name. failures.required - Range requested by the caller. failures.available - Compatibility ranges advertised by the facade, or an empty string array when the facade is unknown. failures.message - Readable incompatibility explanation. Errors: Throws labkit:contract:InvalidRequirements for a malformed req, labkit:contract:InvalidVersionInfo for a malformed versions array, labkit:contract:InvalidFacadeName for invalid names, and labkit:contract:InvalidVersionRange when a version or constraint cannot be parsed. Versions contain one to three numeric components, such as 6, 6.1, or 6.1.2. Example: req = labkit.contract.requirements(\"thermal\", \">=1.0 <2\"); report = labkit.contract.checkRequirements(req); if ~report.ok warning(\"%s\", report.message) end See also labkit.contract.requirements, labkit.contract.assertRequirements, labkit.contract.versionInfo"},{"title":"labkit.contract.requirements","url":"reference/api/labkit/contract/requirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.requirements","text":"REQUIREMENTS Describe the LabKit API versions required by a caller. REQUIREMENTS Describe the LabKit API versions required by a caller. Usage: req = labkit.contract.requirements(\"app\", \">=1.0 <2\", ...) Description: Builds a normalized requirement structure from facade/range pairs. A facade name may be written as \"app\" or \"labkit.app\". Names are converted to lowercase and the optional \"labkit.\" prefix is removed. No compatibility check is performed until checkRequirements or assertRequirements is called. Inputs: varargin - Alternating facade names and version ranges. Facade names are text scalars containing letters, digits, or underscores. A range is a nonempty text scalar containing whitespace-separated constraints such as \">=2.0 <3\". Supported operators are >, >=, <, <=, =, and ==. Outputs: req - Scalar structure with type=\"labkit.requirements\" and a facades structure array. Each facades element has normalized facade and range fields. Calling requirements() with no pairs returns an empty list. Errors: Throws labkit:contract:InvalidRequirements for an odd number of arguments, labkit:contract:InvalidFacadeName for an invalid name, labkit:contract:InvalidVersionRange for an empty or nontext range, and labkit:contract:DuplicateRequirement when a facade appears more than once. Example: req = labkit.contract.requirements( ... \"app\", \">=1 <2\", ... \"image\", \">=4.0 <5\"); report = labkit.contract.checkRequirements(req); See also labkit.contract.checkRequirements, labkit.contract.assertRequirements, labkit.contract.versionInfo"},{"title":"labkit.contract.versionInfo","url":"reference/api/labkit/contract/versionInfo.html","kind":"reference","section":"reference","keywords":"labkit.contract.versionInfo","text":"VERSIONINFO Describe one LabKit API version and its compatibility range. VERSIONINFO Describe one LabKit API version and its compatibility range. Usage: info = labkit.contract.versionInfo(facade, current, compatible, status, notes) Description: Creates the standard structure returned by module version functions. The function normalizes text and validates the status value, but it does not parse current or compatible as semantic versions. checkRequirements parses those values when it performs a comparison. Inputs: facade - Character vector or string scalar, such as \"ui\", \"dta\", or \"labkit.image\". The optional \"labkit.\" prefix is removed. current - Character vector or string scalar containing the current API version, normally a semantic version such as \"2.0.0\". compatible - Character vector, string scalar, or string array containing one or more supported requirement ranges, such as \">=2.0 <3\". status - \"stable\", \"deprecated\", or \"experimental\". Matching is case-insensitive. notes - Character vector or string scalar summarizing the module contract. Outputs: info - Scalar structure with name, facade, current, compatible, status, and notes. name includes the \"labkit.\" prefix; compatible is a string column vector; other text values are string scalars. Errors: Throws labkit:contract:InvalidVersionInfo for nontext scalar inputs, an empty facade, no nonempty compatible ranges, or an unsupported status. Example: info = labkit.contract.versionInfo( ... \"image\", \"4.1.0\", \">=4 <5\", \"stable\", ... \"Image file IO and processing functions.\"); See also labkit.contract.requirements, labkit.contract.checkRequirements"},{"title":"labkit.dta.detectPulses","url":"reference/api/labkit/dta/detectPulses.html","kind":"reference","section":"reference","keywords":"labkit.dta.detectPulses","text":"DETECTPULSES Locate cathodic and anodic pulse windows in chrono data. DETECTPULSES Locate cathodic and anodic pulse windows in chrono data. Usage: [pulse, message] = labkit.dta.detectPulses(t, Im, meta) [pulse, message] = labkit.dta.detectPulses(t, Im, meta, mode) Description: Finds a negative pulse followed by a positive pulse. Metadata modes use ISTEP/TSTEP values when available, or VSTEP/TSTEP values when current-step metadata is absent. Current-based detection identifies dominant negative and positive segments directly from Im. Inputs: t - Numeric time vector in seconds. Values are used in their supplied order and must correspond element-for-element with Im. Im - Numeric current vector in amperes with the same number of elements as t. meta - Chrono metadata structure, usually item.meta from loadFile. A steps field may contain I, V, and T values parsed from ISTEP, VSTEP, and TSTEP metadata. Use struct() when metadata is unavailable. mode - Character vector, string scalar, or structure with a mode field. See Mode Values. Default: \"Metadata first, then auto\". Mode Values: metadata_first - Accepts \"metadata_first\" or \"Metadata first, then auto\". Uses metadata first and falls back to Im when metadata detection fails. This is the default. metadata_only - Accepts \"metadata_only\" or \"Metadata only\". Does not use measured-current fallback. current_only - Accepts \"current_only\" or \"Auto from Im only\". Ignores metadata and detects segments from Im. The threshold is 25 percent of max(abs(Im)), with a 1e-12 A numerical floor. Outputs: pulse - Scalar structure describing the detected windows. pulse.ok is false and numeric window fields are NaN when detection fails. message - Character vector explaining which method succeeded or why detection failed. The same text is stored in pulse.message. Output Fields: ok - Logical success flag. method - \"metadata-current\", \"metadata-voltage\", \"auto-from-Im\", or \"-\". pre - Structure with start_s and end_s. cath - Structure with start_s, end_s, and current_A. gap - Structure with start_s, end_s, and center_s. anod - Structure with start_s, end_s, and current_A. post - Structure with start_s and end_s. Failure Behavior: Missing usable metadata, absent opposite-polarity current phases, or unsupported mode text returns pulse.ok=false with NaN window fields and an explanatory message. t and Im must be corresponding numeric vectors; incompatible MATLAB values or lengths may raise the originating indexing or conversion error. Example: t = (0:0.001:0.020).'; Im = zeros(size(t)); Im(4:7) = -1e-3; Im(13:16) = 1e-3; [pulse, message] = labkit.dta.detectPulses( ... t, Im, struct(), \"Auto from Im only\"); See also labkit.dta.loadFile, labkit.dta.getMainCurve"},{"title":"labkit.dta.detectType","url":"reference/api/labkit/dta/detectType.html","kind":"reference","section":"reference","keywords":"labkit.dta.detectType","text":"DETECTTYPE Identify the supported Gamry DTA data family in a file. DETECTTYPE Identify the supported Gamry DTA data family in a file. Usage: [kind, status] = labkit.dta.detectType(filepath) Description: Reads enough of one DTA file to determine whether it contains EIS, cyclic-voltammetry/charge-time, or chrono data. Detection verifies usable curve columns rather than relying on the filename. A missing or unsupported file is reported in status instead of throwing a read error. Inputs: filepath - Character vector or string scalar naming one DTA file. Outputs: kind - String scalar: \"chrono\", \"eis\", \"cvct\", or \"unknown\". status - Scalar structure describing detection. ok is true only for a recognized file; message is empty on success and explains failure otherwise; kind repeats the detected value; and filepath contains the normalized character-vector path. Errors: Throws labkit:dta:InvalidFilepath when filepath is not a character vector or string scalar. Example: [kind, status] = labkit.dta.detectType(\"experiment.DTA\"); if status.ok fprintf(\"Detected %s data.\\n\", kind) else warning(\"%s\", status.message) end See also labkit.dta.loadFile, labkit.dta.findFiles"},{"title":"labkit.dta.findFiles","url":"reference/api/labkit/dta/findFiles.html","kind":"reference","section":"reference","keywords":"labkit.dta.findFiles","text":"FINDFILES Find DTA files in a folder and its subfolders. FINDFILES Find DTA files in a folder and its subfolders. Usage: filepaths = labkit.dta.findFiles(rootDir) Description: Recursively walks rootDir and collects files whose extension is .dta, using a case-insensitive comparison. The function does not inspect file contents; use detectType or loadFile to validate each result. Inputs: rootDir - Character vector or string scalar naming an existing folder. Outputs: filepaths - Cell row vector of full file paths in directory traversal order. The value is an empty cell array when no DTA files are found. Errors: Throws labkit:dta:InvalidFolder when rootDir is not a text scalar or does not name an existing folder. Typical Call: files = labkit.dta.findFiles(\"data\"); [items, report] = labkit.dta.loadFiles(files, \"auto\"); See also labkit.dta.detectType, labkit.dta.loadFiles, labkit.dta.loadFolder"},{"title":"labkit.dta.getColumn","url":"reference/api/labkit/dta/getColumn.html","kind":"reference","section":"reference","keywords":"labkit.dta.getColumn","text":"GETCOLUMN Extract a named column from a parsed DTA table. GETCOLUMN Extract a named column from a parsed DTA table. Usage: col = labkit.dta.getColumn(tbl, name) Description: Finds the first header that matches name without regard to letter case and returns the corresponding numeric data column. The function does not convert units or remove NaN values. Inputs: tbl - Scalar parsed table or curve structure with headers and data fields. data has one column for each element of headers. name - Character vector or string scalar naming the requested header. Outputs: col - Numeric column vector from tbl.data, or [] when no header matches. Failure Behavior: A missing header returns []. tbl must expose corresponding headers and data fields; malformed structures or an out-of-range data layout raise the originating MATLAB field or indexing error. Typical Call: timeSec = labkit.dta.getColumn(item.curve, \"T\"); currentA = labkit.dta.getColumn(item.curve, \"Im\"); See also labkit.dta.getCurveXY, labkit.dta.getMainCurve, labkit.dta.getZCurve"},{"title":"labkit.dta.getCurveXY","url":"reference/api/labkit/dta/getCurveXY.html","kind":"reference","section":"reference","keywords":"labkit.dta.getCurveXY","text":"GETCURVEXY Extract paired X and Y data from a parsed CV/CT curve. GETCURVEXY Extract paired X and Y data from a parsed CV/CT curve. Usage: [x, y, xname, yname] = labkit.dta.getCurveXY(curve, xsel, ysel) Description: Looks up two headers using case-sensitive, exact-name matching. Rows where either selected column is NaN are removed from both outputs so x and y remain paired. Use getColumn when case-insensitive lookup or unfiltered data is required. Inputs: curve - Scalar parsed curve structure with headers and data fields. xsel - Character vector or string scalar matching one header exactly. ysel - Character vector or string scalar matching one header exactly. Outputs: x - Numeric column vector for xsel, with paired NaN rows removed. y - Numeric column vector for ysel, with paired NaN rows removed. xname - Matched header character vector, or '' when lookup fails. yname - Matched header character vector, or '' when lookup fails. Failure Behavior: If either exact header is absent, all numeric outputs remain empty and unmatched names are ''. curve must expose corresponding headers and data fields; malformed structures raise the originating MATLAB error. Typical Call: [voltageV, currentA, xname, yname] = ... labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); See also labkit.dta.getColumn, labkit.dta.loadFile"},{"title":"labkit.dta.getMainCurve","url":"reference/api/labkit/dta/getMainCurve.html","kind":"reference","section":"reference","keywords":"labkit.dta.getMainCurve","text":"GETMAINCURVE Select the transient table containing T, Vf, and Im data. GETMAINCURVE Select the transient table containing T, Vf, and Im data. Usage: [curve, ok, msg] = labkit.dta.getMainCurve(tables) Description: First selects a table named CURVE or CURVE1. If neither name is present, selects the first table whose headers include T, Vf, and Im, using case-insensitive comparisons. No table is selected when the input is empty or no compatible columns are found. Inputs: tables - Structure array of parsed DTA tables. Each element is expected to contain name, headers, and data fields. Outputs: curve - Selected table structure, or an empty structure on failure. ok - Logical scalar indicating whether a table was selected. msg - Character vector naming the selected table or stating that the main transient table was not found. Failure Behavior: Empty input or no table with the required headers returns curve=struct(), ok=false, and a diagnostic message. Nonempty elements must expose name and headers fields; malformed table structures raise the originating MATLAB field-access error. Typical Call: [curve, ok, msg] = labkit.dta.getMainCurve(item.tables); if ok timeSec = labkit.dta.getColumn(curve, \"T\"); end See also labkit.dta.getColumn, labkit.dta.detectPulses, labkit.dta.loadFile"},{"title":"labkit.dta.getZCurve","url":"reference/api/labkit/dta/getZCurve.html","kind":"reference","section":"reference","keywords":"labkit.dta.getZCurve","text":"GETZCURVE Select the impedance table from parsed EIS data. GETZCURVE Select the impedance table from parsed EIS data. Usage: [curve, ok, msg] = labkit.dta.getZCurve(tables) Description: First selects a table named ZCURVE. If that name is absent, selects the first table whose headers include Freq, Zreal, and Zimag, using case-insensitive comparisons. Inputs: tables - Structure array of parsed DTA tables. Each element is expected to contain name, headers, and data fields. Outputs: curve - Selected impedance table structure, or an empty structure on failure. ok - Logical scalar indicating whether a table was selected. msg - Character vector naming the selected table or stating that a ZCURVE table was not found. Failure Behavior: Empty input or no impedance table returns curve=struct(), ok=false, and a diagnostic message. Nonempty elements must expose name and headers fields; malformed table structures raise the originating MATLAB field-access error. Typical Call: [curve, ok, msg] = labkit.dta.getZCurve(item.tables); if ok frequencyHz = labkit.dta.getColumn(curve, \"Freq\"); end See also labkit.dta.getColumn, labkit.dta.loadFile"},{"title":"labkit.dta.loadFile","url":"reference/api/labkit/dta/loadFile.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFile","text":"LOADFILE Read one supported Gamry DTA file. LOADFILE Read one supported Gamry DTA file. Usage: [item, status] = labkit.dta.loadFile(filepath) [item, status] = labkit.dta.loadFile(filepath, expectedKind) [item, status] = labkit.dta.loadFile(filepath, expectedKind, opts) Description: Detects and parses one chrono, EIS, or CV/CT DTA file. Set expectedKind to a specific family when a workflow must reject other DTA types. Parse and format failures are returned in status; invalid arguments throw errors. Inputs: filepath - Character vector or string scalar naming one DTA file. expectedKind - Character vector or string scalar. Allowed values are \"auto\", \"chrono\", \"eis\", and \"cvct\". Matching is case-insensitive, surrounding whitespace is ignored, and a blank value means \"auto\". Default: \"auto\". opts - Optional scalar structure. Options apply to chrono loading only. Options: pulseMode - Pulse-detection mode accepted by detectPulses, such as \"Metadata first, then auto\", \"Metadata only\", or \"Auto from Im only\". pulseOptions - Structure with a mode field accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: item - Scalar parsed item structure when status.ok is true, or an empty structure array when loading fails. See Output Fields. status - Scalar structure with ok, message, kind, expectedKind, and filepath fields. A kind mismatch reports both the expected and detected family in message. Output Fields: type - \"chrono\", \"eis\", or \"cvct\". filepath - Source path as a character vector. name - Source filename including extension. meta - Parsed metadata for chrono and EIS items. tables - Parsed table structures for chrono and EIS items. t_s - Chrono time in seconds. Vf_V - Chrono measured voltage in volts. Im_A - Chrono measured current in amperes. pulse - Chrono pulse structure returned by detectPulses. freq_Hz - EIS frequency in hertz. Zreal_ohm - EIS real impedance in ohms. Zimag_ohm - EIS imaginary impedance in ohms. Zmod_ohm - EIS impedance magnitude in ohms. Zphz_deg - EIS phase angle in degrees. scanRate_V_per_s - CV/CT scan rate in volts per second. curves - Parsed CV/CT curve structures in file order. analysis - Empty structure reserved for caller-owned results. Errors: Throws labkit:dta:InvalidFilepath for a nonscalar path and labkit:dta:InvalidKind for an unsupported expectedKind. Missing files, unsupported content, parse failures, and type mismatches are returned as status.ok=false rather than thrown. Example: [item, status] = labkit.dta.loadFile(\"measurement.DTA\", \"eis\"); if status.ok semilogx(item.freq_Hz, item.Zmod_ohm) else warning(\"%s\", status.message) end See also labkit.dta.detectType, labkit.dta.loadFiles, labkit.dta.detectPulses"},{"title":"labkit.dta.loadFiles","url":"reference/api/labkit/dta/loadFiles.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFiles","text":"LOADFILES Read a list of Gamry DTA files. LOADFILES Read a list of Gamry DTA files. Usage: [items, report] = labkit.dta.loadFiles(filepaths) [items, report] = labkit.dta.loadFiles(filepaths, expectedKind) [items, report] = labkit.dta.loadFiles(filepaths, expectedKind, opts) Description: Calls loadFile for each path in input order. Successful items are returned in items; failures do not stop the batch and are recorded in report. The positions in items therefore need not match the positions in filepaths; use report.statuses when per-input correspondence matters. Inputs: filepaths - One character-vector or string-scalar path, a string array, or a cell array of character vectors and string scalars. An empty input is allowed and returns an empty report. expectedKind - \"auto\", \"chrono\", \"eis\", or \"cvct\", with the same normalization as loadFile. Default: \"auto\". opts - Optional scalar structure forwarded unchanged to loadFile. Options: pulseMode - Pulse-detection mode used for chrono files. See detectPulses for the legal labels and normalization behavior. pulseOptions - Scalar structure accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: items - Cell row vector containing successfully loaded item structures in input order, with failed inputs omitted. report - Scalar structure summarizing every requested path. Output Fields: loaded - Cell array of paths that loaded successfully. failed - Structure array with filepath, kind, and message for failures. statuses - One loadFile status structure per requested input, in input order. nRequested - Number of input paths. nLoaded - Number of successful items. nFailed - Number of failed inputs. Errors: Throws labkit:dta:InvalidFilepaths or labkit:dta:InvalidFilepath when the path collection contains unsupported values, and labkit:dta:InvalidKind for an unsupported expectedKind. Individual file read failures are stored in report instead of thrown. Example: files = [\"run-01.DTA\", \"run-02.DTA\"]; [items, report] = labkit.dta.loadFiles(files, \"chrono\"); fprintf(\"Loaded %d of %d files.\\n\", report.nLoaded, report.nRequested) See also labkit.dta.loadFile, labkit.dta.findFiles, labkit.dta.loadFolder"},{"title":"labkit.dta.loadFolder","url":"reference/api/labkit/dta/loadFolder.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFolder","text":"LOADFOLDER Read every DTA file below a folder. LOADFOLDER Read every DTA file below a folder. Usage: [items, report] = labkit.dta.loadFolder(rootDir) [items, report] = labkit.dta.loadFolder(rootDir, expectedKind) [items, report] = labkit.dta.loadFolder(rootDir, expectedKind, opts) Description: Recursively discovers .dta files with findFiles and passes the resulting list to loadFiles. Unsupported or unreadable files are recorded in report while readable items are retained. Inputs: rootDir - Character vector or string scalar naming an existing folder. expectedKind - \"auto\", \"chrono\", \"eis\", or \"cvct\", with the same normalization as loadFile. Default: \"auto\". opts - Optional scalar structure forwarded to loadFiles and loadFile. Options: pulseMode - Pulse-detection mode used for discovered chrono files. See detectPulses for the legal labels and normalization behavior. pulseOptions - Scalar structure accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: items - Cell row vector of successfully loaded item structures. report - loadFiles report with three additional discovery fields. Output Fields: folder - Requested root folder as a character vector. filepaths - Cell array of discovered DTA paths in traversal order. nDiscovered - Number of discovered paths. This equals nRequested. loaded - Cell array of paths that loaded successfully. failed - Structure array describing failed inputs. statuses - One loadFile status per discovered input. nRequested - Number of discovered paths sent to loadFiles. nLoaded - Number of successful items. nFailed - Number of failed inputs. Errors: Throws labkit:dta:InvalidFolder for an invalid rootDir and labkit:dta:InvalidKind for an unsupported expectedKind. Typical Call: [items, report] = labkit.dta.loadFolder(\"experiment\", \"auto\"); fprintf(\"Found %d DTA files; loaded %d.\\n\", ... report.nDiscovered, report.nLoaded) See also labkit.dta.findFiles, labkit.dta.loadFiles, labkit.dta.loadFile"},{"title":"labkit.dta.version","url":"reference/api/labkit/dta/version.html","kind":"reference","section":"reference","keywords":"labkit.dta.version","text":"VERSION Return version information for the DTA API. VERSION Return version information for the DTA API. Usage: info = labkit.dta.version() Description: Reports the API version and compatibility range used when an app declares a dependency on labkit.dta. This is not the Gamry software version or a version identifier read from a DTA file. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.dta\"; facade is \"dta\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.dta.version(); fprintf(\"DTA API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.image.adjustBrightnessContrast","url":"reference/api/labkit/image/adjustBrightnessContrast.html","kind":"reference","section":"reference","keywords":"labkit.image.adjustBrightnessContrast","text":"ADJUSTBRIGHTNESSCONTRAST Apply simple brightness and contrast adjustment. ADJUSTBRIGHTNESSCONTRAST Apply simple brightness and contrast adjustment. Usage: imageOut = labkit.image.adjustBrightnessContrast(imageIn, brightnessPct, contrastPct) Description: Converts imageIn to clamped RGB double, then applies a linear adjustment around mid-gray. For normalized input x, the calculation is (x - 0.5)*(1 + contrastPct/100) + 0.5 + brightnessPct/100. A contrast scale below zero is limited to zero, and final values are clamped to [0,1]. Positive brightness shifts every channel upward. Positive contrast moves values away from 0.5; negative contrast moves them toward 0.5. A contrastPct of -100 produces a flat mid-gray image before the brightness offset is applied. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. brightnessPct - Numeric scalar offset as percent of normalized full scale. contrastPct - Numeric scalar percent change in contrast around 0.5. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. brightnessPct and contrastPct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = reshape(linspace(0, 1, 12), 3, 4); imageOut = labkit.image.adjustBrightnessContrast(imageIn, 10, 20); See also labkit.image.adjustHueSaturation, labkit.image.grayWorldWhiteBalance, labkit.image.im2double"},{"title":"labkit.image.adjustHueSaturation","url":"reference/api/labkit/image/adjustHueSaturation.html","kind":"reference","section":"reference","keywords":"labkit.image.adjustHueSaturation","text":"ADJUSTHUESATURATION Apply HSV hue and saturation adjustment. ADJUSTHUESATURATION Apply HSV hue and saturation adjustment. Usage: imageOut = labkit.image.adjustHueSaturation(imageIn, hueDeg, saturationPct) Description: Converts imageIn to clamped RGB double and then to HSV. hueDeg rotates the hue circle and wraps at 360 degrees. Saturation is multiplied by 1 + saturationPct/100 and limited to [0,1]. The HSV value channel is not changed directly. A saturationPct of -100 or less produces grayscale. Positive saturation increases colorfulness until individual saturation values reach 1. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. hueDeg - Numeric scalar hue rotation in degrees. Values outside one turn are wrapped. saturationPct - Numeric scalar percent change in HSV saturation. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. hueDeg and saturationPct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = cat(3, ones(2), 0.4*ones(2), zeros(2)); imageOut = labkit.image.adjustHueSaturation(imageIn, 30, 25); See also labkit.image.adjustBrightnessContrast, labkit.image.grayWorldWhiteBalance"},{"title":"labkit.image.assertSupportedPaths","url":"reference/api/labkit/image/assertSupportedPaths.html","kind":"reference","section":"reference","keywords":"labkit.image.assertSupportedPaths","text":"ASSERTSUPPORTEDPATHS Throw when any path has an unsupported image extension. ASSERTSUPPORTEDPATHS Throw when any path has an unsupported image extension. Usage: labkit.image.assertSupportedPaths(paths) Description: Validates a collection of source-image paths by filename extension. The function accepts the same path forms as labkit.image.normalizePaths and stops at the first unsupported extension. It does not test whether files exist or contain valid image data. Empty input succeeds. Inputs: paths - Character vector, string array, cell array, or empty value accepted by labkit.image.normalizePaths. Outputs: None. Errors: labkit:image:UnsupportedImageFile - A normalized path has an extension outside supportedExtensions. labkit:image:InvalidPaths - paths has an unsupported container type. Example: labkit.image.assertSupportedPaths([\"frame01.png\"; \"frame02.TIF\"]); See also labkit.image.normalizePaths, labkit.image.isSupportedPath, labkit.image.supportedExtensions"},{"title":"labkit.image.displayName","url":"reference/api/labkit/image/displayName.html","kind":"reference","section":"reference","keywords":"labkit.image.displayName","text":"DISPLAYNAME Return a short image-file display name. DISPLAYNAME Return a short image-file display name. Usage: name = labkit.image.displayName(pathValue) Description: Removes the folder portion of one path and returns the final filename with its extension. The function performs text processing only; the path does not need to exist. If fileparts cannot derive a filename, the trimmed input text is returned. Inputs: pathValue - Character vector, string scalar, or another value convertible to one string scalar. Outputs: name - String scalar containing the filename and extension. Errors: labkit:image:InvalidPath - pathValue converts to more than one string. Example: name = labkit.image.displayName(fullfile(\"images\", \"frame01.tif\")); See also labkit.image.normalizePaths, labkit.image.readFiles"},{"title":"labkit.image.ensureRgb","url":"reference/api/labkit/image/ensureRgb.html","kind":"reference","section":"reference","keywords":"labkit.image.ensureRgb","text":"ENSURERGB Return image data with exactly three color channels. ENSURERGB Return image data with exactly three color channels. Usage: rgb = labkit.image.ensureRgb(imageData) Description: Converts grayscale image arrays to RGB by copying the same values into the red, green, and blue channels. RGB input passes through unchanged, and input with more than three channels keeps only the first three. This is useful for removing an alpha or auxiliary channel after image import. The function does not change numeric values, normalize intensity, or convert the input class. Empty input is returned with its original empty shape. Inputs: imageData - Nonsparse numeric or logical M-by-N grayscale, M-by-N-by-1 grayscale, M-by-N-by-3 RGB, or M-by-N-by-C array with C greater than three. Outputs: rgb - Array with the same class and first two dimensions as imageData. Nonempty output has exactly three channels. Errors: labkit:image:ensureRgb:InvalidChannelCount - Nonempty 3-D input has two color channels. Example: gray = uint8([0 64; 128 255]); rgb = labkit.image.ensureRgb(gray); See also labkit.image.im2double, labkit.image.rgb2gray"},{"title":"labkit.image.fileDialogFilter","url":"reference/api/labkit/image/fileDialogFilter.html","kind":"reference","section":"reference","keywords":"labkit.image.fileDialogFilter","text":"FILEDIALOGFILTER Return a file-chooser-compatible image filter. FILEDIALOGFILTER Return a file-chooser-compatible image filter. Usage: filterSpec = labkit.image.fileDialogFilter() filterSpec = labkit.image.fileDialogFilter(\"IncludeAll\", true) Description: Builds the two-column filter specification used by MATLAB file-selection dialogs and LabKit file panels. The first row contains every extension returned by labkit.image.supportedExtensions. By default, files outside that set are hidden from the chooser. Inputs: None. Name-Value Arguments: IncludeAll - Logical scalar. true appends an \"All files (*.*)\" row after the image filter. The default is false. Numeric scalars are converted to logical values. Outputs: filterSpec - One- or two-row cell array. Column 1 contains wildcard patterns and column 2 contains user-visible descriptions. Errors: MATLAB inputParser errors are raised for an unknown name, a missing name-value partner, or an IncludeAll value that is not a logical or numeric scalar. Example: filterSpec = labkit.image.fileDialogFilter(\"IncludeAll\", true); See also labkit.image.supportedExtensions, labkit.image.isSupportedPath"},{"title":"labkit.image.grayWorldWhiteBalance","url":"reference/api/labkit/image/grayWorldWhiteBalance.html","kind":"reference","section":"reference","keywords":"labkit.image.grayWorldWhiteBalance","text":"GRAYWORLDWHITEBALANCE Apply generic gray-world white balance. GRAYWORLDWHITEBALANCE Apply generic gray-world white balance. Usage: imageOut = labkit.image.grayWorldWhiteBalance(imageIn, strengthPct, temperaturePct) Description: Estimates one gain per RGB channel so that the three channel means move toward their common mean. strengthPct blends between the original image and the fully balanced result. This is the gray-world assumption: over a representative scene, the average reflected color is expected to be neutral. temperaturePct adds a small red offset and subtracts the same blue offset after gain correction. Positive values produce a warmer result; negative values produce a cooler result. The temperature offset is 0.08 times the normalized percentage. Final values are clamped to [0,1]. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. strengthPct - Numeric scalar blend percentage. Values below 0 become 0; values above 100 become 100. temperaturePct - Numeric scalar warm/cool adjustment in percent. Use 0 for neutral gray-world balancing. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. strengthPct and temperaturePct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = cat(3, 0.3*ones(4), 0.5*ones(4), 0.7*ones(4)); imageOut = labkit.image.grayWorldWhiteBalance(imageIn, 100, 0); See also labkit.image.adjustBrightnessContrast, labkit.image.adjustHueSaturation"},{"title":"labkit.image.im2double","url":"reference/api/labkit/image/im2double.html","kind":"reference","section":"reference","keywords":"labkit.image.im2double","text":"IM2DOUBLE Convert image data to double using MATLAB's im2double contract. IM2DOUBLE Convert image data to double using MATLAB's im2double contract. Usage: imageOut = labkit.image.im2double(imageIn) imageOut = labkit.image.im2double(indexedImage, \"indexed\") Description: Converts image data to double without requiring Image Processing Toolbox. Logical values become 0 or 1. uint8 and uint16 values are divided by the maximum value of their class. int16 values are mapped linearly from the complete signed range to [0,1]. single and double values are converted to double without rescaling or clipping. For uint8 or uint16 indexed images, the \"indexed\" form converts the zero-based integer storage to one-based double colormap indices. For example, uint8 values [0 1] become [1 2]. Empty input returns an empty double array. Inputs: imageIn - Logical, uint8, uint16, int16, single, or double image array. imageType - Optional character vector or scalar string \"indexed\". No other image-type value is accepted. Outputs: imageOut - Double array with the same size as imageIn. Errors: labkit:image:im2double:InvalidImageType - imageType is not \"indexed\". labkit:image:im2double:UnsupportedClass - imageIn has an unsupported numeric class. Example: intensity = labkit.image.im2double(uint8([0 128 255])); indices = labkit.image.im2double(uint8([0 1 2]), \"indexed\"); See also labkit.image.ensureRgb, labkit.image.rgb2gray"},{"title":"labkit.image.isSupportedPath","url":"reference/api/labkit/image/isSupportedPath.html","kind":"reference","section":"reference","keywords":"labkit.image.isSupportedPath","text":"ISSUPPORTEDPATH Return true when a path has a supported image extension. ISSUPPORTEDPATH Return true when a path has a supported image extension. Usage: tf = labkit.image.isSupportedPath(pathValue) Description: Compares the final filename extension with labkit.image.supportedExtensions. The comparison is case-insensitive and does not open the file, inspect its contents, or require it to exist. A path with no extension returns false. Inputs: pathValue - One value convertible to text and accepted by fileparts. Outputs: tf - Logical scalar indicating whether the extension is supported. Failure Behavior: Missing or unknown extensions return false without accessing the file system. pathValue must be convertible to scalar text; unsupported MATLAB values or shapes may raise the originating string or fileparts error. Example: tf = labkit.image.isSupportedPath(\"experiment.TIFF\"); See also labkit.image.supportedExtensions, labkit.image.assertSupportedPaths, labkit.image.normalizePaths"},{"title":"labkit.image.localContrast","url":"reference/api/labkit/image/localContrast.html","kind":"reference","section":"reference","keywords":"labkit.image.localContrast","text":"LOCALCONTRAST Enhance local value-channel contrast with a mean-filter mask. LOCALCONTRAST Enhance local value-channel contrast with a mean-filter mask. Usage: imageOut = labkit.image.localContrast(imageIn, amountPct, radiusPx) Description: Converts imageIn to clamped RGB double and operates on the HSV value channel. A square local mean with width 2*radius+1 forms the low-frequency reference. The difference between the value channel and that mean is multiplied by 1.5*amountPct/100 and added back before clipping. Hue and saturation are preserved by the adjustment. At image boundaries, the local mean uses only available pixels. This is a lightweight local contrast operation, not adaptive histogram equalization. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. amountPct - Numeric scalar effect strength in percent. Negative values are treated as 0. radiusPx - Numeric scalar neighborhood radius in pixels. The value is rounded and limited to at least 1. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. amountPct and radiusPx must be numeric scalars; incompatible values raise the originating conversion or indexing error. Example: imageIn = repmat(linspace(0.2, 0.8, 20), 10, 1); imageOut = labkit.image.localContrast(imageIn, 40, 3); See also labkit.image.meanFilter2, labkit.image.sharpen"},{"title":"labkit.image.meanFilter2","url":"reference/api/labkit/image/meanFilter2.html","kind":"reference","section":"reference","keywords":"labkit.image.meanFilter2","text":"MEANFILTER2 Apply normalized 2-D mean filtering with edge correction. MEANFILTER2 Apply normalized 2-D mean filtering with edge correction. Usage: imageOut = labkit.image.meanFilter2(imageIn, windowSize) Description: Replaces each sample with the arithmetic mean of the rectangular neighborhood that overlaps the image. At an edge or corner, the divisor is reduced to the number of available samples instead of assuming zero padding. Each channel of a 3-D image is filtered independently. windowSize is rounded to an integer and limited to at least 1. Both odd and even widths are accepted; conv2 determines the alignment of an even window. Input is converted to double for calculation. NaN values are not omitted and therefore propagate to neighborhoods that include them. Inputs: imageIn - 2-D image matrix or 3-D image with channels in dimension 3. windowSize - Numeric scalar neighborhood width in pixels. Outputs: imageOut - Double array with the same size as imageIn. Failure Behavior: imageIn must be compatible with double and conv2, and windowSize must be convertible to one numeric scalar. Unsupported types, sparse arrays, or nonscalar window values raise the originating MATLAB error. Example: impulse = [0 0 0; 0 1 0; 0 0 0]; smoothed = labkit.image.meanFilter2(impulse, 3); See also labkit.image.localContrast, labkit.image.sharpen"},{"title":"labkit.image.normalizePaths","url":"reference/api/labkit/image/normalizePaths.html","kind":"reference","section":"reference","keywords":"labkit.image.normalizePaths","text":"NORMALIZEPATHS Normalize image file path inputs to a string column. NORMALIZEPATHS Normalize image file path inputs to a string column. Usage: paths = labkit.image.normalizePaths(paths) paths = labkit.image.normalizePaths(paths, \"AllowEmpty\", false) Description: Converts the path forms commonly returned by MATLAB file selection into one string column. Leading and trailing whitespace is removed and blank entries are discarded. Order and duplicate paths are preserved. A single character vector is treated as one path rather than one path per row. This function normalizes text only. It does not expand relative paths, validate extensions, remove duplicates, or test file existence. Inputs: paths - Character vector, string array, cell array, or empty value. Outputs: paths - String column containing the nonblank normalized paths. Name-Value Arguments: AllowEmpty - Logical scalar controlling whether zero paths are accepted. The default is true. false throws labkit:image:NoPaths when normalization produces an empty result. Errors: labkit:image:InvalidPaths - paths is not char, string, cell, or empty. labkit:image:NoPaths - No path remains and AllowEmpty is false. Example: paths = labkit.image.normalizePaths([\" frame01.png \"; \"\"; \"frame02.JPG\"]); See also labkit.image.assertSupportedPaths, labkit.image.isSupportedPath, labkit.image.readFiles"},{"title":"labkit.image.previewBudget","url":"reference/api/labkit/image/previewBudget.html","kind":"reference","section":"reference","keywords":"labkit.image.previewBudget","text":"PREVIEWBUDGET Downsample image data to fit a display pixel budget. PREVIEWBUDGET Downsample image data to fit a display pixel budget. Usage: [preview, info] = labkit.image.previewBudget(imageData) [preview, info] = labkit.image.previewBudget(imageData, Name, Value) Description: When MaxPixels is finite, produces a lightweight preview by taking every Nth row and column. N is the smallest integer stride whose estimated processing area does not exceed MaxPixels. The estimate is source rows times source columns times Expansion, so callers can reserve memory for workflows that pad, tile, or otherwise enlarge an image before display. The default is Inf and preserves native pixels; each caller owns whether a display budget is appropriate for its workflow. This is sampling rather than interpolating resize. The first source pixel is always retained, and image class and channel count are preserved. A preview may use fewer pixels than the budget because the stride is an integer. Inputs: imageData - Nonempty numeric or logical 2-D or 3-D image array. Name-Value Arguments: MaxPixels - Positive display-area budget or Inf. Default: Inf, retaining native pixels. Invalid, empty, or nonpositive values fall back to the default rather than throwing an error. Expansion - Positive multiplier applied only to estimatedPixels. The default is 1. Invalid values fall back to 1. Outputs: preview - imageData sampled with the selected integer stride. info - Scalar structure describing the preview calculation. Info Fields: scaleFactor - Integer row and column stride N. coordinateScale - Reciprocal 1/N for mapping source distances to the sampled preview scale. maxPixels - Validated display-area budget. estimatedPixels - Source row-column area multiplied by Expansion. sourceSize - Original size vector. previewSize - Returned preview size vector. Errors: labkit:image:InvalidImageData - imageData is empty, nonnumeric/nonlogical, or has more than three dimensions. labkit:image:InvalidPreviewBudgetOptions - Name-value arguments are not paired. labkit:image:InvalidPreviewBudgetOption - An option name is unsupported. Example: rgb = zeros(100, 200, 3, 'uint8'); [preview, info] = labkit.image.previewBudget(rgb, ... \"MaxPixels\", 5000, \"Expansion\", 2); See also labkit.image.resizeToFit"},{"title":"labkit.image.readFiles","url":"reference/api/labkit/image/readFiles.html","kind":"reference","section":"reference","keywords":"labkit.image.readFiles","text":"READFILES Read image files into path/name/image records. READFILES Read image files into path/name/image records. Usage: records = labkit.image.readFiles(paths) records = labkit.image.readFiles(paths, opts) Description: Reads one or more image files in the supplied order and returns a uniform record for each file. The default path validates source extensions and file existence, converts data with labkit.image.im2double, expands grayscale to RGB, discards channels after the first three, and clamps values to [0,1]. Set Normalize to false when the exact class and channel layout returned by imread must be preserved. progressFcn is called immediately before and after every imread. If a read, conversion, or callback fails, the error propagates and no partial records array is returned. Inputs: paths - Character vector, string array, cell array, or empty value accepted by labkit.image.normalizePaths. opts - Optional scalar struct containing the fields listed below. Options: Normalize - Logical scalar controlling conversion to clamped RGB double. The default is true. AllowEmpty - Logical scalar controlling whether an empty path collection returns an empty record column. The default is true. ValidateExtensions - Logical scalar controlling validation with assertSupportedPaths. The default is true. RequireExisting - Logical scalar controlling the explicit file-existence check before imread. The default is true. imread can still fail when this option is false. progressFcn - Function handle or []. The callback receives a scalar structure before and after each read. The default is []. Outputs: records - N-by-1 structure array with path, name, and image fields. path and name are strings; image contains the normalized or raw image matrix. Progress Fields: stage - \"beforeRead\" or \"afterRead\". index - 1-based index of the current path. count - Total number of paths. path - Current path as a string scalar. name - Filename and extension from labkit.image.displayName. Errors: labkit:image:InvalidOptions - opts or one of its fields is invalid. labkit:image:NoPaths - paths is empty while AllowEmpty is false. labkit:image:UnsupportedImageFile - An extension is unsupported while ValidateExtensions is true. labkit:image:ImageFileNotFound - A file is missing while RequireExisting is true. Typical Call: opts = struct('Normalize', true, 'progressFcn', @updateProgress); records = labkit.image.readFiles(selectedPaths, opts); See also labkit.image.normalizePaths, labkit.image.assertSupportedPaths, labkit.image.writeFile"},{"title":"labkit.image.resizeToFit","url":"reference/api/labkit/image/resizeToFit.html","kind":"reference","section":"reference","keywords":"labkit.image.resizeToFit","text":"RESIZETOFIT Resize an image to fit within maximum row/column limits. RESIZETOFIT Resize an image to fit within maximum row/column limits. Usage: imageOut = labkit.image.resizeToFit(imageIn) imageOut = labkit.image.resizeToFit(imageIn, Name, Value) [imageOut, scale] = labkit.image.resizeToFit(___) Description: Computes one uniform scale that satisfies both the maximum row and column limits, preserving aspect ratio. With the default AllowUpscale value, images that already fit are returned unchanged. Target dimensions are rounded to the nearest integer and limited to at least one pixel. \"nearest\" samples endpoint-aligned nearest input pixels. \"bilinear\" performs endpoint-aligned linear interpolation independently in each channel. Output keeps the input class; integer linear results are rounded and limited to the class range. Inputs: imageIn - Numeric or logical 2-D image or 3-D multichannel image. Empty input is returned unchanged with scale 1. Name-Value Arguments: MaxHeight - Positive maximum output row count. The default is Inf. MaxWidth - Positive maximum output column count. The default is Inf. AllowUpscale - Logical scalar controlling enlargement when both limits permit it. The default is false. Method - \"bilinear\" (default) or \"nearest\". Outputs: imageOut - Resized image, or imageIn unchanged when scale is 1. scale - Uniform requested scale before target dimensions are rounded. Errors: MATLAB inputParser errors are raised for invalid limits, AllowUpscale, malformed name-value arguments, or unsupported option names. labkit:image:InvalidResizeMethod - Method is not \"bilinear\" or \"nearest\". Unsupported image classes or dimensions may raise the originating MATLAB interpolation or cast error. Example: imageIn = reshape(uint8(1:200), 10, 20); [imageOut, scale] = labkit.image.resizeToFit(imageIn, ... \"MaxHeight\", 5, \"Method\", \"nearest\"); See also labkit.image.previewBudget"},{"title":"labkit.image.rgb2gray","url":"reference/api/labkit/image/rgb2gray.html","kind":"reference","section":"reference","keywords":"labkit.image.rgb2gray","text":"RGB2GRAY Convert RGB data using MATLAB's rgb2gray call contract. RGB2GRAY Convert RGB data using MATLAB's rgb2gray call contract. Usage: gray = labkit.image.rgb2gray(rgb) Description: Converts an RGB image with the ITU-R BT.601 luma coefficients 0.2989, 0.5870, and 0.1140 for red, green, and blue. The output retains the input numeric class. Integer results therefore use MATLAB's normal cast and rounding behavior. For an M-by-3 colormap, each row is converted to one luma value and that value is repeated across three columns. This preserves the colormap shape while removing color. The function does not clamp floating-point input. Inputs: rgb - Real, nonsparse M-by-N-by-3 RGB image or M-by-3 colormap of class uint8, uint16, int16, single, or double. Outputs: gray - M-by-N grayscale image or M-by-3 grayscale colormap with the same numeric class as rgb. Errors: labkit:image:rgb2gray:InvalidShape - rgb is neither an RGB image nor an M-by-3 colormap. Example: rgb = cat(3, ones(2), zeros(2), zeros(2)); gray = labkit.image.rgb2gray(rgb); See also labkit.image.ensureRgb, labkit.image.im2double"},{"title":"labkit.image.sharpen","url":"reference/api/labkit/image/sharpen.html","kind":"reference","section":"reference","keywords":"labkit.image.sharpen","text":"SHARPEN Apply generic unsharp-mask sharpening. SHARPEN Apply generic unsharp-mask sharpening. Usage: imageOut = labkit.image.sharpen(imageIn, amountPct, radiusPx) Description: Converts imageIn to clamped RGB double and constructs an unsharp mask by subtracting a square local mean from the image. The detail mask is scaled by 2*amountPct/100 and added to the original. Each RGB channel is treated independently, and the result is clamped to [0,1]. radiusPx selects an odd mean-filter width of 2*round(radius)+1, with a minimum width of 3 pixels. This function uses a box blur rather than a Gaussian blur, so radius describes neighborhood size rather than a Gaussian standard deviation. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. amountPct - Numeric scalar effect strength in percent. Negative values are treated as 0. radiusPx - Numeric scalar blur radius in pixels. Values below 0.5 are treated as 0.5 before rounding. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. amountPct and radiusPx must be numeric scalars; incompatible values raise the originating conversion or indexing error. Example: imageIn = zeros(9); imageIn(3:7, 3:7) = 0.6; imageOut = labkit.image.sharpen(imageIn, 50, 1); See also labkit.image.meanFilter2, labkit.image.localContrast"},{"title":"labkit.image.supportedExtensions","url":"reference/api/labkit/image/supportedExtensions.html","kind":"reference","section":"reference","keywords":"labkit.image.supportedExtensions","text":"SUPPORTEDEXTENSIONS Return extensions supported by LabKit image file inputs. SUPPORTEDEXTENSIONS Return extensions supported by LabKit image file inputs. Usage: extensions = labkit.image.supportedExtensions() Description: Lists the filename extensions accepted by the generic LabKit image input functions. Matching in labkit.image.isSupportedPath is case-insensitive. This list describes source-image support; an individual app may offer a smaller set of export formats. Inputs: None. Outputs: extensions - String column of lowercase extensions, including the leading dot. The current values are .png, .jpg, .jpeg, .tif, .tiff, and .bmp. Failure Behavior: The function accepts no caller input and performs no file-system access. It returns the fixed facade extension list without a recoverable failure state. Example: extensions = labkit.image.supportedExtensions(); acceptsTiff = any(extensions == \".tiff\"); See also labkit.image.isSupportedPath, labkit.image.fileDialogFilter"},{"title":"labkit.image.version","url":"reference/api/labkit/image/version.html","kind":"reference","section":"reference","keywords":"labkit.image.version","text":"VERSION Return the LabKit image facade contract version. VERSION Return the LabKit image facade contract version. Usage: info = labkit.image.version() Description: Reports the semantic version and compatibility range of the public labkit.image API. Apps can use this structure to check whether the installed image facade satisfies a saved or declared requirement. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo with name, facade, current, compatible, status, and notes fields. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.image.version(); currentVersion = info.current; See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.image.writeFile","url":"reference/api/labkit/image/writeFile.html","kind":"reference","section":"reference","keywords":"labkit.image.writeFile","text":"WRITEFILE Write one image file, creating the parent folder when needed. WRITEFILE Write one image file, creating the parent folder when needed. Usage: labkit.image.writeFile(imageData, filepath) Description: Writes imageData with MATLAB imwrite. The extension in filepath selects the output format. If the parent folder does not exist, the function creates it, including intermediate folders supported by mkdir. Existing files are replaced according to imwrite behavior. The function does not rescale, clamp, or otherwise normalize imageData. Errors from folder creation and imwrite propagate to the caller. Inputs: imageData - Image matrix accepted by MATLAB imwrite. filepath - Nonempty character vector or scalar string. The filename extension controls the output format. Outputs: None. Errors: labkit:image:InvalidOutputPath - filepath is empty or nonscalar. Other folder and image-format errors are raised by mkdir or imwrite. Typical Call: labkit.image.writeFile(processedImage, fullfile(outputFolder, \"result.png\")); See also labkit.image.readFiles, labkit.image.displayName"},{"title":"labkit.rhs.findFiles","url":"reference/api/labkit/rhs/findFiles.html","kind":"reference","section":"reference","keywords":"labkit.rhs.findFiles","text":"FINDFILES Find Intan RHS files in a folder and its subfolders. FINDFILES Find Intan RHS files in a folder and its subfolders. Usage: filepaths = labkit.rhs.findFiles(rootDir) Description: Recursively collects files whose extension is .rhs and sorts their full paths for reproducible batch order. The function does not inspect file headers; use inspectFile when the contents must be validated. Inputs: rootDir - Character vector or string scalar naming an existing folder. Outputs: filepaths - Cell column vector of sorted full paths. The value is an empty cell array when no RHS files are found. Errors: Throws labkit:rhs:InvalidFolder when rootDir is not a text scalar or does not name an existing folder. Typical Call: files = labkit.rhs.findFiles(\"recordings\"); [info, status] = labkit.rhs.inspectFile(files{1}); See also labkit.rhs.inspectFile, labkit.rhs.indexFile, labkit.rhs.readWindow"},{"title":"labkit.rhs.indexFile","url":"reference/api/labkit/rhs/indexFile.html","kind":"reference","section":"reference","keywords":"labkit.rhs.indexFile","text":"INDEXFILE Build a block index for reading selected RHS time windows. INDEXFILE Build a block index for reading selected RHS time windows. Usage: [index, status] = labkit.rhs.indexFile(filepath) Description: Inspects the file header and records the byte and sample layout used by readWindow. No waveform arrays are loaded. Header-only files produce a valid index with hasData=false. When trailing partial bytes are present, complete blocks remain usable and status.message describes the condition. Inputs: filepath - Character vector or string scalar naming one RHS file. Outputs: index - Scalar structure containing header information and block layout. On inspection failure, hasData is false and numeric layout fields use empty defaults. status - Scalar structure with ok, message, and filepath. The index can be usable when ok is true and message reports trailing partial bytes. Output Fields: info - Full structure returned by inspectFile. sampleRateHz - Sample rate in hertz. samplesPerBlock - Number of samples in each complete block; currently 128. dataOffsetBytes - Byte offset of the first waveform block. bytesPerBlock - Calculated bytes in one complete block. dataBytes - Number of bytes following the header. blockCount - Number of complete waveform blocks. sampleCount - Number of samples in complete blocks. durationSec - Complete-block duration in seconds. exactBlocks - True when no trailing partial bytes remain. hasData - True when at least one complete sample is available. Errors: Throws labkit:rhs:InvalidFilepath for an invalid filepath argument. File and header failures are returned in status. Example: [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); if status.ok && index.hasData fprintf(\"%d samples in %d blocks\\n\", ... index.sampleCount, index.blockCount) end See also labkit.rhs.inspectFile, labkit.rhs.readWindow"},{"title":"labkit.rhs.inspectFile","url":"reference/api/labkit/rhs/inspectFile.html","kind":"reference","section":"reference","keywords":"labkit.rhs.inspectFile","text":"INSPECTFILE Read metadata and data-layout information from an RHS file. INSPECTFILE Read metadata and data-layout information from an RHS file. Usage: [info, status] = labkit.rhs.inspectFile(filepath) Description: Reads the Intan RHS header and calculates the complete-block count, available sample count, and recording duration without loading waveform arrays. A missing, malformed, or unsupported file is returned as status.ok=false after filepath validation. Inputs: filepath - Character vector or string scalar naming one RHS file. Outputs: info - Scalar structure containing the parsed header and calculated file layout. On failure, identity fields remain available and other fields contain empty or NaN values. status - Scalar structure with ok, message, and filepath. message is empty on success and explains the read failure otherwise. Output Fields: fileVersion - Two-element numeric vector [major minor]. sampleRateHz - Amplifier sample rate in hertz. durationSec - Duration represented by complete data blocks, in seconds. channelFamilies - Structures for amplifier, boardAdc, boardDac, boardDigIn, and boardDigOut channels. channelTable - Table containing family and channel-name metadata for all enabled stored channels. frequencyParameters - Requested and actual filter/sample frequencies. stimParameters - Stimulation step size and charge-recovery settings. dcAmplifierSaved - Logical flag indicating whether DC amplifier samples are present. dataOffsetBytes - Byte offset of the first waveform block. bytesPerBlock - Calculated bytes in one complete 128-sample block. blockCount - Number of complete waveform blocks. sampleCount - Number of samples in complete blocks. exactBlocks - True when the data section has no trailing partial bytes. Errors: Throws labkit:rhs:InvalidFilepath when filepath is not a character vector or string scalar. File-open and header-format errors are returned in status instead of thrown. Example: [info, status] = labkit.rhs.inspectFile(\"recording.rhs\"); if status.ok fprintf(\"%.1f s at %.0f Hz\\n\", info.durationSec, info.sampleRateHz) disp(info.channelTable(:, [\"family\", \"nativeName\", \"customName\"])) end See also labkit.rhs.indexFile, labkit.rhs.readWindow, labkit.rhs.findFiles"},{"title":"labkit.rhs.readWindow","url":"reference/api/labkit/rhs/readWindow.html","kind":"reference","section":"reference","keywords":"labkit.rhs.readWindow","text":"READWINDOW Read selected channels and times from an Intan RHS file. READWINDOW Read selected channels and times from an Intan RHS file. Usage: [window, status] = labkit.rhs.readWindow(filepath) [window, status] = labkit.rhs.readWindow(filepath, opts) Description: Reads only the complete data blocks needed for one time interval and one channel family. Returned values are converted to physical units using the Intan RHS scaling constants. File/header/read failures are returned in status; invalid option values throw errors. Inputs: filepath - Character vector or string scalar naming one RHS file. opts - Optional scalar structure. See Options. Options: family - String scalar selecting a channel family. Canonical values are \"amplifier\", \"stim\", \"dcAmplifier\", \"boardAdc\", \"boardDac\", \"boardDigIn\", and \"boardDigOut\". Matching ignores case and punctuation. Short aliases include \"amp\", \"dc\", \"adc\", \"dac\", \"digin\", and \"digout\". Default: \"amplifier\". channels - Channel selection within the chosen family. Use [] for all channels; positive one-based numeric indices for positions in that family; or a character vector, string array, or cell array of native or custom channel names. Names are matched exactly first, then with case and punctuation removed. Default: []. timeRangeSec - Two-element numeric vector [start end] in seconds. A negative start is clamped to zero; Inf as the end reads through the last complete sample. Both calculated endpoint samples are included. Default: [0 Inf]. Outputs: window - Scalar structure containing the selected waveform data. On a read failure it retains source identity and contains empty arrays. status - Scalar structure with ok, message, and filepath. A valid request beyond the recording duration returns ok=true, empty values, and a message stating that no samples were in the requested window. Output Fields: sourcePath - Source RHS path as a character vector. name - Source filename including extension. family - Canonical selected family name. unit - \"microvolts\" for amplifier, \"microamps\" for stim, \"volts\" for DC amplifier and board analog channels, or \"logical\" for digital channels. sampleRateHz - Sample rate in hertz. sampleRange - One-based inclusive [start end] sample indices. channels - String row vector of returned native channel names. timeSec - Sample-by-one time vector in seconds, derived from stored Intan timestamps. values - Samples-by-channels numeric matrix in the unit named by unit. Errors: Throws labkit:rhs:InvalidFilepath for an invalid path, labkit:rhs:InvalidFamily for an unsupported family, labkit:rhs:InvalidChannel for an unknown or out-of-range channel, and labkit:rhs:InvalidTimeRange for a malformed or reversed time range. Missing files, malformed headers, absent channel families, files without complete data blocks, and waveform read failures return status.ok=false. Example: opts = struct(\"family\", \"stim\", ... \"channels\", [\"C-001\", \"C-002\"], ... \"timeRangeSec\", [0.1 0.2]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", opts); if status.ok plot(window.timeSec, window.values) xlabel(\"Time (s)\") ylabel(\"Stimulation current (microamps)\") legend(window.channels) end See also labkit.rhs.indexFile, labkit.rhs.inspectFile"},{"title":"labkit.rhs.version","url":"reference/api/labkit/rhs/version.html","kind":"reference","section":"reference","keywords":"labkit.rhs.version","text":"VERSION Return version information for the RHS API. VERSION Return version information for the RHS API. Usage: info = labkit.rhs.version() Description: Reports the API version and compatibility range used when an app declares a dependency on labkit.rhs. This is not the file-format version stored in an Intan recording. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.rhs\"; facade is \"rhs\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.rhs.version(); fprintf(\"RHS API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.thermal.fileDialogFilter","url":"reference/api/labkit/thermal/fileDialogFilter.html","kind":"reference","section":"reference","keywords":"labkit.thermal.fileDialogFilter","text":"FILEDIALOGFILTER Create a file-selection filter for thermal images. FILEDIALOGFILTER Create a file-selection filter for thermal images. Usage: filterSpec = labkit.thermal.fileDialogFilter() filterSpec = labkit.thermal.fileDialogFilter(\"IncludeAll\", true) Description: Returns the pattern and description rows used by MATLAB file-selection dialogs and LabKit file panels. The thermal row includes .jpg, .jpeg, and .rjpg files. A matching extension does not prove that a JPEG contains radiometric data; use labkit.thermal.inspectFile to inspect its contents. Name-Value Arguments: IncludeAll - Logical or numeric scalar. When true, adds an \"All files\" row after the thermal-image row. Default: false. Outputs: filterSpec - One- or two-row cell array. Column 1 contains wildcard patterns and column 2 contains the text shown in the file dialog. Errors: MATLAB inputParser errors are raised for an unknown name, a missing name-value partner, or an IncludeAll value that is not a logical or numeric scalar. Example: filters = labkit.thermal.fileDialogFilter(\"IncludeAll\", true); assert(isequal(size(filters), [2 2])) See also labkit.thermal.supportedExtensions, labkit.thermal.inspectFile"},{"title":"labkit.thermal.inspectFile","url":"reference/api/labkit/thermal/inspectFile.html","kind":"reference","section":"reference","keywords":"labkit.thermal.inspectFile","text":"INSPECTFILE Check whether a file contains supported radiometric data. INSPECTFILE Check whether a file contains supported radiometric data. Usage: info = labkit.thermal.inspectFile(path) info = labkit.thermal.inspectFile(path, opts) Description: Performs the same format check and radiometric read as readFile, but returns the outcome instead of throwing when the extension, file contents, or calibration data are unsupported. Use this function to label files in a chooser or batch review before deciding which ones to read. Inputs: path - Character vector or string scalar naming one candidate file. opts - Optional scalar structure. See Options. Options: RequireExisting - Logical scalar forwarded to readFile. Default: true. TemperatureCorrection - \"environment\" or \"planck-basic\", forwarded to readFile. Default: \"environment\". Outputs: info - Scalar structure with path, name, supportedExtension, isThermal, format, identifier, and message. supportedExtension reports only the filename extension. isThermal is true only after a supported radiometric payload is read successfully. On failure, identifier and message contain the caught reader error. Failure Behavior: Unsupported extensions and all readFile failures return isThermal=false with identifier and message populated; those reader exceptions are not rethrown. path must still be convertible to scalar text, and malformed MATLAB values may raise the originating conversion error. Example: info = labkit.thermal.inspectFile(\"candidate.jpg\"); if info.isThermal fprintf(\"Readable thermal format: %s\\n\", info.format) else warning(\"%s\", info.message) end See also labkit.thermal.readFile, labkit.thermal.isSupportedPath"},{"title":"labkit.thermal.isSupportedPath","url":"reference/api/labkit/thermal/isSupportedPath.html","kind":"reference","section":"reference","keywords":"labkit.thermal.isSupportedPath","text":"ISSUPPORTEDPATH Test whether a path has a supported thermal extension. ISSUPPORTEDPATH Test whether a path has a supported thermal extension. Usage: tf = labkit.thermal.isSupportedPath(path) Description: Compares the filename extension with the extensions returned by labkit.thermal.supportedExtensions. The comparison is case-insensitive. This function does not access the file system and does not check whether a JPEG contains radiometric data. Use labkit.thermal.inspectFile when the file contents must be verified. Inputs: path - Character vector or string scalar containing a filename or path. Outputs: tf - Logical scalar. The value is true for .jpg, .jpeg, and .rjpg names and false for all other extensions. Failure Behavior: Unknown extensions return false without accessing the file system. path must be convertible to scalar text; unsupported MATLAB values or shapes may raise the originating string or fileparts conversion error. Example: labkit.thermal.isSupportedPath(\"capture.RJPG\") % returns true labkit.thermal.isSupportedPath(\"capture.png\") % returns false See also labkit.thermal.supportedExtensions, labkit.thermal.inspectFile"},{"title":"labkit.thermal.rawToTemperatureC","url":"reference/api/labkit/thermal/rawToTemperatureC.html","kind":"reference","section":"reference","keywords":"labkit.thermal.rawToTemperatureC","text":"RAWTOTEMPERATUREC Convert FLIR raw sensor values to degrees Celsius. RAWTOTEMPERATUREC Convert FLIR raw sensor values to degrees Celsius. Usage: temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration) temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration, opts) [temperatureC, diagnostics] = labkit.thermal.rawToTemperatureC(...) Description: Applies the FLIR Planck calibration stored in a radiometric image. The default correction also compensates for emissivity, reflected radiation, atmospheric transmission, and an infrared window. Select \"planck-basic\" when the raw counts have already been corrected or when only the camera's Planck constants should be applied. Inputs: raw - Numeric array of raw sensor counts. The output has the same size. calibration - Scalar structure containing the five required Planck fields and, for environmental correction, the fields described below. opts - Optional scalar structure. See Options. Calibration Fields: PlanckR1 - Required finite scalar from the camera metadata. PlanckB - Required finite scalar from the camera metadata. PlanckF - Required finite scalar from the camera metadata. PlanckO - Required finite scalar from the camera metadata. PlanckR2 - Required finite scalar from the camera metadata. Emissivity - Object emissivity. Must be positive. Default: 1. ObjectDistanceM - Camera-to-object distance in metres. Negative values are treated as zero. Default: 1. ReflectedApparentTemperatureC - Reflected apparent temperature in degrees Celsius. Default: 20. AtmosphericTemperatureC - Atmospheric temperature in degrees Celsius. Default: 20. IRWindowTemperatureC - Infrared-window temperature in degrees Celsius. Defaults to ReflectedApparentTemperatureC. IRWindowTransmission - Infrared-window transmission. Must be positive. Default: 1. RelativeHumidity - Relative humidity as a fraction from 0 to 1 or as a percentage greater than 2. Default: 0.5. AtmosphericTransAlpha1 - First atmospheric alpha coefficient. Default: 0.006569. AtmosphericTransAlpha2 - Second atmospheric alpha coefficient. Default: 0.01262. AtmosphericTransBeta1 - First atmospheric beta coefficient. Default: -0.002276. AtmosphericTransBeta2 - Second atmospheric beta coefficient. Default: -0.00667. AtmosphericTransX - Atmospheric mixing coefficient. Default: 1.9. Options: Correction - String scalar selecting the conversion model. \"environment\" uses the environmental fields and records every fallback value. \"planck-basic\" uses only the five Planck fields. Default: \"environment\". Outputs: temperatureC - Double array in degrees Celsius with the same size as raw. Pixels for which the Planck expression is undefined are NaN. diagnostics - Scalar structure describing the conversion. correction is the selected mode; usedDefaults is true if an environmental fallback was used; defaultedFields lists those fields; parameterSources maps each environmental field to \"calibration\" or \"default\"; and message provides a readable summary. In \"planck-basic\" mode, no environmental fields are consumed and defaultedFields is empty. Errors: Throws labkit:thermal:MissingCalibration when a required Planck field is absent or not finite, and labkit:thermal:InvalidOptions when Correction is not one of the two supported values. Example: calibration = struct(\"PlanckR1\", 21106.77, \"PlanckB\", 1501, ... \"PlanckF\", 1, \"PlanckO\", -7340, \"PlanckR2\", 0.012545258); raw = [15000 15500; 16000 16500]; [temperatureC, diagnostics] = ... labkit.thermal.rawToTemperatureC(raw, calibration, ... struct(\"Correction\", \"planck-basic\")); See also labkit.thermal.readFile, labkit.thermal.renderImage"},{"title":"labkit.thermal.readFile","url":"reference/api/labkit/thermal/readFile.html","kind":"reference","section":"reference","keywords":"labkit.thermal.readFile","text":"READFILE Read radiometric image data and convert it to degrees Celsius. READFILE Read radiometric image data and convert it to degrees Celsius. Usage: record = labkit.thermal.readFile(path) record = labkit.thermal.readFile(path, opts) Description: Reads the embedded FLIR FFF RawThermalImage record from a radiometric JPEG or RJPEG. The ordinary visible JPEG pixels are not used as temperature values. Raw sensor counts are retained alongside the converted matrix and the calibration metadata used for conversion. Inputs: path - Character vector or string scalar naming one .jpg, .jpeg, or .rjpg file. The file must contain FLIR radiometric data. opts - Optional scalar structure. See Options. Options: RequireExisting - Logical scalar. When true, report a missing file before checking its extension. Default: true. TemperatureCorrection - String scalar selecting the temperature model. Allowed values are \"environment\" and \"planck-basic\". \"environment\" accounts for emissivity, object distance, reflected temperature, atmosphere, humidity, and an infrared window when the metadata is available. \"planck-basic\" applies only the camera's Planck constants to the raw counts. Default: \"environment\". Outputs: record - Scalar structure with fields path, name, format, raw, temperatureC, units, metadata, and message. raw is the decoded sensor matrix. temperatureC is a same-size double matrix in degrees Celsius, or NaN when the raw data cannot be converted. metadata includes the parsed FLIR tags and temperatureConversion diagnostics, including any environmental values that used documented defaults. Errors: Throws labkit:thermal:InvalidPath for a nonscalar path, labkit:thermal:FileNotFound for a missing file when RequireExisting is true, labkit:thermal:UnsupportedFile for another extension or a JPEG with no supported radiometric payload, and labkit:thermal:InvalidOptions for an invalid option value. Typical Call: opts = struct(\"TemperatureCorrection\", \"environment\"); record = labkit.thermal.readFile(\"capture.rjpg\", opts); imagesc(record.temperatureC) axis image colorbar See also labkit.thermal.inspectFile, labkit.thermal.readFiles, labkit.thermal.rawToTemperatureC"},{"title":"labkit.thermal.readFiles","url":"reference/api/labkit/thermal/readFiles.html","kind":"reference","section":"reference","keywords":"labkit.thermal.readFiles","text":"READFILES Read several radiometric image files. READFILES Read several radiometric image files. Usage: records = labkit.thermal.readFiles(paths) records = labkit.thermal.readFiles(paths, opts) [records, report] = labkit.thermal.readFiles(paths, opts) Description: Calls labkit.thermal.readFile for each path in input order. By default, the first unreadable file stops the operation. Set SkipInvalid to true when a batch should keep its readable records and return a failure entry for each skipped file. Inputs: paths - Character vector, string array, or cell array of character vectors and strings. Empty strings are removed. Output record order follows the remaining input order. opts - Optional scalar structure. See Options. Options: AllowEmpty - Logical scalar. When true, an empty path list returns empty records and a zero-count report. When false, an empty list throws labkit:thermal:NoPaths. Default: true. RequireExisting - Logical scalar forwarded to readFile. Default: true. SkipInvalid - Logical scalar. When true, an unreadable file is added to report.failures and processing continues. When false, its exception is rethrown immediately. Default: false. TemperatureCorrection - String scalar forwarded to readFile. Allowed values are \"environment\" and \"planck-basic\". Default: \"environment\". progressFcn - Empty or a function handle called once before each read and again after it loads or is skipped. The callback receives a scalar structure with stage, index, count, path, and name. stage is \"beforeRead\", \"afterRead\", or \"skipped\". Default: []. Outputs: records - Column structure array of successfully read thermal records. See labkit.thermal.readFile for the fields in each element. report - Scalar structure. requested is the number of normalized paths; loaded and skipped are counts. failures is a structure array with path, name, identifier, and message for each skipped file. Errors: In addition to readFile errors, throws labkit:thermal:InvalidOptions when opts or a callback is invalid and labkit:thermal:NoPaths when AllowEmpty is false and no usable path remains. Typical Call: opts = struct(\"SkipInvalid\", true, ... \"TemperatureCorrection\", \"environment\"); [records, report] = labkit.thermal.readFiles(files, opts); fprintf(\"Loaded %d of %d files.\\n\", report.loaded, report.requested) See also labkit.thermal.readFile, labkit.thermal.inspectFile"},{"title":"labkit.thermal.renderImage","url":"reference/api/labkit/thermal/renderImage.html","kind":"reference","section":"reference","keywords":"labkit.thermal.renderImage","text":"RENDERIMAGE Map a thermal matrix to an RGB image. RENDERIMAGE Map a thermal matrix to an RGB image. Usage: rgb = labkit.thermal.renderImage(values) rgb = labkit.thermal.renderImage(values, opts) Description: Linearly maps numeric values between the selected limits onto a thermal colour palette. Values below or above the limits are clipped to the first or last palette colour. NaN and Inf values use the first palette colour. This function changes only the display representation; it does not modify the source temperatures or apply a calibration. Inputs: values - Numeric M-by-N matrix containing temperatures or raw sensor values. opts - Optional scalar structure. See Options. Options: Limits - Two finite, increasing numeric values [low high]. When omitted, the finite minimum and maximum of values are used. An all-nonfinite matrix uses [0 1]. A constant matrix uses [value value+1]. Palette - String scalar naming the colour palette. Allowed values are \"turbo\", \"parula\", \"hot\", \"gray\", and \"iron\". Default: \"turbo\". Levels - Finite numeric scalar giving the palette length. Values are rounded to the nearest integer and must be at least 2. Default: 256. Outputs: rgb - M-by-N-by-3 double array with channel values in the interval [0, 1]. Errors: Throws labkit:thermal:InvalidOptions when Limits, Palette, or Levels is invalid. Example: temperatureC = [20 24 28; 32 36 40]; rgb = labkit.thermal.renderImage(temperatureC, ... struct(\"Limits\", [20 40], \"Palette\", \"iron\", \"Levels\", 256)); assert(isequal(size(rgb), [2 3 3])) See also labkit.thermal.rawToTemperatureC, labkit.thermal.readFile"},{"title":"labkit.thermal.supportedExtensions","url":"reference/api/labkit/thermal/supportedExtensions.html","kind":"reference","section":"reference","keywords":"labkit.thermal.supportedExtensions","text":"SUPPORTEDEXTENSIONS List the supported thermal filename extensions. SUPPORTEDEXTENSIONS List the supported thermal filename extensions. Usage: extensions = labkit.thermal.supportedExtensions() Description: Returns the filename extensions recognized by the thermal file-selection and reading functions. JPEG is a container format, so a listed extension does not guarantee that a file contains FLIR radiometric data. Use labkit.thermal.inspectFile to check a particular file. Outputs: extensions - String row vector [\".jpg\", \".jpeg\", \".rjpg\"]. Extensions are lowercase and include the leading period. Failure Behavior: The function accepts no caller input and performs no file-system access. It returns the fixed facade extension list without a recoverable failure state. Example: extensions = labkit.thermal.supportedExtensions(); pattern = \"*\" + strjoin(extensions, \";*\"); See also labkit.thermal.isSupportedPath, labkit.thermal.fileDialogFilter, labkit.thermal.inspectFile"},{"title":"labkit.thermal.version","url":"reference/api/labkit/thermal/version.html","kind":"reference","section":"reference","keywords":"labkit.thermal.version","text":"VERSION Return version information for the thermal API. VERSION Return version information for the thermal API. Usage: info = labkit.thermal.version() Description: Reports the version and compatibility range used when an app declares a dependency on labkit.thermal. This is the API contract version, not the MATLAB release or the version of a particular thermal camera format. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.thermal\"; facade is \"thermal\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.thermal.version(); fprintf(\"Thermal API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"nerve_response_analysis.analysisRun.analyzeRecording","url":"reference/api/nerve_response_analysis/analysisRun/analyzeRecording.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.analyzeRecording","text":"ANALYZERECORDING Analyze one RHS recording according to a protocol. ANALYZERECORDING Analyze one RHS recording according to a protocol. Usage: analysis = nerve_response_analysis.analysisRun.analyzeRecording(filepath) analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... filepath, protocol) analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... filepath, protocol, opts) Description: Indexes one Intan RHS file, maps protocol roles to amplifier channels, chooses an event source, detects pulse trains, and measures compound action potential features for every configured or inferred differential pair. Waveforms are read lazily over a bounded time range. The function does not open a GUI or write results. File, channel, and pair failures are recorded in analysis.issues whenever the remaining work can continue. This allows a batch analysis to retain successfully detected events or measured pairs instead of discarding the entire recording. Inputs: filepath - Path to one Intan RHS recording. protocol - Optional scalar protocol structure, normally loaded from an RHS Preview protocol JSON. An empty structure still allows stimulation- channel event detection and fallback amplifier detection. Default: struct(). opts - Optional scalar structure containing run and detector options. Protocol Fields: channels.roles - Structure array assigning logical roles to channels. Each entry uses id and may include label, nativeName, and match.anyNativeName. Matching is case-insensitive and ignores punctuation against both native and custom RHS channel names. channels.pairs - Structure array with id, label, positive, and negative role ids. Each pair is analyzed as positive-minus-negative. If pairs are absent, roles ending in \"_positive\" are paired with the corresponding \"_negative\" role when both resolve. A pair's optional mode field is retained in the protocol snapshot but does not change this calculation. Options: recordingId - Text identifier inserted into every event, train, metric, and issue row. Default: \"\". maxDurationSec - Maximum recording duration read from the start of the file. A nonpositive, nonfinite, or omitted value uses the indexed duration; values beyond the file are limited to that duration. Default: Inf. eventDetection - Structure accepted by nerve_response_analysis.analysisRun.detectEventTrains. Default: detector defaults. Event Source Priority: The first usable source is selected in this order: nonzero RHS stimulation current (maximum absolute value across stimulation channels); the amplifier channel assigned to role \"reference\"; channels named by configured response pairs; conventional positive/negative response roles; and finally the first amplifier channel. Amplifier sources are differentiated by detectEventTrains. A fallback source adds a warning to issues. Pair Measurement: Each pair reads its positive and negative amplifier channels and forms a differential response. When a reference role is available, it is supplied to common-mode correction. CAP features use the default windows documented by nerve_response_analysis.analysisRun.measureCapMetrics. Missing pair channels or failed reads add a warning and leave other pairs available. Outputs: analysis - Scalar recording-analysis structure. Analysis Fields: type, version - \"nerveResponseRecordingAnalysis\" and schema version 1. recordingId, filepath - Effective recording identity and source path. protocolSnapshot - Protocol value supplied by the caller. events - Event table from detectEventTrains with recordingId prepended when events exist. trains - Train table from detectEventTrains with recordingId prepended when trains exist. metrics - One row per event and analyzed pair. Columns are recordingId, pairId, pairLabel, followed by all measureCapMetrics columns. issues - Table with recordingId, severity (\"warning\" or \"error\"), and a reader-facing message. An unreadable file produces an error row and no further analysis; missing sources, events, or pairs produce warnings. Failure Behavior: Expected data and IO failures return a partial analysis structure rather than throwing. Invalid MATLAB types or unexpected parser/calculation errors may still throw; analyzeSession converts those exceptions into issue rows. Typical Call: protocol = jsondecode(fileread(\"nerve-response-protocol.json\")); opts = struct(\"recordingId\", \"R001\", \"maxDurationSec\", 30, ... \"eventDetection\", struct(\"stdMultiplier\", 3)); analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... \"recording.rhs\", protocol, opts); disp(analysis.metrics) disp(analysis.issues) See also nerve_response_analysis.analysisRun.analyzeSession, nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.measureCapMetrics, labkit.rhs.indexFile, labkit.rhs.readWindow"},{"title":"nerve_response_analysis.analysisRun.analyzeSession","url":"reference/api/nerve_response_analysis/analysisRun/analyzeSession.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.analyzeSession","text":"ANALYZESESSION Analyze accepted RHS recordings and combine their results. ANALYZESESSION Analyze accepted RHS recordings and combine their results. Usage: analysis = nerve_response_analysis.analysisRun.analyzeSession(session) analysis = nerve_response_analysis.analysisRun.analyzeSession( ... session, protocol) analysis = nerve_response_analysis.analysisRun.analyzeSession( ... session, protocol, opts) Description: Selects accepted rows from an RHS Preview filter record, analyzes each file with analyzeRecording, and vertically concatenates event, train, metric, and issue tables. A failure in one recording becomes an issue row and does not stop later recordings. Inputs: session - Scalar structure whose recordings field is a table or structure array. Required recording columns are recordingId and filePath. label, comment, qcFlag, and keep are optional selection/provenance fields. protocol - Optional channel-role and pair protocol passed unchanged to each recording. See analyzeRecording. Default: struct(). opts - Optional scalar structure. Recording-level fields are forwarded to analyzeRecording after recordingId is replaced by the current row id. Recording Selection: If keep exists, rows with logical keep=true are selected. Otherwise label selects normalized \"good\" rows; accepted synonyms are good, keep, kept, true, 1, yes, and y, while bad/reject synonyms normalize to \"bad\". If label is absent but qcFlag exists, rows equal to \"accepted\" are selected. With no selection column, every row is treated as good. This precedence means keep overrides label and qcFlag when more than one is present. Options: maxRecordings - Positive finite limit applied after recording selection. Zero, nonpositive, nonfinite, or omitted input means no limit. Default: Inf. maxDurationSec - Forwarded to analyzeRecording for every selected file. eventDetection - Forwarded to analyzeRecording for every selected file. Outputs: analysis - Scalar session-analysis structure. Analysis Fields: type, version - \"nerveResponseSessionAnalysis\" and schema version 1. recordingCount - Total rows before acceptance filtering. analyzedCount - Number of selected rows after maxRecordings. This counts attempted recordings, including files that later produce issues. protocolSnapshot - Protocol supplied by the caller. events, trains, metrics - Combined nonempty tables returned by recording analyses. issues - Combined recording issues plus caught exceptions, with recordingId, severity, and message columns. Failure Behavior: A recording-level analysis exception is converted to one issue row and later recordings continue. Missing or unsupported session.recordings input becomes an empty session; a nonempty table without recordingId or filePath, incompatible selection columns, or unappendable result tables may still raise the originating table/field error before or between recordings. Example: recordings = table(strings(0,1), strings(0,1), ... 'VariableNames', {'recordingId', 'filePath'}); session = struct(\"recordings\", recordings); analysis = nerve_response_analysis.analysisRun.analyzeSession(session); assert(analysis.recordingCount == 0 && analysis.analyzedCount == 0) See also nerve_response_analysis.analysisRun.analyzeRecording, nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.measureCapMetrics"},{"title":"nerve_response_analysis.analysisRun.detectEventTrains","url":"reference/api/nerve_response_analysis/analysisRun/detectEventTrains.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.detectEventTrains","text":"DETECTEVENTTRAINS Detect pulse candidates and group them into trains. DETECTEVENTTRAINS Detect pulse candidates and group them into trains. Usage: [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains(timeSec, signal) [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, signal, opts) Description: Detects abrupt signal transitions and groups nearby candidates into pulse trains. Missing signal samples are linearly interpolated before the absolute first difference is calculated. Candidate peaks must exceed both a robust noise threshold and an optional absolute score, and nearby candidates are reduced to the one with the larger score. Reported event times may then be shifted to compensate for the delay between a derivative peak and physical stimulus onset. Inputs: timeSec - Numeric sample-time vector in seconds. signal - Numeric event-source vector with the same number of samples as timeSec. Units are arbitrary but must be consistent with minScore. opts - Optional scalar structure. Options may be supplied directly. A protocol-style structure may instead place source definitions and train policy under opts.eventDetection. Options: eventDetection - Optional scalar protocol structure containing sources and train fields. When omitted, the supplied opts structure itself is interpreted as the event-detection policy. Default: direct opts. sourceId - Text copied to event and train source columns. Default: the first derivative source id in eventDetection.sources, or \"analog_derivative\". stdMultiplier - Numeric scalar multiplier applied to robust score noise. A derivative source's threshold.multiplier takes precedence. Default: 2. minScore - Finite numeric scalar lower bound in signal-difference units. The effective threshold is max(minScore, stdMultiplier*noise). Default: 0. minPeakDistanceSec - Minimum separation between retained candidates. A derivative source's value takes precedence. Default: 0.005 seconds. groupGapSec - A gap larger than this value starts a new train. Supply it in opts.train or opts.eventDetection.train. Default: 0.100 seconds. minDetectedPulses - Minimum pulse count for a valid train. Default: 4. maxTrainDurationSec - Maximum first-to-last event duration for a valid train. Default: 0.060 seconds. isolationWindowSec - Minimum preceding and following inter-train gaps for isIsolated=true. Missing neighboring trains imply an infinite gap. Default: 0.500 seconds. requireIsolation - When true, isolation is also required for train validity. Default: false. stimShiftSec - Offset added to every retained raw event time. Default: -0.0005 seconds, moving derivative peaks 0.5 ms earlier. Detection Details: Robust noise is median(abs(score-median(score)))/0.6745, with ordinary standard deviation used when that estimate is zero or invalid. A local maximum is at least as large as its left neighbor and strictly larger than its right neighbor. Train validity requires pulseCount >= minDetectedPulses, durationSec <= maxTrainDurationSec, and isolation when requested. Outputs: events - One row per retained candidate. trains - One row per temporal group. Event Columns: trainId, pulseIndex - One-based group and within-group indices. timeSec - Shifted event time in seconds. rawTimeSec - Unshifted sample time in seconds. sampleIndex - One-based index into the input vectors. score - Absolute first-difference score. source - Effective sourceId. Train Columns: trainId, startSec, endSec, pulseCount, durationSec - Group identity and shifted-time geometry. isIsolated - True when both neighboring train gaps satisfy isolationWindowSec. isValid - Result of pulse-count, duration, and optional isolation rules. source - Effective sourceId. status - \"valid\" or \"needsReview\". Failure Behavior: Empty input or a signal with no qualifying candidates returns empty tables with stable column names. Errors: nerve_response_analysis:EventSizeMismatch - timeSec and signal lengths differ. Example: sampleRate = 10000; timeSec = (0:1/sampleRate:0.12).'; signal = zeros(size(timeSec)); pulseTimes = 0.03 + (0:4).' * 0.01; signal(round(pulseTimes*sampleRate)+1) = 5; opts = struct(\"sourceId\", \"stim\", \"stimShiftSec\", 0, ... \"train\", struct(\"minDetectedPulses\", 5)); [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, signal, opts); assert(height(events) == 5 && trains.isValid(1)) See also nerve_response_analysis.analysisRun.measureCapMetrics, nerve_response_analysis.analysisRun.analyzeRecording"},{"title":"nerve_response_analysis.analysisRun.measureCapMetrics","url":"reference/api/nerve_response_analysis/analysisRun/measureCapMetrics.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.measureCapMetrics","text":"MEASURECAPMETRICS Measure event-locked CAP response features. MEASURECAPMETRICS Measure event-locked CAP response features. Usage: metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, eventTimesSec) metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, eventTimesSec, opts) Description: Measures baseline, noise, positive and negative response peaks, peak-to-peak amplitude, dominant-peak latency, and signal-to-noise ratio for each stimulus time. All time options and outputs are in seconds. Signal-derived values retain the units of signal; SNR is reported in decibels. Inputs: timeSec - Numeric sample-time vector in seconds. signal - Numeric response vector with the same length as timeSec. eventTimesSec - Numeric vector of stimulus times in seconds. One output row is produced for each value, in input order. opts - Optional scalar structure containing the measurement windows below. Options: segments - Optional scalar protocol structure. segments.capSearch may contain blankingAfterPulseSec and searchEndAfterPulseSec; nested values override direct options. Default: struct(). baselineWindowSec - Duration before each event used to estimate baseline. The inclusive baseline interval is [event-baselineWindowSec, event-blankingAfterPulseSec]. Default: 0.050 seconds. blankingAfterPulseSec - Time excluded immediately before and after the stimulus. It may also be supplied as opts.segments.capSearch. blankingAfterPulseSec. The nested value takes precedence. Default: 0.002 seconds. searchEndAfterPulseSec - End of the inclusive response-search interval, measured after the event. It may also be supplied in opts.segments.capSearch and takes the same precedence. Default: 0.008 seconds. Calculations: baselineMean is the finite-value mean in the baseline interval. noiseRms is the root mean square deviation from that mean. If the interval contains no samples, the recording-wide finite median becomes the baseline and noiseRms remains NaN. The response search is [event+blankingAfterPulseSec, event+searchEndAfterPulseSec]. Peaks are measured after baseline subtraction. peakTimeSec belongs to whichever of the positive or negative peaks has larger absolute amplitude, and latencySec = peakTimeSec-stimTimeSec. When noiseRms is positive and finite, snrDb = 20*log10(abs(peakToPeak)/noiseRms). Outputs: metrics - Table with one row per event. Metric Columns: eventIndex - One-based event order. stimTimeSec - Input stimulus time. baselineMean, noiseRms - Pre-stimulus baseline and noise estimate. peakPositive, peakNegative, peakToPeak - Baseline-subtracted extrema and their difference. peakTimeSec, latencySec - Dominant absolute peak time and event-relative latency. snrDb - Peak-to-peak SNR in decibels, or NaN when noise is unavailable or zero. status - \"ok\" when the search interval contains samples; \"noSamples\" otherwise. Rows without samples retain NaN response metrics. Errors: nerve_response_analysis:MetricSizeMismatch - timeSec and signal lengths differ. Example: timeSec = (0:0.0001:0.04).'; signal = zeros(size(timeSec)); signal(abs(timeSec-0.014) < 0.0002) = 2; signal(abs(timeSec-0.016) < 0.0002) = -1; opts = struct(\"baselineWindowSec\", 0.004, ... \"blankingAfterPulseSec\", 0.002, ... \"searchEndAfterPulseSec\", 0.008); metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, 0.010, opts); assert(metrics.status == \"ok\" && metrics.peakToPeak > 2.5) See also nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.analyzeRecording"},{"title":"response_review_stats.analysisRun.alignSegments","url":"reference/api/response_review_stats/analysisRun/alignSegments.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.alignSegments","text":"ALIGNSEGMENTS Interpolate segment traces to a shared time grid. ALIGNSEGMENTS Interpolate segment traces to a shared time grid. Usage: aligned = response_review_stats.analysisRun.alignSegments(segments) aligned = response_review_stats.analysisRun.alignSegments(segments, opts) Description: Shifts each segment's time axis to a common alignment origin, constructs a shared uniformly spaced grid, and linearly interpolates every trace onto that grid. Duplicate source timestamps retain their first sample. Values outside a segment's aligned source range remain NaN. An optional baseline subtraction is performed after interpolation. Inputs: segments - Structure array with timeSec and values vectors of matching length and a name value for each segment. A segment-specific alignTimeSec may identify the source time that should become zero. opts - Optional scalar structure containing the alignment options below. Options: sampleIntervalSec - Positive output-grid interval in seconds. When omitted, the median of every finite positive source time difference is used. If no source supplies a usable difference, the fallback is 0.0001 seconds. windowSec - Two-element [start end] output interval in aligned seconds. When omitted, the function uses the intersection of all source ranges after alignment-time shifts. alignTimeSec - Global source time shifted to zero for segments without their own alignTimeSec field. Default: 0 seconds. baselineWindowSec - Optional inclusive [start end] interval in aligned seconds. The finite mean in this output-grid interval is subtracted from the entire corresponding segment. Default: no subtraction. Outputs: aligned - Scalar structure with these fields: Aligned Fields: timeSec - G-by-1 uniformly spaced aligned time vector in seconds. values - G-by-N double matrix with one interpolated segment per column. segmentNames - N-by-1 string vector copied from segments.name. status - \"ok\", or \"empty\" when segments is empty. Failure Behavior: Empty segments returns stable empty arrays with status=\"empty\". Nonempty elements must provide compatible timeSec, values, and name fields; invalid option intervals, disjoint derived ranges, or interpolation-incompatible data propagate the originating MATLAB field, colon, or interp1 error. Example: segments = struct( ... \"timeSec\", {[10; 11; 12], [20; 21; 22]}, ... \"values\", {[1; 2; 3], [4; 5; 6]}, ... \"name\", {\"first\", \"second\"}, ... \"alignTimeSec\", {10, 20}); aligned = response_review_stats.analysisRun.alignSegments( ... segments, struct(\"sampleIntervalSec\", 1)); assert(isequal(aligned.timeSec, [0; 1; 2])) assert(isequal(aligned.values, [1 4; 2 5; 3 6])) See also response_review_stats.analysisRun.measureAlignedSegments, response_review_stats.analysisRun.summarizeMetrics"},{"title":"response_review_stats.analysisRun.measureAlignedSegments","url":"reference/api/response_review_stats/analysisRun/measureAlignedSegments.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.measureAlignedSegments","text":"MEASUREALIGNEDSEGMENTS Measure peak-to-peak, noise, and SNR per segment. MEASUREALIGNEDSEGMENTS Measure peak-to-peak, noise, and SNR per segment. Usage: metrics = response_review_stats.analysisRun.measureAlignedSegments(aligned) metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, opts) Description: Baseline-corrects every aligned trace, measures its positive and negative extrema, and estimates baseline noise and peak-to-peak SNR. Each segment is measured independently and produces one output row. Peaks use only finite samples; NaN samples are omitted from baseline and noise means. Inputs: aligned - Structure returned by response_review_stats.analysisRun.alignSegments. timeSec is G-by-1, values is G-by-N, and segmentNames contains N names. opts - Optional scalar structure containing the measurement windows below. Options: baselineWindowSec - Inclusive [start end] interval in aligned seconds used to calculate and subtract the baseline mean. Default: [0.007 0.009]. noiseWindowSec - Two-element numeric [start end] interval used for noise RMS after baseline subtraction. Default: baselineWindowSec. measurementWindowSec - Inclusive interval used for positive and negative peaks. Empty input uses all aligned samples. Default: []. Calculations: PeakToPeak = Peak1Value-Peak2Value, where Peak1 is the finite maximum and Peak2 is the finite minimum in the measurement window. NoiseRMS is the root mean square deviation from the finite noise-window mean. When NoiseRMS is positive and PeakToPeak is finite, SNR_dB is 20*log10(abs(PeakToPeak)/NoiseRMS). A window without usable samples leaves its metrics as NaN. A missing baseline also makes that segment's corrected trace and downstream measurements NaN. Outputs: metrics - N-row table with SegmentName, BaselineStart_s, BaselineEnd_s, PeakToPeak, Peak1Time_s, Peak1Value, Peak2Time_s, Peak2Value, NoiseStart_s, NoiseEnd_s, NoiseRMS, and SNR_dB columns. Failure Behavior: Windows without usable finite samples produce NaN metrics for the affected segment. aligned must provide compatible timeSec, values, and segmentNames fields, and every supplied window must contain two numeric endpoints; malformed values propagate the originating field, indexing, or conversion error. Example: aligned = struct(\"timeSec\", (0:0.001:0.012).', ... \"values\", zeros(13, 1), \"segmentNames\", \"response\", \"status\", \"ok\"); aligned.values(7) = 2; aligned.values(11) = -1; opts = struct(\"baselineWindowSec\", [0 0.002], ... \"noiseWindowSec\", [0 0.002]); metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, opts); assert(metrics.PeakToPeak == 3) See also response_review_stats.analysisRun.alignSegments, response_review_stats.analysisRun.summarizeMetrics"},{"title":"response_review_stats.analysisRun.summarizeMetrics","url":"reference/api/response_review_stats/analysisRun/summarizeMetrics.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.summarizeMetrics","text":"SUMMARIZEMETRICS Compute grouped metric counts and finite means. SUMMARIZEMETRICS Compute grouped metric counts and finite means. Usage: summary = response_review_stats.analysisRun.summarizeMetrics(metrics) Description: Produces compact descriptive statistics from response metric rows. Groups follow their first occurrence. The function performs no weighting, confidence interval, hypothesis test, or outlier removal. Inputs: metrics - Metric table. pairId selects groups when that variable exists; otherwise SegmentName is used, and otherwise all rows belong to \"all\". Amplitude may be named PeakToPeak or peakToPeak. SNR may be named SNR_dB or snrDb. Outputs: summary - Table with one row per group. Summary Columns: Group - Group name in stable first-occurrence order. Count - Number of input rows in the group, including rows whose metrics are nonfinite. MeanPeakToPeak - Arithmetic mean of finite group amplitudes, or NaN when the amplitude variable is absent or has no finite values. MeanSnrDb - Arithmetic mean of finite group SNR values in decibels, or NaN when the SNR variable is absent or has no finite values. Failure Behavior: Empty or non-table input returns one \"all\" row with Count=0 and NaN means. Example: metrics = table([\"cp\"; \"cp\"; \"ta\"], [3; 5; 2], [20; 22; 10], ... 'VariableNames', {'pairId', 'peakToPeak', 'snrDb'}); summary = response_review_stats.analysisRun.summarizeMetrics(metrics); assert(summary.Group(1) == \"cp\" && summary.MeanPeakToPeak(1) == 4) See also response_review_stats.analysisRun.measureAlignedSegments, response_review_stats.analysisRun.alignSegments"},{"title":"rhs_preview.analysisRun.readPreviewWindow","url":"reference/api/rhs_preview/analysisRun/readPreviewWindow.html","kind":"reference","section":"reference","keywords":"rhs_preview.analysisRun.readPreviewWindow","text":"READPREVIEWWINDOW Read the requested RHS preview window from app state. READPREVIEWWINDOW Read the requested RHS preview window from app state. Usage: [S, ok, logMessage] = rhs_preview.analysisRun.readPreviewWindow( ... S, selectedChannels, actionLabel) [S, ok, logMessage] = rhs_preview.analysisRun.readPreviewWindow( ... S, selectedChannels, actionLabel, preserveRoi) Description: Reads a bounded time window from the RHS file recorded in S and stores the returned waveform structure in S.preview. The requested start time is clamped to the indexed recording duration before labkit.rhs.readWindow is called. This function updates data and status fields only; it does not create graphics, open dialogs, or write files. Inputs: S - Scalar RHS Preview state structure. See State Fields for the values read or changed by this function. selectedChannels - Channel identifiers accepted by the channels option of labkit.rhs.readWindow. Supply at least one identifier from S.family. actionLabel - Text stored in S.lastAction after a successful read. This lets the caller distinguish an initial read from a refresh or a move to another part of the recording. preserveRoi - Optional logical scalar. true keeps the previous ROI after clamping it to the new time vector; false resets S.roiSec to [NaN NaN]. Default: false. Outputs: S - Updated state. On success, preview, roiSec, windowStartSec, statusMessage, and lastAction describe the completed read. ok - Logical scalar. true only when labkit.rhs.readWindow returns a status whose ok field is true. logMessage - Text summary for the app log. It reports the sample count on success, describes a read failure, or is empty when no read was attempted because a file or channel selection is missing. State Fields: rhsFile - Path to the indexed RHS file. Empty text prevents the read. family - RHS channel family passed to labkit.rhs.readWindow, such as \"amplifier\" or \"stim\". windowStartSec - Requested start time in seconds. It is replaced by the valid value returned by clampWindowStartSec. windowDurationSec - Requested duration in seconds. The read interval ends at windowStartSec + max(windowDurationSec, eps). index - File index used by clampWindowStartSec to enforce recording bounds. roiSec - Two-element ROI interval in seconds. It is preserved and clamped only when preserveRoi is true and the read contains time samples. preview - Waveform window returned by labkit.rhs.readWindow. A thrown read error clears this field; a returned failure status leaves the returned window here so its diagnostic content remains available. statusMessage - Reader-facing explanation of success, missing input, or a failed read. lastAction - actionLabel after success, or \"Preview read failed\" after a read error. Missing inputs leave the previous value unchanged. Failure Behavior: A missing file or empty channel selection returns ok=false without calling the reader. Exceptions from labkit.rhs.readWindow are caught, copied into statusMessage and logMessage, and clear preview. A nonthrowing failed read returns its status message and ok=false. State-shape errors, such as a missing required field, are programming errors and may still throw. Typical Call: channels = [\"A-000\", \"A-001\"]; [state, ok, message] = rhs_preview.analysisRun.readPreviewWindow( ... state, channels, \"Read preview\", true); See also labkit.rhs.readWindow, rhs_preview.analysisRun.clampWindowStartSec, rhs_preview.analysisRun.clampRoi"},{"title":"ttest_wizard.testRun.runGroupTTests","url":"reference/api/ttest_wizard/testRun/runGroupTTests.html","kind":"reference","section":"reference","keywords":"ttest_wizard.testRun.runGroupTTests","text":"RUNGROUPTTESTS Compare every group after the first with the first group. RUNGROUPTTESTS Compare every group after the first with the first group. Usage: results = ttest_wizard.testRun.runGroupTTests(groups, options) Description: Runs one explicit t-test for each group after the first, always using the first group as vector A/reference and the later group as vector B. It preserves group order, copies per-comparison labels and observations into canonical result snapshots, and performs no plotting or file IO. Inputs: groups - Struct vector with scalar text label and numeric-vector values fields. The first element is the reference group. Nonfinite values are reported through the affected runTTest result. options - Scalar struct with method, alternative, and alpha fields accepted by runTTest. Per-comparison labels are supplied from groups. Outputs: results - Column struct vector of canonical runTTest results in group order. Element k compares groups(1) minus groups(k+1). Options: method - Test label from testRun.choices or \"welch\", \"pooled\", or \"paired\". No default is applied; the field is required. alternative - Alternative label from testRun.choices or \"two_sided\", \"greater\", or \"less\". Direction is reference minus comparison. No default is applied; the field is required. alpha - Finite numeric scalar strictly between zero and one. No default is applied; the field is required. Units: Every group must use the same measurement unit. Means, standard deviations, differences, standard errors, and confidence bounds retain that unit. Test statistics, degrees of freedom, and p-values are dimensionless. Assumptions: Test choice, independence, and pairing remain the caller's scientific responsibility. A paired method pairs each comparison group separately with the first group by displayed row order. No multiple-comparison correction is applied. Failure Behavior: Fewer than two groups throws ttest_wizard:InsufficientGroups. A malformed group throws ttest_wizard:InvalidGroups. Individual scientific failures are returned through the stable runTTest result statuses. Errors: ttest_wizard:InsufficientGroups - groups has fewer than two elements. ttest_wizard:InvalidGroups - A group label is not scalar text or values is not a numeric vector. ttest_wizard:InvalidTestOptions - options is missing method, alternative, or alpha, or runTTest rejects one of their values. Example: groups = struct( ... \"label\", {\"Reference\", \"Treatment 1\", \"Treatment 2\"}, ... \"values\", {[1 2 3], [2 3 4], [1 2 4]}); options = struct(\"method\", \"welch\", ... \"alternative\", \"two_sided\", \"alpha\", 0.05); results = ttest_wizard.testRun.runGroupTTests(groups, options); assert(numel(results) == 2) See also ttest_wizard.testRun.runTTest"},{"title":"ttest_wizard.testRun.runTTest","url":"reference/api/ttest_wizard/testRun/runTTest.html","kind":"reference","section":"reference","keywords":"ttest_wizard.testRun.runTTest","text":"RUNTTEST Run one independent Welch, pooled, or paired t-test. RUNTTEST Run one independent Welch, pooled, or paired t-test. Usage: result = ttest_wizard.testRun.runTTest(vectorA, vectorB, options) Description: Calculates one App-owned t-test with Base MATLAB beta functions. Difference direction is always A minus B. Scientific validation failures return a result row with ok=false and a stable status instead of fabricating a statistic. Inputs: vectorA - Finite numeric vector with at least two values. vectorB - Finite numeric vector with at least two values. options - Scalar struct with method, alternative, alpha, labelA, and labelB. method accepts a label from testRun.choices or welch, pooled, or paired. alternative accepts a choice label or two_sided, greater, or less. alpha is a finite scalar strictly between zero and one. Outputs: result - Scalar struct created by emptyResult. Successful results include vector snapshots, summaries, A-minus-B estimate and confidence bounds, standard error, t statistic, degrees of freedom, and p-value. Units: A and B must use the same measurement unit. Means, standard deviations, the A-minus-B estimate, standard error, and confidence bounds retain that unit. The t statistic, degrees of freedom, and p-value are dimensionless. Assumptions: Independent tests require observations that are independent within and between A and B. The pooled method additionally assumes equal population variances. The paired method requires displayed A(k) and B(k) to describe the same experimental unit; it analyzes A(k)-B(k). Test choice and independence remain the caller's scientific responsibility. Failure Behavior: Invalid options throw ttest_wizard:InvalidTestOptions. Nonfinite vectors, sample sizes below two, unequal paired lengths, or zero standard error return ok=false with status invalid_input, insufficient_n, unequal_pairs, or zero_standard_error. Example: options = struct( ... \"method\", \"welch\", ... \"alternative\", \"two_sided\", ... \"alpha\", 0.05, ... \"labelA\", \"Condition A\", ... \"labelB\", \"Condition B\"); result = ttest_wizard.testRun.runTTest( ... [1.2 1.4 1.3 1.5], [1.8 1.7 2.0 1.9 1.6], options); assert(result.ok) See also ttest_wizard.testRun.choices, ttest_wizard.testRun.emptyResult"},{"title":"video_marker.coordinateExport.buildTable","url":"reference/api/video_marker/coordinateExport/buildTable.html","kind":"reference","section":"reference","keywords":"video_marker.coordinateExport.buildTable","text":"BUILDTABLE Convert confirmed video annotations to a coordinate table. BUILDTABLE Convert confirmed video annotations to a coordinate table. Usage: T = video_marker.coordinateExport.buildTable( ... annotations, skeleton, videoInfo, calibration, opts) Description: Exports a continuous range of confirmed frames in a rectangular table suitable for CSV files or direct MATLAB analysis. Coordinates can remain in pixels or be converted with the project's physical scale. The origin and vertical-axis direction are applied consistently to every keypoint while the stored annotation arrays remain unchanged. Inputs: annotations - Frame annotation structure. annotations.coords must be an F-by-P-by-2 array in [x y] pixel coordinates, and frameStatus must mark every exported frame as \"confirmed\". skeleton - Skeleton structure with P ordered pointIds. Each point ID is converted with matlab.lang.makeValidName for table variable names. videoInfo - Scalar structure with frameRate in frames per second. time_s is calculated as (frame_index-1)/frameRate. calibration - Scale structure with pixelsPerUnit and unit. It is required only when opts.unitMode is \"calibrated_physical\". opts - Coordinate export structure, normally created by video_marker.coordinateExport.options. Options: startFrame - Finite numeric scalar giving the first exported one-based frame index. It is rounded and limited to at least one. Default from options: 1. endFrame - Numeric scalar or Inf giving the last exported frame index, inclusive. It is rounded and limited to the annotation frame count. Default from options: Inf. unitMode - \"pixels\" retains pixel distances and reports \"px\"; \"calibrated_physical\" divides distances by calibration.pixelsPerUnit and reports calibration.unit. Default: \"pixels\". originMode - \"top_left_pixel_center\" subtracts [1 1], so the center of the upper-left pixel is [0 0]. \"first_point\" subtracts the first skeleton point's coordinates at startFrame from all frames. Default: \"top_left_pixel_center\". yAxisMode - \"down\" preserves image-row direction; \"up\" negates vertical displacement so positive y points upward. Default: \"up\". Outputs: T - Table with one row per exported frame. Fixed variables are frame_index, time_s, coordinate_unit, y_axis, origin_mode, origin_frame_index, origin_point_id, and pixels_per_unit. Every skeleton point adds __x and __y numeric variables in skeleton order. Errors: labkit_VideoMarker_app:InvalidFrameRange - The limited end frame precedes the limited start frame. labkit_VideoMarker_app:UnconfirmedFrameRange - Any selected frame is not confirmed. labkit_VideoMarker_app:MissingScaleCalibration - Physical units are requested without a positive finite pixelsPerUnit value. labkit_VideoMarker_app:InvalidOriginMode - originMode is unsupported. labkit_VideoMarker_app:InvalidYAxisMode - yAxisMode is unsupported. Example: skeleton = video_marker.skeletonDefinition.fromText(\"hip, knee\", \"hip-knee\"); annotations = video_marker.frameAnnotations.emptyAnnotations(2, 2); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 1, [11 21; 31 41], \"confirmed\"); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 2, [13 25; 35 45], \"confirmed\"); info = struct(\"frameRate\", 10); opts = video_marker.coordinateExport.options( ... \"startFrame\", 1, \"endFrame\", 2, \"unitMode\", \"pixels\", ... \"originMode\", \"first_point\", \"yAxisMode\", \"up\"); T = video_marker.coordinateExport.buildTable( ... annotations, skeleton, info, struct(), opts); assert(isequal(T.hip__x, [0; 2]) && isequal(T.hip__y, [0; -4])) See also video_marker.coordinateExport.options, video_marker.frameAnnotations.emptyAnnotations, video_marker.skeletonDefinition.fromText"},{"title":"video_marker.motionEstimate.trackPoints","url":"reference/api/video_marker/motionEstimate/trackPoints.html","kind":"reference","section":"reference","keywords":"video_marker.motionEstimate.trackPoints","text":"TRACKPOINTS Track ordered image points between two consecutive video frames. TRACKPOINTS Track ordered image points between two consecutive video frames. Usage: [points, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, previousPoints) [points, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, previousPoints, priorDisplacement) Description: Tracks each point with deterministic base-MATLAB multiscale patch matching. A grayscale pyramid provides up to four resolution levels. At every level, a 7-by-7 mean-centered template is compared by normalized correlation in a plus-or-minus four-pixel search around the predicted location. A parabolic peak fit refines the final match to subpixel coordinates. Points are processed independently and preserve their input order. A flat, out-of-frame, or weakly correlated patch is not treated as a hard error: the output retains the clamped motion-prior prediction and reports zero confidence for that point. Inputs: previousImage - Finite numeric or logical grayscale or RGB source frame. currentImage - Finite frame with exactly the same size as previousImage. previousPoints - Finite N-by-2 array of [x y] coordinates in the previous frame, where x is the column and y is the row. N may be zero. priorDisplacement - Optional finite [dx dy] motion prediction. A 1-by-2 value is shared by all points; an N-by-2 array supplies one prior per point. Default: zeros(N,2). Outputs: points - N-by-2 tracked [x y] coordinates, limited to currentImage bounds. Failed matches contain the limited previousPoints+priorDisplacement. confidence - N-by-1 values in [0,1]. Accepted normalized-correlation scores are mapped with (score+1)/2; failed matches are zero. A correlation of at least 0.55 is required for acceptance. diagnostics - Scalar structure with engine, valid, and failureMessage. engine is \"multiscale_patch\". valid is an N-by-1 logical mask. failureMessage is empty when every point succeeds and explains partial tracking failure otherwise. Errors: labkit_VideoMarker_app:FrameSizeMismatch - Frame arrays differ in size. labkit_VideoMarker_app:InvalidTrackingPoints - previousPoints is not a finite N-by-2 array. labkit_VideoMarker_app:InvalidMotionPrior - priorDisplacement is neither a finite 1-by-2 nor finite N-by-2 array. labkit_VideoMarker_app:InvalidTrackingImage - A frame is not grayscale or RGB after conversion, or contains nonfinite values. Example: rng(7) previousImage = rand(60, 70); currentImage = circshift(previousImage, [-2 3]); [point, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, [35 30]); assert(norm(point - [38 28]) < 0.5) assert(confidence > 0.5 && diagnostics.valid) See also video_marker.motionEstimate.predictForward, video_marker.frameAnnotations.setFramePoints"},{"title":"vt_resistance.analysisRun.computeResistance","url":"reference/api/vt_resistance/analysisRun/computeResistance.html","kind":"reference","section":"reference","keywords":"vt_resistance.analysisRun.computeResistance","text":"COMPUTERESISTANCE Estimate cathodic and anodic resistance from a VT pulse. COMPUTERESISTANCE Estimate cathodic and anodic resistance from a VT pulse. Usage: A = vt_resistance.analysisRun.computeResistance(item) A = vt_resistance.analysisRun.computeResistance(item, opts) Inputs: item - Scalar DTA item struct. item.curve, or the main curve selected from item.tables, must provide T in seconds, Vf in volts, and Im in amperes. Pt is optional; zero-based sample numbers are generated when absent. item.meta may contain pulse timing and current metadata. opts - Optional scalar struct described below. Default: struct(). Options: windowMode - Samples used for steady voltage/current medians: \"Full pulse median\" or \"Center 60% median\". The latter excludes the first and last 20% of each detected phase. Default: \"Full pulse median\". voltageMode - Resistance numerator: \"Baseline-corrected dV/I\" or \"Raw Vf/I\". Default: \"Baseline-corrected dV/I\". pulseMode - Pulse-detection policy: \"Metadata first, then auto\", \"Metadata only\", or \"Auto from Im only\". Default: \"Metadata first, then auto\". Outputs: A - Scalar result struct. Success is indicated by A.ok and A.message=\"OK\". Data-validation failures return ok=false and the fields calculated up to the failed stage. Result Fields: ok - True when the final mean absolute resistance is finite. message - \"OK\" or an explanation of the first failed stage. windowMode - Effective steady-window label. voltageMode - Effective voltage-numerator label. logOnFailure - True when the app should surface the failure in its log. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. pt - Filtered source point numbers or generated zero-based sample numbers. pulse - Pulse geometry returned by labkit.dta.detectPulses. detectMode - Detection method that produced pulse. detectMsg - Pulse-detection status message. cathMask - Logical samples in the selected cathodic steady window. anodMask - Logical samples in the selected anodic steady window. cathSteadyStart, cathSteadyEnd - Cathodic steady-window bounds in seconds. anodSteadyStart, anodSteadyEnd - Anodic steady-window bounds in seconds. Ic_est_A - Median cathodic current in amperes. Ia_est_A - Median anodic current in amperes. Vc_ss_V - Median cathodic steady voltage in volts. Va_ss_V - Median anodic steady voltage in volts. cathBaselineStart, cathBaselineEnd - Requested pre-pulse cathodic baseline bounds in seconds. anodBaselineStart, anodBaselineEnd - Requested post-pulse anodic baseline bounds in seconds. Vc_baseline_V - Median pre-pulse voltage, or 0 when unavailable. Va_baseline_V - Median post-pulse voltage; falls back to the cathodic baseline and then 0. cathBaselineWindow_s - Duration of the requested cathodic baseline window. anodBaselineWindow_s - Duration of the requested anodic baseline window. dVc_V - Vc_ss_V-Vc_baseline_V in volts. dVa_V - Va_ss_V-Va_baseline_V in volts. Rc_raw_ohm - Vc_ss_V/Ic_est_A in ohms. Ra_raw_ohm - Va_ss_V/Ia_est_A in ohms. Rc_dV_ohm - dVc_V/Ic_est_A in ohms. Ra_dV_ohm - dVa_V/Ia_est_A in ohms. Rc_ohm - Selected cathodic raw or baseline-corrected resistance. Ra_ohm - Selected anodic raw or baseline-corrected resistance. Rc_abs_ohm - Absolute Rc_ohm. Ra_abs_ohm - Absolute Ra_ohm. Ravg_abs_ohm - Mean of the finite absolute cathodic and anodic values. Description: computeResistance analyzes one detected biphasic voltage transient and reports phase-specific as well as averaged resistance. Apps may choose raw steady voltage or subtract the adjacent baseline before division. The function performs no plotting, file selection, or export. Calculations: Rows containing NaN in T, Vf, or Im are removed together. Pulse detection is delegated to labkit.dta.detectPulses. Steady values are phase-window medians. Division returns NaN when either operand is nonfinite or current is smaller than eps in magnitude. The reported average uses absolute phase resistances so opposite current signs do not cancel. Failure Behavior: Missing curve data, fewer than five remaining samples, failed pulse detection, undersampled steady windows, or nonfinite final resistance returns ok=false. Malformed structs and unsupported MATLAB value types may still throw from DTA access or numeric operations. Typical Call: [item, status] = labkit.dta.loadFile(\"transient.DTA\", \"chrono\"); assert(status.ok, status.message) opts = struct(\"windowMode\", \"Center 60% median\", ... \"voltageMode\", \"Baseline-corrected dV/I\", ... \"pulseMode\", \"Metadata first, then auto\"); A = vt_resistance.analysisRun.computeResistance(item, opts); assert(A.ok, A.message) fprintf(\"Mean resistance: %.4g ohm\\n\", A.Ravg_abs_ohm) See also labkit.dta.detectPulses, cic.analysisRun.computeCIC"}];
\ No newline at end of file
diff --git a/site/assets/search-index.json b/site/assets/search-index.json
index 75e9ee186..2a9f9b5d9 100644
--- a/site/assets/search-index.json
+++ b/site/assets/search-index.json
@@ -1 +1 @@
-[{"title":"LabKit Documentation","url":"index.html","kind":"overview","section":"general","keywords":"LabKit Documentation","text":"LabKit Documentation LabKit documentation is organized by the question you are trying to answer. Start with a task guide, then open an API reference only when you need exact MATLAB call syntax or returned data shapes. Choose A Starting Point I want to Start here Install, update, or open LabKit [Getting started](getting-started/README.md) Understand every launcher action or call it from MATLAB [LabKit Launcher](apps/labkit-core/launcher/README.md) Choose an app and understand its inputs and outputs [App guide](apps/README.md) Call a reusable `labkit.*` function [Public API reference](reference/README.md) Understand ownership and package boundaries [Architecture](development/build-apps/architecture.md) Create or modify an app [App development](development/build-apps/app-development.md) Run tests or diagnose performance [Testing](development/maintain-and-release/testing.md) Call packaging, profiling, codecheck, or documentation tools [Maintainer tools](development/tools/README.md) Understand documentation sources and generated HTML [Documentation system](development/maintain-and-release/documentation.md) Maintain a private app workspace [Private apps](development/maintain-and-release/private-apps.md) Prepare a release [Release process](development/maintain-and-release/release.md) Documentation Layers getting-started/ installation, launcher, updates, and first-run concepts apps/ one directory per family and one subdirectory per app including the LabKit Launcher under LabKit Core framework/ UI runtime concepts, behavior, and app-authoring contracts libraries/ one directory per reusable public MATLAB facade reference/ generated-function reference landing page development/ reader-task folders for app building, maintenance, and tools history/ chronological change records and the project history index Every Markdown file is published automatically. Its path owns navigation and its first level-one heading owns the page title. Public App manuals are matched to `labkit_launcher(\"list\")`; App-owned function pages are discovered from complete public MATLAB help contracts. Reference Conventions Public function pages follow the MATLAB reference pattern: summary and syntax description and behavior input arguments and options output arguments and data shapes algorithms or scientific semantics where relevant errors and limitations executable examples version history, See Also, and related topics Each app family has a landing page. Each concrete app owns a directory whose `README.md` is its Get Started and detailed behavior page; complex apps can add focused workflow, file-format, or algorithm topics beside it. App pages explain launch, task flow, interaction rules, inputs, outputs, persistence, scientific meaning, non-GUI APIs, errors, limitations, examples, related topics, and component history as applicable. They do not document internal callbacks. Framework and library landing pages follow the Qt module pattern: overview and ownership first, followed by grouped concepts, supported public members, detailed behavior, examples, and related modules. Generated function pages are owned by MATLAB source help blocks. Handwritten HTML is never a source. Project History And Support [Project history](history/README.md) lists all change records and connects them to the affected apps, framework, and libraries. [Support](../.github/SUPPORT.md) explains how to report workflow problems."},{"title":"EIS","url":"apps/electrochemistry/eis.html","kind":"app","section":"apps","keywords":"apps electrochemistry eis EIS","text":"EIS EIS overlays impedance data from one or more Gamry `ZCURVE` tables, supports Nyquist and Bode-style axis combinations, and exports the values currently selected for plotting. Requirements And Launch labkit_EIS_app Inputs Add one or more `.DTA` files containing a readable EIS `ZCURVE`. Files that do not contain the required curve are reported and omitted from the plot. The successfully decoded source list and its order are preserved in project state through portable references. Basic Workflow Add the EIS DTA files. Choose X and Y quantities. Choose **mΩ**, **Ω**, **kΩ**, or **MΩ** for impedance axes. New projects default to **kΩ**. Enable logarithmic X or Y scaling only for strictly positive plotted data. Use **Fit X/Y limits** to re-estimate independent limits from the current data, or **Use equal X/Y scale** when equal data units are wanted. Adjust marker, line, grid, and legend presentation. Export the current plot data CSV. Axis Quantities The available quantities are frequency, log10 frequency, time, point number, real impedance, imaginary impedance, negative imaginary impedance, impedance magnitude, phase, DC current, and DC voltage. Default axes are `Zreal` and `-Zimag`, displayed in kΩ for a new project. Use `Zreal` versus `-Zimag` for the conventional Nyquist orientation. Use frequency versus magnitude or phase for Bode-style views. The log-axis checkbox changes MATLAB axes scaling; choosing `log10(Freq)` changes the data coordinate itself. Do not apply both transformations unless that is explicitly intended. Plot Parameters Parameter Default Impedance unit kΩ Line width 1.4 Marker size 6 Show markers on Log X / Log Y off / off Legend / Grid on / on The app never infers an equal aspect ratio from the selected quantities: a Nyquist plot starts with independently fitted limits. Use **Use equal X/Y scale** only when equal data units are useful for the current comparison. Use **Fit X/Y limits** to return to independent limits after equal scaling or a manual zoom. Equal scaling expands a fitted limit when necessary so X and Y data units have the same on-screen length; it is a one-time reset and does not constrain later wheel zooming. Axis and styling changes preserve the current source set and the current viewport; the two view buttons explicitly replace that viewport. Output **Export current plot CSV** writes the selected X/Y values for each valid file on a shared row index. Each file retains its own X and Y pair, so unequal curve lengths do not imply interpolation. Impedance columns use the selected display unit and include an ASCII unit suffix such as `kohm` in the column name. A result manifest records the selected axes, impedance unit, plot parameters, source references, and output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"spectrum.DTA\", \"eis\"); assert(status.ok, status.message); units = eis.impedanceDisplay.catalog(); x = eis.analysisRun.valuesForAxis(item, \"Zreal\", units.choices(3)); y = eis.analysisRun.valuesForAxis(item, \"-Zimag\", units.choices(3)); plot(x, y, \"o-\"); axis equal `valuesForAxis` is app-owned and not currently part of the published app API catalog. The DTA loader and `getZCurve` are supported reusable APIs. Errors And Limitations Log axes omit or reject nonpositive coordinates according to MATLAB axes behavior; inspect the data rather than treating missing points as zero. Overlaying files does not normalize electrode area or fixture geometry. Changing the impedance display unit rescales impedance axes and exported impedance columns; it does not alter the DTA values stored in base ohms. Axis labels describe parsed DTA columns; they do not validate the experiment configuration recorded by the instrument. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md)"},{"title":"2.1: RHS apps and shared runtime stability","url":"history/records/2026/06/LK-20260621-v2-1-rhs-and-runtime-stability.html","kind":"history","section":"history","keywords":"LK-20260621-v2-1-rhs-and-runtime-stability 2026-06-21 7 feat compatible historical project evolution","text":"2.1: RHS apps and shared runtime stability id: LK-20260621-v2-1-rhs-and-runtime-stability date: 2026-06-21 sequence: 7 type: feat compatibility: compatible scope: historical project evolution Context After v2.0, the UI runtime supported several app families but still repeated important behavior around busy actions, path events, preview zoom, and layout ownership. A new neurophysiology family would amplify those inconsistencies if each app solved them independently. Decision and rationale Add RHS reading and review as a first-class app family while centralizing only the runtime mechanics shared across domains. Filtering choices and response analysis remained in the RHS workflows; transactions, path events, zoom, and layout ownership belonged to the UI layer. Changes Added RHS Preview, Nerve Response Analysis, and Response Review Stats. Folded the separate screening step into preview filtering so file review used one workflow rather than two overlapping stages. Added duplicate Batch Crop tasks for repeated crop layouts. Unified preview scroll zoom and centralized action busy transactions. Centralized path-event contracts and app layout ownership. Added a ZIP-based updater and stabilized image and app interactions before release. Reduced focused test discovery and guardrail cost as the repository grew. User and data impact Neurophysiology users gained file preview, response analysis, and review statistics apps. Existing users saw more consistent zoom, busy feedback, path updates, and responsive layout behavior. The updater provided a repository- owned way to install a packaged release. Compatibility and migration The RHS family was additive. UI implementations that maintained their own copies of busy, path, or layout mechanics were migrated to the shared runtime contracts. Validation The stage added RHS-focused tests, interaction regressions, compatibility input checks, path-event checks, and faster focused test routing. Tag `2.1` points to `76ddf7d0`. Evidence RHS app family `7ddc036f` and preview filtering `ca8a37dd`. Shared interaction mechanics `eb52eb17`, `f3e42b8e`, `1fe89ba2`. Layout ownership `3ce4c8f7`, `09baf7fe`. Updater `e4b02f3a`. 2.1 release stabilization `76ddf7d0`. Known limitations and follow-up The updater still depended on how the launcher was installed, and several image workflows reset zoom or recomputed previews during edits. The following 2.2 and 2.3 releases concentrated on self-contained launch and direct image manipulation."},{"title":"Adjacent navigation for history records","url":"history/records/2026/07/LK-20260716-history-adjacent-navigation.html","kind":"history","section":"history","keywords":"LK-20260716-history-adjacent-navigation 2026-07-16 64 docs compatible documentation `tools/docs/` `site/history/records/`","text":"Adjacent navigation for history records id: LK-20260716-history-adjacent-navigation date: 2026-07-16 sequence: 64 type: docs compatibility: compatible component: `documentation` scope: `tools/docs/` scope: `site/history/records/` Context The complete timeline linked every recorded change, but a reader who opened one detail page had to return to the index before continuing chronologically. Dates and filenames could not safely identify adjacent records because several changes may share one date. Decision and rationale Use validated history `sequence` metadata to add adjacent-record links at the end of every rendered history detail page. **Previous change** means the record with `sequence - 1`; **Next change** means `sequence + 1`. This preserves one unambiguous project-history order without binding authored content to Git. Changes Added responsive previous/next navigation immediately before the generated page footer. Showed only existing directions at the oldest and newest ends. Added regression coverage for link targets, ordering, and page placement. User and data impact Readers can traverse project history linearly without returning to the index. No MATLAB runtime, app state, scientific data, or exports change. Compatibility and migration Existing history URLs and record metadata remain valid. Regenerating `site/` adds navigation to every record page and updates the search index normally. Validation Renderer regression tests verify middle and endpoint records against source sequence metadata. The documentation consistency check independently rebuilds and byte-compares the tracked site. Evidence `renderLabKitHistorySequenceNavigation` owns adjacent link generation. `DocumentationRendererRegressionTest` verifies the generated navigation. Known limitations and follow-up Navigation is intentionally chronological across all project components. It does not filter to the current app or library; component-specific history remains available from each component manual."},{"title":"App SDK explicit contract replaces the retired UI runtime","url":"history/records/2026/07/LK-20260719-ui-explicit-contract-migration.html","kind":"history","section":"history","keywords":"LK-20260719-ui-explicit-contract-migration 2026-07-19 138 refactor breaking labkit.app labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app App Framework DIC Electrochem Gait Image Measurement LabKit Core Neurophysiology Statistics Wearable Project persistence Result provenance","text":"App SDK explicit contract replaces the retired UI runtime id: LK-20260719-ui-explicit-contract-migration date: 2026-07-19 sequence: 138 type: refactor compatibility: breaking component: `labkit.app` | `new -> 1.0.0` component: `labkit_DICPostprocess_app` | `1.4.7 -> 1.5.0` component: `labkit_DICPreprocess_app` | `1.5.8 -> 1.6.0` component: `labkit_ChronoOverlay_app` | `1.4.7 -> 1.5.0` component: `labkit_CIC_app` | `1.4.7 -> 1.5.0` component: `labkit_CSC_app` | `1.4.8 -> 1.5.0` component: `labkit_EIS_app` | `1.4.7 -> 1.5.0` component: `labkit_VTResistance_app` | `1.4.7 -> 1.5.0` component: `labkit_GaitAnalysis_app` | `2.0.8 -> 2.1.0` component: `labkit_BatchImageCrop_app` | `1.7.7 -> 1.8.0` component: `labkit_CurvatureMeasurement_app` | `1.4.6 -> 1.5.0` component: `labkit_FLIRThermal_app` | `1.4.8 -> 1.5.0` component: `labkit_FocusStack_app` | `1.5.6 -> 1.6.0` component: `labkit_ImageEnhance_app` | `1.6.7 -> 1.7.0` component: `labkit_ImageMatch_app` | `1.6.8 -> 1.7.0` component: `labkit_VideoMarker_app` | `1.5.7 -> 1.6.0` component: `labkit_FigureStudio_app` | `0.2.9 -> 0.3.0` component: `labkit_NerveResponseAnalysis_app` | `1.4.8 -> 1.5.0` component: `labkit_ResponseReviewStats_app` | `1.4.7 -> 1.5.0` component: `labkit_RHSPreview_app` | `1.4.6 -> 1.5.0` component: `labkit_TTestWizard_app` | `1.0.1 -> 1.1.0` component: `labkit_ECGPrint_app` | `1.4.6 -> 1.5.0` scope: App Framework scope: DIC scope: Electrochem scope: Gait scope: Image Measurement scope: LabKit Core scope: Neurophysiology scope: Statistics scope: Wearable scope: Project persistence scope: Result provenance Context The retired UI runtime removed substantial per-App lifecycle code, but Apps still registered callback tables, repeated bound values in presenters, authored standard file add/remove/clear behavior, and depended on nested event/service structs. A replacement SDK kernel had already established immutable semantic values, pre-GUI validation, transactional state/presentation commits, project documents, result manifests, resources, and portable sources. The migration then had to restore the complete behavior and visual contract of every tracked App before the retired production facade could be deleted. Decision and rationale Create `labkit.app` as the stable SDK rather than misnaming the expanded contract `labkit.ui` or adapting it back to Runtime V2 transport structs. Keep the public root small, partition authoring by capability, and concentrate complexity in a paved path: direct-callback `layout.*` nodes, strict bindings, runtime-completed `view.Snapshot` values, standard file lifecycle, fixed `CreateSession(project,context)`, and private native adapters. Migrate all 21 tracked Apps through capability waves, treating their previous controls, tabs, layout proportions, interactions, project behavior, results, debug samples, and workflow wording as product contracts rather than reducing the task to launch compatibility. Changes Added the private native MATLAB adapter with semantic component ownership, typed RuntimeKernel callbacks, native dialog results, complete-presentation reconciliation, and rollback to the previous native view after a failed renderer commit. Added `Definition.launch`, fixed renderer `(axes,model)` dispatch, semantic labels, runtime-owned file add/remove/clear and selection, and transient session rebuild after source collection changes. Added strict table view options, typed complete-data edits, and distinct `event.TableCellEdit`, `event.TableCellSelection`, and `event.ListSelection` values; the private adapter absorbs native MATLAB table-value differences. Fixed session construction to `CreateSession(project,context)` so Apps resolve opaque portable sources without reading their representation. Migrated Chrono Overlay to one directly bound export callback, four state bindings, one directly bound two-axis renderer, and a two-operation view snapshot. Partitioned the public SDK into `layout`, `view`, `event`, `project`, `result`, and `dialog`; layout nodes, option parsing, stores, adapters, and runtime execution remain hidden under `internal`. Reduced Chrono's noncomment layout/action/presenter code from 277 lines to 86 while preserving its DTA alignment, plot options, project schema, CSV columns, and result provenance. Migrated T-Test Wizard as the typed editable-table, feature-fragment, and multi-page workspace proof: table selections and edits have explicit payload classes, `+workbench` exposes product assembly, workflow packages own their layout/presentation/actions, and the private adapter owns concrete layout. Migrated VT Resistance to direct file and analysis-setting bindings, a complete summary/table/two-axis snapshot, and an App-owned result-package export. Plot renderers and scientific choices now live with their owning analysis capabilities instead of a technical UI package. Migrated Gait Analysis to capability-owned source adoption, option invalidation, deterministic analysis, step selection/navigation, three-axis rendering, CSV-set export, and result packaging. Migrated DIC Preprocess to two role-bound image sources, paired-anchor registration, managed crop and mask editors, two-axis rendering, edit replay, and result-package exports owned by its analysis, mask, and result capabilities. Migrated Batch Image Crop to framework-owned source selection plus App-owned duplicate crop tasks, direct crop-center and scale-reference interactions, capability snapshots, and result-package export. Added `labkit.app.project.sourceRecord` so pure payload migrations can convert legacy paths into portable sources without constructing the framework-owned reference representation. Corrected folder chooser dispatch to its one-path backend contract and applied table data before table selection during native reconciliation, so a selection may legally target rows introduced by the same snapshot. Preserved cell-valued interaction payloads in the transactional event queue and kept multi-target interaction bridge specifications scalar, enabling paired-anchor gestures across two semantic axes. Removed handler objects, callback tables, renderer registries, and their forwarding from the App authoring contract. Layout controls and plot areas reference concrete functions directly. Migrated the remaining electrochemistry, DIC, image-measurement, neurophysiology, core, wearable, and high-state workflows, including editable tables, workspace pages, file roles, multi-axis plots, managed point/rectangle/interval/scale interactions, long-lived video resources, project recovery, result packages, and synthetic diagnostic samples. Restored shared product presentation: versioned titles and dirty markers, startup progress and failure surfaces, guarded close behavior, utility menus, adjustable pane dividers, scroll and grow policies, numeric panners, adaptive action grids, usage text, plot navigation, pop-out/export tools, and viewport-preserving overlays. Internalized contract compilation, runtime construction, native platform plans, target inventories, and callback-context creation. `Definition` is the sole author-created root; `CallbackContext` is a sealed runtime-injected port; optional concepts remain grouped under purpose-specific packages. Deleted the complete retired `labkit.ui` production facade and the migration-only analyzer, prototypes, compatibility tools, and debt-only tests after source scans and focused App tests proved that no production consumer remained. User and data impact Chrono Overlay retains its input formats, pulse-gap alignment, plot meanings, parameter defaults, CSV table, and version-2 project payload. T-Test Wizard retains its source formats, group/test calculations, plot meaning, two CSV exports, and version-2 project payload. VT Resistance retains its pulse detection, resistance calculations, plot semantics, CSV schema, and version-1 project payload while recomputing the decoded batch under shared settings. Gait Analysis retains its Video Marker payload contract, project migrations, step segmentation, gait metrics, CSV set, and version-3 project payload. File identities and portable paths remain runtime-owned. DIC Preprocess retains its rigid registration, common crop, mask editing, image/mask exports, and version-1 project payload. Batch Image Crop retains duplicate tasks per source, fixed-pixel and physical crops, rotation/padding, scale calibration, scale-bar placement, image/CSV exports, and its version-2 payload. Existing payload migrations remain App-owned. The other 15 Apps retain their documented source formats, scientific calculations, units, thresholds, project payload versions, export schemas, and result meanings. Their product versions advance once from the `main` baseline to identify the new App SDK source contract and restored complete UI behavior. No project payload version was increased merely because the UI framework changed. Compatibility and migration `labkit.app` 1 is a source-breaking replacement contract for App definitions, presenters, callbacks, events, and interactions. Every tracked App migrated before the retired `labkit.ui` boundary was deleted; there is no runtime adapter or dual authoring surface. Public App entrypoint commands remain stable. Project documents retain their format and App payload versions independently of the facade transition. Validation Focused headless tests cover strict values, transactional runtime behavior, project save/restore, authoring defaults, and Chrono calculations/exports. Hidden GUI tests cover native semantic construction, typed control and table callbacks, bound side effects, standard file lifecycle, transient session rebuild, two-axis rendering, viewport preservation, renderer rollback, Chrono export, and project restore. VT Resistance focused tests cover resistance calculations, CSV compatibility, native layout, shared batch recomputation, two-axis rendering, result packaging, and project restore. Gait focused tests cover project migration, pose decoding, scientific calculations, CSV compatibility, typed table navigation, three-axis rendering, folder selection, result packaging, and project restore. DIC Preprocess focused tests cover project state, image loading, edit replay, registration/crop/mask helpers, export manifests, native two-axis layout, paired-anchor alignment, and managed crop interaction. Framework regression tests cover cell-valued event payloads and native multi-axis interaction bridging. Batch Image Crop focused tests cover crop geometry, padding, physical scaling, project migration, duplicate tasks, output planning/writes, native semantic layout, current-center editing, and standard result manifests. Focused framework and App tests additionally cover all 21 semantic layouts, typed events, managed interactions, project migration/recovery, result writing, synthetic sample packs, diagnostics, resource cleanup, window titles, startup success/failure, close behavior, and native adapter reconciliation. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Batch Image Crop](../../../../apps/image-measurement/batch-crop/README.md) [App catalog](../../../../apps/README.md) [LabKit App Framework](../../../../framework/README.md) [Build a Complete App](../../../../development/build-apps/complete-app.md) Known limitations and follow-up Automated GUI evidence does not replace developer-led validation of native dialogs, editable-table feel, pointer interaction, long-lived resource use, representative exports, visual quality, or scientific workflow suitability. That interactive validation remains a release input for the exact integrated commit. labkit.app.project.sourceRecord Create a portable source value during project migration."},{"title":"App SDK supports equal-data-unit plot fitting","url":"history/records/2026/07/LK-20260721-app-sdk-equal-data-unit-fit.html","kind":"history","section":"history","keywords":"LK-20260721-app-sdk-equal-data-unit-fit 2026-07-21 148 fix compatible labkit.app App Framework EIS Overlay","text":"App SDK supports equal-data-unit plot fitting id: LK-20260721-app-sdk-equal-data-unit-fit date: 2026-07-21 sequence: 148 type: fix compatibility: compatible component: `labkit.app` | `1.2.1 -> 1.2.2` scope: App Framework scope: EIS Overlay Context Apps needed an equal X/Y data-unit view for plots such as Nyquist diagrams, but setting an axes aspect mode directly also changed the allocated drawing area and persisted into later wheel and zoom interactions. Decision and rationale Keep viewport geometry in the framework fitter. An App can request equal data units while fitting the current plotted data, after which ordinary framework viewport preservation continues without an aspect lock. Changes Added the public `EqualDataUnits` option to `labkit.app.plot.fitAxesToGraphics`. Fit equal data units by expanding the smaller data span for the current axes pixel geometry, including logarithmic dimensions in log space. Removed the retired EIS axis-label rule; EIS now exposes explicit fit and equal-scale view actions. User and data impact The new option is additive. Existing fit calls keep independent X/Y limits. EIS projects and exports are unchanged; equal scaling is a transient view action and does not alter later zoom or wheel behavior. Compatibility and migration No project or result migration is required. Existing callers retain their independent fit behavior unless they opt into `EqualDataUnits=true`. Validation EIS GUI coverage exercises independent fitting, explicit equal scaling, and later viewport preservation. The App SDK plot helper tests cover finite data, logarithmic axes, and the public option contract. Evidence The fitting helper applies limits from finite plotted data and expands only the smaller working-space span for the axes pixel geometry. The EIS workflow test then verifies that a later framework redraw preserves the chosen viewport. Known limitations and follow-up Developer-led visual validation remains responsible for assessing layout balance on target displays and MATLAB releases. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data."},{"title":"App actions require explanatory hover help","url":"history/records/2026/07/LK-20260720-app-action-tooltips.html","kind":"history","section":"history","keywords":"LK-20260720-app-action-tooltips 2026-07-20 140 feat compatible labkit.app labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app App Framework All tracked Apps","text":"App actions require explanatory hover help id: LK-20260720-app-action-tooltips date: 2026-07-20 sequence: 140 type: feat compatibility: compatible component: `labkit.app` | `1.0.0 -> 1.1.0` component: `labkit_DICPostprocess_app` | `1.5.0 -> 1.5.1` component: `labkit_DICPreprocess_app` | `1.6.0 -> 1.6.1` component: `labkit_ChronoOverlay_app` | `1.5.0 -> 1.5.1` component: `labkit_CIC_app` | `1.5.0 -> 1.5.1` component: `labkit_CSC_app` | `1.5.0 -> 1.5.1` component: `labkit_EIS_app` | `1.5.0 -> 1.5.1` component: `labkit_VTResistance_app` | `1.5.0 -> 1.5.1` component: `labkit_GaitAnalysis_app` | `2.1.0 -> 2.1.1` component: `labkit_BatchImageCrop_app` | `1.8.0 -> 1.8.1` component: `labkit_CurvatureMeasurement_app` | `1.5.0 -> 1.5.1` component: `labkit_FLIRThermal_app` | `1.5.0 -> 1.5.1` component: `labkit_FocusStack_app` | `1.6.0 -> 1.6.1` component: `labkit_ImageEnhance_app` | `1.7.0 -> 1.7.1` component: `labkit_ImageMatch_app` | `1.7.0 -> 1.7.1` component: `labkit_VideoMarker_app` | `1.6.0 -> 1.6.1` component: `labkit_FigureStudio_app` | `0.3.0 -> 0.3.1` component: `labkit_NerveResponseAnalysis_app` | `1.5.0 -> 1.5.1` component: `labkit_ResponseReviewStats_app` | `1.5.0 -> 1.5.1` component: `labkit_RHSPreview_app` | `1.5.0 -> 1.5.1` component: `labkit_TTestWizard_app` | `1.1.0 -> 1.1.1` component: `labkit_ECGPrint_app` | `1.5.0 -> 1.5.1` scope: App Framework scope: All tracked Apps Context The native text-fit adapter copied button text through a column-shaped char conversion. MATLAB therefore received one newline between every character and rendered hover help as a narrow vertical strip. The generated tooltip also only repeated the visible label, so it did not explain the scientific or workflow consequence of an action. Decision and rationale Make explanatory hover help part of the semantic layout contract. Every `layout.button` produces a nonempty Tooltip, and tracked Apps must replace the label-based framework fallback with App-owned explanatory text. File-list actions expose dedicated tooltip fields and retain framework-owned defaults for generic folder, remove, and clear mechanics. Scientific meaning stays in the owning App rather than in the native adapter. All tracked Apps now describe what their actions consume, calculate, mutate, or export. The contract guardrail rejects App tooltips that only repeat the visible action label and also requires an explanatory input-selection tooltip. Changes Preserved char row vectors as one text line during native text fitting, eliminating the injected character-by-character newlines. Added `Tooltip` support with a nonempty label fallback to `layout.button` and dedicated file-list tooltips for choose, folder, recursive folder, remove, and clear actions. Added scientific and workflow-specific text for all 138 tracked App business buttons and all 26 App input selectors. Centralized the shared tooltip contract in the App Framework and App catalog instead of repeating it in every App manual. User and data impact Existing tracked Apps and third-party layouts retain their actions and calculations. Hovering now always shows readable text; tracked Apps additionally carry domain-specific explanations. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation App SDK unit coverage for required and compiled tooltip values. Cross-App definition guardrail for non-label business and input tooltips. Native adapter GUI coverage for exact tooltip text without injected newlines. DIC Postprocess GUI coverage for scientific action and Ncorr input help. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive checks should confirm tooltip timing, width, and line wrapping on supported MATLAB releases and desktop platforms."},{"title":"App alerts through UI facade","url":"history/records/2026/06/LK-20260630-app-alerts-through-ui-facade.html","kind":"history","section":"history","keywords":"LK-20260630-app-alerts-through-ui-facade 2026-06-30 22 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_ECGPrint_app App alerts through UI facade","text":"App alerts through UI facade id: LK-20260630-app-alerts-through-ui-facade date: 2026-06-30 sequence: 22 type: feat compatibility: compatible component: `labkit.ui` | `3.2.7 -> 3.2.8` component: `labkit_DICPostprocess_app` | `1.2.2 -> 1.2.3` component: `labkit_DICPreprocess_app` | `1.2.1 -> 1.2.2` component: `labkit_ChronoOverlay_app` | `1.2.0 -> 1.2.1` component: `labkit_CIC_app` | `1.2.0 -> 1.2.1` component: `labkit_CSC_app` | `1.2.0 -> 1.2.1` component: `labkit_EIS_app` | `1.2.0 -> 1.2.1` component: `labkit_VTResistance_app` | `1.2.0 -> 1.2.1` component: `labkit_BatchImageCrop_app` | `1.3.6 -> 1.3.7` component: `labkit_CurvatureMeasurement_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.2.4 -> 1.2.5` component: `labkit_ImageEnhance_app` | `1.3.3 -> 1.3.4` component: `labkit_ImageMatch_app` | `1.3.4 -> 1.3.5` component: `labkit_ECGPrint_app` | `1.2.1 -> 1.2.2` scope: App alerts through UI facade Context Apps opened alerts directly, which made hidden GUI tests unreliable and led to small differences in modal behavior across workflows. Decision and rationale Route user-visible alerts through one UI service that can display a modal message normally and record it safely during hidden tests. Keep each app responsible for the message and the decision that triggers it. Changes `labkit.ui` `3.2.7 -> 3.2.8` DIC, electrochem, image-measurement, and ECG apps patch bumped where alert routing changed. Routed app alerts through hidden-test-safe `labkit.ui.app.showAlert`. User and data impact Errors continued to appear as app alerts, but test and debug runs could capture them without blocking on an invisible dialog. No saved or exported data changed. Compatibility and migration App error conditions and messages remained app-owned. Only the display route changed, so user data and app project formats required no conversion. Validation Commit `8d7c83b1` migrated alert call sites and updated hidden workflow tests across the listed apps. Evidence Main commit `8d7c83b1`. Known limitations and follow-up The historical `labkit.ui.app.showAlert` entry point was later replaced by the injected `services.dialogs.alert` service in Runtime V2."},{"title":"App diagnostics and hardened UI workflows","url":"history/records/2026/06/LK-20260628-app-diagnostics-and-hardened-ui-workflows.html","kind":"history","section":"history","keywords":"LK-20260628-app-diagnostics-and-hardened-ui-workflows 2026-06-28 15 feat compatible labkit_launcher labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app App diagnostics and hardened UI workflows","text":"App diagnostics and hardened UI workflows id: LK-20260628-app-diagnostics-and-hardened-ui-workflows date: 2026-06-28 sequence: 15 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.1 -> 1.1.2` component: `labkit.ui` | `3.1.0 -> 3.1.2` component: `labkit.ui` | `3.1.2 -> 3.1.3` component: `labkit_BatchImageCrop_app` | `1.3.0 -> 1.3.1` component: `labkit_BatchImageCrop_app` | `1.3.1 -> 1.3.2` component: `labkit_FocusStack_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.2.1 -> 1.2.2` component: `labkit_ImageMatch_app` | `1.2.0 -> 1.2.1` component: `labkit_NerveResponseAnalysis_app` | `1.2.0 -> 1.2.1` component: `labkit_ResponseReviewStats_app` | `1.2.0 -> 1.2.1` component: `labkit_RHSPreview_app` | `1.2.0 -> 1.2.1` scope: App diagnostics and hardened UI workflows Context When an app stalled or caught an exception, screenshots and the visible Log tab often omitted the active callback and stack information needed to reproduce the problem. Decision and rationale Capture active operations, uncaught crashes, caught errors, and suspected stalls in structured local reports while hardening the UI paths that generate those events. Keep reports outside project data so diagnostics cannot alter results. Changes `labkit.ui` `3.1.0 -> 3.1.3` Batch Crop, Focus Stack, Image Enhance/Match, neurophysiology apps, and the launcher patch bumped where runtime behavior changed. Hardened LabKit UI workflows. Added crash reports, active-operation reports, caught-error reports, and stall diagnostics. User and data impact Debug runs produced evidence that could identify the failing operation and app state without copying laboratory inputs into the repository. Normal projects and exports kept their previous formats. Compatibility and migration The diagnostic files were additive and existing app inputs and saved results remained readable. Debug runs began producing more detailed local artifacts. Validation Commits `e966457b` and `f5bc6f98` updated launcher, framework, and affected app workflow tests for diagnostic creation and hardened callbacks. Evidence Main commits `e966457b` and `f5bc6f98`. Known limitations and follow-up Diagnostics report the state and stack available at capture time; they do not replace a reproducible input or a focused regression test."},{"title":"App file-selection and electrochem control fixes","url":"history/records/2026/07/LK-20260703-app-file-selection-and-electrochem-control-fixes.html","kind":"history","section":"history","keywords":"LK-20260703-app-file-selection-and-electrochem-control-fixes 2026-07-03 33 fix compatible labkit_CIC_app labkit_CIC_app labkit_CSC_app labkit_CSC_app labkit_VTResistance_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"App file-selection and electrochem control fixes id: LK-20260703-app-file-selection-and-electrochem-control-fixes date: 2026-07-03 sequence: 33 type: fix compatibility: compatible component: `labkit_CIC_app` | `1.3.1 -> 1.3.2` component: `labkit_CIC_app` | `1.3.2 -> 1.3.3` component: `labkit_CSC_app` | `1.3.1 -> 1.3.2` component: `labkit_CSC_app` | `1.3.2 -> 1.3.3` component: `labkit_VTResistance_app` | `1.3.1 -> 1.3.2` component: `labkit_VTResistance_app` | `1.3.2 -> 1.3.3` component: `labkit_BatchImageCrop_app` | `1.6.2 -> 1.6.3` component: `labkit_FLIRThermal_app` | `1.2.1 -> 1.2.2` component: `labkit_FocusStack_app` | `1.4.1 -> 1.4.2` component: `labkit_ImageEnhance_app` | `1.5.1 -> 1.5.2` component: `labkit_ImageMatch_app` | `1.5.1 -> 1.5.2` scope: historical project evolution Context Adding files to an existing multi-file session could replace or select the wrong entry in several apps. CIC, CSC, and VT Resistance also exposed manual plot controls that no longer matched their intended result-review workflow. Decision and rationale Preserve the complete file list when new selections are appended and select a newly added item predictably. Remove obsolete electrochem controls instead of keeping buttons whose effect was ambiguous or redundant. Changes CIC, CSC, VT Resistance, Batch Crop, FLIR Thermal, Focus Stack, Image Enhance, and Image Match patch bumped for appended file selections. CIC, CSC, and VT Resistance patch bumped again for manual plot-control removal. Preserved appended file selections. Removed electrochem manual plot controls that no longer matched the workflow. User and data impact Users could add another batch without losing files already loaded, across the listed electrochem and image apps. The electrochem workbenches became simpler; calculations and exported result schemas were unchanged. Compatibility and migration Existing loaded-file and result formats remained valid. Appended selections now preserved the active item, and the removed manual plot controls did not alter the underlying calculation options. Validation The file-selection commit added GUI regression coverage to every affected app. The control-removal commit updated the three electrochem GUI workflows and shared test helpers. Exact historical commands were not recorded. Evidence Main commits `6348185e` and `674d5d4b`. Known limitations and follow-up The change covered app-level append handling. Later framework work unified the native file-selection behavior across platforms."},{"title":"App manuals become reader-oriented and focused tests gain one wrapper","url":"history/records/2026/07/LK-20260720-reader-oriented-app-manuals-and-test-wrapper.html","kind":"history","section":"history","keywords":"LK-20260720-reader-oriented-app-manuals-and-test-wrapper 2026-07-20 142 docs compatible App documentation Documentation validation Agent testing workflow Agent skill maintenance","text":"App manuals become reader-oriented and focused tests gain one wrapper id: LK-20260720-reader-oriented-app-manuals-and-test-wrapper date: 2026-07-20 sequence: 142 type: docs compatibility: compatible scope: App documentation scope: Documentation validation scope: Agent testing workflow scope: Agent skill maintenance Context Concrete App manuals repeated shared hover-help and runtime architecture contracts. The repetition displaced workflow and scientific content, became stale when the framework changed, and made every App page appear more implementation-oriented than user-oriented. Focused test execution also required agents to repeatedly assemble low-level runner arguments; omitting GUI inclusion could produce a zero-match selection that looked like a test problem. Decision and rationale Keep shared behavior in the App catalog and framework manual. Concrete App pages describe only their inputs, workflow, App-specific interactions, scientific semantics, outputs, recovery, programmatic surface, and limitations. Protect that boundary with a documentation guardrail. Provide one platform-independent MATLAB wrapper with explicit operations for listing a file's canonical test names, running an exact file, running a canonical class or method within its owning file, and running a suite. Keep broad final gates on the existing `buildtool` interface. Treat recurring reasoning, command construction, discovery, and retry cost as skill-maintenance signals. Improvements must describe durable concepts and semantic operations rather than one product version, App, transient failure, or current repository snapshot. Changes Removed repeated hover-help and Framework Compatibility boilerplate from all tracked App manuals. Rewrote project-state passages around user-visible persistence, recovery, source relinking, and recalculation rather than internal factories, callbacks, and schema implementation. Clarified the App-manual authoring contract and added a guardrail against reintroducing shared framework boilerplate. Added `runLabKitTestTarget.m` to the test-planner skill with automatic repository discovery, GUI inference for exact files and GUI suites, hidden figures, focused artifacts, and fail-on-zero-match behavior. Made exact canonical selectors resolve their unique owning test file, while retaining an explicit file constraint for ambiguous ownership. Made local parallel build tasks retain structured shard state while relaying concise human-readable heartbeats, clear stale transient status at worker startup, and print only failing worker logs instead of remaining silent and then dumping every shard log. Moved child-process orchestration out of `buildfile.m` into a MATLAB/Java runner shared by local macOS, Linux, and Windows, removing generated POSIX scripts and adding an independent ETA to each shard summary. Hosted CI remains single-process pending explicit concurrent-license evidence. Partitioned broad local workers by unique test-class owner file so each worker discovers only its assigned files instead of repeating full-tree discovery. Batched App path registration while keeping every setup call sensitive to newly added App entrypoints, added quiet discovery probes, and removed one unreferenced GUI assertion helper. Updated the skill to list canonical names before exact method reruns and to distinguish selection failures from passing evidence. Added a root agent rule to improve skills and scripts when reusable automation can remove recurring inference or retry cost. User and data impact App behavior, calculations, saved data, and exports are unchanged. Existing manual content about scientific meaning and recovery remains available with less implementation noise. The wrapper is an agent convenience over the official test runner and does not add another CI or public build interface. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Documentation guardrails scan every concrete App manual for shared boilerplate. Documentation rendering and byte-for-byte site comparison cover all edited pages and this record. The wrapper lists and runs canonical GUI methods through the official runner, demonstrating automatic hidden-GUI selection. Test-runner profiling measured fresh full discovery before and after the path change at 5.83 and 4.82 seconds; path setup fell from 1.27 to 0.32 seconds. The retained rescan avoids a stale-cache failure when an App is added during a MATLAB session. Build contracts prove file partitions are disjoint, cover every selected test, accept deeply nested owning files, and preserve explicit failures for missing or ambiguous owners. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Continue reviewing App manuals when user-visible behavior changes; structural guardrails prevent known boilerplate but cannot judge scientific clarity."},{"title":"App tools and presentation controls become more deliberate","url":"history/records/2026/07/LK-20260720-app-tools-menu-and-screenshot-clipboard.html","kind":"history","section":"history","keywords":"LK-20260720-app-tools-menu-and-screenshot-clipboard 2026-07-20 141 feat compatible labkit.app App Framework All tracked Apps","text":"App tools and presentation controls become more deliberate id: LK-20260720-app-tools-menu-and-screenshot-clipboard date: 2026-07-20 sequence: 141 type: feat compatibility: compatible component: `labkit.app` | `1.1.0 -> 1.2.0` scope: App Framework scope: All tracked Apps Context Plot, Screenshot, Save State, and Load State appeared as separate root-level native menu items. Plot was a container while the other items executed immediately, so controls at the same visual level had inconsistent behavior and consumed unnecessary horizontal space. Screenshot also required a file dialog even when the user only needed an image for another application. Short status summaries consumed the same height as detail panels, and Apps could preserve plot zoom but had no declarative one-shot reset revision. Decision and rationale Install one framework-owned **Tools** root with purpose-based **Plots**, **Screenshot**, and conditional **Project State** submenus. Keep scientific workflow controls in the App layout while grouping domain-neutral native utilities predictably. Use MATLAB's existing `copygraphics` clipboard path so the capability remains platform-neutral and introduces no external runtime. Changes Moved plot utilities beneath **Tools > Plots**. Added **Tools > Screenshot > Copy to Clipboard** for the complete App surface and retained file export as **Save to File...**. Moved project persistence beneath **Tools > Project State** when the App owns a project document. Preserved existing executable action tags for compatibility with GUI automation and added stable tags for the new menu containers and clipboard action. Added a line-count hint for compact status summaries and a general plot-view revision that accepts renderer-fitted limits once while preserving user zoom during ordinary redraws. Updated the getting-started and framework manuals. User and data impact App calculations, project files, plots, and exports are unchanged. Existing action tags continue to identify save screenshot, save state, load state, and plot commands, but users now reach them through the Tools hierarchy. Clipboard copy uses the operating system clipboard and may be unavailable in a noninteractive MATLAB session. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Hidden native-adapter GUI coverage verifies the menu hierarchy, labels, conditional Project State menu, stable action tags, and clipboard callback wiring. Focused framework tests validate the App SDK version and native runtime. Documentation rendering and consistency checks cover the updated manuals and history record. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive checks should confirm clipboard image fidelity and native menu appearance on supported MATLAB desktop platforms."},{"title":"App validation no longer relies on sibling App paths","url":"history/records/2026/07/LK-20260717-isolated-app-validation.html","kind":"history","section":"history","keywords":"LK-20260717-isolated-app-validation 2026-07-17 128 test compatible Public App path isolation Changed-file validation routing GUI CI evaluation","text":"App validation no longer relies on sibling App paths id: LK-20260717-isolated-app-validation date: 2026-07-17 sequence: 128 type: test compatibility: compatible scope: Public App path isolation scope: Changed-file validation routing scope: GUI CI evaluation Context The ordinary public test setup adds every App entry folder so family and cross-App contract suites can run efficiently. That broad path can conceal an accidental dependency on a sibling App package. Static source scans rejected direct sibling calls, and individual Gait/private launch tests used isolated paths, but there was no uniform executable contract for every public App or automatic changed-file route to that contract. Decision and rationale Keep the convenient broad path for ordinary suites, but add one fast independent proof. For each public App, restore MATLAB's default path, add only the repository root and the owning App root, load its complete definition, check declared facade compatibility, and write its synthetic debug sample. Run this contract whenever public App source changes. Static call scanning and dynamic isolation cover different risks and remain separate. The former identifies the forbidden source reference; the latter proves metadata, framework requirements, and debug generation work in the same path shape as a single-App package. Changes Added one isolated-path contract covering all 20 public Apps. Routed every App source change to that contract in both conservative and fast changed-file plans. Added a planner regression so future routing changes cannot silently drop isolation coverage. Documented why independent private workspaces must run their own compatibility checks without inheriting public App roots. Evaluated, but did not add, a hidden-GUI job to every pull request. User and data impact There is no App or saved-data behavior change. A new sibling package dependency, stale facade requirement, incomplete definition, or broken debug sample now fails in a focused non-GUI contract instead of passing because all Apps happened to be on the developer's MATLAB path. Compatibility and migration No migration is required. Existing test commands remain unchanged; the changed-file planner adds the focused isolation selection automatically. Validation The all-App isolation contract completed in about 14 seconds locally. Existing changed-file evidence measured representative hidden image workflows at about 18 seconds each and a Gait hidden launch at about 30 seconds, before MATLAB startup and license acquisition. Since those synthetic GUI checks still do not prove native dialogs, pointer feel, or visual quality, ordinary CI keeps the fast non-GUI contract while App-specific GUI checks remain changed-file, scheduled, manual, and release validation. The private workspace's independent suite passes after removing public App roots from its runner path. Evidence [Testing and validation](../../../../development/maintain-and-release/testing.md) [Architecture](../../../../development/build-apps/architecture.md) [App development](../../../../development/build-apps/app-development.md) Known limitations and follow-up MATLAB process-global function caches cannot replace package-boundary source review, so the static sibling-call guard remains required. Hidden GUI tests remain bounded semantic checks rather than substitutes for manual interaction and visual assessment."},{"title":"Base-MATLAB image compatibility","url":"history/records/2026/07/LK-20260713-base-matlab-image-compatibility.html","kind":"history","section":"history","keywords":"LK-20260713-base-matlab-image-compatibility 2026-07-13 45 feat compatible labkit.image labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app Base-MATLAB image compatibility","text":"Base-MATLAB image compatibility id: LK-20260713-base-matlab-image-compatibility date: 2026-07-13 sequence: 45 type: feat compatibility: compatible component: `labkit.image` | `1.1.0 -> 1.2.0` component: `labkit_DICPostprocess_app` | `1.3.4 -> 1.3.5` component: `labkit_DICPreprocess_app` | `1.3.5 -> 1.3.6` component: `labkit_FocusStack_app` | `1.4.7 -> 1.4.8` component: `labkit_ImageEnhance_app` | `1.5.6 -> 1.5.7` component: `labkit_ImageMatch_app` | `1.5.6 -> 1.5.7` scope: Base-MATLAB image compatibility Context Several image workflows appeared toolbox-optional but still called Image Processing Toolbox functions for conversion, grayscale luminance, resizing, smoothing, alignment, or warping. Development machines with the toolbox hid those calls, so a nominally supported Base MATLAB installation could fail only after the user entered a particular workflow. Decision and rationale Own the essential image operations used by LabKit and make their Base MATLAB paths the tested behavior. Optional toolbox acceleration could remain only when the call was explicit, a repository-owned fallback existed, and parity tests protected the app-consumed result. Changes `labkit.image` `1.1.0 -> 1.2.0` `labkit_DICPreprocess_app` `1.3.5 -> 1.3.6` `labkit_DICPostprocess_app` `1.3.4 -> 1.3.5` `labkit_FocusStack_app` `1.4.7 -> 1.4.8` `labkit_ImageEnhance_app` `1.5.6 -> 1.5.7` `labkit_ImageMatch_app` `1.5.6 -> 1.5.7` Added `labkit.image.toDouble` and `labkit.image.toLuma`, and replaced hard Image Processing Toolbox calls in shared image facade code and image-app workflow paths with base-MATLAB implementations. DIC preprocessing now uses a toolbox-free phase-correlation translation path for automatic alignment and a base-MATLAB rigid warp for control-point alignment. DIC postprocessing, Focus Stack, Image Enhance, and Image Match now use app-local or facade-owned image normalization, resizing, smoothing, and luma helpers instead of requiring toolbox functions. Added a project hygiene guardrail that rejects unguarded toolbox image helper calls under `apps/` and `+labkit/`, while still allowing explicit optional toolbox paths with fallbacks. User and data impact DIC alignment and overlays, Focus Stack, Image Enhance, and Image Match could run without Image Processing Toolbox. Users with the toolbox retained compatible workflows, while CI now exercised the installation that previously failed late and silently. Compatibility and migration Existing app workflows and exported schemas are preserved. Optional toolbox acceleration paths remain allowed only when a base-MATLAB fallback is present. Validation The commit expanded DIC, Focus Stack, Image Enhance, Image Match, and image- facade unit suites and added `ToolboxDependencyGuardrailTest` to detect new unguarded calls. GUI layout tests covered the affected DIC workflows. The exact historical command was not recorded. Evidence Mainline commit `bcd5f51f`. Known limitations and follow-up Base MATLAB compatibility does not mean every optional accelerated algorithm is numerically identical by construction. Where a toolbox branch affects scientific values, representative parity and idempotency tests remain required until the repository-owned implementation fully replaces it."},{"title":"Batch Crop assigns state to workflow capabilities","url":"history/records/2026/07/LK-20260716-batch-crop-structure.html","kind":"history","section":"history","keywords":"LK-20260716-batch-crop-structure 2026-07-16 101 refactor compatible labkit_BatchImageCrop_app Image Measurement App structure","text":"Batch Crop assigns state to workflow capabilities id: LK-20260716-batch-crop-structure date: 2026-07-16 sequence: 101 type: refactor compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.2 -> 1.7.3` scope: Image Measurement scope: App structure Context Batch Crop split static metadata across separate files, spread project creation, validation, migration, and session construction through a generic lifecycle package, and placed source decoding, crop-task operations, geometry, scale calibration, export planning, and transient caches together under `+appState`. The App also retained an unused scale-unit adapter and a startup callback whose only job duplicated the shared source-adjacent output-folder default. Decision and rationale Use the compact Runtime V2 definition and one project contract, then assign remaining helpers to the workflow capability that owns their meaning. Crop geometry, scale calibration, and export planning are real App complexity and remain App-owned; generic lifecycle and state buckets are structural debt and are removed. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderers, and optional capabilities in `definition.m`. Concentrated durable creation, validation, and the version-1 upgrade in the single `projectSpec.m` entry; root `createSession.m` rebuilds transient state and lazily loads the selected source. Replaced embedded image data and direct path fields with canonical source records and `sourceId` task references. Assigned file reconstruction to `+sourceFiles`, task operations to `+cropTasks`, coordinate and cache behavior to `+cropGeometry`, calibration semantics to `+scaleCalibration`, and export state to `+resultFiles`. Removed the generic lifecycle/state packages, separate metadata files, redundant startup callback, and unused scale-unit wrapper. User and data impact Multiple crop tasks per source, draggable and resizable ROIs, zoom-preserving preview edits, rotation, edge-continuous padding, pixel and physical crop modes, per-source calibration, scale bars, manifests, and exports keep their existing behavior. Empty launch no longer computes an output folder; importing sources still selects the source-adjacent default. Developers can now find each calculation and state transition under its owning workflow concept without learning a generic App state package. Compatibility and migration Version-1 saved projects upgrade automatically to schema version 2. The migration discards reconstructible embedded pixels, creates portable source records for legacy paths, and preserves crop-task settings. Runtime source reconciliation handles moved or missing required files. Validation Focused unit tests cover project creation, validation and migration, source reading, crop geometry, scale planning, export planning, and manifest output. The hidden GUI workflow loads an image, manipulates the crop workflow, and exports a synthetic crop through the real Runtime callbacks. Evidence [Batch Image Crop](../../../../apps/image-measurement/batch-crop/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Image Library](../../../../libraries/image/README.md) Known limitations and follow-up Automated GUI tests do not replace manual judgment of ROI pointer feel, resampling quality, scale-reference placement, or export suitability for a particular quantitative workflow. Other Apps that still use generic lifecycle or state packages remain scheduled for the same ownership review."},{"title":"Batch Crop file workflow feedback","url":"history/records/2026/06/LK-20260628-batch-crop-file-workflow-feedback.html","kind":"history","section":"history","keywords":"LK-20260628-batch-crop-file-workflow-feedback 2026-06-28 14 feat compatible labkit.ui labkit_BatchImageCrop_app Batch Crop file workflow feedback","text":"Batch Crop file workflow feedback id: LK-20260628-batch-crop-file-workflow-feedback date: 2026-06-28 sequence: 14 type: feat compatibility: compatible component: `labkit.ui` | `3.0.1 -> 3.1.0` component: `labkit_BatchImageCrop_app` | `1.2.0 -> 1.3.0` scope: Batch Crop file workflow feedback Context In a multi-file Batch Crop session, the window and preview did not consistently show which list item supplied the current image and crop settings. Decision and rationale Make the selected file part of the shared window-title context and update Batch Crop feedback whenever selection changes. Keep the title derived from the file panel so the app does not maintain a second selection label. Changes `labkit.ui` `3.0.1 -> 3.1.0` Batch Crop `1.2.0 -> 1.3.0` Added selected-file title context. Improved Batch Crop file workflow feedback. User and data impact The active filename became visible while reviewing or editing a crop task, reducing the chance of applying settings to the wrong image. Crop geometry and exported data were unchanged. Compatibility and migration Batch Crop tasks and exports kept their existing format. The change affected file-selection context and feedback in the window only. Validation Commit `61e8edd3` updated shared title-context and Batch Crop GUI workflow coverage. Evidence Main commit `61e8edd3`. Known limitations and follow-up Later file-panel work extended the same title context and append behavior to the rest of the app fleet."},{"title":"Binding-only controls and default resource cleanup","url":"history/records/2026/07/LK-20260717-binding-and-resource-defaults.html","kind":"history","section":"history","keywords":"LK-20260717-binding-and-resource-defaults 2026-07-17 122 refactor compatible labkit.ui labkit_VideoMarker_app Runtime V2 action registration Runtime V2 resource ownership","text":"Binding-only controls and default resource cleanup id: LK-20260717-binding-and-resource-defaults date: 2026-07-17 sequence: 122 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.5 -> 7.4.6` component: `labkit_VideoMarker_app` | `1.5.4 -> 1.5.5` scope: Runtime V2 action registration scope: Runtime V2 resource ownership Context Runtime V2 already supported controls whose complete behavior is writing one bound state path, but Video Marker still registered an empty action for its skeleton-preset selector. The private resource registry also had a default cleanup policy, while the injected App service required every call to supply a fourth cleanup function. Video Marker therefore kept a second empty function for its plain decoded-video resource. Decision and rationale Use the existing binding-only contract for state-only controls and expose the resource registry's existing default cleanup through the injected service. Apps should declare an action only when a user event has workflow behavior beyond committing the bound value. Plain structs and values require no App-owned cleanup placeholder. Changes Removed Video Marker's empty skeleton-preset change action and left the dropdown as a direct session binding. Made the injected `services.resources.set(scope,id,value)` form select the Runtime's default cleanup, while retaining the optional fourth custom cleanup function. Removed Video Marker's empty video-resource cleanup function. Added Runtime GUI coverage for binding-only state commits and resources registered without a custom cleanup. User and data impact There is no workflow, scientific, project-data, or saved-file change. Selecting a skeleton preset still updates the session immediately, and **Use preset** still performs the only durable skeleton edit. Video cache replacement and session cleanup retain the same observable behavior. Compatibility and migration The change is source-compatible. Existing four-argument resource registration continues to use the supplied cleanup function. No project migration is required. Validation The focused Runtime V2 GUI test exercises both service forms and proves that a binding without `Event` commits state without invoking an App action. Video Marker structure and targeted behavior tests cover the simplified definition. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents bound controls and injected resource ownership. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) describes the preset and decoded-video resource behavior. Known limitations and follow-up Other bound events still require App actions when they normalize values, invalidate results, update previews, or log workflow changes. They should be reviewed by behavior rather than removed merely because their handlers are short."},{"title":"CIC consolidates its project contract without losing lazy loading","url":"history/records/2026/07/LK-20260716-cic-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-cic-project-spec 2026-07-16 87 refactor compatible labkit_CIC_app Electrochemistry Project lifecycle","text":"CIC consolidates its project contract without losing lazy loading id: LK-20260716-cic-project-spec date: 2026-07-16 sequence: 87 type: refactor compatibility: compatible component: `labkit_CIC_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context CIC split static metadata across three files and its first-version project across a generic lifecycle package. Its session factory also encodes an important performance policy: restore only the first source for preview rather than parsing an entire large batch. Decision and rationale Consolidate product metadata in `definition.m` and durable schema behavior in `projectSpec.m`. Keep `createSession.m` separate and explicit because lazy source decoding is real transient reconstruction and a user-visible performance boundary, not lifecycle boilerplate. Changes Moved command metadata, version, update date, and requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved lazy preview restoration to one package-root session factory. Removed separate metadata files and the generic lifecycle package. Kept pulse detection, CIC formulas, units, limits, plots, and exports unchanged. User and data impact Launch, source selection, large-batch behavior, computation, save/load, and CSV exports behave unchanged. The App structure is smaller while the reason for its lazy session factory is now documented next to the code. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing CIC projects require no migration. Validation Unit tests cover the project contract, CIC calculation, units, failed rows, CSV schema, and presentation. The hidden GUI workflow covers real launch, source selection, calculation, save/load, and export through Runtime V2. Evidence [CIC](../../../../apps/electrochemistry/cic/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The session currently reads `reference.originalPath` directly. That internal portable-reference representation will be replaced by a simple framework source-path service across all Apps rather than patched only in CIC."},{"title":"CIC uses Runtime-owned source and workflow mechanics","url":"history/records/2026/07/LK-20260716-cic-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-cic-runtime-source-reconciliation 2026-07-16 112 refactor compatible labkit_CIC_app Electrochemistry App structure Runtime adoption","text":"CIC uses Runtime-owned source and workflow mechanics id: LK-20260716-cic-runtime-source-reconciliation date: 2026-07-16 sequence: 112 type: refactor compatibility: compatible component: `labkit_CIC_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context CIC intentionally registers paths before decoding them so large batches remain lazy, but it still generated, appended, and deleted portable source records in App code. It also wrote directly to `session.workflow.logLines`, requiring its session factory to reproduce Runtime-owned empty workflow and view buckets. Those details increased the amount of framework structure an App maintainer had to understand without expressing CIC analysis behavior. Decision and rationale CIC continues to own the ordered lazy path set, current-index policy, deferred decoding, scientific options, and result workflow. It now passes that ordered path set to `services.project.reconcileSources` after add, removal, and clear operations. Runtime preserves matching identities and allocates new unique ones. Analysis messages use `services.workflow.log`, the same service as other workflow messages. `createSession` returns only CIC-specific selection and decoded-cache state; Runtime creates missing transient buckets before actions run. Changes Removed App-local source-ID generation and source append helpers. Reconciled registered paths through the Runtime project service. Routed analysis messages through the Runtime workflow service. Removed empty workflow and view boilerplate from `createSession`. Added GUI checks for stable and unique identities across batch add, save, reopen, removal, and re-addition. Advanced the CIC App version to 1.4.4. User and data impact Lazy decoding, file order, selected preview, analysis values, plots, exports, logs, and project files retain their behavior. Source identities remain stable for retained files and collision-free for newly registered files. Scientific calculations and output schemas are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing source records and their identities are preserved when reopened. Validation The CIC hidden GUI workflow covers three-source lazy registration, selection, whole-batch recomputation, export, save, clear, reopen, remove, re-add, source identity, viewport refresh, and final clear. Focused calculation/view, App structure, version, documentation, and history tests cover the surrounding contracts. Evidence [Charge-Injection Capacity](../../../../apps/electrochemistry/cic/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up CSC and VT Resistance still own similar source registration helpers. Their result indexing and lazy-load behavior must be checked independently before adopting the Runtime service."},{"title":"CSC consolidates its product and project contracts","url":"history/records/2026/07/LK-20260716-csc-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-csc-project-spec 2026-07-16 88 refactor compatible labkit_CSC_app Electrochemistry Project lifecycle","text":"CSC consolidates its product and project contracts id: LK-20260716-csc-project-spec date: 2026-07-16 sequence: 88 type: refactor compatibility: compatible component: `labkit_CSC_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context CSC split product metadata across three files and its durable schema across a generic lifecycle package even though it has no historical payload migration. The additional entry points did not own distinct electrochemical behavior. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because reloading CV/CT curves and restoring file/cycle selection is genuine transient work. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved decoded curve and selection restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Kept integration formulas, sign splitting, scan-rate handling, area normalization, plots, and export schemas unchanged. User and data impact Launch, source and cycle selection, save/load, calculations, plots, and both export paths behave unchanged. The durable contract is now readable in one place without hiding scientific behavior in the framework. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing CSC projects require no migration. Validation Unit tests cover CT/CV calculations, edge-cycle policy, normalization, result tables, export schemas, project/session contracts, and presentation. The hidden GUI workflow covers real launch, file/cycle selection, calculation, save/load, plot overlays, and exports. Evidence [CSC](../../../../apps/electrochemistry/csc/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Decoded source restoration still reaches into portable-reference internals. That cross-App framework boundary remains scheduled for one shared source-path service rather than an App-specific wrapper."},{"title":"CSC delegates decoded-source identity to Runtime V2","url":"history/records/2026/07/LK-20260716-csc-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-csc-runtime-source-reconciliation 2026-07-16 113 refactor compatible labkit_CSC_app Electrochemistry App structure Runtime adoption","text":"CSC delegates decoded-source identity to Runtime V2 id: LK-20260716-csc-runtime-source-reconciliation date: 2026-07-16 sequence: 113 type: refactor compatibility: compatible component: `labkit_CSC_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context CSC eagerly decodes accepted CV/CT files and keeps decoded item order aligned with durable source order. Its action file nevertheless generated source IDs, appended records, and deleted records by index. The session factory also declared empty workflow and view buckets already guaranteed by Runtime V2. Those mechanics were framework persistence responsibilities rather than CSC curve-selection or scientific behavior. Decision and rationale Continue using successfully decoded item order as the App-owned source order, then reconcile durable records through `services.project.reconcileSources` after additions, removals, and clearing. Runtime preserves records for retained paths and allocates collision-free IDs for new paths. CSC continues to own decoding, active file and curve selection, plot defaults, calculation, exports, and failure messages. Return only the CSC-specific selection fields and decoded item cache from `createSession`; Runtime supplies missing transient buckets. Changes Removed App-local source-ID generation and source append helpers. Reconciled source records from the decoded item list. Replaced the growing failure array with a single first-failure record while retaining per-file workflow logs. Removed empty workflow and view session boilerplate. Added GUI checks for canonical buckets and stable, unique identities across add, save, reopen, removal, and re-addition. Advanced the CSC App version to 1.4.4. User and data impact File order, selected curve, plot resets, CSC calculations, reload, exports, logs, and saved projects retain their behavior. Retained source identities no longer depend on App-local append/delete logic. Scientific values and result schemas are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing portable records retain their IDs when the corresponding paths remain loaded. Validation The CSC hidden GUI workflow covers two-file decoding, file and cycle selection, plot changes, calculation display, export, save, clear, reopen, remove, re-add, source identity, and final plot clearing. Focused CSC calculation/view and repository contract tests cover the remaining behavior. Evidence [Charge-Storage Capacity](../../../../apps/electrochemistry/csc/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up VT Resistance remains the last electrochem batch App with the same local source-ID and append pattern and requires a separate behavior audit."},{"title":"CSC export and viewport policy","url":"history/records/2026/07/LK-20260703-csc-export-and-viewport-policy.html","kind":"history","section":"history","keywords":"LK-20260703-csc-export-and-viewport-policy 2026-07-03 35 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app CSC export and viewport policy","text":"CSC export and viewport policy id: LK-20260703-csc-export-and-viewport-policy date: 2026-07-03 sequence: 35 type: feat compatibility: compatible component: `labkit.ui` | `4.0.0 -> 4.1.0` component: `labkit_DICPostprocess_app` | `1.3.2 -> 1.3.3` component: `labkit_DICPreprocess_app` | `1.3.2 -> 1.3.3` component: `labkit_ChronoOverlay_app` | `1.3.2 -> 1.3.3` component: `labkit_CIC_app` | `1.3.4 -> 1.3.5` component: `labkit_CSC_app` | `1.3.4 -> 1.3.6` component: `labkit_EIS_app` | `1.3.2 -> 1.3.3` component: `labkit_VTResistance_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.6.4 -> 1.6.5` component: `labkit_CurvatureMeasurement_app` | `1.3.2 -> 1.3.3` component: `labkit_FLIRThermal_app` | `1.2.3 -> 1.2.4` component: `labkit_FocusStack_app` | `1.4.3 -> 1.4.4` component: `labkit_ImageEnhance_app` | `1.5.3 -> 1.5.4` component: `labkit_ImageMatch_app` | `1.5.3 -> 1.5.4` component: `labkit_NerveResponseAnalysis_app` | `1.3.2 -> 1.3.3` component: `labkit_ResponseReviewStats_app` | `1.3.2 -> 1.3.3` component: `labkit_RHSPreview_app` | `1.3.2 -> 1.3.3` component: `labkit_ECGPrint_app` | `1.3.3 -> 1.3.4` scope: CSC export and viewport policy Context CSC could export the selected cycle, but comparing activation and stability across a recording required all cycles in one dataset. In parallel, app layouts made their own assumptions about minimum window size and scrollable content, so the same control panel could behave differently in a small viewport. Decision and rationale Add an explicit all-cycle CSC export without changing the existing selected- cycle result. Define viewport behavior in the UI contract so each app declares its content needs while the framework decides when scrolling or minimum sizing is necessary. Changes `labkit.ui` `4.0.0 -> 4.1.0` All supported apps received aligned patch bumps. Added CSC all-cycle export. Added viewport policy support and aligned app contracts with the UI 4.x line. User and data impact CSC users could export every cycle for downstream comparison while retaining the existing focused export. Across the workbench, small windows followed one scrolling and sizing policy instead of clipping controls according to the app that happened to create them. Compatibility and migration The selected-cycle CSC export remained available and the all-cycle table was a new option. Existing app definitions inherited the default viewport behavior until they declared more specific needs. Validation Commit `a69829c6` added a dedicated CSC export suite, extended the CSC GUI layout test, and added workbench viewport coverage. Evidence Main commit `a69829c6`. Known limitations and follow-up All-cycle CSV output was a separate export choice rather than a new session format. Later CSC work refined column naming and voltage/current organization."},{"title":"Callback-safe native layout synchronization","url":"history/records/2026/07/LK-20260721-callback-safe-layout-synchronization.html","kind":"history","section":"history","keywords":"LK-20260721-callback-safe-layout-synchronization 2026-07-21 152 fix compatible labkit.app App Framework Hidden GUI validation","text":"Callback-safe native layout synchronization id: LK-20260721-callback-safe-layout-synchronization date: 2026-07-21 sequence: 152 type: fix compatibility: compatible component: `labkit.app` | `1.2.3 -> 1.2.4` scope: App Framework scope: Hidden GUI validation Context The App SDK synchronizes native layout before calculating pixel-dependent plot geometry and while publishing busy, startup, close-prompt, and popout views. A full MATLAB `drawnow` also executes pending callbacks, which can introduce unrelated UI or timer work during those internal rendering operations. Decision and rationale Keep the required native graphics synchronization, but defer callbacks while the framework is calculating or publishing its own view. Long-running interactive launcher and profiler waits continue to process callbacks so users can close their windows. Changes Use callback-deferred graphics updates for equal-scale fitting and fixed-aspect preview geometry. Use callback-deferred updates while the native App runtime publishes busy, startup, failure, close-prompt, and popout-style state. Verify equal-scale EIS fitting, preview-canvas layout, and native runtime lifecycle behavior with hidden GUI tests. User and data impact Apps retain the same layouts and fitted limits while framework rendering no longer processes unrelated pending callbacks mid-operation. Projects, source records, results, and exports are unchanged. Compatibility and migration This compatible patch does not alter the public API or require project/result migration. Validation Focused hidden-GUI EIS equal-axis, Figure Studio preview, and native adapter lifecycle checks pass locally. The complete platform matrix remains the final validation for native GUI scheduling behavior. Evidence MATLAB documents that full `drawnow` both updates figures and processes pending callbacks, while the `nocallbacks` forms defer those callbacks. The framework uses callback-deferred synchronization only where it must read settled native geometry or publish internal view state. Known limitations and follow-up Automated hidden GUI checks do not substitute for manual visual and interaction assessment on supported MATLAB desktop platforms."},{"title":"Canonical role-based source collections","url":"history/records/2026/07/LK-20260716-canonical-role-based-sources.html","kind":"history","section":"history","keywords":"LK-20260716-canonical-role-based-sources 2026-07-16 73 refactor compatible labkit_RHSPreview_app labkit_NerveResponseAnalysis_app project payload schema source roles and relinking","text":"Canonical role-based source collections id: LK-20260716-canonical-role-based-sources date: 2026-07-16 sequence: 73 type: refactor compatibility: compatible component: `labkit_RHSPreview_app` | `1.4.0 -> 1.4.1` component: `labkit_NerveResponseAnalysis_app` | `1.4.0 -> 1.4.1` scope: project payload schema scope: source roles and relinking Context RHS Preview and Nerve Response Analysis stored external files in separate app-specific fields such as `rhsSource`, `filterSources`, and `protocolSource`. The records already carried explicit roles, but Runtime V2 could only discover the canonical `project.inputs.sources` collection during portable save/load and missing-file relinking. Decision and rationale Store every external dependency in one canonical collection and select records by their app-owned role. The framework remains domain-neutral, while each app continues to distinguish preview recordings, filter recordings, filter JSON, and optional protocols without duplicating persistence mechanics. Changes Advanced both project payload schemas from version 1 to version 2. Added ordered migrations that combine the former role-specific fields without changing source record contents or order. Added app-local role selection and replacement operations used consistently by lifecycle creation, actions, presenters, exports, and validation. Kept at most one primary recording/protocol per relevant app while allowing RHS Preview to retain an ordered collection of filter recordings. User and data impact Project reopen, result provenance, and future missing-file recovery can now see all selected dependencies. Preview, filtering, event detection, CAP metrics, and output formats are unchanged. Compatibility and migration Existing version 1 payloads remain readable and preserve every source record. The old fields are removed only from the migrated in-memory project; saving writes the version 2 canonical collection. Validation Focused unit tests verify role-preserving migrations and project versions. Focused GUI workflows verify RHS indexing, multiple filter-file management, project save/reopen, filter analysis, export, and transient cache rebuilding. Evidence [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) documents its three source roles. [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) documents filter/protocol restoration and relinking. [App Framework](../../../../framework/README.md) defines the canonical project source collection. Known limitations and follow-up This change makes every source discoverable but does not yet rebase relative paths at the final project save destination. That framework serialization change is tracked as the next persistence batch."},{"title":"Canonical single-source app projects","url":"history/records/2026/07/LK-20260716-canonical-single-source-projects.html","kind":"history","section":"history","keywords":"LK-20260716-canonical-single-source-projects 2026-07-16 72 refactor compatible labkit_GaitAnalysis_app labkit_CurvatureMeasurement_app labkit_ECGPrint_app labkit_ResponseReviewStats_app project payload schema source relinking","text":"Canonical single-source app projects id: LK-20260716-canonical-single-source-projects date: 2026-07-16 sequence: 72 type: refactor compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.0 -> 2.0.1` component: `labkit_CurvatureMeasurement_app` | `1.4.0 -> 1.4.1` component: `labkit_ECGPrint_app` | `1.4.0 -> 1.4.1` component: `labkit_ResponseReviewStats_app` | `1.4.0 -> 1.4.1` scope: project payload schema scope: source relinking Context The Runtime V2 project contract discovers external files through the `project.inputs.sources` collection. Four single-source apps instead stored their record under a singular `project.inputs.source` field. Their own actions could open that path, but framework save/load relinking could not discover it. Decision and rationale Use the same canonical source collection for every single-source app. The plural field is a collection even when it currently contains zero or one record, so persistence and relinking do not need app-specific field knowledge. Changes Moved Gait Analysis, Curvature Measurement, ECG Print, and Response Review Stats source ownership to `project.inputs.sources`. Advanced each payload schema by one version and added the corresponding ordered migration. Updated action, presenter, session, validation, export, and manual references to the canonical collection. Corrected the Gait family overview to describe its current strict Video Marker project input instead of retired generic MAT/table inputs. User and data impact New saves expose their source records to the common portable-project resolver. Scientific calculations, UI choices, annotations, parameters, and result values are unchanged. Compatibility and migration Existing payloads remain readable. Loading copies the former singular field to the canonical collection and removes the retired field. A later save writes the new payload version; source record contents are not transformed. Validation Focused unit tests verify each field migration, current project version, and existing calculations. Focused hidden-GUI tests verify that each app still launches and follows its source workflow with the renamed durable field. Evidence [App Framework](../../../../framework/README.md) defines the canonical source collection used by project persistence. [Gait Analysis](../../../../apps/gait/gait-analysis/README.md), [Curvature Measurement](../../../../apps/image-measurement/curvature/README.md), [ECG Print](../../../../apps/wearable/ecg-print/README.md), and [Response Review Stats](../../../../apps/neurophysiology/response-review-stats/README.md) describe their upgraded project behavior. Known limitations and follow-up This record covers single-source apps. Multi-role neurophysiology projects are audited separately because preserving source roles requires a richer migration than renaming one field."},{"title":"Chrono Overlay adopts one version-aware project migration entry","url":"history/records/2026/07/LK-20260716-chrono-overlay-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-chrono-overlay-project-spec 2026-07-16 86 refactor compatible labkit_ChronoOverlay_app Electrochemistry Project lifecycle","text":"Chrono Overlay adopts one version-aware project migration entry id: LK-20260716-chrono-overlay-project-spec date: 2026-07-16 sequence: 86 type: refactor compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context Chrono Overlay split product metadata across three files and represented its only payload upgrade as a separately named `migrateProjectV1ToV2.m` file in a generic lifecycle package. Adding another schema version would have added yet another file and exposed migration sequencing to the App definition. Decision and rationale Make `definition.m` the product declaration and `projectSpec.m` the sole durable-schema entry. Its `Migrate(project, fromVersion)` callback owns App semantics for one version step; Runtime V2 owns the loop, version increment, validation after every step, and unsupported-envelope handling. Changes Consolidated project creation, validation, and migration in one project spec. Replaced the per-version migration file with one version-aware local function. Moved transient DTA reconstruction to a package-root session factory. Removed separate requirements, version, and generic lifecycle files. Kept DTA parsing, pulse-gap alignment, interpolation, plots, and exports unchanged. User and data impact Launch, plotting, save/load, v1 project upgrade, and CSV export behavior remain unchanged. Maintainers now add future schema cases in one ordered migration entry instead of editing both definition wiring and file inventories. Compatibility and migration Version-1 payloads still remove the obsolete decoded `inputs.items` field so portable source records remain the only durable input. Version-2 payloads are unchanged and need no migration. Validation Unit tests call the new migration entry with a synthetic v1 payload and verify validation, session reconstruction, pulse alignment, interpolation, and presenter output. The hidden GUI workflow covers real launch, source loading, plotting, and export through the consolidated definition. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Only v1-to-v2 is currently required. Future cases remain App-owned in the same function; they must not bypass the framework's stepwise validation loop."},{"title":"Chrono Overlay removes App-owned source identity bookkeeping","url":"history/records/2026/07/LK-20260716-chrono-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-chrono-runtime-source-reconciliation 2026-07-16 111 fix compatible labkit_ChronoOverlay_app Electrochemistry App structure Project identity","text":"Chrono Overlay removes App-owned source identity bookkeeping id: LK-20260716-chrono-runtime-source-reconciliation date: 2026-07-16 sequence: 111 type: fix compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Project identity Context Chrono Overlay generated a new source ID from the current record count. After removing an earlier source, adding another file could therefore reuse an ID still owned by a retained record. Runtime validation correctly rejects duplicate IDs, but the App should not have been maintaining this persistence invariant itself. The App also duplicated source append/removal helpers and empty transient workflow/view buckets already supplied by Runtime V2. Decision and rationale After every successful add, removal, or clear operation, derive the ordered path list from the decoded items and delegate durable-record reconciliation to `services.project.reconcileSources`. Chrono Overlay continues to own decoding, pulse-gap alignment, item order, selection, logs, and failure wording. Runtime owns stable identity reuse and collision-free allocation. Only App-specific selection and decoded cache data are returned by `createSession`; Runtime canonicalizes the remaining transient buckets. Changes Removed App-local source record append, removal, and count-based ID logic. Reconciled source records from the accepted decoded item order. Removed empty workflow and view boilerplate from the session factory. Replaced the dynamically growing failure list with one first-failure record while retaining per-file log messages. Added a GUI regression sequence that removes the first of two sources, adds it again, checks unique IDs, saves, clears, and reopens the project. Advanced the Chrono Overlay App version to 1.4.4. User and data impact Normal file loading, ordering, selection, plots, exports, and saved project payloads retain their behavior. The remove-then-add sequence no longer risks a duplicate source ID and failed callback. Persistence IDs remain internal and do not change curve values or scientific output. Compatibility and migration The current payload remains version 2 and no new migration is needed. Existing records retain their IDs when their paths remain in the project. Validation The Chrono Overlay hidden GUI workflow covers two-file loading, selection, remove and re-add, unique identity, export, save, clear, reopen, option redraw, and final plot clearing. Focused unit, structure, version, documentation, and history tests protect the calculation and product contracts. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up CIC, CSC, and VT Resistance still have equivalent App-local source identity helpers and require their own ordering and behavior audits before conversion."},{"title":"Clean-room cross-platform CI","url":"history/records/2026/07/LK-20260717-clean-room-cross-platform-ci.html","kind":"history","section":"history","keywords":"LK-20260717-clean-room-cross-platform-ci 2026-07-17 131 ci compatible project validation and release automation","text":"Clean-room cross-platform CI id: LK-20260717-clean-room-cross-platform-ci date: 2026-07-17 sequence: 131 type: ci compatibility: compatible scope: project validation and release automation Context The previous MATLAB workflow combined ordinary CI, scheduled reports, release validation, and raw tag creation. Its optional Base MATLAB job also mixed static source checks with installed-product dependency analysis, even though that analysis can only report products present on the runner. The design made it difficult to tell which evidence was required and did not exercise every candidate outside the developer's operating system. Decision and rationale Make a Toolbox-free MATLAB installation the baseline for all required CI behavior. Let one CI workflow run complete headless and hidden-GUI tasks on Linux, macOS, and Windows. Keep coverage as an explicit local report. Release creation remains a developer-initiated process after manual App validation, requires a successful main-push CI run for the exact commit, and stops at a draft for final human review. This directly tests the promised runtime instead of trying to infer the same fact from a runner with optional products installed. Changes Replaced the monolithic MATLAB workflow with one ordinary/reusable CI workflow and one manual release workflow. Made cross-platform Base MATLAB headless and hidden-GUI validation mandatory for pull requests and main pushes, and required exact same-commit CI evidence before release. Removed the installed-product dependency-analysis task and all CI Toolbox installation. Kept static known-Toolbox call detection and representative shadowed-helper workflows inside the ordinary headless suite. Kept coverage out of CI because it has no failure threshold and repeats the non-GUI tests; maintainers can still request the local report explicitly. Replaced raw Git-ref creation with a validated annotated tag, tag-blob launcher staging, remote asset verification, and a draft GitHub Release. User and data impact No App runtime, calculation, project, or saved-data behavior changes. CI may now expose operating-system, path, case-sensitivity, layout, or missing- dependency failures that a configured development machine did not reveal. Compatibility and migration Maintainers should use `Continuous Integration` for required checks, `buildtool coverage` for an on-demand local report, and the manual `Release` workflow only after interactive App validation. The retired `buildtool baseMatlab` task has no compatibility alias because clean Base MATLAB execution is now the environment of every required build task rather than a separate partial check. Validation The CI policy contract checks trigger separation, read-only ordinary permissions, three-platform matrices, absence of `products:` installation, public build-task use, timeouts, diagnostics, exact-sha CI evidence, manual release confirmation, tag-blob asset generation, and draft-only publication. Build-task and Toolbox guardrails verify the simplified catalog and retained static/fallback checks. Evidence `.github/workflows/ci.yml` and `release.yml` define the executable pipeline. `tests/cases/contract/project/ci/CiValidationPolicyGuardrailTest.m` protects the new boundaries. Known limitations and follow-up Hidden GUI tests cannot drive native dialogs or judge visual and pointer quality. Developer-led manual App testing therefore remains mandatory before starting a release. CI also detects only Toolbox calls reached by tests or covered by the maintained static-call set; temporary product use still needs its declared fallback, idempotency, and parity evidence."},{"title":"Close guards and caught-exception diagnostics","url":"history/records/2026/06/LK-20260630-close-guards-and-caught-exception-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260630-close-guards-and-caught-exception-diagnostics 2026-06-30 21 feat compatible labkit.ui labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Close guards and caught-exception diagnostics","text":"Close guards and caught-exception diagnostics id: LK-20260630-close-guards-and-caught-exception-diagnostics date: 2026-06-30 sequence: 21 type: feat compatibility: compatible component: `labkit.ui` | `3.2.6 -> 3.2.7` component: `labkit_DICPostprocess_app` | `1.2.1 -> 1.2.2` component: `labkit_BatchImageCrop_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.3.5 -> 1.3.6` component: `labkit_CurvatureMeasurement_app` | `1.2.1 -> 1.2.2` component: `labkit_FocusStack_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.2.3 -> 1.2.4` component: `labkit_ImageEnhance_app` | `1.3.2 -> 1.3.3` component: `labkit_ImageMatch_app` | `1.3.2 -> 1.3.3` component: `labkit_ImageMatch_app` | `1.3.3 -> 1.3.4` component: `labkit_NerveResponseAnalysis_app` | `1.2.3 -> 1.2.4` component: `labkit_ResponseReviewStats_app` | `1.2.2 -> 1.2.3` component: `labkit_RHSPreview_app` | `1.2.1 -> 1.2.2` component: `labkit_ECGPrint_app` | `1.2.0 -> 1.2.1` scope: Close guards and caught-exception diagnostics Context Many app callbacks caught an exception, showed its message, and returned. That protected the UI but discarded the stack and callback context needed to diagnose the failure. Image apps also tracked unsaved or incomplete work in slightly different ways when deciding whether a window could close. Decision and rationale Report caught exceptions to the existing debug trace before presenting the user-facing error, and promote the file-index and close-guard mechanics that were shared by several image apps. Preserve app ownership of what counts as dirty or incomplete work. Changes `labkit.ui` `3.2.6 -> 3.2.7` DIC, Batch Crop, Curvature, Focus Stack, Image Match, neurophysiology apps, and ECG Print patch bumped for diagnostics or close-guard work. Reported caught app-runner exceptions through framework debug diagnostics. Promoted file-entry index helpers. Connected dirty/incomplete workflow state to close guards. User and data impact An app could still recover from a failed load, calculation, or export, while its debug report retained the exception evidence. Closing an image workflow with unfinished state produced a consistent warning instead of silently discarding work. Scientific results and saved schemas were unchanged. Compatibility and migration Existing app state and result files remained valid. Closing an incomplete workflow gained an additional confirmation, and debug reports gained exception details. Validation The exception-reporting commit expanded app/library compatibility checks across the affected runners. The close-guard commit added public-surface and compatibility coverage for promoted file indices and app close behavior. Exact historical commands were not recorded. Evidence Main commits `c0028a81` and `a81853ef`. Known limitations and follow-up Runtime V2 later replaced direct debug-log and close-guard calls with lifecycle and diagnostic services, but retained the requirement that caught exceptions remain observable."},{"title":"Consistent electrochemistry batch analysis","url":"history/records/2026/07/LK-20260713-electrochem-batch-consistency.html","kind":"history","section":"history","keywords":"LK-20260713-electrochem-batch-consistency 2026-07-13 46 fix compatible labkit_CIC_app labkit_VTResistance_app Consistent electrochemistry batch analysis","text":"Consistent electrochemistry batch analysis id: LK-20260713-electrochem-batch-consistency date: 2026-07-13 sequence: 46 type: fix compatibility: compatible component: `labkit_CIC_app` | `1.3.7 -> 1.3.8` component: `labkit_VTResistance_app` | `1.3.7 -> 1.3.8` scope: Consistent electrochemistry batch analysis Context CIC and VT Resistance could retain per-file derived values from different control settings, and CIC could sample outside the recorded time range while displaying a delay without units. Decision and rationale Treat analysis controls as one batch contract and recompute every file before display/export so rows cannot silently mix stale and current parameters. Changes Recompute all loaded files when shared controls change and once more before CSV export. Label CIC delay in microseconds, reject out-of-range sampling, and export the area and delay used for each result. Added family regression coverage and audited sibling electrochem apps. User and data impact Batch rows now represent one consistent area, delay, pulse-detection, resistance-window, and voltage-mode configuration. Invalid delay choices fail instead of extrapolating a misleading value. Compatibility and migration CIC CSV adds trailing `Area_cm2` and `Delay_us` columns. Existing columns keep their names and order; VT Resistance exports remain schema-compatible. Validation Electrochem unit and GUI tests cover batch recomputation, display units, out-of-range handling, and export-time refresh. Evidence The guarded calculations and export builders are app-owned. Commit `67ea2286` introduced the consistent batch-analysis and export behavior. Known limitations and follow-up The fix enforces internal batch consistency but does not choose scientifically appropriate area, delay, or resistance windows for the user."},{"title":"Core, neurophysiology, and ECG project validation ownership","url":"history/records/2026/07/LK-20260716-core-neuro-ecg-project-validation.html","kind":"history","section":"history","keywords":"LK-20260716-core-neuro-ecg-project-validation 2026-07-16 121 refactor compatible labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app remaining public App project schemas App maintenance cost","text":"Core, neurophysiology, and ECG project validation ownership id: LK-20260716-core-neuro-ecg-project-validation date: 2026-07-16 sequence: 121 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.6 -> 0.2.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.4 -> 1.4.5` component: `labkit_ResponseReviewStats_app` | `1.4.4 -> 1.4.5` component: `labkit_RHSPreview_app` | `1.4.4 -> 1.4.5` component: `labkit_ECGPrint_app` | `1.4.4 -> 1.4.5` scope: remaining public App project schemas scope: App maintenance cost Context The remaining public project validators still surrounded their own style, recording-role, metric-window, protocol, ECG, and result rules with repeated canonical bucket and source-record format checks. Decision and rationale Complete the public-App validation ownership split. Runtime owns canonical bucket structs and source-record internals. Each App continues to require its source collection and to enforce its own fields, role/cardinality rules, numeric limits, tables, annotations, and results. Changes Removed 66 net lines of repeated framework structure checks from Figure Studio, the three neurophysiology Apps, and ECG Print. Preserved Nerve Response Analysis's fixed source ID-role pairs and RHS Preview's recording/protocol/filter roles and cardinalities. Added focused project-spec contracts for default acceptance, missing-source rejection, and the retained neurophysiology role rules. User and data impact Valid projects, migrations, signal calculations, previews, and exports are unchanged. Framework-shape failures now have one Runtime owner; domain-schema failures remain attributable to the App that defines them. Compatibility and migration No payload format changed and no migration is required. All supported saved projects continue through their existing centralized `projectSpec.m` migration entry. Validation Focused project-spec tests cover all five defaults and missing-source cases, plus invalid Nerve and RHS source roles. Representative unit and hidden-GUI workflows cover project reconstruction, preview, analysis, and export behavior. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Neurophysiology Apps](../../../../apps/neurophysiology/README.md) [ECG Print](../../../../apps/wearable/ecg-print/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Project validator boilerplate is now removed across the public App fleet. Further App simplification must target independently evidenced action, presentation, or cache patterns rather than weakening domain validation."},{"title":"Cross-platform validation exits cleanly after settled plot fitting","url":"history/records/2026/07/LK-20260721-cross-platform-validation-exit.html","kind":"history","section":"history","keywords":"LK-20260721-cross-platform-validation-exit 2026-07-21 151 fix compatible labkit.app App Framework Continuous Integration","text":"Cross-platform validation exits cleanly after settled plot fitting id: LK-20260721-cross-platform-validation-exit date: 2026-07-21 sequence: 151 type: fix compatibility: compatible component: `labkit.app` | `1.2.2 -> 1.2.3` scope: App Framework scope: Continuous Integration Context Windows headless validation completed every test and report but retained a progress heartbeat resource until the GitHub Actions step timed out. Hosted macOS GUI validation also measured equal-scale plot limits before native layout had settled, producing a platform-specific false failure. Decision and rationale Make lifecycle ownership explicit at the test-runner boundary and measure equal-data-unit limits only after MATLAB has applied pending native layout. This keeps CI diagnostics available while ensuring successful validation exits promptly, without changing the App SDK surface or EIS workflow meaning. Changes Explicitly release each progress plugin and its heartbeat timer when a test runner returns, including nested focused runs. Verify focused runs leave no tagged heartbeat timer behind. Settle native layout before equal-data-unit fitting and assert EIS scaling from the post-action axes allocation. User and data impact Users receive the same transient equal-scale EIS view, now calculated against the displayed axes allocation. Projects, source records, results, and exports are unchanged. Compatibility and migration The patch release is compatible with all 1.x App SDK requirements. No project or result migration is required. Validation Focused nested-runner coverage verifies heartbeat cleanup after return. Focused EIS GUI coverage verifies independent fitting and equal-scale limits. GitHub Actions will re-run the complete platform matrices after delivery. Evidence The timed-out Windows job reported all 468 headless tests and `** Finished headless` more than twenty minutes before the action timeout. The macOS GUI artifact consistently identified the pre-layout equal-scale pixel assertion. Known limitations and follow-up The Windows hosted runner remains the final verification environment for process teardown. Manual visual assessment of plot layout remains outside hidden-GUI automation."},{"title":"Curvature consolidates migration and analysis state","url":"history/records/2026/07/LK-20260716-curvature-project-structure.html","kind":"history","section":"history","keywords":"LK-20260716-curvature-project-structure 2026-07-16 97 refactor compatible labkit_CurvatureMeasurement_app Image Measurement App structure","text":"Curvature consolidates migration and analysis state id: LK-20260716-curvature-project-structure date: 2026-07-16 sequence: 97 type: refactor compatibility: compatible component: `labkit_CurvatureMeasurement_app` | `1.4.2 -> 1.4.3` scope: Image Measurement scope: App structure Context Curvature split static metadata across files, exposed four generic lifecycle functions including a filename-encoded migration, and stored five computation tasks/results under `+appState`. Session restoration also read the nested portable source path directly. Decision and rationale Use one project specification with a version-aware migration entry. Runtime V2 calls `Migrate(project,fromVersion)` for each missing version; Curvature keeps only the actual version-1 transformation. Put fit and length task/result shapes beside the analysis functions that consume them. Changes Consolidated product metadata and requirements in `definition.m`. Consolidated project creation, validation, and the v1 source migration in `projectSpec.m`. Moved decoded-image reconstruction to root `createSession.m`. Moved all five generic state helpers to `+analysisRun`. Removed separate metadata and lifecycle files. Replaced direct portable-reference access with the Runtime path accessor. Kept managed anchor editing, scale calibration, curve fitting, length measurement, overlay rendering, and exports unchanged. User and data impact Opening current or version-1 projects, tracing/removing anchors, calibrating scale, fitting curvature, measuring length, and exporting results behave unchanged. Developers now find every durable-version rule in one file and every numerical result/task next to the numerical implementation. Compatibility and migration The current payload stays at version 2. Version-1 `inputs.source` is still moved to `inputs.sources` exactly once. No new payload migration is introduced. Validation Unit tests cover current project/session construction, v1 migration, fit and length numerics, physical/pixel scales, densification, invalid points, result tables, and deterministic task fingerprints. The hidden GUI suite covers image loading, anchor editing guidance, managed interactions, viewport behavior, scale calibration, results, and save/load. Evidence [Curvature Measurement](../../../../apps/image-measurement/curvature/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other versioned Apps still use filename-per-step migrations and will move to one project entry during their own behavior audits."},{"title":"DIC Postprocess adopts one product and project declaration","url":"history/records/2026/07/LK-20260716-dic-postprocess-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-dic-postprocess-project-spec 2026-07-16 83 refactor compatible labkit_DICPostprocess_app DIC Project lifecycle","text":"DIC Postprocess adopts one product and project declaration id: LK-20260716-dic-postprocess-project-spec date: 2026-07-16 sequence: 83 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.1 -> 1.4.2` scope: DIC scope: Project lifecycle Context DIC Postprocess split static product metadata across `definition.m`, `requirements.m`, and `version.m`, while its version-1 durable schema occupied three generic `+appLifecycle` files. None of those separations represented a separate scientific capability or independently evolving contract. Decision and rationale Make `definition.m` the product declaration and `projectSpec.m` the only durable-schema entry. Keep `createSession.m` separate because it restores file-backed strain, image, mask, and prepared overlay caches that must not be serialized into the project. Changes Moved command metadata, version, update date, and facade requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved transient reconstruction to one explicitly named package-root entry. Removed the two metadata files and generic lifecycle package. Replaced the stale GUI-free manual example with an executable call matching the real input structure, complete parameter contract, and three outputs. Kept all calculation, parameter, action, presentation, and export behavior unchanged. User and data impact Ncorr loading, overlay preparation, statistics, save/load, and exports behave unchanged. Maintainers can now find the complete product contract in one file and the complete durable schema in one adjacent file. Compatibility and migration The command, project ID, payload version, fields, defaults, validation rules, and source records are unchanged. Existing DIC Postprocess projects require no migration. Validation Unit tests exercise Ncorr loading, cached session reconstruction, numerical preparation, summary/export behavior, and presentation. The hidden GUI flow launches the real App and generates overlays and a summary through Runtime V2. Evidence [DIC Postprocess](../../../../apps/dic/dic-postprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The action file remains a meaningful workflow boundary because it coordinates input selection, calculation, export, diagnostics, and dialogs. Its naming and possible smaller capability boundaries will be compared across the DIC family instead of changed in isolation."},{"title":"DIC Preprocess consolidates product and project declarations","url":"history/records/2026/07/LK-20260716-dic-preprocess-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-dic-preprocess-project-spec 2026-07-16 84 refactor compatible labkit_DICPreprocess_app DIC Project lifecycle","text":"DIC Preprocess consolidates product and project declarations id: LK-20260716-dic-preprocess-project-spec date: 2026-07-16 sequence: 84 type: refactor compatibility: compatible component: `labkit_DICPreprocess_app` | `1.5.1 -> 1.5.2` scope: DIC scope: Project lifecycle Context DIC Preprocess split static product metadata across three files and split one version-1 durable schema across a generic lifecycle package. That structure made a maintainer traverse six declarations before reaching any registration, crop, or mask behavior. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the only durable-schema entry. Keep `createSession.m` explicit because decoding source images and replaying alignment/crop steps reconstructs transient cache state rather than defining persistence. Changes Moved command metadata, version, update date, and facade requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved transient image and edit replay to one package-root session factory. Removed the metadata files and generic lifecycle package. Kept registration, crop, mask, history, managed interactions, export, and scientific parameter behavior unchanged. User and data impact Manual and automatic alignment, zoom-preserving point/ROI editing, crop and mask history, project save/load, and exports behave unchanged. The durable and transient state boundaries are now visible from two adjacent files. Compatibility and migration The command, project ID, payload version, fields, defaults, validation, source records, edit steps, and mask history are unchanged. Existing projects require no migration. Validation Unit tests cover project/session reconstruction, state history, registration, crop/mask calculations, source IO, exports, and presenter models. The hidden GUI workflow covers real launch, manual point matching, ROI crop, mask editing, viewport preservation, and generated outputs. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The `+appState` package mixes edit-history and mask-geometry capabilities. It is retained for this compatibility-preserving checkpoint and will be split or renamed by responsibility after its callers and tests are audited."},{"title":"DIC Preprocess replaces its generic state bucket","url":"history/records/2026/07/LK-20260716-dic-preprocess-state-capabilities.html","kind":"history","section":"history","keywords":"LK-20260716-dic-preprocess-state-capabilities 2026-07-16 85 refactor compatible labkit_DICPreprocess_app DIC App ownership","text":"DIC Preprocess replaces its generic state bucket id: LK-20260716-dic-preprocess-state-capabilities date: 2026-07-16 sequence: 85 type: refactor compatibility: compatible component: `labkit_DICPreprocess_app` | `1.5.2 -> 1.5.3` scope: DIC scope: App ownership Context The generic `+appState` package mixed three unrelated concepts: durable align/crop undo history, mask editing and undo, and a predicate over transient source-image cache state. The name exposed storage mechanics without helping a maintainer find the behavior they needed. Decision and rationale Group these operations by the capability that owns their invariants. `editHistory` owns align/crop snapshots and reset behavior, `maskEditing` owns mask canvas composition and undo snapshots, and `sourceFiles` owns the loaded image-pair predicate. None is promoted to LabKit because the project fields, undo limits, image semantics, and operation order are specific to DIC Preprocess. Changes Moved four edit-history operations into `+editHistory`. Moved five mask composition/history operations into `+maskEditing`. Moved the image-pair readiness predicate into `+sourceFiles`. Updated actions, presenters, preview requests, and direct state tests to use the capability paths. Removed the generic `+appState` package without adding an adapter. User and data impact All controls, calculations, undo limits, project fields, masks, alignment/crop history, and viewport behavior are unchanged. Code navigation now starts from the user capability rather than an undifferentiated state bucket. Compatibility and migration This is an app-internal ownership change. Project payloads and public GUI-free scientific APIs are unchanged, so saved projects require no migration. Validation State tests cover history trimming, reset/restore behavior, mask composition, and image-pair readiness through the new capability paths. The full DIC Preprocess unit and hidden GUI workflows verify the unchanged callers. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up `definitionActions.m` remains a large but cohesive workflow coordinator. Its callbacks will be compared with other interaction-heavy image Apps before any further extraction; file length alone is not a reason to create another technical package."},{"title":"DIC and Gait project validation ownership","url":"history/records/2026/07/LK-20260716-dic-gait-project-validation-ownership.html","kind":"history","section":"history","keywords":"LK-20260716-dic-gait-project-validation-ownership 2026-07-16 120 refactor compatible labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_GaitAnalysis_app DIC and Gait project schemas App maintenance cost","text":"DIC and Gait project validation ownership id: LK-20260716-dic-gait-project-validation-ownership date: 2026-07-16 sequence: 120 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.4 -> 1.4.5` component: `labkit_DICPreprocess_app` | `1.5.5 -> 1.5.6` component: `labkit_GaitAnalysis_app` | `2.0.5 -> 2.0.6` scope: DIC and Gait project schemas scope: App maintenance cost Context DIC Postprocess, DIC Preprocess, and Gait repeated Runtime's canonical project and source-record shape checks around their real domain constraints. The duplication made it harder to see which saved fields these Apps actually own. Decision and rationale Retain each App's required source collection and domain schema while relying on Runtime for canonical bucket structs and source-record internals. DIC continues to own alignment, crop, mask, summary, and parameter fields; Gait continues to own pose-analysis options, numeric limits, and result structure. Changes Removed 26 net lines of repeated framework structure checks from the three project specifications. Preserved every App-required source field and domain-specific validator. Added GUI-free DIC and Gait contracts that accept default projects and reject projects missing their required source collection. User and data impact Valid DIC and Gait projects, migrations, calculations, and exports are unchanged. Malformed framework structures still fail in Runtime and malformed domain fields still fail in the owning App. Compatibility and migration No payload format changed and no migration is required. Supported older projects continue through the same App-owned migration callbacks. Validation Focused project-spec tests cover default acceptance and missing-source rejection. Hidden GUI workflows cover representative DIC load/edit/analysis and Gait project load/step-analysis behavior. Evidence [DIC Apps](../../../../apps/dic/README.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up This boundary cleanup does not change the scientific gait analysis or image registration algorithms. Their domain complexity remains App-owned."},{"title":"DIC uses framework-owned optional source slots","url":"history/records/2026/07/LK-20260716-dic-source-slots.html","kind":"history","section":"history","keywords":"LK-20260716-dic-source-slots 2026-07-16 93 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app DIC Runtime source boundary","text":"DIC uses framework-owned optional source slots id: LK-20260716-dic-source-slots date: 2026-07-16 sequence: 93 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.0 -> 7.4.1` component: `labkit_DICPostprocess_app` | `1.4.2 -> 1.4.3` component: `labkit_DICPreprocess_app` | `1.5.3 -> 1.5.4` scope: DIC scope: Runtime source boundary Context DIC Preprocess and Postprocess use stable semantic source slots such as `referenceImage`, `movingImage`, `maskImage`, and `dicMat`. Those slots are legitimately absent before the user selects a file. Each App therefore carried an identical `pathForId.m` wrapper that returned empty text for an unset slot and otherwise inspected the nested portable reference. The first source accessor required every requested ID to exist. Applying that rule would have preserved the duplicate wrapper instead of lowering App cost. Decision and rationale Make ID-based source lookup preserve the requested shape and return an empty string for a semantic slot that has not been added. Continue to reject malformed records and references. This matches ordinary presenter and session behavior without hiding corrupt project data. Changes Refined `sourcePaths(sources,ids)` for optional semantic slots. Removed both DIC `pathForId.m` wrappers and their duplicate schema knowledge. Migrated DIC loading, summaries, presenters, save defaults, and export paths to the Runtime accessor. Preserved alignment, crop, mask, anchor, rendering, calculation, and export behavior. User and data impact An empty DIC project still presents empty file controls and enables them as the workflow requires. Selected and relinked files load through the same paths. Developers no longer implement a source-ID lookup helper for each App. Compatibility and migration The accessor change relaxes one error case and is compatible within UI 7. No saved project shape changed and no DIC payload migration is required. Validation Runtime unit tests cover mixed present and absent IDs. DIC unit and hidden-GUI tests cover empty startup, file selection, point matching, ROI and mask edits, viewport-preserving interaction, project state, and result export. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [DIC Postprocess](../../../../apps/dic/dic-postprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other families still contain direct portable-reference reads. They will move to the same accessor before the repository-wide no-leak contract is enabled."},{"title":"DTA facade and app ownership boundaries","url":"history/records/2026/05/LK-20260529-dta-facade-and-app-boundaries.html","kind":"history","section":"history","keywords":"LK-20260529-dta-facade-and-app-boundaries 2026-05-29 2 refactor compatible historical project evolution","text":"DTA facade and app ownership boundaries id: LK-20260529-dta-facade-and-app-boundaries date: 2026-05-29 sequence: 2 type: refactor compatibility: compatible scope: historical project evolution Context The first extraction pass separated calculations from the imported GUIs, but the electrochem apps still depended on a broad internal package and shared session helpers. A new app could reuse a parser only by learning implementation details that had nothing to do with reading a DTA file. Decision and rationale Make DTA loading the reusable boundary and keep workflow decisions in the app that owns them. The public layer would load one file, discover files, or load a folder and return documented report structures. Plot labels, callback order, session state, result summaries, and export choices would remain app concerns. This distinction became an early form of LabKit's present architecture: a small library presents stable data operations, while an app owns the laboratory workflow assembled around those operations. Changes Added GUI-independent DTA loading and adopted it in the EIS and Chrono Overlay apps. Added discovery and folder-loading operations, including validation for empty selections, missing folders, and expected experiment kinds. Documented and tested the report schemas returned by batch operations. Moved EIS, Chrono Overlay, CSC, VT Resistance, and CIC out of the original `gamrywb` namespace and into app-owned entry points. Returned calculation details, labels, callback flow, and exports to their owning apps when they were not genuinely reusable. Introduced boundary tests to prevent UI, data, IO, analysis, and utility packages from growing through accidental cross-dependencies. User and data impact Electrochem users kept the same principal workflows, but file-loading failures became more explicit and batch loading gained a defined result report. App authors could use the DTA layer without starting a GUI or depending on the layout of an existing app. No laboratory file format or scientific result schema was intentionally changed by the ownership cleanup. Compatibility and migration The supported app commands replaced direct use of the old `gamrywb` package. Code that reached into that package's internal helpers needed to move either to the DTA facade or into the app that owned the behavior. Validation The period added DTA schema tests, empty- and missing-folder tests, architectural boundary checks, and behavior-focused calculation tests. The repository did not yet have the later unified MATLAB test command, so no single historical validation invocation is available. Evidence GUI-free loading facade `88b19851` and initial app adoption `789ef507`, `0ccc3ad6`. App ownership migration `1ee8e82d` through `1b6042bf`. Discovery and folder loading `c06946d9`, `82f4146d`. Report documentation and contract tests `669eea38`, `e04292c0`. Boundary and failure-handling work from `ad2b6c74` through `7506fa32`. Known limitations and follow-up The public surface was still organized around the first electrochem use cases. The next stage renamed the workbench, narrowed the package map further, and tested whether the same app-first approach could support image and biosignal workflows."},{"title":"Debug sample packs","url":"history/records/2026/07/LK-20260701-debug-sample-packs.html","kind":"history","section":"history","keywords":"LK-20260701-debug-sample-packs 2026-07-01 28 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Debug sample packs","text":"Debug sample packs id: LK-20260701-debug-sample-packs date: 2026-07-01 sequence: 28 type: feat compatibility: compatible component: `labkit.ui` | `3.3.1 -> 3.4.0` component: `labkit_DICPostprocess_app` | `1.2.4 -> 1.3.0` component: `labkit_DICPreprocess_app` | `1.2.2 -> 1.3.0` component: `labkit_ChronoOverlay_app` | `1.2.1 -> 1.3.0` component: `labkit_CIC_app` | `1.2.1 -> 1.3.0` component: `labkit_CSC_app` | `1.2.1 -> 1.3.0` component: `labkit_EIS_app` | `1.2.1 -> 1.3.0` component: `labkit_VTResistance_app` | `1.2.1 -> 1.3.0` component: `labkit_BatchImageCrop_app` | `1.5.1 -> 1.6.0` component: `labkit_CurvatureMeasurement_app` | `1.2.4 -> 1.3.0` component: `labkit_FLIRThermal_app` | `1.1.2 -> 1.2.0` component: `labkit_FocusStack_app` | `1.3.0 -> 1.4.0` component: `labkit_ImageEnhance_app` | `1.4.1 -> 1.5.0` component: `labkit_ImageMatch_app` | `1.4.1 -> 1.5.0` component: `labkit_NerveResponseAnalysis_app` | `1.2.4 -> 1.3.0` component: `labkit_ResponseReviewStats_app` | `1.2.3 -> 1.3.0` component: `labkit_RHSPreview_app` | `1.2.4 -> 1.3.0` component: `labkit_ECGPrint_app` | `1.2.2 -> 1.3.0` scope: Debug sample packs Context Debug reports could identify a failed callback, but reproducing the failure still depended on the original laboratory file and the user's exact sequence of actions. Those inputs were often unavailable to the person investigating the report and were unsuitable as permanent test fixtures. Decision and rationale Let every app create a small, synthetic sample pack that exercises its normal loading path without containing laboratory data. Store the generated inputs and expected output location beside the debug artifacts so a report can be replayed from one self-contained folder. Changes `labkit.ui` `3.3.1 -> 3.4.0` All supported apps moved into the `1.3.x`, `1.4.x`, `1.5.x`, or `1.6.x` debug-sample-pack lines. Added app-owned debug sample packs. Added debug artifact sample and output folders. User and data impact Debug mode gained a repeatable demonstration dataset for each supported app. These packs were synthetic and intended for diagnosis, not as substitutes for real experiment files or as reference scientific results. Compatibility and migration The sample packs were additive debug assets. Existing app inputs and outputs were unchanged, and no user file was copied into a pack. Validation Commit `279befbc` added coverage tests for every app family, focused tests for DIC, electrochem, image, RHS, and wearable sample writers, and layout checks that exercised the generated packs. Evidence Main commit `279befbc`. Known limitations and follow-up Sample packs were deliberately app-owned because each workflow knows which inputs make a useful reproduction. They do not contain or archive a user's original files."},{"title":"Debug workflows, launcher tools, and changelog governance","url":"history/records/2026/07/LK-20260707-debug-workflows-launcher-tools-and-changelog-governance.html","kind":"history","section":"history","keywords":"LK-20260707-debug-workflows-launcher-tools-and-changelog-governance 2026-07-07 39 feat compatible labkit_launcher labkit_launcher labkit_launcher labkit.ui labkit.ui labkit_DICPreprocess_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_FigureStudio_app Debug workflows, launcher tools, and changelog governance","text":"Debug workflows, launcher tools, and changelog governance id: LK-20260707-debug-workflows-launcher-tools-and-changelog-governance date: 2026-07-07 sequence: 39 type: feat compatibility: compatible component: `labkit_launcher` | `1.2.4 -> 1.2.5` component: `labkit_launcher` | `1.2.5 -> 1.2.6` component: `labkit_launcher` | `1.2.6 -> 1.2.7` component: `labkit.ui` | `5.0.0 -> 5.0.1` component: `labkit.ui` | `5.0.1 -> 5.0.2` component: `labkit_DICPreprocess_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.6.6 -> 1.6.7` component: `labkit_FocusStack_app` | `1.4.5 -> 1.4.6` component: `labkit_FigureStudio_app` | `0.1.4 -> 0.1.5` scope: Debug workflows, launcher tools, and changelog governance Context The first complete debug sample packs exposed several false failures. A modal file chooser could be mistaken for a stalled callback, DIC could read an older ROI snapshot than the editor displayed, duplicate crops needed confirmation before export, and Focus Stack's folder workflow was hard to reach from its sample instructions. The launcher was also becoming a practical maintenance and distribution tool. Code Analyzer reports needed a durable home outside launcher source, individual apps needed an offline package format, and private app workspaces needed to remain discoverable without entering the public repository. Finally, the growing changelog needed to explain the direction of the project, not merely repeat tags and commit subjects. Decision and rationale Use v3.1.0 as a stabilization release for reproducible debug workflows. Treat native modal dialogs as active UI, read live editor state when it is newer than the stored snapshot, and make required gestures visible in the app. Move code analysis and packaging into dedicated tools, let the launcher invoke them, and keep private app discovery separate from public release contents. Changes Release tag `v3.1.0` `labkit_launcher` `1.2.4 -> 1.2.7` `labkit.ui` `5.0.1 -> 5.0.2` `labkit_FigureStudio_app` `0.1.4 -> 0.1.5` `labkit_DICPreprocess_app` `1.3.4 -> 1.3.5` `labkit_BatchImageCrop_app` `1.6.6 -> 1.6.7` `labkit_FocusStack_app` `1.4.5 -> 1.4.6` DIC Preprocess ROI mask export now reads the live ROI editor anchors when building a mask, so preview/save do not misreport a drawn ROI as empty when editor state is newer than the app state snapshot. DIC Preprocess keeps the double-click ROI anchor workflow and makes the double-click requirement explicit in the visible details text. Batch Image Crop duplicate tasks now redraw with finite preview overlay coordinates while still requiring users to confirm the duplicated crop center before export. Figure Studio quick PNG/JPG/SVG export actions use runtime-compatible handler signatures. Focus Stack exposes a direct `Choose folder` action for loading all supported images from a focus-stack folder. Debug trace diagnostics no longer write stalled-callback crash reports while a file chooser modal is active. Moved the launcher Code Analyzer scan into `tools/codecheck`, which writes timestamped JSON/HTML report pairs under `artifacts/code-check/` without overwriting earlier runs. Added launcher actions and a deployment tool that package one selected LabKit app into a standalone zip, either as source `.m` files or encoded `.p` files. Added launcher discovery for local private app workspaces under `private_apps/apps/` and roots named by `LABKIT_PRIVATE_APP_ROOTS`. Clarified the public changelog model as a project evolution map organized by reader-facing evolution entries, with release tags and commits kept as anchors and evidence rather than the primary structure. User and data impact The supplied debug packs could be followed without producing a false crash report or an apparently disabled workflow. DIC mask export used the anchors currently visible in the editor, duplicate crops remained blocked until their centers were confirmed, and Focus Stack offered a direct folder action. Maintainers gained timestamped HTML and JSON Code Analyzer reports. A selected app could be packaged for production or offline use without tests, unrelated apps, or repository metadata. Private apps could live beside the checkout, appear in the ordinary launcher, and retain their own repository and history. Compatibility and migration DIC ROI editing still uses double-click to add anchors; no interaction-mode migration is required. Existing file-panel image selection remains available in Focus Stack. Code Analyzer report consumers should read the timestamped `artifacts/code-check/matlab_code_issues_*.json` files. Full LabKit checkout installs are unchanged. Single-app packages can start through either the packaged launcher or the direct run file; P-code packages require MATLAB to run the generated `.p` files. Public apps, public releases, and public CI remain scoped to `apps/`. Validation PR #34 updated debug sample, ROI, crop, folder-loading, launcher-tool, deployment, and private-workspace tests before its squash merge. Tag `v3.1.0` points to release commit `9db01952`; the exact local command sequence was not recorded in this page. Evidence PR #34 squash merge `9db01952` and release tag `v3.1.0`. Known limitations and follow-up The first deployment tool packaged one app at a time. Multi-app bundles were added later. The changelog model introduced here was subsequently replaced by component-linked history pages generated with the documentation site."},{"title":"Declarative app runtime","url":"history/records/2026/07/LK-20260703-declarative-app-runtime.html","kind":"history","section":"history","keywords":"LK-20260703-declarative-app-runtime 2026-07-03 32 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Declarative app runtime","text":"Declarative app runtime id: LK-20260703-declarative-app-runtime date: 2026-07-03 sequence: 32 type: refactor compatibility: compatible component: `labkit.ui` | `3.4.4 -> 3.4.5` component: `labkit_DICPostprocess_app` | `1.3.0 -> 1.3.1` component: `labkit_DICPreprocess_app` | `1.3.0 -> 1.3.1` component: `labkit_ChronoOverlay_app` | `1.3.0 -> 1.3.1` component: `labkit_CIC_app` | `1.3.0 -> 1.3.1` component: `labkit_CSC_app` | `1.3.0 -> 1.3.1` component: `labkit_EIS_app` | `1.3.0 -> 1.3.1` component: `labkit_VTResistance_app` | `1.3.0 -> 1.3.1` component: `labkit_BatchImageCrop_app` | `1.6.1 -> 1.6.2` component: `labkit_CurvatureMeasurement_app` | `1.3.0 -> 1.3.1` component: `labkit_FLIRThermal_app` | `1.2.0 -> 1.2.1` component: `labkit_FocusStack_app` | `1.4.0 -> 1.4.1` component: `labkit_ImageEnhance_app` | `1.5.0 -> 1.5.1` component: `labkit_ImageMatch_app` | `1.5.0 -> 1.5.1` component: `labkit_NerveResponseAnalysis_app` | `1.3.0 -> 1.3.1` component: `labkit_ResponseReviewStats_app` | `1.3.0 -> 1.3.1` component: `labkit_RHSPreview_app` | `1.3.0 -> 1.3.1` component: `labkit_ECGPrint_app` | `1.3.1 -> 1.3.2` scope: Declarative app runtime Context Apps had adopted common shells and controls, but each runner still assembled callbacks, startup steps, refresh behavior, and debug hooks in its own order. Understanding an app meant reading a long construction procedure before reaching the scientific workflow. Decision and rationale Describe an app through a definition containing its layout, actions, state, startup, and rendering contracts, then let the runtime perform the common wiring. Keep action bodies, calculations, and result structures in the app so the definition exposes workflow structure without becoming a generic analysis language. Changes `labkit.ui` `3.4.4 -> 3.4.5` All supported apps received patch bumps. Migrated apps to declarative workflow runtime. User and data impact The visible workflows were intended to remain the same. For maintainers, each app gained a recognizable definition and lifecycle, while repeated setup moved out of its runner. Debug sample packs and focused app tests continued to call the same app-owned operations. Compatibility and migration Users kept the same app entry points and data files. App implementations moved to definitions and runtime actions; unsupported code that reached into old runner construction details needed to adopt the declarative APIs. Validation The migration changed 545 files and updated the affected app, layout, export, calculation, sample-pack, and package-boundary suites in the same commit. The commit message records final profiler and debug evidence; the exact local command was not preserved in this history. Evidence Main commit `568b3e9b`. Known limitations and follow-up The first declarative vocabulary still used the mixed `app`, `spec`, `view`, `tool`, and `diag` package names. The UI 4 and UI 5 passes refined how groups, plots, interactions, and lifecycle responsibilities were named."},{"title":"Default LabKit close protection","url":"history/records/2026/07/LK-20260709-default-labkit-close-protection.html","kind":"history","section":"history","keywords":"LK-20260709-default-labkit-close-protection 2026-07-09 43 fix compatible labkit.ui labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app Default LabKit close protection","text":"Default LabKit close protection id: LK-20260709-default-labkit-close-protection date: 2026-07-09 sequence: 43 type: fix compatibility: compatible component: `labkit.ui` | `5.0.2 -> 5.0.3` component: `labkit_FocusStack_app` | `1.4.6 -> 1.4.7` component: `labkit_ImageEnhance_app` | `1.5.5 -> 1.5.6` component: `labkit_ImageMatch_app` | `1.5.5 -> 1.5.6` scope: Default LabKit close protection Context Only apps that installed a dirty-state guard warned before closing. A newly created public or private app could therefore discard work silently until its author implemented and tested a separate guard. Repeated close shortcuts could also race with a modal confirmation. Decision and rationale Make close confirmation a default property of every framework-owned app window and remove the optional app-facing guard API. Keep one in-window prompt active at a time; a repeated close shortcut confirms that prompt instead of opening another one. Changes `labkit.ui` `5.0.2 -> 5.0.3` `labkit_FocusStack_app` `1.4.6 -> 1.4.7` `labkit_ImageEnhance_app` `1.5.5 -> 1.5.6` `labkit_ImageMatch_app` `1.5.5 -> 1.5.6` LabKit runtime figures now show an in-window confirmation prompt before any framework-owned app window closes, even when the app has not marked itself dirty. Removed the app-facing `labkit.ui.runtime.setCloseGuard` API and migrated existing app close-guard dirty checks to the framework default behavior. Repeating or holding the app close shortcut while the in-window prompt is active confirms the close. User and data impact Every public or private LabKit app asked before its window closed, even if the app had no custom dirty-state logic. Users gained a consistent protection against accidental closure at the cost of one confirmation step. Compatibility and migration Closing LabKit apps now requires one confirmation step by default. App code that calls `labkit.ui.runtime.setCloseGuard` must remove that call; close confirmation is framework-owned. Validation Commit `0c9f472b` expanded the UI busy-state GUI suite for initial close, cancel, confirmation, and repeated-shortcut behavior, then updated public- surface and app-compatibility checks. Evidence Mainline commit `0c9f472b`. Known limitations and follow-up This policy protected the window, not the semantic completeness of each saved project. Runtime V2 later combined default close handling with app-owned dirty state and lifecycle services."},{"title":"Destination-rebased source references","url":"history/records/2026/07/LK-20260716-destination-rebased-source-references.html","kind":"history","section":"history","keywords":"LK-20260716-destination-rebased-source-references 2026-07-16 74 fix compatible labkit.ui project serialization named save, explicit autosave, and recovery","text":"Destination-rebased source references id: LK-20260716-destination-rebased-source-references date: 2026-07-16 sequence: 74 type: fix compatibility: compatible component: `labkit.ui` | `6.0.4 -> 6.0.5` scope: project serialization scope: named save, explicit autosave, and recovery Context Runtime source records were created before an App knew its future project-file location, so they began with an empty `relativePath`. Project-envelope creation copied those records unchanged. As a result, the resolver supported relative references, but ordinary Runtime V2 saves did not actually generate them. Decision and rationale Rebase source references at the serialization boundary using the actual MAT destination. A named project, app-owned autosave, and framework recovery file can live in different folders, so each write must calculate its own relative path rather than reusing a path cached in live state. Changes Passed the real destination path into every Runtime V2 envelope writer. Copied the durable project and refreshed reference schema, relative path, original path, and filename immediately before serialization. Preserved additive app/reference fields while replacing standard path fields. Kept resolved absolute paths in live state so current-session readers do not need to interpret portable references. User and data impact A project and its source directory can now move together to another root or machine and reopen through the saved relative relationship even when the old absolute path no longer exists. Existing projects without a relative path remain readable through their original-path and relink fallbacks. Compatibility and migration The envelope and source-reference schema versions are unchanged. No payload migration is needed: the next named save, explicit autosave, or recovery write adds the destination-correct relative path. Validation The Runtime V2 project GUI test saves a source/project directory tree, verifies the generated relative path, moves the tree, and reloads from the new root. Video Marker verifies that its source-adjacent explicit autosave is rebased from the autosave destination. Existing atomic-save, relink, recovery, and additive field tests remain in the same focused suites. Evidence [Runtime and Data Model](../../../../framework/guides/runtime.md) explains when relative paths are created and how they are resolved. [UI Framework](../../../../framework/README.md) describes Runtime V2 project ownership and persistence. Known limitations and follow-up The portable-reference creation and resolution algorithms remain public in UI 6 for compatibility. They are implementation mechanics rather than App-facing workflow APIs and are reviewed with the remaining Runtime public surface for a single future major-boundary cleanup."},{"title":"Documentation navigation follows topic hierarchy","url":"history/records/2026/07/LK-20260717-hierarchical-documentation-navigation.html","kind":"history","section":"history","keywords":"LK-20260717-hierarchical-documentation-navigation 2026-07-17 132 docs compatible MATLAB documentation site navigation","text":"Documentation navigation follows topic hierarchy id: LK-20260717-hierarchical-documentation-navigation date: 2026-07-17 sequence: 132 type: docs compatibility: compatible scope: MATLAB documentation site navigation Context The documentation site exposed its major areas in the top bar, but each area's left sidebar was a flat list. Related app families, development tasks, and API packages were not visually distinguished, so readers could not readily see the path from an area to a topic and then to a specific page. Decision and rationale Render contextual hierarchy from the existing page and catalog metadata. App families, libraries, development tasks, and MATLAB packages become labeled sidebar branches. This preserves one structured source of navigation truth while making downstream topics visible from their area landing pages. Changes Grouped app navigation by family and indented app manuals below family pages. Grouped function, framework, and development navigation by topic. Grouped API siblings under their owning MATLAB package. Defined the second `nav` entry in `docs/site.json` as the branch label and ordered branches by their earliest page. Added responsive sidebar styling and renderer regression coverage for the hierarchy. User and data impact Readers can move from a top-level area to a topic branch and then to a specific manual without searching a flat list. Documentation content, URLs, MATLAB behavior, and scientific data are unchanged. Compatibility and migration Existing page URLs and top-level navigation remain compatible. Documentation authors should assign new multi-page topics a stable second-level `nav` label in `docs/site.json`. Validation The documentation renderer regression verifies Development topic groups, App family branches, and API package labels. `docsCheck` rebuilds and compares the complete generated site. Evidence The generated area landing pages contain labeled branch headings and parent/child link classes derived from tracked page and app catalog metadata. Known limitations and follow-up Single-page areas do not display an artificial empty branch. History records remain in the generated timeline because reproducing the full record set in the sidebar would make navigation less usable."},{"title":"Documentation rendering avoids repeated model capture","url":"history/records/2026/07/LK-20260717-documentation-render-performance.html","kind":"history","section":"history","keywords":"LK-20260717-documentation-render-performance 2026-07-17 130 perf compatible MATLAB documentation renderer","text":"Documentation rendering avoids repeated model capture id: LK-20260717-documentation-render-performance date: 2026-07-17 sequence: 130 type: perf compatibility: compatible scope: MATLAB documentation renderer Context Two project documentation tests each rebuild the complete tracked site. Profiling showed that most render time was not file generation or tree comparison: every inline Markdown fragment created an anonymous function that captured the complete documentation model before scanning links. Decision and rationale Pass the shared model and current page directly into token protection and dispatch code, image, and link rendering by token type. This preserves the renderer contract and generated bytes while avoiding thousands of large closure allocations. Changes Replaced per-fragment renderer closures with direct token dispatch. Retained the same code, image, link, emphasis, and nested inline-code behavior. Added a focused regression guard against restoring the measured closure pattern. User and data impact Generated HTML is byte-for-byte unchanged. Documentation rebuilds, consistency checks, and the launcher documentation action complete substantially sooner. Compatibility and migration `renderLabKitDocs`, `checkLabKitDocs`, `buildtool docs`, and `buildtool docsCheck` retain their existing syntax, outputs, and failure behavior. No migration is required. Validation On the same MATLAB R2025a host and complete `checkLabKitDocs` scenario, profiler total time decreased from 46.52 seconds to 9.66 seconds, approximately 79 percent. The after-profile run also completed the byte comparison against the tracked site. Evidence The focused renderer regression protects inline link behavior and the allocation fix. `docsCheck` independently rebuilds all narrative and API pages and compares every generated file. Known limitations and follow-up Absolute times vary by machine and filesystem. API help rendering is now the largest measured renderer-owned cost and should be changed only after a separate profile identifies a safe optimization."},{"title":"Documentation search ranks page intent and scopes history explicitly","url":"history/records/2026/07/LK-20260720-ranked-and-scoped-documentation-search.html","kind":"history","section":"history","keywords":"LK-20260720-ranked-and-scoped-documentation-search 2026-07-20 146 docs compatible Documentation system tools/docs/ site search","text":"Documentation search ranks page intent and scopes history explicitly id: LK-20260720-ranked-and-scoped-documentation-search date: 2026-07-20 sequence: 146 type: docs compatibility: compatible scope: Documentation system scope: tools/docs/ scope: site search Context The generated documentation search treated every term in a page title, body, and rendered component-history links as equivalent. A shared history record could therefore make an unrelated App page appear before the App named by a search. Readers also had no way to restrict a query to history records. Decision and rationale Keep component-history links visible on their related pages, but index history records as their own searchable documents. Give title and explicit keywords more weight than body text, then provide a visible section filter for readers who want Apps, APIs, history, or another documentation area. Changes Added section and keyword fields to generated search entries. Excluded rendered component-history link text from narrative page search bodies while retaining complete history-record search entries. Added deterministic field-weighted ranking, result excerpts, and section filtering to the offline-safe client search. Added regression coverage for search-entry separation, filters, and ranking contracts. User and data impact Searching a named App now favors that App and its family over unrelated pages that share a historical record. Readers can search only History when tracing a decision. No App behavior, calculations, projects, exports, or laboratory data changes. Compatibility and migration The generated site remains usable from file:// without network fetches. Existing documentation URLs, history records, and page-level change-history links remain unchanged. Validation Focused documentation search contract tests verify the generated index and client controls. Documentation rendering and consistency checks regenerate the tracked site and validate the history record. Evidence renderLabKitDocs now emits separated search fields. The client script ranks title, keyword, and body matches independently and filters by the generated section field. Known limitations and follow-up Search is intentionally dependency-free and uses exact lexical matching. Typo-tolerant suggestions can be considered later only if they preserve the current deterministic title-first ordering."},{"title":"Documentation structure becomes the navigation contract","url":"history/records/2026/07/LK-20260719-path-derived-documentation.html","kind":"history","section":"history","keywords":"LK-20260719-path-derived-documentation 2026-07-19 136 refactor compatible labkit_launcher Documentation renderer Documentation maintenance","text":"Documentation structure becomes the navigation contract id: LK-20260719-path-derived-documentation date: 2026-07-19 sequence: 136 type: refactor compatibility: compatible component: `labkit_launcher` | `1.5.1 -> 1.5.2` scope: Documentation renderer scope: Documentation maintenance Context The local HTML site duplicated documentation ownership across Markdown files, `site.json`, two catalog JSON files, and generated output. Moving or adding a page therefore required several coordinated edits. Markdown links also embedded many relative paths without a repository-owned way to repair links after a move. Decision and rationale Treat the `docs/` directory structure as the narrative navigation contract. Discover public Apps from `labkit_launcher(\"list\")`, associate each App with its unique path-conventional manual, and discover public API pages from complete MATLAB help contracts. Keep ordinary relative Markdown links so source pages remain useful in GitHub and add a checker that can repair a broken link only when its destination is unambiguous. The generated `site/` remains the offline and local HTML product. It is still produced exclusively by the repository renderer. Changes Removed `docs/site.json` and the App and API catalog JSON files. Reorganized development and framework guides into path-owned navigation groups and moved the API landing page to `docs/reference/README.md`. Made the renderer discover Markdown pages, public Apps, and complete public MATLAB help without duplicated registries. Added `maintainLabKitDocLinks` for checking and uniquely repairing relative Markdown links after file moves. Changed launcher documentation lookup to derive the selected App manual and generated HTML path from App discovery and documentation structure. Removed obsolete compatibility redirect pages instead of preserving legacy documentation routes. User and data impact Local HTML documentation and launcher documentation actions remain available. Contributors add or move a page by placing Markdown in the intended directory and running the link maintainer and renderer; no navigation catalog needs a matching edit. Scientific behavior and project data are unchanged. Compatibility and migration Existing current documentation links are rewritten to their new relative paths. Old generated documentation routes are intentionally retired and are not maintained as redirect pages. Rebuild `site/` after updating a checkout. Validation Focused documentation contract and renderer regression tests cover discovery, navigation, link repair, public API help, and launcher documentation lookup. The documentation checker validates the generated site after a clean local render. Evidence [Documentation Build Tools](../../../../development/tools/documentation.md) [Maintaining LabKit Documentation](../../../../development/maintain-and-release/documentation.md) [LabKit Launcher](../../../../apps/labkit-core/launcher/README.md) [API Reference](../../../../reference/README.md) Known limitations and follow-up Automatic link repair deliberately refuses ambiguous destinations. Authors must resolve those cases explicitly so the checker never guesses between pages with the same filename or title."},{"title":"Durable Video Marker source metadata","url":"history/records/2026/07/LK-20260716-video-marker-durable-metadata.html","kind":"history","section":"history","keywords":"LK-20260716-video-marker-durable-metadata 2026-07-16 70 feat compatible labkit_VideoMarker_app project payload schema downstream gait inputs","text":"Durable Video Marker source metadata id: LK-20260716-video-marker-durable-metadata date: 2026-07-16 sequence: 70 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `1.4.1 -> 1.5.0` scope: project payload schema scope: downstream gait inputs Context Video Marker previously kept frame rate, frame count, duration, and image size only in transient decoded-video state. Its MAT project preserved coordinates but a GUI-independent downstream analysis could not recover time semantics without reopening the original video or inventing a frame rate. Decision and rationale Make immutable decoded-video facts part of the durable Video Marker project. The portable source record continues to own the file path; the metadata record contains only finite numeric facts required to interpret saved annotations. Changes Advanced the Video Marker project payload from version 1 to version 2. Added durable frame count, frame rate, duration, height, and width. Populate metadata when a video or marker CSV is opened and seed rebuilt sessions from saved metadata before optional source decoding. Refresh durable metadata from the reopened source before an explicit **Save autosave**, so a loaded version 1 autosave upgrades in place. Validate metadata values and annotation/frame-count consistency. Added an ordered v1-to-v2 migration that recovers frame count from saved annotation coordinates and leaves unknowable source facts at zero. User and data impact New named projects and source-adjacent autosaves contain enough timing and geometry information for Gait Analysis to treat the MAT document as its authoritative input. Paths are not duplicated into the metadata record. Compatibility and migration Version 1 LabKit project envelopes remain readable. Opening one marks it for an upgrade; saving writes payload version 2. A frame rate absent from version 1 cannot be inferred from coordinates alone and remains zero until the source is opened by the current Video Marker app. Validation Video Marker unit tests verify metadata selection, absence of transient paths, frame-count recovery, payload validation, and mismatched annotation rejection. The existing GUI suite covers video opening, marker CSV import, project saves, and recovery loading. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the persisted fields and downstream use. Gait Analysis input tests exercise the current payload contract separately. Known limitations and follow-up Projects whose old payload never recorded a frame rate must reopen their source video before producing a fully self-describing current project. Opening that source through the saved reference and pressing **Save autosave** performs the upgrade without another location prompt."},{"title":"ECG Print uses one project contract","url":"history/records/2026/07/LK-20260716-ecg-print-structure.html","kind":"history","section":"history","keywords":"LK-20260716-ecg-print-structure 2026-07-16 106 refactor compatible labkit_ECGPrint_app Wearable App structure","text":"ECG Print uses one project contract id: LK-20260716-ecg-print-structure date: 2026-07-16 sequence: 106 type: refactor compatibility: compatible component: `labkit_ECGPrint_app` | `1.4.2 -> 1.4.3` scope: Wearable scope: App structure Context ECG Print kept product metadata in separate requirement and version functions, and split one durable schema across project creation, validation, migration, and session reconstruction files under a generic lifecycle package. Session and presentation code also read the Runtime-owned portable-reference structure directly. Decision and rationale Use the compact Runtime V2 App structure. The definition owns product metadata and optional capabilities, one project contract owns durable schema evolution, and one root session factory rebuilds decoded signal state. ECG-specific parsing, analysis, presentation, and result packages already express real workflow capabilities and remain App-owned. Changes Consolidated command identity, display metadata, version, requirements, layout, actions, presenter, renderer, and debug capability in `definition`. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved transient recording and analysis reconstruction to root `createSession` and reused that factory when clearing a failed decode. Replaced nested portable-reference reads with semantic `sourcePaths` lookup. Removed the generic lifecycle package and separate requirement/version files. Updated the App package structure guardrail so actions, presentation, project persistence, and transient sessions are optional capabilities; Apps that adopt root `projectSpec` cannot regress to split lifecycle metadata. User and data impact Recording import, parsing controls, channel selection, filtering, peak detection, segmentation, template construction, SNR measurements, plots, exports, manifests, project reopening, and user wording are unchanged. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still move their singular source record into the canonical source collection before validation. Runtime V2 now invokes the single migration entry and owns iteration to the current payload version. Current version-2 project files require no data transformation. Validation Focused GUI-free tests cover the migration callback, definition contract, import parsing, analysis products, presentation models, and result tables. The hidden GUI workflow covers launch, recording import, analysis, plots, both exports, project save, and session reconstruction after load. Project guardrails cover the compact optional-capability structure, embedded metadata, version/history ownership, and generated documentation consistency. Evidence [ECG Print](../../../../apps/wearable/ecg-print/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up Automated tests do not judge ECG morphology, peak quality, or the visual suitability of exported waveforms for a particular experiment. Remaining Runtime V2 App migrations and the stale agent guidance require separate review."},{"title":"EIS consolidates its product and project contracts","url":"history/records/2026/07/LK-20260716-eis-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-eis-project-spec 2026-07-16 89 refactor compatible labkit_EIS_app Electrochemistry Project lifecycle","text":"EIS consolidates its product and project contracts id: LK-20260716-eis-project-spec date: 2026-07-16 sequence: 89 type: refactor compatibility: compatible component: `labkit_EIS_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context EIS split static product metadata across three files and one first-version project schema across a generic lifecycle package. Those files did not own separate impedance or plotting contracts. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because restoring decoded ZCURVE items and source selection is genuine transient work. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved decoded curve and selection restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Delayed log-scale assignment until filtered positive data establishes valid automatic limits, and corrected the GUI regression fixture to use a legal positive manual range while testing stale-zoom replacement. Kept impedance values, Nyquist/Bode axis semantics, log scaling, zoom behavior, and export schemas unchanged. User and data impact Launch, multi-file selection, plotting, zoom, save/load, and export behavior remain unchanged. Project and transient state ownership are now adjacent and explicit. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing EIS projects require no migration. Validation Unit tests cover ZCURVE parsing, axis values, Nyquist mode, log scaling, project/session contracts, presentation, and export columns. The hidden GUI workflow covers real launch, file loading, plot interaction, save/load, and export. Evidence [EIS](../../../../apps/electrochemistry/eis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Source restoration still consumes portable-reference internals. The planned shared source-path service will remove that leak across all Apps together."},{"title":"EIS delegates source identity reconciliation to Runtime V2","url":"history/records/2026/07/LK-20260716-eis-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-eis-runtime-source-reconciliation 2026-07-16 110 refactor compatible labkit_EIS_app Electrochemistry App structure Runtime adoption","text":"EIS delegates source identity reconciliation to Runtime V2 id: LK-20260716-eis-runtime-source-reconciliation date: 2026-07-16 sequence: 110 type: refactor compatibility: compatible component: `labkit_EIS_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context EIS already stored only portable source records in its durable project, but its action file still generated source IDs, appended records, and removed records with App-local helpers. That duplicated Runtime V2's source reconciliation service and made the App responsible for an identity invariant owned by project persistence. The EIS session factory also repeated empty workflow and view buckets even though Runtime V2 canonicalizes missing transient buckets. Decision and rationale Use `services.project.reconcileSources` as the single source-record owner after successful file additions, removals, and clearing. The EIS workflow continues to decide which decoded files are accepted and their order; Runtime preserves matching source identities and allocates collision-free identities for new files. Return only EIS-specific selection and decoded-cache state from `createSession`. Runtime supplies the empty workflow and view buckets before the state becomes visible to actions or presenters. Changes Removed EIS-local source-ID generation, append, and removal helpers. Reconciled durable source records from the successfully decoded EIS items. Removed empty workflow and view boilerplate from the session factory. Added GUI assertions for canonical Runtime buckets, unique source IDs, and identity preservation across additions and project reopen. Advanced the EIS App version to 1.4.4. User and data impact File selection, decoding, plotting, export, save, and reopen behavior are unchanged. Existing saved source IDs remain stable when their paths match. Newly added records receive Runtime-managed IDs. IDs are persistence identities and are not displayed as scientific results. Compatibility and migration The project payload remains version 1 and needs no migration. Existing project files continue to load through their stored portable references. Validation The EIS hidden GUI workflow covers two-file loading from separate folders, selection, plotting, export, project save, clear, and reopen. Focused unit and contract tests cover EIS calculations, App structure, version ownership, documentation, and history ordering. Evidence [EIS](../../../../apps/electrochemistry/eis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up Other multi-file Apps still contain equivalent local source-list helpers. They should adopt the same Runtime service only after their role and ordering semantics are verified by their own focused tests."},{"title":"EIS uses explicit plot-view actions","url":"history/records/2026/07/LK-20260721-eis-explicit-plot-views.html","kind":"history","section":"history","keywords":"LK-20260721-eis-explicit-plot-views 2026-07-21 147 fix compatible labkit_EIS_app EIS Overlay","text":"EIS uses explicit plot-view actions id: LK-20260721-eis-explicit-plot-views date: 2026-07-21 sequence: 147 type: fix compatibility: compatible component: `labkit_EIS_app` | `1.5.1 -> 1.5.2` scope: EIS Overlay Context EIS inferred an equal X/Y aspect ratio from specific Nyquist axis selections. That implicit choice could shrink the usable plotting area and make a redraw look unlike the user's current view. Decision and rationale Keep plot semantics in EIS, but use the App framework's viewport revision and data-fitting capability for view behavior. Equal data units are now an explicit one-time view reset rather than a rule tied to axis labels or a constraint on later zooming. Changes Removed the EIS axis-label rule that automatically selected `axis equal`. Added **Fit X/Y limits** for independent data-fitted limits. Added **Use equal X/Y scale** for an explicit equal-data-unit view without changing the plot size allocated by the App layout. Kept ordinary zoom and wheel viewports under the framework-managed viewport preservation path. User and data impact Nyquist plots now begin with independent limits and a stable plot area. Equal scaling preserves the App layout's plot size. The new view buttons change only transient presentation; EIS sources, projects, calculations, and exports are unchanged. Compatibility and migration Existing saved projects remain valid. The selected axes and Log X/Y options keep their existing meaning; the equal-aspect choice is intentionally not saved with the project. Validation The EIS GUI workflow test covers both buttons, independent fitting, explicit equal aspect, and preservation of an ordinary manually chosen viewport. Evidence The renderer now uses `labkit.app.plot.fitAxesToGraphics` for both independent fits and equal-data-unit limits, and receives a new view revision only from an explicit view action. Known limitations and follow-up Automated GUI coverage cannot assess visual balance on every monitor and MATLAB release. Verify the Nyquist layout interactively before a release. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data."},{"title":"Electrochem Apps stop reading portable references","url":"history/records/2026/07/LK-20260716-electrochem-source-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-electrochem-source-boundary 2026-07-16 92 refactor compatible labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app Electrochemistry Runtime source boundary","text":"Electrochem Apps stop reading portable references id: LK-20260716-electrochem-source-boundary date: 2026-07-16 sequence: 92 type: refactor compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.2 -> 1.4.3` component: `labkit_CIC_app` | `1.4.2 -> 1.4.3` component: `labkit_CSC_app` | `1.4.2 -> 1.4.3` component: `labkit_EIS_app` | `1.4.2 -> 1.4.3` component: `labkit_VTResistance_app` | `1.4.2 -> 1.4.3` scope: Electrochemistry scope: Runtime source boundary Context The Electrochem Apps already delegated portable source creation, save-time rebasing, and load-time relinking to Runtime V2. Their session factories, source loaders, actions, and presenters nevertheless read the runtime's nested path field directly. CIC and VT Resistance also carried duplicate local path extraction functions. Decision and rationale Use the public Runtime source-path accessor at every App boundary. This keeps lazy-loading and batch-selection policies App-owned while removing knowledge of the portable-reference storage schema. Changes Replaced direct nested-reference reads in all five Electrochem Apps. Removed duplicate CIC and VT Resistance path extraction functions. Preserved source order and lazy first-item decoding. Kept DTA parsing, formulas, thresholds, plots, exports, and error wording unchanged. User and data impact File selection, project reopen/relink, calculation, preview, and export behavior are unchanged. App code now expresses only its workflow use of paths; Runtime V2 owns how those paths are stored and resolved. Compatibility and migration No project schema or saved source record changed. Existing projects require no migration and remain compatible with the same UI 7 contract range. Validation The Electrochem unit and hidden-GUI suites cover project reconstruction, DTA loading, selection, plotting, computation, export, and saved-state workflows. Package and version guardrails verify the shared API and component metadata. Evidence [Electrochemistry Apps](../../../../apps/electrochemistry/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other App families still contain direct reads and will migrate in their own behavior-tested commits before a repository-wide no-leak guard is enabled."},{"title":"Electrochemistry source-field validation boundary","url":"history/records/2026/07/LK-20260716-electrochem-source-field-validation.html","kind":"history","section":"history","keywords":"LK-20260716-electrochem-source-field-validation 2026-07-16 118 fix compatible labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app electrochemistry project schemas malformed project rejection","text":"Electrochemistry source-field validation boundary id: LK-20260716-electrochem-source-field-validation date: 2026-07-16 sequence: 118 type: fix compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.5 -> 1.4.6` component: `labkit_CIC_app` | `1.4.5 -> 1.4.6` component: `labkit_CSC_app` | `1.4.5 -> 1.4.6` component: `labkit_EIS_app` | `1.4.5 -> 1.4.6` component: `labkit_VTResistance_app` | `1.4.5 -> 1.4.6` scope: electrochemistry project schemas scope: malformed project rejection Context Runtime owns the format of a portable source record but intentionally allows a project whose `inputs` bucket has no `sources` field. Static and embedded-data Apps may not use external sources. The first electrochemistry validator reduction correctly removed record-format duplication but also stopped declaring that these five file-backed Apps require a source collection. Decision and rationale Keep field presence with the App schema and record shape with Runtime. This is the narrow ownership split: the App decides whether sources are required; Runtime decides what every supplied source record means. Changes Restored the `inputs.sources` presence requirement in all five electrochemistry project validators. Added a GUI-free unit contract that accepts each default project and rejects the same project after its App-required source collection is removed. Kept canonical bucket and source-record field validation out of the Apps. User and data impact Valid projects are unchanged. A malformed electrochemistry project missing its entire source collection is rejected by the App validator before session reconstruction instead of failing later while decoded data are rebuilt. Compatibility and migration No saved format changed and no migration is required. Every project created or saved by these Apps already contains `inputs.sources`. Validation The focused project-spec test covers all five accepted defaults and all five missing-source rejection cases. The Runtime project and electrochemistry GUI tests continue to cover canonical record validation and working file flows. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) separates framework-owned record shape from App-owned fields and roles. The [Electrochemistry App manuals](../../../../apps/electrochemistry/README.md) identify the required App source collection. Known limitations and follow-up The same distinction must be applied explicitly while reducing validators in other App families; a generic source format does not imply that every App has the same required source fields or roles."},{"title":"Explicit application autosave","url":"history/records/2026/07/LK-20260716-explicit-autosave.html","kind":"history","section":"history","keywords":"LK-20260716-explicit-autosave 2026-07-16 68 feat compatible labkit.ui labkit_VideoMarker_app Runtime V2 project recovery Video Marker session workflow","text":"Explicit application autosave id: LK-20260716-explicit-autosave date: 2026-07-16 sequence: 68 type: feat compatibility: compatible component: `labkit.ui` | `6.0.2 -> 6.0.3` component: `labkit_VideoMarker_app` | `1.3.0 -> 1.4.0` scope: Runtime V2 project recovery scope: Video Marker session workflow Context Runtime V2 already wrote debounced recovery generations after durable edits, but an app could only expose the named-project save operation. Video Marker users who wanted to force the current recovery point had no direct control and using **Save State** introduced a destination prompt for an unnamed project. Decision and rationale Expose the existing framework-owned recovery writer as an injected action service. The service accepts the current action state, writes the same bounded recovery generations as the timer, and deliberately leaves the named project path and dirty status unchanged. Video Marker exposes that operation as **Save autosave** in its Session panel. Changes Factored the atomic recovery-generation writer out of the timer scheduler. Added `services.project.saveAutosave(state)` for an immediate, pathless recovery write. Added the Video Marker **Save autosave** button and visible workflow log acknowledgement. Kept ordinary **Save State** as the separate operation for choosing or updating a named project file. User and data impact Clicking **Save autosave** does not open a native file dialog. It updates the current app/document recovery file and retains one previous generation. It does not make the recovery file the active named project and does not suppress later unsaved-close protection. Compatibility and migration Existing projects, recovery files, and automatic autosave behavior remain compatible. Apps that do not expose the new service behave exactly as before. Validation The focused Video Marker GUI workflow verifies that the button immediately creates a recovery file while the runtime document path remains empty. The existing Runtime V2 project test continues to cover debouncing, bounded generations, and recovery loading. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) documents the injected recovery operation and its distinction from named saves. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the Session button and no-dialog behavior. Known limitations and follow-up An autosave is a recovery aid, not a user-named archival project. Users still use **Save State** when they need a deliberate project filename or location."},{"title":"Explicit layout action contract","url":"history/records/2026/07/LK-20260717-explicit-layout-action-contract.html","kind":"history","section":"history","keywords":"LK-20260717-explicit-layout-action-contract 2026-07-17 123 refactor compatible labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Runtime V2 layout callback resolution public App maintenance cost","text":"Explicit layout action contract id: LK-20260717-explicit-layout-action-contract date: 2026-07-17 sequence: 123 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.5 -> 1.4.6` component: `labkit_DICPreprocess_app` | `1.5.6 -> 1.5.7` component: `labkit_ChronoOverlay_app` | `1.4.6 -> 1.4.7` component: `labkit_CIC_app` | `1.4.6 -> 1.4.7` component: `labkit_CSC_app` | `1.4.6 -> 1.4.7` component: `labkit_EIS_app` | `1.4.6 -> 1.4.7` component: `labkit_VTResistance_app` | `1.4.6 -> 1.4.7` component: `labkit_GaitAnalysis_app` | `2.0.6 -> 2.0.7` component: `labkit_BatchImageCrop_app` | `1.7.5 -> 1.7.6` component: `labkit_CurvatureMeasurement_app` | `1.4.5 -> 1.4.6` component: `labkit_FLIRThermal_app` | `1.4.5 -> 1.4.6` component: `labkit_FocusStack_app` | `1.5.4 -> 1.5.5` component: `labkit_ImageEnhance_app` | `1.6.5 -> 1.6.6` component: `labkit_ImageMatch_app` | `1.6.5 -> 1.6.6` component: `labkit_VideoMarker_app` | `1.5.5 -> 1.5.6` component: `labkit_FigureStudio_app` | `0.2.7 -> 0.2.8` component: `labkit_NerveResponseAnalysis_app` | `1.4.5 -> 1.4.6` component: `labkit_ResponseReviewStats_app` | `1.4.5 -> 1.4.6` component: `labkit_RHSPreview_app` | `1.4.5 -> 1.4.6` component: `labkit_ECGPrint_app` | `1.4.5 -> 1.4.6` scope: Runtime V2 layout callback resolution scope: public App maintenance cost Context Every public App layout carried the same local `callbackValue` helper. The helper returned an empty value when a callback field was absent, so a misspelled or unregistered action could survive startup as a control that silently did nothing. Twenty copies also obscured that Runtime already creates the complete callback table from each App definition. Decision and rationale Layouts reference Runtime-generated callbacks directly. MATLAB field access therefore validates the action ID while the data-only layout is built. Runtime remains the single callback-adapter owner; Apps own only their semantic action registries and layout references. Changes Removed the duplicated callback fallback from all 20 public App layouts. Replaced static lookups with direct `callbacks.actionId` references and kept dynamic field access only in three small controls whose action names are supplied by the surrounding layout. Added a fleet-wide contract that creates every App definition and layout with the Runtime callback inventory. Updated direct neurophysiology layout tests to supply explicit callbacks derived from their action registries. User and data impact Correctly registered controls behave as before. A broken App definition now fails during layout construction instead of presenting an inert control. No scientific calculation, project payload, saved file, or interaction semantics changed. Compatibility and migration No project migration is required. This is an App-development contract tightening: custom layouts must register every referenced semantic action. Validation The focused fleet contract builds every public App layout and catches missing or misspelled callback fields. The App package guardrail rejects reintroduced silent callback fallbacks, and focused workflow-layout tests cover direct construction outside a running figure. Evidence [Runtime layout and action rules](../../../../framework/guides/runtime.md#layout-and-action-rules) [Apps](../../../../apps/README.md) [App development](../../../../development/build-apps/app-development.md) Known limitations and follow-up This change deliberately does not merge short semantic actions or dynamic control helpers merely to reduce line count. Those remain candidates only when a repeated behavior has one stable, domain-neutral owner."},{"title":"FLIR Thermal separates durable annotations from decoded sources","url":"history/records/2026/07/LK-20260716-flir-thermal-structure.html","kind":"history","section":"history","keywords":"LK-20260716-flir-thermal-structure 2026-07-16 98 refactor compatible labkit_FLIRThermal_app Image Measurement App structure","text":"FLIR Thermal separates durable annotations from decoded sources id: LK-20260716-flir-thermal-structure date: 2026-07-16 sequence: 98 type: refactor compatibility: compatible component: `labkit_FLIRThermal_app` | `1.4.2 -> 1.4.3` scope: Image Measurement scope: App structure Context FLIR Thermal split static metadata across files, spread a version-1 project over a generic lifecycle package, grouped decoded records, durable annotations, and numerical reading updates under `+appState`, and read nested portable reference fields in session, actions, and presentation. Its startup callback also selected an output directory before the user loaded a source. Decision and rationale Use the compact Runtime V2 contract while preserving the boundary between decoded radiometric data and the lightweight annotations safe to persist. Assign each remaining helper to the capability it describes instead of moving thermal semantics into the framework. Changes Consolidated product metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated durable project creation and validation in `projectSpec.m` and moved selected-image reconstruction to root `createSession.m`. Moved decoded item shape to `+sourceFiles`, point/ROI updates to `+analysisRun`, and persistent per-image readings to `+thermalAnnotations`. Removed separate metadata files, generic lifecycle/state packages, and the redundant App startup callback. Replaced all direct portable-reference field access with the Runtime source path accessor. User and data impact Radiometric decoding, Celsius conversion, range controls, point and ROI readings, viewport-preserving overlays, project reopen, and exports keep their existing behavior. Empty launch no longer chooses an output folder; adding sources still establishes the same source-adjacent default. Developers can find transient decoded matrices, scientific readings, and durable annotations under their actual owners without learning a generic state layer. Compatibility and migration The durable payload remains version 1 with identical fields and defaults. Existing projects require no migration. The removed App-specific debug line is replaced by the Runtime's standard debug-startup message. Validation Five unit methods cover radiometric import, thermal values, calibration diagnostics, range controls, independent ROIs, rendering mappings, manifests, and exported matrices. Two hidden GUI workflows cover multi-file selection, session caching, managed region interaction, project save/load reconstruction, shared ranges, and batch export. Evidence [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Thermal Library](../../../../libraries/thermal/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Image Measurement Apps still use generic lifecycle/state packages and will be reviewed individually. Automated GUI tests do not replace manual judgment of pointer feel, visual calibration, or camera-specific accuracy."},{"title":"FLIR display tuning","url":"history/records/2026/07/LK-20260703-flir-display-tuning.html","kind":"history","section":"history","keywords":"LK-20260703-flir-display-tuning 2026-07-03 36 feat compatible labkit_CSC_app labkit_FLIRThermal_app labkit_FLIRThermal_app labkit_FLIRThermal_app FLIR display tuning","text":"FLIR display tuning id: LK-20260703-flir-display-tuning date: 2026-07-03 sequence: 36 type: feat compatibility: compatible component: `labkit_CSC_app` | `1.3.6 -> 1.3.7` component: `labkit_FLIRThermal_app` | `1.2.4 -> 1.2.5` component: `labkit_FLIRThermal_app` | `1.2.5 -> 1.2.6` component: `labkit_FLIRThermal_app` | `1.2.6 -> 1.2.7` scope: FLIR display tuning Context The initial FLIR renderer used a fixed color transfer. Images with a narrow or skewed temperature distribution could therefore hide useful contrast even when the numeric range was correct. CSC voltage/current CSV output also needed clearer cycle organization for direct downstream use. Decision and rationale Separate the displayed color mapping from the temperature values. Apply gamma only while rendering the normalized image and expose it as a user control; keep the temperature matrix, point readings, ROI means, and exports numerically unchanged. Refine CSC CSV structure at its result writer rather than in the UI. Changes `labkit_FLIRThermal_app` `1.2.4 -> 1.2.7` `labkit_CSC_app` `1.3.6 -> 1.3.7` Refined CSC CV export. Added FLIR gamma color mapping and made gamma adjustable. User and data impact FLIR users could reveal contrast in hotter or cooler parts of an image without changing the underlying temperatures. The same gamma was used in displayed and exported color renderings. CSC users received a more directly usable voltage/current table. Compatibility and migration Existing FLIR inputs remained valid, and gamma affected rendered color only. Temperature matrices and measurement values did not require conversion. Validation The commits extended CSC export assertions, FLIR rendering and output tests, and the FLIR layout test for the gamma control. Exact historical commands were not recorded. Evidence Main commits `ee5b8f79`, `65dbf5ae`, and `f076561e`. Known limitations and follow-up Gamma changes visual contrast only. Quantitative interpretation must use the temperature values and scale, not colors sampled from the rendered image."},{"title":"Facade contract baseline and release validation hardening","url":"history/records/2026/06/LK-20260623-facade-contract-baseline-and-release-validation-hardening.html","kind":"history","section":"history","keywords":"LK-20260623-facade-contract-baseline-and-release-validation-hardening 2026-06-23 11 ci compatible labkit.biosignal labkit.dta labkit.rhs labkit.ui labkit.ui labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_CurvatureMeasurement_app Facade contract baseline and release validation hardening","text":"Facade contract baseline and release validation hardening id: LK-20260623-facade-contract-baseline-and-release-validation-hardening date: 2026-06-23 sequence: 11 type: ci compatibility: compatible component: `labkit.biosignal` | `new -> 1.0.0` component: `labkit.dta` | `new -> 1.0.0` component: `labkit.rhs` | `new -> 1.0.0` component: `labkit.ui` | `new -> 2.0.0` component: `labkit.ui` | `2.0.0 -> 2.1.0` component: `labkit.ui` | `2.2.0 -> 2.2.1` component: `labkit_DICPostprocess_app` | `1.0.0 -> 1.0.1` component: `labkit_DICPreprocess_app` | `1.0.0 -> 1.0.1` component: `labkit_CurvatureMeasurement_app` | `1.0.0 -> 1.0.1` scope: Facade contract baseline and release validation hardening Context Reusable packages exposed public MATLAB functions, but apps had no machine-checkable way to state which API versions they required. Release and CI checks also needed one consistent build-task entry path. Decision and rationale Give each reusable package version metadata and let apps declare compatible ranges that are checked at launch and in tests. Route MATLAB CI shards through the same build tasks used by maintainers so release validation exercises the documented commands. Changes `labkit.biosignal` `1.0.0` `labkit.dta` `1.0.0` `labkit.rhs` `1.0.0` `labkit.ui` `2.0.0 -> 2.2.1` DIC Pre/Post and Curvature `1.0.0 -> 1.0.1` Release tags `v2.4.1` and `v2.4.2` Added facade contract metadata and requirement checks. Hardened app lifecycle and release validation contracts. Routed MATLAB CI shards through build tasks. User and data impact An incompatible app/package combination could fail with a direct requirement message instead of producing a later missing-function error. Existing project and result data were unaffected. Compatibility and migration The contract metadata described the existing public facades and was additive for app callers. Apps with an incompatible declared requirement now failed at launch with a version diagnostic instead of continuing unpredictably. Validation The listed commits introduced contract, lifecycle, and CI build-task tests. Release tags `v2.4.1` and `v2.4.2` identify the shipped checkpoints; exact local commands were not recorded. Evidence Main commits `a25b79f9`, `3673e548`, `49d9f41b`, and `7e39b558`. Known limitations and follow-up App and launcher display versions were added in the separate version-metadata change later the same day."},{"title":"Figure Studio adopts one product definition","url":"history/records/2026/07/LK-20260716-figure-studio-single-definition.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-single-definition 2026-07-16 79 refactor compatible labkit_FigureStudio_app labkit.ui LabKit Core App structure","text":"Figure Studio adopts one product definition id: LK-20260716-figure-studio-single-definition date: 2026-07-16 sequence: 79 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.1 -> 0.2.2` component: `labkit.ui` | `7.2.0 -> 7.2.1` scope: LabKit Core scope: App structure Context Figure Studio declared its command, names, family, App version, and LabKit requirement in two files separate from the runtime definition. Its entrypoint joined all three factories even though they described one product. Decision and rationale Use Figure Studio as the first reviewed App migration to the single-definition contract. This validates the reduced structure on an App that also accepts a typed axes handoff, without changing its plotting or export behavior. Changes Moved exact product metadata and the UI requirement into `definition.m`. Reduced the public entrypoint to one definition plus its existing typed request adapter. Removed the redundant `requirements.m` and `version.m` files. Taught the version guard to use `AppVersion` in a migrated definition while comparing the first migration against the previous `version.m` baseline. Fixed the shared source reconciler so it creates the canonical empty source array without first constructing an invalid empty-ID placeholder record. Added `services.results.emptyOutputs()` and used it for Figure Studio's variable-length export manifest instead of constructing an invalid empty-ID placeholder output. User and data impact Launch, axes handoff, style controls, project payload, FIG reading, plot snapshot extraction, and all export formats retain their existing behavior. Maintainers now update one product contract instead of three files. Compatibility and migration The App command and project ID are unchanged. App version metadata requests still return the same fields through the public entrypoint. Existing project payloads remain at version 1 and require no data migration. Validation Focused tests cover ordinary launch, axes handoff, style changes, project round-trip, exports, launcher discovery, requirements/version requests, and version/history governance. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) documents the unchanged workflow and new definition ownership. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the shared product contract. Known limitations and follow-up Figure Studio still has a meaningful version-1 project schema, rebuildable plot session cache, and startup resource installation. These will move to the projectSpec/action capability model after the framework supports that model; they were not deleted merely to reduce file count."},{"title":"Figure Studio consolidates its project schema","url":"history/records/2026/07/LK-20260716-figure-studio-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-project-spec 2026-07-16 82 refactor compatible labkit_FigureStudio_app LabKit Core Project lifecycle","text":"Figure Studio consolidates its project schema id: LK-20260716-figure-studio-project-spec date: 2026-07-16 sequence: 82 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.2 -> 0.2.3` scope: LabKit Core scope: Project lifecycle Context Figure Studio's version-1 project creation and validation lived in separate files under a generic `+appLifecycle` package. The package name described a framework phase rather than the durable capability those functions owned. Decision and rationale Consolidate the complete durable schema behind `projectSpec.m`. Keep `createSession.m` separate at the App package root because it performs a different job: rebuilding transient selection and decoded plot cache from a validated project. Changes Added one project declaration with local create and validate functions. Moved session reconstruction to one explicitly named package-root entry. Removed the three-file generic `+appLifecycle` package. Left meaningful startup, action, presentation, source, style, and export capabilities unchanged. User and data impact Figure loading, axes handoff, styling, project save/load, session restoration, and exports behave unchanged. A maintainer can now understand the entire durable schema in one file. Compatibility and migration The App command, project ID, payload version, fields, validation, and source record format are unchanged. Existing Figure Studio projects need no migration. Validation The existing unit and hidden GUI suites cover FIG reading, source style, project round-trip, axes handoff, canvas resizing, quick export, and data package export through the consolidated project declaration. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The startup hook still owns axes handoff and resize-resource installation. It will be evaluated with other App startup hooks before any shared mount/action capability is introduced."},{"title":"Figure Studio derives its publication preset from the nine-panel reference","url":"history/records/2026/07/LK-20260721-figure-studio-nine-panel-calibration.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-nine-panel-calibration 2026-07-21 155 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio derives its publication preset from the nine-panel reference id: LK-20260721-figure-studio-nine-panel-calibration date: 2026-07-21 sequence: 155 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.1 -> 0.6.4` scope: LabKit Core Context The previous Figure Studio publication profile was described as a visual calibration even though its dimensions had been taken from a separate manual FIG example. It also allowed native figure chrome to reduce the exported drawable height. Decision and rationale Use only the maintained 3-by-3 scientific reference. Normalize every panel by its detected axes frame, measure typography and strokes in pixels, and select one robust profile from all nine panels. Keep those measurements app-local and size the chromeless export client area directly so the configured inner frame survives raster and vector export. Changes Recalibrated the reference plot frame, typography, semantic strokes, frame, grid, legend border, and legend token length from panels A through I. Set the data stroke to 6.5 pt after registered raster checks matched panel A exactly and reduced the remaining one-pixel deficits in panels E and H. Removed menu and toolbar chrome from temporary export figures and preserved the complete figure bounds through `exportgraphics`. Ignored zero-area text extents when computing outer margins so native log rulers cannot inflate an export canvas to tens of thousands of pixels. Added the calibrated 900 px plot width to the selectable sizes and made it the new-project default. Retained source token geometry for **FIG default** while applying the measured long token only to the LabKit preset. Replaced dynamic-array analyzer suppressions with bounded collection and documented the 72-to-96-PPI range used by cross-platform visual metrics. User and data impact New LabKit-style projects open at the nine-panel reference scale. Existing scientific values, limits, source graphics, FIG files, and saved projects are not recalculated. Exports retain the requested inner-frame aspect instead of losing native-window height. Compatibility and migration The style record remains extensible and existing projects stay readable. Switching an existing document to **LabKit figure** opts into the new profile; **FIG default** continues to use source presentation values. Validation Pixel-frame, OCR glyph-height, colored-stroke, and registered-overlay checks against all nine reference panels. Focused Figure Studio result-file and project tests. Hidden-GUI Figure Studio workflow tests. Native logarithmic-axis margin regression. Linux-compatible visual-ratio and analyzer-policy regressions. Repository `changedFast` validation. Evidence After width registration, the nine detected inner-frame height errors range from -1.71 to +2.16 pixels; panel A is exact. Shared OCR glyph heights have a median scale error below one percent. Reference and generated comparison rasters remain untracked validation artifacts. Known limitations and follow-up Raster antialiasing varies slightly by renderer and platform. Exact scientific curve coordinates are source data and are not part of the style preset."},{"title":"Figure Studio edits one native subplot panel at a time","url":"history/records/2026/07/LK-20260721-figure-studio-native-panel-editing.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-native-panel-editing 2026-07-21 153 feat compatible labkit_FigureStudio_app Figure Studio","text":"Figure Studio edits one native subplot panel at a time id: LK-20260721-figure-studio-native-panel-editing date: 2026-07-21 sequence: 153 type: feat compatibility: compatible component: `labkit_FigureStudio_app` | `0.4.0 -> 0.6.0` scope: Figure Studio Context Figure Studio reconstructed a limited portable snapshot from the first axes in a FIG. This could omit composite graphics such as traditional `boxplot` groups, gave no reliable way to choose a subplot from a mixed figure, and made export canvas controls look like live plot-resize controls. Decision and rationale Treat an opened FIG as a transient native document resource and select one axes from it by an ordered subplot panel choice. The selected axes remains the authoritative hierarchy for preview and editable/image export; the portable snapshot remains an explicit secondary data-package representation. This gives ordinary MATLAB charts a broader preservation path without promising that an arbitrary custom graphics class is portable. Use a fixed, visually calibrated single-panel default (1600 by 1333, 6:5). The workbench now displays a raster rendering of that real export canvas, fit as one image into the available workspace. Resizing the window never feeds back into style sizing; supersampling remains export-only. Provide an explicit automatic X/Y-limit recovery action instead of trying to infer limits during unrelated style changes. Changes Added ordered mixed-FIG subplot-panel selection; preview, styling, limits, and exports all operate on the selected axes only. Added private native-source cloning for popout handoff and FIG import, with cleanup through the existing document-resource lifecycle. Preserved native child hierarchies in preview and editable FIG export, including traditional grouped `boxplot` graphics and hidden-handle visual children. Preserved source tick labels and tick configuration when copying native axes. Replaced free-form preview resizing with source/fixed export-size choices, aspect choices, export supersampling, and a stable true-export preview. Added **Recalculate X/Y limits** and project persistence for explicit limit overrides. Migrated project schema 3 to remember the selected panel index. User and data impact Existing projects migrate automatically. A saved mixed FIG project restores the same selected panel when its source is available. Popout projects retain a portable snapshot after reload; their private native source is never saved. No scientific calculation, source file, or source axes is modified. Compatibility and migration Schema 1--3 projects migrate to schema 4. Schema 3 adds `panelIndex = 1`; earlier migrations retain their existing style and limit conversions. Validation Project-schema tests cover migrations through schema 4. Hidden-GUI tests cover four-panel FIG selection, native boxplot preservation for FIG import and popout handoff, export, limit recovery, and a stable export-canvas preview through workspace resizing. Focused visual review checks a generated single-panel scientific probe at the default 1600-by-1333 reference size. Evidence The focused test artifacts and generated local visual probe are supporting evidence. No external laboratory data was used. Known limitations and follow-up Developer-led visual review remains necessary for unusual custom charts, native dialogs, and publication-specific annotation overlap. Portable data packages intentionally support fewer object types than native FIG export."},{"title":"Figure Studio fixes publication-frame preview and axis controls","url":"history/records/2026/07/LK-20260721-figure-studio-plot-frame-preview.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-plot-frame-preview 2026-07-21 154 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio fixes publication-frame preview and axis controls id: LK-20260721-figure-studio-plot-frame-preview date: 2026-07-21 sequence: 154 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.0 -> 0.6.1` scope: LabKit Core Context Figure Studio had treated a requested output size as a whole figure canvas. That made labels and titles consume the configured plot area, and resizing the preview could make the displayed typography unrelated to the eventual export. Copied UIAxes also needed a portable reconstruction path for reliable export. Decision and rationale Treat the requested width and aspect as the inner axes frame. Keep all publication-style calibration within Figure Studio, then calculate an outer figure from the live rendered text extents so visual styling cannot reduce the configured data area. Changes Defined the plot width and aspect controls as the inner axes frame; export now calculates an outer figure around that fixed frame from visible labels, ticks, titles, legends, and annotations. Calibrated the LabKit publication preset from the maintained single-panel visual measurements, including category-specific typography and strokes. Kept the preview as an interactive axes, reflowing its display scale when its allocated area changes without changing saved/export settings. Added explicit X/Y minimum and maximum controls, plus visible-data recalculation with a finite-data envelope expanded by 50 percent. Rebuilt UIAxes sources through the portable renderer for conventional export, retaining displayed scientific axis exponents. User and data impact Existing Figure Studio projects remain compatible. Existing style sizes retain their meaning as a plot frame; the enclosing export figure may grow to avoid clipping external text. No scientific calculations or source figure data are changed. Compatibility and migration The project schema migrates existing style records without changing their stored data. UIAxes exports use the portable display snapshot only for the screen-specific aspect geometry that cannot be transferred to normal axes. Validation Focused Figure Studio result-file and source-axes tests. Hidden-GUI Figure Studio layout tests, including UIAxes export and preview reflow behavior. Pixel-scale visual metrics for the calibrated single-panel output. Evidence The calibrated profile fixes the inner frame at 1237 by 942 pixels. Its generated output reports that same axes size while the enclosing figure grows only for required outer text and annotations. Known limitations and follow-up Manual review remains appropriate for native MATLAB graphics that cannot be represented by the portable package."},{"title":"Figure Studio names its post-layout initialization capability","url":"history/records/2026/07/LK-20260716-figure-studio-initializer.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-initializer 2026-07-16 109 refactor compatible labkit_FigureStudio_app LabKit Core App structure","text":"Figure Studio names its post-layout initialization capability id: LK-20260716-figure-studio-initializer date: 2026-07-16 sequence: 109 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.4 -> 0.2.5` scope: LabKit Core scope: App structure Context Figure Studio was the only public App with a generic `startup.m` file. Its definition already declared the function through Runtime V2's optional `Start` capability, but the filename obscured who invoked it and made the function look like an independent lifecycle entrypoint. Decision and rationale Keep the Runtime-owned `Start` hook because Figure Studio genuinely needs a post-layout phase: it consumes an optional axes handoff, resolves the managed preview axes, and registers a cleanup-owned resize resource. Rename the App callback to `initializeWorkbench` so its capability and invocation boundary are explicit. Do not move GUI/service work into the GUI-free session factory or reintroduce App-owned timers and readiness state. Changes Renamed the Start callback from generic `startup` to `initializeWorkbench` and declared the new handle in `definition`. Documented the callback timing and the request, preview, resource, dialog, workflow, and debug services supplied by Runtime V2. Tightened the App structure guardrail so compact Apps cannot add a generic `startup.m` file outside their declared capability vocabulary. Advanced the Figure Studio App version to 0.2.5. User and data impact Normal launch, default output folder selection, popout axes handoff, source style adoption, preview canvas resize behavior, debug logging, project data, and exports are unchanged. No project migration is required. Compatibility and migration The public command and Runtime definition contract are unchanged. `startup` was an App-internal callback referenced only by the definition; callers should launch the App rather than invoke either callback directly. Validation The Figure Studio hidden GUI suite covers ordinary launch, style controls, FIG loading, preview resize, quick/package export, axes handoff, project save/load, stable canvas sizing, and popout-to-Studio transfer. It also asserts that the definition exposes the semantic initializer handle. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up The Start capability remains necessary for post-layout resources; removing it would require a different framework service phase rather than a file move. App and agent guidance still need a repository-wide terminology audit."},{"title":"Figure Studio preserves common graphics and styles semantic categories","url":"history/records/2026/07/LK-20260720-figure-studio-semantic-restyling.html","kind":"history","section":"history","keywords":"LK-20260720-figure-studio-semantic-restyling 2026-07-20 145 feat compatible labkit_FigureStudio_app labkit.app Figure Studio App Framework GUI validation Test runner","text":"Figure Studio preserves common graphics and styles semantic categories id: LK-20260720-figure-studio-semantic-restyling date: 2026-07-20 sequence: 145 type: feat compatibility: compatible component: `labkit_FigureStudio_app` | `0.3.1 -> 0.4.0` component: `labkit.app` | `1.2.0 -> 1.2.1` scope: Figure Studio scope: App Framework GUI validation scope: Test runner Context Figures sent from Apps, reopened from FIG files, or recreated from Figure Studio data packages could omit common composite graphics such as bars, error bars, areas, and rectangles. Legends were reconstructed only from display names, and the preset exposed one broad line-width control. This made restyling incomplete and could enlarge unrelated text enough to overlap data. Separately, the official hidden-GUI policy was applied while native App windows were constructed but the final show operation could make direct-entrypoint tests visible. Decision and rationale Preserve the supported visible plot structure before applying a presentation preset. Style text and strokes by semantic role so users can establish a coherent baseline and then refine categories that have different visual weights. A preset may style an existing legend, but it retains source placement by default because moving or creating a legend can cover data. Keep GUI-test visibility in the private native adapter. App authors and test methods should not maintain a second hiding convention, and no new public API is needed for a framework-owned launch consistency rule. Keep broad tests in one MATLAB process. A complete local comparison showed that three workers increased both wall-clock and CPU time, so portability work around child-process orchestration would preserve cost without user benefit. Changes Added bar, error-bar, area, and display-only rectangle extraction and reconstruction for previews, FIG imports, and portable recreation scripts. Preserved constant-line labels without interpreting scientific text as a MATLAB line specification. Preserved existing legend text, visibility, placement, orientation, columns, font, interpreter, and border through every supported reconstruction path. Preserved explicit axis ticks, tick labels, rotations, locations, and tick geometry instead of asking MATLAB to infer them again during reconstruction. Included visible hidden-handle graphics in snapshots while retaining their exclusion from automatic legend discovery. Made the LabKit preset the initial style for direct plot handoff while retaining the captured source style as **FIG default**. Added separate controls for title, axis-label, tick, annotation, legend, data, uncertainty, graphic-boundary, reference, and axes styling, plus source/horizontal/45-degree X tick labels. Calibrated the LabKit preset against a representative standard Figure: a 720-by-600 reference canvas, 24/20/15 pt semantic text tiers, 1.1/1.2 pt semantic strokes, Helvetica when available, boxed axes and legend, and a 600 dpi export at the default scale. Scaled text and strokes dynamically from each style's reference canvas while compensating for preview fitting, and reduced UI steps to 0.5 pt for fonts, 0.1 pt for strokes, and 10 px for canvas dimensions. Migrated schema 1 projects to the expanded semantic style model while preserving every previously saved style value. Made the final native show operation honor hidden and minimized GUI validation modes. Bound heartbeat work at timer creation so path-isolation contracts cannot remove a method needed by an active progress callback. Removed internal worker planning, partitioning, process launch, shard selectors, and speculative JUnit shard estimates. Local and CI broad tasks now share the same single-process runner. User and data impact Existing schema 1 documents migrate automatically to schema 2, keeping saved values and supplying defaults only for newly introduced style categories. Supported figures retain more visible content, while unsupported MATLAB chart classes continue to produce explicit warnings. Normal App launches remain visible; only official hidden or minimized validation modes change final window presentation. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Result-file tests exercise extraction, FIG reopening, recreation-script execution, legend metadata, constant-line labels, semantic styling, and idempotent preset application. Figure Studio source-axis and hidden-GUI suites cover source defaults, LabKit handoff defaults, the new control surface, preview stability, and popout handoff. Project tests cover schema 1 style migration. Framework GUI coverage verifies that the final show operation retains hidden mode, and a direct DIC Preprocess entrypoint test exercises the full launch path. A same-machine cold-process comparison measured 470 seconds wall time and 876 seconds CPU time for the serial headless gate versus 490 seconds wall time and 1,215 seconds CPU time for three workers. Worker artifacts also confirmed that heartbeat updates survived the isolated-path contract. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led visual review remains responsible for aesthetic judgment, unusual third-party chart classes, native window behavior, and plot-specific overlap after manual legend placement."},{"title":"Figure Studio preserves overlays above source images","url":"history/records/2026/07/LK-20260722-figure-studio-image-overlay-stacking.html","kind":"history","section":"history","keywords":"LK-20260722-figure-studio-image-overlay-stacking 2026-07-22 156 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio preserves overlays above source images id: LK-20260722-figure-studio-image-overlay-stacking date: 2026-07-22 sequence: 156 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.4 -> 0.6.5` scope: LabKit Core Context Figure Studio copied every visible child from native MATLAB axes, but reversed the source child vector before copying it into the interactive preview. Because MATLAB stores axes children from front to back and `copyobj` preserves vector order, an opaque image became the frontmost child and hid line, marker, and text overlays that had been drawn above it. Decision and rationale Pass the native axes child order through unchanged. The source axes remains the authority for stacking, and Figure Studio must not infer a new order from graphics classes or special-case images. Changes Preserved native front-to-back child order when copying an axes into the interactive preview. Added a synthetic image, line, and text stack regression at the renderer boundary. Added a hidden-GUI regression covering both FIG-file import and axes handoff. User and data impact Image-backed plots now show their visible overlays in Figure Studio and in preview-derived exports. Source files, scientific values, coordinates, object styles, and project data are unchanged. Compatibility and migration No project migration is required. Existing projects receive the corrected stacking when their native source is rebuilt. Validation Exact source-axes stacking regression. Hidden-GUI FIG import and axes-handoff stacking regression. Visual inspection of an authorized local diagnostic FIG through both paths; the real file remains untracked and is not a test fixture. Evidence Both real-source paths retained all 12 visible graphics children with the opaque image last and the 11 overlay children in front. Temporary preview rasters confirmed the overlays were visible. Known limitations and follow-up Custom graphics classes still depend on MATLAB supporting their native parent transition. The portable data package remains narrower than the native preview and export path."},{"title":"Figure Studio removes source-reference knowledge","url":"history/records/2026/07/LK-20260716-figure-studio-source-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-source-boundary 2026-07-16 94 refactor compatible labkit_FigureStudio_app LabKit Core Apps Runtime source boundary","text":"Figure Studio removes source-reference knowledge id: LK-20260716-figure-studio-source-boundary date: 2026-07-16 sequence: 94 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.3 -> 0.2.4` scope: LabKit Core Apps scope: Runtime source boundary Context Figure Studio read both the current path and stored filename from the nested portable source reference. It repeated those reads in session reconstruction, actions, file-list presentation, and selection lookup. Decision and rationale Resolve source paths once through Runtime V2 at each workflow boundary. Derive the displayed filename from the resolved path so the App needs no knowledge of any portable-reference field. Changes Migrated FIG session reconstruction, action loading, file presentation, and selection lookup to `labkit.ui.runtime.sourcePaths`. Derived the opened-file status name through `fileparts`. Removed every direct portable-reference field read from Figure Studio. Preserved imported axes data, default style adoption, source order, current selection, editing, and export behavior. User and data impact FIG selection and display behave unchanged. Figure Studio now owns only how it uses a resolved FIG file; Runtime V2 owns how that file remains portable. Compatibility and migration No project or source-record field changed. Existing Figure Studio projects require no migration. Validation Source-axes and result-file unit tests cover import and export semantics. The hidden GUI suite covers empty startup, FIG addition and selection, style presentation, save/load, and output workflows. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The repository-wide portable-reference guard remains deferred until all remaining App families have migrated."},{"title":"File-panel layout stabilization","url":"history/records/2026/06/LK-20260630-file-panel-layout-stabilization.html","kind":"history","section":"history","keywords":"LK-20260630-file-panel-layout-stabilization 2026-06-30 19 fix compatible labkit.ui labkit.ui File-panel layout stabilization","text":"File-panel layout stabilization id: LK-20260630-file-panel-layout-stabilization date: 2026-06-30 sequence: 19 type: fix compatibility: compatible component: `labkit.ui` | `3.2.3 -> 3.2.4` component: `labkit.ui` | `3.2.4 -> 3.2.5` scope: File-panel layout stabilization Context The single-file panel allocated too much height to its rows and could render inconsistently as the containing app resized. This was especially noticeable in file-heavy apps, where the panel competed with analysis controls and plots. Decision and rationale Give the panel explicit, compact row sizing and protect that geometry with GUI layout tests. The change stayed in the shared file-panel builder because the same visual defect appeared wherever the component was used. Changes `labkit.ui` `3.2.3 -> 3.2.5` Stabilized and compacted single file-panel layout. User and data impact File names and actions occupied less vertical space, leaving more room for the workflow below them. The change affected layout only; selections and loaded file data were unchanged. Compatibility and migration App definitions and selected files required no conversion. The shared panel kept the same controls and callbacks with revised row geometry. Validation Both commits extended `GuiLayoutUiDeclarativeAppTest` with the expected panel geometry. Exact historical test commands were not recorded. Evidence Main commits `7f8df1cd` and `02b2f1b6`. Known limitations and follow-up Later runtime generations replaced this private UI 3.x builder, but retained the principle that shared components own and test their responsive geometry."},{"title":"File-panel migration","url":"history/records/2026/06/LK-20260624-file-panel-migration.html","kind":"history","section":"history","keywords":"LK-20260624-file-panel-migration 2026-06-24 12 refactor breaking labkit.dta labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app File-panel migration","text":"File-panel migration id: LK-20260624-file-panel-migration date: 2026-06-24 sequence: 12 type: refactor compatibility: breaking component: `labkit.dta` | `1.0.0 -> 2.0.0` component: `labkit.ui` | `2.2.1 -> 3.0.0` component: `labkit_DICPostprocess_app` | `1.0.1 -> 1.2.0` component: `labkit_DICPreprocess_app` | `1.0.1 -> 1.2.0` component: `labkit_ChronoOverlay_app` | `1.0.0 -> 1.2.0` component: `labkit_CIC_app` | `1.0.0 -> 1.2.0` component: `labkit_CSC_app` | `1.0.0 -> 1.2.0` component: `labkit_EIS_app` | `1.0.0 -> 1.2.0` component: `labkit_VTResistance_app` | `1.0.0 -> 1.2.0` component: `labkit_BatchImageCrop_app` | `1.0.0 -> 1.2.0` component: `labkit_CurvatureMeasurement_app` | `1.0.1 -> 1.2.0` component: `labkit_FocusStack_app` | `1.0.0 -> 1.2.0` component: `labkit_ImageEnhance_app` | `1.0.0 -> 1.2.0` component: `labkit_ImageMatch_app` | `1.0.0 -> 1.2.0` component: `labkit_NerveResponseAnalysis_app` | `1.0.0 -> 1.2.0` component: `labkit_ResponseReviewStats_app` | `1.0.0 -> 1.2.0` component: `labkit_RHSPreview_app` | `1.0.0 -> 1.2.0` component: `labkit_ECGPrint_app` | `1.0.0 -> 1.2.0` scope: File-panel migration Context Apps implemented file selection through separate task-input adapters, which produced inconsistent lists, selection events, and status feedback. The DTA session helper also coupled parsing to that older UI model. Decision and rationale Move supported apps to one reusable file-panel model and make DTA loading a GUI-free file/curve API. Accept the breaking package versions because retaining both task-input and file-panel paths would preserve ambiguous callback behavior. Changes `labkit.dta` `1.0.0 -> 2.0.0` `labkit.ui` `2.2.1 -> 3.0.0` All supported apps moved from `1.0.x` into the `1.2.0` workflow line. Replaced task inputs with file panels. Removed the old DTA session helper surface. User and data impact File lists, current selection, add/remove actions, and status display became consistent across the migrated apps. App code written against the removed task inputs or DTA session helpers required an update. Compatibility and migration This was a breaking workflow migration. Older app code expecting task inputs or the removed DTA session helpers needed migration. Validation Commit `b145c904` migrated app GUI workflows and package compatibility tests to the new file-panel and DTA APIs. Evidence Main commit `b145c904`. Known limitations and follow-up This first file-panel generation was later refined for append behavior, native dialog edge cases, and Runtime V2 events."},{"title":"Focus Stack adopts the compact App contract","url":"history/records/2026/07/LK-20260716-focus-stack-structure.html","kind":"history","section":"history","keywords":"LK-20260716-focus-stack-structure 2026-07-16 95 refactor compatible labkit_FocusStack_app Image Measurement App structure","text":"Focus Stack adopts the compact App contract id: LK-20260716-focus-stack-structure date: 2026-07-16 sequence: 95 type: refactor compatibility: compatible component: `labkit_FocusStack_app` | `1.5.1 -> 1.5.2` scope: Image Measurement scope: App structure Context Focus Stack split static metadata across `requirements.m` and `version.m`, spread a version-1 project over a generic lifecycle package, grouped three calculation concepts under `+appState`, and ran a startup callback only to fill an output path and emit an App-specific debug line. It also duplicated the portable-reference path loop in session, actions, and presentation. Decision and rationale Use the compact Runtime V2 contract: one product definition, one durable project specification, and one explicit session factory. Put calculation result shape, presets, and run fingerprinting beside the computation. Resolve output folders only when file registration or export actually needs them. Changes Consolidated command metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated project creation and validation in `projectSpec.m`. Moved transient image reconstruction to root `createSession.m`. Moved three `+appState` helpers into their owning `+analysisRun` capability. Removed the metadata files, generic lifecycle/state packages, and redundant App startup callback. Replaced all direct portable-reference reads with the Runtime path accessor. User and data impact Focus loading, registration, fusion, previews, duplicate-run detection, project reopen, and exports keep the same scientific behavior. A new App no longer mutates its durable output folder during startup. After inputs are selected, output defaults remain source-adjacent as before. The App package loses six structural files and one generic package while its real workflow capabilities become easier to locate. Compatibility and migration The durable payload remains version 1 with identical fields, defaults, and validation. Existing projects require no migration. The removed startup debug line is replaced by the Runtime's standard debug-startup message. Validation Unit tests cover the definition/project/session contract, numerical fusion, registration, deterministic run fingerprints, summaries, readers, and invalid inputs. The hidden GUI test covers debug startup, file and folder import, fusion, presentation, export manifest, save/load reconstruction, and append. Evidence [Focus Stack](../../../../apps/image-measurement/focus-stack/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The remaining Image Measurement Apps still use the older structural packages and will be reviewed individually rather than copied from this App blindly."},{"title":"Framework guides and function reference keep distinct routes","url":"history/records/2026/07/LK-20260717-framework-guide-and-function-routes.html","kind":"history","section":"history","keywords":"LK-20260717-framework-guide-and-function-routes 2026-07-17 134 fix compatible Documentation information architecture","text":"Framework guides and function reference keep distinct routes id: LK-20260717-framework-guide-and-function-routes date: 2026-07-17 sequence: 134 type: fix compatibility: compatible scope: Documentation information architecture Context An initial Framework documentation alignment also classified individual `labkit.ui.*` and `labkit.contract.*` API pages as Framework pages. Entering those pages from the global Functions index then changed the active top-level area, which mixed conceptual ownership with page type and made return paths hard to predict. Decision and rationale Keep conceptual and workflow guides under Framework. Keep every exact MATLAB function contract under Functions. Visible API group labels still explain that `labkit.ui` is the Framework implementation, but the top navigation remains stable while browsing function reference pages. Changes Moved the canonical compatibility guide to `framework/contracts.html`. Kept a tracked legacy page at `libraries/contracts/index.html` that links to the new guide and the Functions reference. Restored Functions ownership for all individual UI and contract API pages. Updated framework, library, and regression-test links to the canonical guide. User and data impact Readers now follow one predictable route for Framework guides and another for exact function syntax. Existing contract-guide bookmarks still reach a compatibility page. MATLAB code and scientific data are unchanged. Compatibility and migration The old guide URL remains as a compatibility landing page. Public MATLAB symbols remain unchanged. Validation Renderer regression verifies the Framework guide route, legacy landing page, and Functions-active runtime API page. `docsCheck` verifies the complete site. Evidence The generated Framework landing page links directly to `framework/contracts.html`, while `labkit.ui.runtime.launch` highlights Functions and labels its sibling group Framework Runtime. Known limitations and follow-up The legacy page is a visible handoff rather than an HTTP redirect because the tracked static-site renderer does not currently emit redirect responses."},{"title":"Framework owns UI and compatibility documentation","url":"history/records/2026/07/LK-20260717-framework-documentation-ownership.html","kind":"history","section":"history","keywords":"LK-20260717-framework-documentation-ownership 2026-07-17 133 docs compatible App Framework documentation","text":"Framework owns UI and compatibility documentation id: LK-20260717-framework-documentation-ownership date: 2026-07-17 sequence: 133 type: docs compatibility: compatible scope: App Framework documentation Context The documentation described `labkit.ui` as the App Framework but presented its public API and the compatibility contracts through the generic Functions area. The visible information architecture therefore disagreed with the framework terminology used by app development guides. Decision and rationale Make Framework the documentation owner for `labkit.ui.*` and `labkit.contract.*`. Compatibility requirements belong in the Framework because App definitions and startup consume them. Keep the stable `labkit.contract` MATLAB namespace because it checks UI and domain facades alike; moving it below `labkit.ui` would create a misleading dependency and break existing App definitions. Changes Added compatibility contracts as a Framework sidebar branch. Classified UI and contract API pages under the Framework top navigation. Replaced raw UI package headings with visible Framework API labels while retaining fully qualified MATLAB symbols. Removed the separate Contracts entry from the Functions guide table. User and data impact Readers now enter framework runtime, layout, plotting, interaction, debugging, and compatibility material through one consistent Framework route. MATLAB calls, saved projects, scientific results, and URLs remain compatible. Compatibility and migration No code migration is required. Existing `labkit.ui.*` and `labkit.contract.*` symbols are unchanged. Validation Renderer regression tests verify Framework ownership for the contracts guide and a runtime API page. `docsCheck` rebuilds and compares the complete site. Evidence Generated UI and contract pages highlight Framework in the product navigation, and the Framework landing sidebar links the compatibility guide. Known limitations and follow-up The global API reference still indexes all public symbols together so exact MATLAB functions remain searchable from one place."},{"title":"Framework public APIs become an explicit last resort","url":"history/records/2026/07/LK-20260720-public-api-escalation-order.html","kind":"history","section":"history","keywords":"LK-20260720-public-api-escalation-order 2026-07-20 144 docs compatible Architecture guidance Agent skills","text":"Framework public APIs become an explicit last resort id: LK-20260720-public-api-escalation-order date: 2026-07-20 sequence: 144 type: docs compatibility: compatible scope: Architecture guidance scope: Agent skills Context A convenient framework helper can appear attractive for one App-specific need, but every public name becomes a long-lived compatibility and documentation commitment. Conversely, forcing unrelated behavior into an existing API can turn a focused contract into an ambiguous option bucket. Decision and rationale Use a consistent escalation order for new App behavior: keep it App-local, extend an existing focused API when the addition is natural, add private framework/runtime capability when shared implementation does not require an App-authored contract, and add a new public API only for stable use by multiple Apps or when the nearest existing API would otherwise lose a clear single purpose. This makes a new public API the final solution without prohibiting one when a genuine reusable boundary has emerged. Changes Added the escalation order to the root, framework, and App agent contracts. Updated the App builder and boundary guard skills to require evidence for multiple consumers or an anti-bucket clarity need before introducing a new public name. Updated the documentation maintainer skill to distinguish documentation of an existing focused extension from review of a genuinely new public surface. User and data impact Runtime behavior, public signatures, App calculations, projects, and exports are unchanged. The rule affects future design choices and review evidence. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Skill package validation covers every edited skill. Documentation history rendering and consistency checks cover this record. Repository searches confirm the escalation order appears at the three ownership layers and in the relevant skills. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up When a proposed public API crosses the stated threshold, record its consumers, ownership rationale, alternatives considered, and compatibility contract in the component history."},{"title":"Gait Analysis active-swing workflow","url":"history/records/2026/07/LK-20260716-gait-analysis-active-swing-workflow.html","kind":"history","section":"history","keywords":"LK-20260716-gait-analysis-active-swing-workflow 2026-07-16 71 feat breaking labkit_GaitAnalysis_app Video Marker input contract treadmill swing segmentation gait visualization and exports","text":"Gait Analysis active-swing workflow id: LK-20260716-gait-analysis-active-swing-workflow date: 2026-07-16 sequence: 71 type: feat compatibility: breaking component: `labkit_GaitAnalysis_app` | `1.1.1 -> 2.0.0` scope: Video Marker input contract scope: treadmill swing segmentation scope: gait visualization and exports Context The migrated Gait app accepted loosely shaped coordinate files but did not recover the legacy workflow's step segmentation, per-step skeleton report, or complete translation and angle measurements. A coordinate file alone also could not prove its frame rate, skeleton order, or calibration. Decision and rationale Treat a current Video Marker payload-version-2 project or autosave as the sole file input. It is the first durable artifact that owns coordinates, timing, skeleton, calibration, and annotation provenance together. Segment the legacy treadmill active swing from a prominent foot-X maximum (lift-off) to the following minimum (landing), including a final completed swing that has no later lift-off. Changes Load only the named `labkitProject` variable and reject generic tables, arbitrary MAT variables, legacy marker payloads, and missing frame rate. Show all overlaid skeleton trajectories immediately after import. Detect active swings with app-owned prominence, peak-height, and temporal separation logic; compute cycle/stance measures when a following lift-off exists. Present one selected swing with connected landmarks, joint/segment traces, five endpoint translations, and joint minimum, maximum, and range of motion. Export per-frame, coordinate, step, summary, and provenance tables with lift-off/landing and `swing_time_s` terminology. Migrate version-1 Gait option names and invalidate its scientifically incompatible cached result. User and data impact Users first inspect all trajectories, then run analysis and move between segmented swings. Existing Gait project settings are migrated, but old cached results are recalculated. Older or generic pose files must be converted by opening and saving the source in current Video Marker. Compatibility and migration This is an intentional input and output-schema break. The durable project migration preserves equivalent thresholds while renaming stride/contact-era fields to active-swing terms. Video Marker project payload version 2 is required. Validation Focused unit tests cover source metadata recovery, rejection of incomplete and legacy MAT files, lift-off/landing pairs, retention of the final completed swing, migration of old option names, scientific table fields, and exports. The focused GUI test covers trajectory orientation, analysis, step preview, export, and project reopen. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) documents the source, calculation, visualization, and export contracts. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the durable payload used as input. Known limitations and follow-up The detector represents image-kinematic treadmill events, not force-plate contact. Frame annotation status is preserved but is not yet an automatic step exclusion rule."},{"title":"Gait Analysis app","url":"history/records/2026/07/LK-20260714-gait-analysis-app.html","kind":"history","section":"history","keywords":"LK-20260714-gait-analysis-app 2026-07-14 55 feat compatible labkit_GaitAnalysis_app Gait Analysis app","text":"Gait Analysis app id: LK-20260714-gait-analysis-app date: 2026-07-14 sequence: 55 type: feat compatibility: compatible component: `labkit_GaitAnalysis_app` | `new -> 1.0.0` scope: Gait Analysis app Context Existing gait work used a script chain after pose tracking: import coordinates, smooth marker traces, make per-step figures, compute step and joint metrics, select useful steps, and export tables for downstream statistics. That work belongs in its own app family because the downstream task is not image annotation, signal import, or electrochemistry; it is gait-specific pose analysis from already tracked coordinates. Decision and rationale Add an independent Gait Analysis app instead of extending Video Marker or recreating a model-training workflow. The app accepts several coordinate-table shapes, keeps gait event detection and metric definitions app-local, and exports simple CSV tables that can be consumed by plotting or statistical programs. Changes Added `labkit_GaitAnalysis_app` under the new Gait family. Added CSV/TSV/TXT and MAT pose-coordinate import, including generic `point_x`/`point_y` and LabKit `point__x`/`point__y` column shapes. Added smoothing, foot-relative step-event detection, hip/knee/ankle angle calculation, segment lengths, per-step translations, stride length, step time, ROM, summary metrics, and trajectory/angle/step-event previews. Added CSV set export for frame metrics, step metrics, summary metrics, and per-frame coordinates that keep raw pixel columns alongside optional scale-calibrated and first-frame-origin-shifted columns. User and data impact Users can analyze gait from multiple pose-coordinate sources without tying the workflow to a specific tracking model. The exported tables are plain CSV and separate frame-level, coordinate, step-level, and summary data for downstream overlay, plotting, or statistics. Compatibility and migration The app is additive and does not change Video Marker, image measurement apps, public `+labkit` facades, or existing exports. Existing script outputs can be imported when they provide wide coordinate columns or MAT `coords` and `pointNames`. Validation `GaitAnalysisTest` covers coordinate import, synthetic step detection, metric tables, MAT import, coordinate export calibration/origin semantics, coordinate CSV readback, and CSV set export. `GuiLayoutGaitAnalysisTest` covers the hidden GUI launch and semantic control contract. These tests were added with the app in commit `49863964`. Evidence Initial Gait Analysis app `49863964`. Known limitations and follow-up The first version analyzes one tracked subject at a time and does not perform tracking, model training, group-level statistics, EMG/CAP synchronization, multi-limb phase analysis, or automatic step quality classification."},{"title":"Gait Analysis keeps schema history in one project contract","url":"history/records/2026/07/LK-20260716-gait-analysis-structure.html","kind":"history","section":"history","keywords":"LK-20260716-gait-analysis-structure 2026-07-16 104 refactor compatible labkit_GaitAnalysis_app Gait App structure","text":"Gait Analysis keeps schema history in one project contract id: LK-20260716-gait-analysis-structure date: 2026-07-16 sequence: 104 type: refactor compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.3 -> 2.0.4` scope: Gait scope: App structure Context Gait Analysis spread creation, validation, session reconstruction, and two ordered schema upgrades through a generic lifecycle package. Its generic state package actually contained four analysis concepts: defaults, source-derived options, result construction, and duplicate-run fingerprints. Session and presentation code also inspected the Runtime's nested source-reference fields. Decision and rationale Concentrate project history behind one `projectSpec.m`, keep transient pose reconstruction in root `createSession.m`, and assign all former state helpers to `+analysisRun`. This preserves the real complexity—Video Marker parsing, step segmentation, kinematics, QC, and exports—while removing structural categories that did not explain the workflow. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderer, and debug capability in `definition.m`. Replaced two public migration files with one migration callback that selects the version-1 or version-2 transformation from `fromVersion`. Moved default options, pose-derived option resolution, empty results, and deterministic task fingerprints from `+appState` to `+analysisRun`. Replaced direct portable-reference access with semantic `sourcePaths` lookup in session reconstruction and presentation. Removed generic lifecycle/state packages and separate requirement/version files, and updated GUI-free documentation calls to the owning package. User and data impact Current Video Marker MAT loading, full-trajectory inspection, active-swing segmentation, one-step review, joint angles, translations, timing, QC, duplicate-run detection, CSV export, project save, and project reopen retain their behavior. The image-coordinate Y direction and time-series coordinate direction remain deliberately distinct. Developers can now follow project history through one entry and find all analysis policy under the package that owns the scientific workflow. Compatibility and migration Durable schema version 3 is unchanged. Version-1 option renames and result invalidation still run before the version-2 source collection upgrade. Runtime V2 now owns the loop and validates the resulting current payload. Validation Focused tests cover current Video Marker input, timing/scale/role extraction, active-swing segmentation, final-swing retention, gait parameters, CSV round-trip, and both historical migrations. The hidden GUI workflow covers MAT import, trajectory preview, analysis, export, project save, and decoded pose reconstruction after reopen. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not establish scientific validity for recordings outside the documented treadmill model or replace manual inspection of tracked points and segmented events. Neurophysiology and wearable Apps with generic lifecycle packages remain scheduled for the same review."},{"title":"Gait consumes the Video Marker file contract without a sibling App dependency","url":"history/records/2026/07/LK-20260717-gait-app-path-isolation.html","kind":"history","section":"history","keywords":"LK-20260717-gait-app-path-isolation 2026-07-17 124 fix compatible labkit_GaitAnalysis_app Gait App packaging Video Marker file contract","text":"Gait consumes the Video Marker file contract without a sibling App dependency id: LK-20260717-gait-app-path-isolation date: 2026-07-17 sequence: 124 type: fix compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.7 -> 2.0.8` scope: Gait scope: App packaging scope: Video Marker file contract Context Gait Analysis correctly parsed saved Video Marker MAT documents in production, but its synthetic debug writer created those documents by calling `video_marker.projectSpec`, `video_marker.skeletonDefinition`, and `video_marker.frameAnnotations`. Tests added every App root to the MATLAB path, while the launcher adds only the selected App root. Debug startup could therefore pass tests and fail in a real isolated launch. Decision and rationale Treat the saved `labkitProject` payload as the boundary between the producer and consumer. Gait owns its parser, diagnostics, and synthetic consumer fixture. Video Marker owns serialization and schema validation. One explicit integration test may load both Apps to prove their current file contracts agree, but neither Gait production nor debug code may call the Video Marker package. Changes Rebuilt the Gait debug sample from the documented payload fields without loading Video Marker source code. Split consumer parser tests from a producer-consumer integration test that constructs a current Video Marker project, validates it, saves it, and reads it through Gait. Added an isolated-path debug test using only the repository root and Gait App root. Added a repository guardrail that rejects executable calls from one App package into a sibling App package while allowing documentation references and integration tests. Documented the saved-data boundary in the App development guide and Gait manual. User and data impact Gait debug startup and its packaged App behavior no longer depend on every public App being present on the MATLAB path. Users continue to open the same current Video Marker project and autosave MAT files; parsing, timing, calibration, gait calculations, and exports are unchanged. Developers receive an immediate guardrail failure if a shared test path hides a new sibling-App runtime dependency. Compatibility and migration No project or output migration is required. Video Marker payload version 2 remains the accepted input contract. Validation Focused validation covers isolated debug sample generation, consumer parsing, current Video Marker producer-to-Gait import, Gait calculations and hidden GUI workflow, and the App package boundary guardrail. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [App Development](../../../../development/build-apps/app-development.md#cross-app-data-contracts) Known limitations and follow-up The integration test protects the current saved schema, not scientific validity of manually marked trajectories. Those data and workflow checks remain App-owned."},{"title":"Gait trajectory image-coordinate preview","url":"history/records/2026/07/LK-20260716-gait-image-coordinates.html","kind":"history","section":"history","keywords":"LK-20260716-gait-image-coordinates 2026-07-16 67 fix compatible labkit_GaitAnalysis_app trajectory preview","text":"Gait trajectory image-coordinate preview id: LK-20260716-gait-image-coordinates date: 2026-07-16 sequence: 67 type: fix compatibility: compatible component: `labkit_GaitAnalysis_app` | `1.1.0 -> 1.1.1` scope: trajectory preview Context Marker coordinates use the image convention with an upper-left origin and Y increasing downward. Gait Analysis plotted those values on MATLAB's default Cartesian axes, so the trajectory preview appeared vertically flipped relative to the source video. Decision and rationale Preserve the imported coordinate values and scientific calculations. Reverse only the trajectory preview's Y axis so its visual orientation matches Video Marker and Image Marker. Time-series angle and step plots explicitly retain the conventional upward Y direction. Changes Render trajectory previews with a reversed Y axis. Restore the normal Y direction when switching to Angles or Steps. Document the preview coordinate convention. User and data impact Tracked motion now appears in the same orientation as the source image. Stored coordinates, calculations, tables, and exports are unchanged. Compatibility and migration The change is display-only and requires no project or data migration. Validation The hidden Gait Analysis workflow test checks both trajectory and time-series axis directions while exercising source load and analysis. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) documents the image-coordinate preview convention. Known limitations and follow-up The preview assumes imported marker coordinates follow the documented image coordinate convention. Generic coordinate files with a Cartesian convention must be converted by their producer before import."},{"title":"Headless validation follows current documentation and App contracts","url":"history/records/2026/07/LK-20260719-headless-validation-repair.html","kind":"history","section":"history","keywords":"LK-20260719-headless-validation-repair 2026-07-19 137 fix compatible labkit_TTestWizard_app Continuous integration Documentation validation Statistics","text":"Headless validation follows current documentation and App contracts id: LK-20260719-headless-validation-repair date: 2026-07-19 sequence: 137 type: fix compatibility: compatible component: `labkit_TTestWizard_app` | `1.0.0 -> 1.0.1` scope: Continuous integration scope: Documentation validation scope: Statistics Context The first main-branch validation after the path-derived documentation change still asserted several retired documentation paths. The same run exposed T-Test Wizard code-quality findings and a framework GUI test file that had grown beyond its review budget. Decision and rationale Align validation with the current path-owned documentation structure and keep Runtime-owned empty session buckets out of App factories. Resolve the static analysis findings without changing calculations, export values, or visible workflow behavior. Split the workspace-specific GUI coverage by capability so each test file remains focused. Changes Updated documentation guardrails to use the current testing, Runtime, and complete-App guide paths. Removed an empty `view` placeholder that Runtime already supplies. Replaced dynamic array growth in group-label and group-reassignment helpers with bounded collection. Centralized the box-plot label and documented the plot-jitter and significance-display constants. Split workspace-page and dynamic-table GUI coverage into its own focused test file. User and data impact T-Test Wizard calculations, project data, CSV values, plots, and workflows are unchanged. The App version advances to 1.0.1 to record the internal validation repair. Compatibility and migration Existing version-1 projects remain compatible and require no data migration. Runtime continues to provide `selection`, `workflow`, `view`, and `cache` in the final session state. Validation Focused contract and T-Test core tests cover the original headless failures. Hidden GUI tests cover the T-Test workflow and the extracted Runtime workspace coverage. Documentation rendering and changed-file gates validate the final main-branch handoff. Evidence [T-Test Wizard](../../../../apps/statistics/ttest-wizard/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Testing LabKit](../../../../development/maintain-and-release/testing.md) Known limitations and follow-up Interactive native-dialog behavior remains a developer-led manual validation responsibility; hidden GUI automation does not replace it."},{"title":"History records use one canonical authored format","url":"history/records/2026/07/LK-20260721-canonical-history-record-format.html","kind":"history","section":"history","keywords":"LK-20260721-canonical-history-record-format 2026-07-21 149 docs compatible Project History Documentation Search","text":"History records use one canonical authored format id: LK-20260721-canonical-history-record-format date: 2026-07-21 sequence: 149 type: docs compatibility: compatible scope: Project History scope: Documentation Search Context History records mixed a transport-oriented schema marker with several related but inconsistent metadata conventions. Authors had to infer valid fields and values from renderer internals and agent guidance. Decision and rationale Use one compact, authored record format without a schema field. A single public format page defines every legal field, value, ordering rule, and narrative section so a record can be written and reviewed from repository documentation. Changes Rewrote every existing record into the canonical metadata form, preserved its identity, date, sequence, narrative, and component history links, and added a strict renderer parser. Scopes now feed the documentation search index. User and data impact Readers retain the complete history timeline and component links while gaining more useful search terms. This documentation-only change does not alter saved projects, experimental data, or App behavior. Compatibility and migration The source format changes incompatibly for record authors: remove `schema` and replace an introduction entry with a `component` transition using `new ->`. Existing records are migrated in this same change, so no compatibility reader is retained. Validation The documentation renderer validates every migrated record, rebuilds the site, and documentation guardrails verify the canonical source and generated output. Evidence The renderer parser is the executable enforcement of the published format; the record format page provides its human-readable counterpart. Known limitations and follow-up No known follow-up is required. Future history changes use this format directly rather than adding compatibility metadata variants."},{"title":"Image App project validation ownership","url":"history/records/2026/07/LK-20260716-image-project-validation-ownership.html","kind":"history","section":"history","keywords":"LK-20260716-image-project-validation-ownership 2026-07-16 119 refactor compatible labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app image App project schemas App maintenance cost","text":"Image App project validation ownership id: LK-20260716-image-project-validation-ownership date: 2026-07-16 sequence: 119 type: refactor compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.4 -> 1.7.5` component: `labkit_CurvatureMeasurement_app` | `1.4.4 -> 1.4.5` component: `labkit_FLIRThermal_app` | `1.4.4 -> 1.4.5` component: `labkit_FocusStack_app` | `1.5.3 -> 1.5.4` component: `labkit_ImageEnhance_app` | `1.6.4 -> 1.6.5` component: `labkit_ImageMatch_app` | `1.6.4 -> 1.6.5` component: `labkit_VideoMarker_app` | `1.5.3 -> 1.5.4` scope: image App project schemas scope: App maintenance cost Context The seven image workflow validators repeated Runtime's canonical project-bucket checks and, in six cases, the standard portable source-record field list. Those checks obscured the App-specific rules that actually matter: crop task relationships, parameter ranges, annotation shapes, video metadata, skeleton dimensions, and result schemas. Decision and rationale Keep the existence of each App's required source collection and every domain-specific relationship in `projectSpec.m`. Rely on Runtime for the five canonical bucket structs and the internal shape of every supplied source record. This preserves strict malformed-project rejection while making each validator read as its App schema. Changes Removed 71 net lines of repeated Runtime structure checks from the seven image App project specifications. Preserved required `inputs.sources` fields and all item/source, annotation/source, skeleton/frame, parameter, and result constraints. Added a GUI-free contract that accepts all seven default projects and rejects each project when its App-required source collection is removed. User and data impact Valid projects, migrations, image calculations, annotations, and exports are unchanged. Malformed source records continue to fail in Runtime; malformed App fields continue to fail in the owning App validator. Compatibility and migration No payload format changed and no migration is required. The refactor changes which layer reports framework-owned structure failures, not which valid data the Apps accept. Validation The focused project-spec contract covers default acceptance and missing-source rejection for every affected App. Existing focused unit and hidden-GUI tests cover the domain relationships and representative load, edit, analyze, and export workflows. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines canonical project and source validation. Each affected [Image Measurement App](../../../../apps/image-measurement/README.md) manual lists its remaining project and session responsibilities. Known limitations and follow-up This change does not shorten action handlers or scientific workflow code. Those remain App-owned unless a separately measured repeated behavior proves to be stable and domain-neutral."},{"title":"Image Apps stop creating invalid placeholder outputs","url":"history/records/2026/07/LK-20260716-image-manifest-output-arrays.html","kind":"history","section":"history","keywords":"LK-20260716-image-manifest-output-arrays 2026-07-16 80 fix compatible labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_ImageEnhance_app labkit_ImageMatch_app Image Measurement Result manifests","text":"Image Apps stop creating invalid placeholder outputs id: LK-20260716-image-manifest-output-arrays date: 2026-07-16 sequence: 80 type: fix compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.1 -> 1.7.2` component: `labkit_FLIRThermal_app` | `1.4.1 -> 1.4.2` component: `labkit_ImageEnhance_app` | `1.6.1 -> 1.6.2` component: `labkit_ImageMatch_app` | `1.6.1 -> 1.6.2` scope: Image Measurement scope: Result manifests Context Four image Apps preallocated variable-length result arrays by first asking the Runtime output factory to create a record with an empty ID. Runtime V2 rejects empty result IDs, so otherwise valid export workflows could stop before their standard manifest was written. Decision and rationale Use the canonical empty result array introduced by `labkit.ui` and append only validated real outputs. This preserves strict IDs without requiring each App to duplicate the manifest struct shape. Changes Updated Batch Crop crop outputs. Updated FLIR image, colorbar, and temperature-CSV outputs. Updated Image Enhance batch outputs. Updated Image Match batch outputs. User and data impact The exported images, numeric CSV data, filenames, roles, statuses, and manifest schemas are unchanged. Exports no longer fail during internal output-array initialization. Compatibility and migration No saved project or export schema migration is required. Existing result files remain readable. Validation Each affected App's hidden synthetic GUI export workflow verifies its domain output plus the standard LabKit manifest. Version and component-history guardrails cover all four products. Evidence [Batch Crop](../../../../apps/image-measurement/batch-crop/README.md) [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Image Match](../../../../apps/image-measurement/image-match/README.md) Known limitations and follow-up The Apps still need their separate single-definition and projectSpec reviews; this record only fixes the shared manifest construction defect."},{"title":"Image Enhance assigns state helpers to workflow capabilities","url":"history/records/2026/07/LK-20260716-image-enhance-structure.html","kind":"history","section":"history","keywords":"LK-20260716-image-enhance-structure 2026-07-16 99 refactor compatible labkit_ImageEnhance_app Image Measurement App structure","text":"Image Enhance assigns state helpers to workflow capabilities id: LK-20260716-image-enhance-structure date: 2026-07-16 sequence: 99 type: refactor compatibility: compatible component: `labkit_ImageEnhance_app` | `1.6.2 -> 1.6.3` scope: Image Measurement scope: App structure Context Image Enhance split static product metadata across files, spread a version-1 project over a generic lifecycle package, and placed decoded items, durable annotations, numerical histories, preview scaling, UI normalization, and export fingerprints together under `+appState`. Source reconstruction and presentation also read nested portable-reference fields directly. Decision and rationale Use the compact Runtime V2 definition and project contracts, then assign each remaining concept to the workflow capability that owns its behavior. Preserve the important preview/export boundary: pixel-radius parameters scale for the downsampled preview while full-size export continues to use source coordinates. Changes Consolidated product metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated durable creation and validation in `projectSpec.m` and moved transient reconstruction to root `createSession.m`. Assigned decoded items and lazy selected-image loading to `+sourceFiles`, histories and preview replay to `+analysisRun`, durable per-image data to `+enhancementAnnotations`, UI value clamping to `+userInterface`, and export fingerprints to `+resultFiles`. Removed separate metadata files, generic lifecycle/state packages, and the redundant App startup callback. Replaced direct portable-reference access with the Runtime path accessor. Corrected the GUI-free manual example to write the first returned image from the documented cell-array result. User and data impact Shared and per-image histories, white ROI calibration, pending previews, preview-coordinate radius scaling, original-resolution processing, duplicate export detection, project reopen, and outputs keep their existing behavior. Empty launch no longer chooses an output folder; importing sources retains the source-adjacent default. Developers can locate source decoding, calculations, durable annotations, and exports without learning one generic state package. Compatibility and migration The durable payload remains version 1 with identical fields and defaults, so existing projects require no migration. The Runtime's standard debug-startup message replaces the removed App-specific line. Validation The focused unit suite covers numerical tools, white ROI behavior, preview radius scaling, source reading, full-size exports, manifests, per-image steps, task fingerprints, and project/presenter contracts. The hidden GUI workflow covers file append and selection, independent histories, tool application, export, save/load, and transient cache reconstruction. Evidence [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Image Library](../../../../libraries/image/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Apps still using generic lifecycle/state packages will be reviewed individually. Automated GUI tests do not replace manual judgment of visual quality, ROI pointer feel, or enhancement suitability for quantitative images."},{"title":"Image Match adopts capability-owned structure","url":"history/records/2026/07/LK-20260716-image-match-structure.html","kind":"history","section":"history","keywords":"LK-20260716-image-match-structure 2026-07-16 96 refactor compatible labkit_ImageMatch_app Image Measurement App structure","text":"Image Match adopts capability-owned structure id: LK-20260716-image-match-structure date: 2026-07-16 sequence: 96 type: refactor compatibility: compatible component: `labkit_ImageMatch_app` | `1.6.2 -> 1.6.3` scope: Image Measurement scope: App structure Context Image Match split metadata and project lifecycle across eight structural files, used an App startup callback only for an output-folder default and debug line, and grouped unrelated source, analysis, and export structures under `+appState`. Session, actions, and presentation also read portable-reference path fields directly. Decision and rationale Adopt one definition, one project spec, and one session factory. Place each data constructor beside the capability that owns its meaning: loaded image items with source reading, match steps with analysis, and idempotent export tasks with result writing. Changes Consolidated command metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated version-1 project creation and validation in `projectSpec.m`. Moved selected-image reconstruction to root `createSession.m`. Replaced `+appState` with capability-owned source, analysis, and result functions. Removed the metadata files, generic lifecycle package, and redundant startup callback. Replaced all portable-reference field reads with the Runtime path accessor. Corrected the GUI-free manual example to use the real `applyPipeline` argument order and cell output. User and data impact Reference/source selection, lazy preview loading, match history, calculation, duplicate-export detection, project reopen, and export formats behave unchanged. A new project no longer writes an environment-specific output path during startup; source selection still establishes the same adjacent default. The directory now answers where an item, step, or export task belongs without requiring knowledge of a generic state layer. Compatibility and migration The durable payload remains version 1 with identical fields and validation. Existing Image Match projects require no migration. Validation Unit tests cover the definition/project/session contract, matching methods, pipeline replay, preview scaling, result tables, export formats, manifests, and task fingerprints. The hidden GUI suite covers reference/source loading, selection, history, preview, export, and save/load workflows. Evidence [Image Match](../../../../apps/image-measurement/image-match/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Image Measurement Apps retain older lifecycle or state packages and will be reviewed against their own capabilities."},{"title":"Image app workflow improvements","url":"history/records/2026/07/LK-20260701-image-app-workflow-improvements.html","kind":"history","section":"history","keywords":"LK-20260701-image-app-workflow-improvements 2026-07-01 27 feat compatible labkit_launcher labkit.image labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_FLIRThermal_app labkit_ImageEnhance_app labkit_ImageMatch_app Image app workflow improvements","text":"Image app workflow improvements id: LK-20260701-image-app-workflow-improvements date: 2026-07-01 sequence: 27 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.5 -> 1.1.6` component: `labkit.image` | `1.0.0 -> 1.1.0` component: `labkit.ui` | `3.2.10 -> 3.3.0` component: `labkit.ui` | `3.3.0 -> 3.3.1` component: `labkit_BatchImageCrop_app` | `1.4.0 -> 1.5.0` component: `labkit_BatchImageCrop_app` | `1.5.0 -> 1.5.1` component: `labkit_FLIRThermal_app` | `1.0.0 -> 1.1.0` component: `labkit_FLIRThermal_app` | `1.1.0 -> 1.1.2` component: `labkit_ImageEnhance_app` | `1.4.0 -> 1.4.1` component: `labkit_ImageMatch_app` | `1.4.0 -> 1.4.1` scope: Image app workflow improvements Context Image apps used full-resolution data for several previews even when the screen could not display that detail. Controls also accepted ranges that were awkward for the current image, and FLIR point and ROI measurements were not yet a complete direct-manipulation workflow. Decision and rationale Set an explicit preview budget, derive sensible control bounds from the loaded image, and preserve full-resolution data for calculations and exports. Expand the FLIR app around point and ROI temperature tools rather than adding more separate analysis dialogs. Changes `labkit.image` `1.0.0 -> 1.1.0` `labkit.ui` `3.2.10 -> 3.3.1` Batch Crop `1.4.0 -> 1.5.1` FLIR Thermal `1.0.0 -> 1.1.2` `labkit_launcher` `1.1.5 -> 1.1.6` Added preview-budget helpers. Improved image app range and preview controls. Improved image measurement workflows. User and data impact Large images produced bounded previews while exports continued to use source resolution. Batch Crop controls reflected the crop and scale being edited, and FLIR users could place temperature points or regions directly on the image and read their values in the detail panel. Compatibility and migration Existing image inputs and app exports remained valid. Preview budgeting changed display resolution only; calculations and exports continued to use the source data required by each workflow. Validation The two commits expanded Batch Crop export tests, FLIR operation and layout tests, image-facade preview tests, busy-state tests, scale-bar gesture checks, and launcher coverage. Exact historical commands were not recorded. Evidence Main commits `15a798ba` and `70bfcfd4`. Known limitations and follow-up Preview budgeting improved responsiveness but did not by itself remove every eager image read. Batch Crop file selection was made lazy in the following profiling work."},{"title":"Interactive recovery of missing project sources","url":"history/records/2026/07/LK-20260716-runtime-source-relink.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-source-relink 2026-07-16 66 fix compatible labkit.ui project loading portable source references","text":"Interactive recovery of missing project sources id: LK-20260716-runtime-source-relink date: 2026-07-16 sequence: 66 type: fix compatibility: compatible component: `labkit.ui` | `6.0.1 -> 6.0.2` scope: project loading scope: portable source references Context Saved projects could describe required external files with portable source records, but an unresolved path produced a framework error unless the owning app implemented the advanced `Project.RelinkSources` hook. Production apps using standard source records therefore had no common recovery interaction when a project moved between folders, drives, or operating systems. Decision and rationale Make missing-file recovery the default Runtime V2 behavior for canonical source records. The runtime already owns project loading, injected dialogs, and transactional state replacement, so it can provide one consistent prompt without duplicating native chooser code in every app. The advanced app hook remains available for nonstandard source schemas. Changes Report the saved filename and source role when automatic resolution fails. Let the user locate each missing required file with one native file chooser. Rebuild the selected source reference relative to the loaded project before constructing the app session. Mark a successfully repaired document as unsaved so the replacement path can be retained on the next save. Mark migrated payloads, snapshots, and declared legacy variables as unsaved; **Save State** then upgrades the opened MAT path to the current project format through the existing atomic replacement. Treat cancellation as an atomic load cancellation without changing the live project or view. User and data impact Projects whose external files remain in their recorded locations open exactly as before. When a required file moved, the user can locate it instead of seeing only an unresolved-source error. The runtime never searches arbitrary folders or silently substitutes a different file. Merely opening an old document does not rewrite it; saving the migrated document upgrades that same path. Compatibility and migration The change is compatible with current project envelopes, declared legacy imports, and existing custom `Project.RelinkSources` callbacks. Selecting a replacement updates only the candidate project; saving writes the current project format. Validation The hidden Runtime V2 project test covers successful default relinking, relative-reference rebuilding, dirty-document state, cancellation rollback, and the existing custom relink hook. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) documents the default missing-source interaction. Runtime source resolution reruns before fresh-session construction and state commit. Known limitations and follow-up The default flow asks once for each unresolved source. Apps with a specialized multi-file schema may continue to provide a custom relinking callback."},{"title":"LabKit name and the first multi-domain app families","url":"history/records/2026/05/LK-20260530-app-family-expansion.html","kind":"history","section":"history","keywords":"LK-20260530-app-family-expansion 2026-05-30 3 feat compatible historical project evolution","text":"LabKit name and the first multi-domain app families id: LK-20260530-app-family-expansion date: 2026-05-30 sequence: 3 type: feat compatibility: compatible scope: historical project evolution Context By the end of the electrochem extraction, the repository had reusable DTA operations and several app entry points, but its naming and package layout still reflected the original Gamry workbench. It was not yet clear whether the design could serve workflows with different data, interaction, and visualization needs. Decision and rationale Rename the project LabKit and treat apps, rather than one scientific domain, as the main deliverables. Keep a limited set of reusable packages underneath them, then exercise that structure with image registration, region editing, curvature measurement, wearable data import, and ECG viewing. The purpose was not to turn LabKit into one analysis program. It was to make a common MATLAB foundation useful to several independent laboratory tools without erasing the workflow decisions that made each tool understandable. Changes Renamed the workbench namespace to `labkit` and reduced the public package surface to app-facing UI and domain operations. Standardized the basic workbench shell and DTA file panels across the electrochem apps. Added DIC preprocessing and postprocessing workflows with iterative crop, mask, registration, zoom, and preview interactions. Added the Curvature Measurement app and reused its anchor-curve editor across image workflows where the interaction was genuinely the same. Added a biosignal facade and the ECG Print app, including wearable CSV import and signal-column diagnostics. Added MATLAB CI and reorganized tests by source responsibility. User and data impact LabKit now served electrochem, image, and biosignal work from distinct app entry points. Image users gained direct manipulation of regions and anchors; biosignal users gained a viewer for imported wearable recordings. Existing DTA files remained external inputs and were not converted into a central project format. Compatibility and migration The namespace rename was an implementation migration for repository code. The new image and biosignal apps were additive. Scripts that referenced the former workbench namespace needed to adopt `labkit` or an app entry point. Validation Focused DIC interaction tests, biosignal import tests, layout tests, and the first MATLAB CI workflow were added during this stage. The exact historical command used for each commit was not recorded. Evidence Public-surface cleanup and LabKit rename `9bd8ec8f` through `179885e1`. Unified workbench and file panels `2de7dd0f` through `1be52b9d`. DIC family `aa96ae88` through `fd808a56`. Curvature Measurement `9c1f3798`, `8bcde252`. Biosignal and ECG work `d7c31369` through `b7cabd52`. Initial MATLAB CI `5102640c`. Known limitations and follow-up The new apps exposed interaction problems that ordinary callback tests did not capture well, including popout axes, anchor editing, busy-state reentry, and image zoom ownership. Those issues drove the managed interaction work that followed before v1.0."},{"title":"Launcher adopts the App SDK diagnostic launch contract","url":"history/records/2026/07/LK-20260720-launcher-app-sdk-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260720-launcher-app-sdk-diagnostics 2026-07-20 139 feat compatible labkit_launcher LabKit Core App Framework","text":"Launcher adopts the App SDK diagnostic launch contract id: LK-20260720-launcher-app-sdk-diagnostics date: 2026-07-20 sequence: 139 type: feat compatibility: compatible component: `labkit_launcher` | `1.5.2 -> 1.6.0` scope: LabKit Core scope: App Framework Context The Launcher still passed the retired `RequestAdapter` startup seam after Apps had moved to `labkit.app.Definition`. Normal launch therefore failed before the selected App could create its window. Removing that argument restored startup, but temporarily also removed the user's diagnostic launch path. Decision and rationale Treat the App entrypoint and `Definition.launch` as the only Launcher-to-App runtime boundary. A normal launch uses the SDK defaults. A debug launch passes one typed `labkit.app.diagnostic.Options` value that requests verbose persisted events and the App-owned anonymous synthetic sample. The Launcher continues to read catalog metadata from the single app-owned `definition.m` source without executing the SDK. This preserves its recovery role when an installed framework is incomplete or damaged. Changes Replaced retired runtime-adapter injection with direct App SDK entrypoint calls for normal launch and profiling. Restored **Open Debug** using verbose typed diagnostics and each App's `BuildDebugSample` contract. Added one isolated session folder per debug launch under `artifacts/diagnostics/launcher/`, with the event stream, manifests, and anonymous fixture artifacts. Kept Launcher UI state in a Launcher-owned view record instead of the retired framework registry name. Updated Definition metadata discovery for current name-value syntax while retaining the older literal form for repair-oriented catalog compatibility. Excluded hidden class-folder implementation methods from public API and generated-documentation discovery. User and data impact Existing normal launch, profiling, packaging, update, and documentation actions keep their public behavior. Debug launch now creates explicit diagnostic artifacts and opens the App with its synthetic scenario; it no longer invokes the retired string-mode or request-adapter contracts. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Focused Launcher GUI, catalog, progress, profiling, and documentation contracts. App SDK diagnostic integration through DIC Preprocess, including `events.jsonl` and `sample-pack.json` evidence. Documentation source synchronization and public API discovery guardrails. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive validation should confirm the visible debug launch, the selected App's synthetic state, and the usefulness of the generated diagnostic bundle on the deployment MATLAB version. labkit.app.Definition Compile and launch one immutable App SDK contract."},{"title":"Launcher and maintainer tool manuals","url":"history/records/2026/07/LK-20260716-launcher-and-maintainer-manuals.html","kind":"history","section":"history","keywords":"LK-20260716-launcher-and-maintainer-manuals 2026-07-16 63 docs compatible labkit_launcher `docs/apps/labkit-core/launcher/` `docs/development/tools/` `docs/development/maintain-and-release/testing.md`","text":"Launcher and maintainer tool manuals id: LK-20260716-launcher-and-maintainer-manuals date: 2026-07-16 sequence: 63 type: docs compatibility: compatible component: `labkit_launcher` scope: `docs/apps/labkit-core/launcher/` scope: `docs/development/tools/` scope: `docs/development/maintain-and-release/testing.md` Context Launcher behavior was split between Getting Started, architecture notes, source comments, and historical records. Maintainer tools under `tools/` had callable MATLAB entry points but no coherent manual set; profiling details were embedded in the testing command matrix while packaging and codecheck behavior were mostly discoverable only from source. Decision and rationale Document the user-operated launcher beside LabKit Core apps, even though its self-repair implementation remains a root-level standalone file. Document source-checkout tools under Development because they support maintenance rather than app workflows. Keep the existing Testing page as the test-system owner, add a conceptual execution map there, and move profiler call details into the maintainer tool reference. This structure lets readers start from the product they operate or the maintenance task they intend, without mirroring every source directory or mixing tool APIs into reusable `labkit.*` function reference. Changes Added a LabKit Launcher manual covering window actions, programmatic calls, discovery, installation, recovery, tools, limitations, and development milestones. Added a maintainer-tool landing page and detailed references for Code Analyzer reports, app deployment packages, documentation builds, and performance profiling. Added a test-system execution map explaining build tasks, runner discovery, test layers, selectors, plugins, artifacts, and manual-validation boundaries. Connected Getting Started, Apps, LabKit Core, Development, Architecture, Documentation, Testing, Release, the root README, and generated navigation to the new manuals. User and data impact Users can now understand launcher controls and history from the Apps section. Maintainers can copy direct MATLAB calls and inspect every supported option and output without reading tool implementation files. No app workflow, scientific calculation, saved project, input file, or exported result changes. Compatibility and migration This is an additive documentation change. Existing page URLs remain valid. The new launcher and tool pages are generated from Markdown and included in search; no handwritten HTML or third-party documentation package is added. Validation Documentation contracts verify source ownership, generated launcher history, tool entry-point coverage, local links, search indexing, and byte-for-byte agreement between structured sources and the tracked site. Evidence `docs/apps/labkit-core/launcher/README.md` owns the launcher manual. `docs/development/tools/` owns direct maintainer-tool references. `ProjectSurfaceDocumentationTest` protects placement and generated content. Known limitations and follow-up Private app documentation remains in each private repository. Internal private helpers beneath tool folders are intentionally described through their public entry point rather than receiving standalone reference pages."},{"title":"Launcher app version history","url":"history/records/2026/07/LK-20260713-launcher-app-version-history.html","kind":"history","section":"history","keywords":"LK-20260713-launcher-app-version-history 2026-07-13 53 feat compatible labkit_launcher Launcher app version history","text":"Launcher app version history id: LK-20260713-launcher-app-version-history date: 2026-07-13 sequence: 53 type: feat compatibility: compatible component: `labkit_launcher` | `1.3.0 -> 1.4.0` scope: Launcher app version history Context The structured changelog could be parsed by maintainers, but launcher users could not inspect the history of the selected app. Earlier normalization also attached broad release descriptions without recording every app version event, which made histories such as CIC appear to begin years of versions too late. Decision and rationale Expose component-filtered history in the launcher and make exact component events the durable lookup key. A user should be able to move from the current app catalog directly to its evolution record without reading Git history or understanding changelog internals. Changes Added `labkit_launcher(\"history\", appCommand)` for programmatic history lookup and a Version History viewer for the selected launcher app. Added explicit component introduction events and reconstructed every tracked launcher, facade, and app version transition from `origin/main` history. Added continuous-history validation so missing introductions, gaps, and current-version mismatches fail the release guardrail. User and data impact Launcher users can inspect dated version transitions, compatibility, rationale, impact, and evidence for the selected app. App calculations and user data are unchanged. Compatibility and migration The launcher command and button are additive. Existing list, version, package, maintenance, and app-launch workflows remain available. Validation `LauncherGuiTest` covers programmatic filtering and the hidden GUI viewer; `ChangelogGuardrailTest` validates complete version chains against current metadata. These checks were included in the combined mainline change `4a6c41dd`. Evidence Component history, launcher viewer, and changelog validation were included in `4a6c41dd`. Historical component events were reconstructed from version-file changes on the then-current `origin/main` history. Known limitations and follow-up History begins when version metadata was first tracked for each component. Behavior before that point can only be inferred from older source commits and is not assigned invented version numbers."},{"title":"Launcher code-analysis export","url":"history/records/2026/07/LK-20260701-launcher-code-analysis-export.html","kind":"history","section":"history","keywords":"LK-20260701-launcher-code-analysis-export 2026-07-01 29 feat compatible labkit_launcher Launcher code-analysis export","text":"Launcher code-analysis export id: LK-20260701-launcher-code-analysis-export date: 2026-07-01 sequence: 29 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.6 -> 1.2.0` scope: Launcher code-analysis export Context The launcher offered a Code Analyzer action, but its earlier implementation carried substantial custom export code. That made a maintenance tool harder to maintain than the MATLAB analysis it was exposing. Decision and rationale Use MATLAB's native issue representation and export path, and keep the launcher responsible only for choosing the target and destination. This reduced custom conversion logic while preserving a launcher-level entry point for the report. Changes `labkit_launcher` `1.1.6 -> 1.2.0` Exported launcher Code Analyzer issues natively. User and data impact The launcher could export Code Analyzer findings in a MATLAB-supported form without requiring a separate script. This action inspected source code only and did not start or modify an app project. Compatibility and migration Existing launcher actions remained available. Newly exported issue files used MATLAB's native representation rather than the removed custom conversion. Validation `LauncherGuiTest` gained coverage for the native issue-export path and the implementation in `8fd3ddff` removed more launcher code than it added. Evidence Main commit `8fd3ddff`. Known limitations and follow-up Code analysis remained a maintainer tool. It did not become a prerequisite for launching an app or a replacement for the repository's automated checks."},{"title":"Launcher installation and App startup expose actionable progress","url":"history/records/2026/07/LK-20260726-launcher-install-and-startup-feedback.html","kind":"history","section":"history","keywords":"LK-20260726-launcher-install-and-startup-feedback 2026-07-26 160 fix compatible labkit_launcher Launcher double-click startup feedback Duplicate App launch prevention Standalone install and repair target selection Stable main and release-tag download selection","text":"Launcher installation and App startup expose actionable progress id: LK-20260726-launcher-install-and-startup-feedback date: 2026-07-26 sequence: 160 type: fix compatibility: compatible component: `labkit_launcher` | `1.7.0 -> 1.7.1` scope: Launcher double-click startup feedback scope: Duplicate App launch prevention scope: Standalone install and repair target selection scope: Stable main and release-tag download selection Context Opening an App from the Launcher was synchronous. Although the callback assigned an opening message before calling the App entry point, MATLAB could enter the expensive startup call before repainting the Launcher. Users therefore saw no visible acknowledgement, could interpret a legitimate startup as a hang, and could issue repeated double-clicks without knowing which stage was active. Existing GUI coverage asserted only the final opened state. The standalone `labkit_launcher.m` also claimed it could bootstrap an otherwise empty folder, while its safety check and regression test rejected that exact case. Its repair window exposed neither a target folder nor a download-source choice, and did not distinguish a new installation from replacement of an existing one. Decision and rationale Treat startup acknowledgement as part of the Launcher interaction contract. Publish and paint a small number of honest stages before the expensive App entry point, disable launch surfaces while the action is active, and preserve the concrete entry-point failure when startup does not complete. Stages identify the current ownership boundary without inventing percentage progress that the Launcher cannot measure. Keep the standalone entry self-contained but make its narrow responsibility complete: choose and validate a target before network access, distinguish installation from repair, offer stable/main/explicit-tag archives, confirm replacement, validate the extracted candidate, and retain transactional rollback. The installed Launcher continues to own rich version browsing and App catalog behavior. Changes Added visible path-preparation and App-window-initialization stages. Changed the launch button text and pointer while startup is active. Disabled the App table and all action buttons until startup finishes. Restored the normal controls after success or failure. Extended the native Launcher test to inspect status and control state at the instant the selected App entry point begins. Replaced the one-button repair window with target, source, a fetched published-version selector, confirmation, truthful operation stages, and status details. The UI neither requires users to type repository tags nor claims percentage progress for work it cannot measure. Allowed safe installation into a new, empty, or standalone-launcher-only folder while rejecting filesystem roots, Git checkouts, and unrelated nonempty folders before download. Added current-main and selected published-release downloads without importing the installed version manager into the standalone file. User and data impact Users receive immediate acknowledgement after a button press or row double-click, can see whether startup is preparing the path or waiting in the named App entry point, and cannot accidentally queue another launch through the Launcher while the first is active. Scientific data and App projects are unchanged. A user who downloads only `labkit_launcher.m` can now choose where to install, understand whether the operation is a new installation or repair, and select a published stable version or deliberate development source. Unsafe target folders fail before network or replacement activity. Compatibility and migration The launcher command, catalog, table fields, visual layout, update tools, and App entry points are unchanged. The patch is compatible with existing installations and Apps. The standalone window adds choices but preserves latest stable as its recommended default. Validation The focused hidden-GUI Launcher specification verifies the starting message, disabled table, wait pointer, temporary button label, selected command, final opened message, and restoration of controls. Documentation consistency and launcher version/history guardrails cover the published contract. Bootstrap specifications verify standalone UI choices, root-only and selected empty-folder installation, stable-release fallback, exact main and tag archive URLs, candidate validation, Git and unrelated-folder rejection, preservation, rollback, and actionable network failure. Evidence The direct-main fix commit, focused Launcher dispatch result, generated documentation check, and exact main-push CI run are the delivery evidence. Known limitations and follow-up The Launcher can identify that control has entered a named App entry point, but cannot report finer App-owned initialization phases until that App creates its Runtime diagnostics. Native dialog appearance, folder-picker behavior, and perceived responsiveness remain manual checks."},{"title":"Launcher manager and stale callback fix","url":"history/records/2026/06/LK-20260626-launcher-manager-and-stale-callback-fix.html","kind":"history","section":"history","keywords":"LK-20260626-launcher-manager-and-stale-callback-fix 2026-06-26 13 fix compatible labkit_launcher labkit_launcher labkit.ui Launcher manager and stale callback fix","text":"Launcher manager and stale callback fix id: LK-20260626-launcher-manager-and-stale-callback-fix date: 2026-06-26 sequence: 13 type: fix compatibility: compatible component: `labkit_launcher` | `1.0.0 -> 1.1.0` component: `labkit_launcher` | `1.1.0 -> 1.1.1` component: `labkit.ui` | `3.0.0 -> 3.0.1` scope: Launcher manager and stale callback fix Context The launcher could update LabKit but did not offer an explicit choice among a recent release, a tag, and the main branch. Separately, image drag tools could leave old callbacks attached after an interaction ended. Decision and rationale Add a managed version selector backed by an install manifest, and make image interaction cleanup restore or release every temporary callback. Both changes reduce hidden state that otherwise survives beyond the user's selected action. Changes `labkit_launcher` `1.0.0 -> 1.1.1` `labkit.ui` `3.0.0 -> 3.0.1` Added the launcher version manager and managed-manifest requirement. Released stale image drag callbacks. User and data impact Users could deliberately install a supported release or development revision. Ending an image drag no longer left the figure responding to an obsolete tool. Saved app data did not change. Compatibility and migration Existing launcher installations remained usable. Managed installations needed the launcher manifest for version replacement; image editors required no saved- data conversion after their stale callbacks were released. Validation The listed commits added launcher-manager/manifest coverage and regression checks for image callback cleanup. Exact historical commands were not recorded. Evidence Main commits `fe8654c9`, `ef89cf77`, and `3d23b7f1`. Known limitations and follow-up Version selection still depended on network access for remote revisions; already installed local versions remained usable offline."},{"title":"Launcher reads the single App definition","url":"history/records/2026/07/LK-20260716-launcher-definition-metadata.html","kind":"history","section":"history","keywords":"LK-20260716-launcher-definition-metadata 2026-07-16 78 refactor compatible labkit_launcher App discovery Product metadata ownership","text":"Launcher reads the single App definition id: LK-20260716-launcher-definition-metadata date: 2026-07-16 sequence: 78 type: refactor compatibility: compatible component: `labkit_launcher` | `1.5.0 -> 1.5.1` scope: App discovery scope: Product metadata ownership Context The launcher discovered entrypoints independently from App runtime creation, but obtained versions by parsing a separate package `version.m`. That kept the old metadata file alive even after Runtime V2 gained a single definition contract. Decision and rationale Keep discovery lightweight and self-contained while changing its source of truth. The launcher now reads `AppVersion` and `Updated` literals from the App's `definition.m`; it does not execute the definition or start the App. Changes Preferred `definition.m` product metadata during public and private App discovery. Added a synthetic private-App catalog test with no `version.m` file. Retained a documented internal fallback only for Apps awaiting migration. User and data impact Launcher catalogs continue to show version and update dates. A migrated App does not need a second metadata file solely for discovery. Compatibility and migration The change is compatible. Existing Apps remain discoverable through the temporary fallback; migrated Apps use only their definition. The fallback is removed after the last App migration. Validation The launcher catalog test creates an isolated App definition, points private discovery at it, and verifies the returned version and date without a `version.m` file. Evidence [LabKit Launcher](../../../../apps/labkit-core/launcher/README.md) explains definition-backed discovery. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the App product metadata fields. Known limitations and follow-up P-code packaging metadata will be audited with the deployment workflow before the transitional parser fallback is removed."},{"title":"Launcher update reliability","url":"history/records/2026/07/LK-20260701-launcher-update-reliability.html","kind":"history","section":"history","keywords":"LK-20260701-launcher-update-reliability 2026-07-01 25 fix compatible labkit_launcher labkit_launcher Launcher update reliability","text":"Launcher update reliability id: LK-20260701-launcher-update-reliability date: 2026-07-01 sequence: 25 type: fix compatibility: compatible component: `labkit_launcher` | `1.1.3 -> 1.1.4` component: `labkit_launcher` | `1.1.4 -> 1.1.5` scope: Launcher update reliability Context The self-contained launcher could install a ZIP release, but the replacement path performed more filesystem work than necessary and duplicated logic for locating, unpacking, and replacing the workbench. Slow replacement made a successful update look stalled; duplicated recovery paths made failures harder to reason about. Decision and rationale Minimize the files touched during an update and give ZIP replacement one straight-line implementation. Preserve the existing installation until the download and unpack steps have produced a usable replacement. Changes `labkit_launcher` `1.1.3 -> 1.1.5` Sped up launcher zip updates. Simplified launcher zip replacement. User and data impact ZIP updates completed with less waiting and fewer intermediate operations. A failure before replacement left the existing checkout available, while a successful update continued to present the same launcher entry point. Compatibility and migration Existing managed installations remained updateable. The replacement algorithm changed internally and did not require users to reinstall a working checkout. Validation Both commits extended `LauncherGuiTest` around download, replacement, and recovery behavior. The exact historical commands were not recorded. Evidence Main commits `ebf86cf2` and `becf9391`. Known limitations and follow-up The updater still depended on the release artifact being trustworthy. Later release work added stronger tag-to-asset verification and update diagnostics."},{"title":"Legacy import and first app workbench","url":"history/records/2026/05/LK-20260528-initial-app-workbench-foundation.html","kind":"history","section":"history","keywords":"LK-20260528-initial-app-workbench-foundation 2026-05-28 1 feat compatible historical project evolution","text":"Legacy import and first app workbench id: LK-20260528-initial-app-workbench-foundation date: 2026-05-28 sequence: 1 type: feat compatibility: compatible scope: historical project evolution Context LabKit began with a direct import of older MATLAB analysis and GUI code. The electrochem workflows mixed file parsing, calculations, session state, plots, and exports inside large entry scripts, and several root commands exposed the same legacy implementation through different paths. Decision and rationale Separate the imported code by responsibility before adding new features. Move Gamry parsing and pulse detection into reusable DTA operations; keep CIC, CSC, VT resistance, EIS, and chrono calculations with their workflows; then expose each workflow through one package-backed app command. Extract shared UI pieces only after the app behavior is visible in more than one workflow. Changes Imported the legacy MATLAB tree without rewriting its scientific formulas. Extracted chrono, EIS, and CV/CT DTA parsers plus pulse detection and named synthetic fixtures. Separated chrono alignment/export, CIC, CSC, VT resistance, and EIS result operations from their GUI assembly. Added named app entry points and package-backed runners, then removed the duplicate root legacy entry points and legacy GUI directory. Introduced the first shared two-pane/tabbed shells, file panels, plot controls, log panels, and axes helpers used by the electrochem apps. User and data impact Users gained named electrochem app commands instead of opening the old GUI scripts directly. Calculations and exported values were intended to remain unchanged while code ownership moved; laboratory files remained external inputs rather than being copied into a central LabKit database. Compatibility and migration Component/app version files did not exist yet. Old root GUI entry points were removed as package-backed app commands became the supported launch path. Validation The first regression evidence included named DTA fixtures, calculation tests, an optional GUI smoke runner, and GUI compatibility checks. A single standard test command had not yet been established. Evidence Initial import `5973bde0`. Parser and calculation extraction from `fc70f9b2` through `9f7c6e4e`. App entrypoint and package migration from `40f46561` through `4d58066c`. First shared app-shell work from `f05f4a35` through `61a8cb87`. Known limitations and follow-up This stage improved structure but still contained several broad shared UI helpers and session abstractions that were narrowed during the following DTA facade and app-boundary work."},{"title":"Legacy import preserves portable references without exposing their schema","url":"history/records/2026/07/LK-20260716-opaque-source-import.html","kind":"history","section":"history","keywords":"LK-20260716-opaque-source-import 2026-07-16 102 feat compatible labkit.ui App Framework Project portability","text":"Legacy import preserves portable references without exposing their schema id: LK-20260716-opaque-source-import date: 2026-07-16 sequence: 102 type: feat compatibility: compatible component: `labkit.ui` | `7.4.2 -> 7.4.3` scope: App Framework scope: Project portability Context The GUI-free source-record factory accepted filepaths, but an App importing an older MAT variable could already receive a portable reference containing the only usable relative location. Recreating the record from its original path would discard that portability; preserving it required the importer to copy the Runtime's private nested reference schema. Decision and rationale Keep one public source factory and allow its source value to be either a normal filepath or an existing portable reference passed as one opaque struct. The Runtime validates and canonicalizes the latter. Apps still never construct or read nested reference fields. Changes Extended `labkit.ui.runtime.sourceRecord` with an opaque-reference overload. Added strict validation for the supported portable-reference schema, scalar text fields, filename, and usable relative or original path. Canonicalized imported references so unrelated legacy fields do not enter a current project. Documented the distinction between normal filepath creation and legacy reference preservation. User and data impact Moved legacy projects retain their relative source fallback during upgrade. App importers no longer need to duplicate Runtime serialization fields or choose between architectural ownership and data portability. Compatibility and migration Existing filepath calls and injected source-record services are unchanged. The overload accepts only the current supported reference schema; malformed or newer references fail before they can enter a project. Validation Focused Runtime tests cover normal path creation, opaque-reference canonicalization, relative-path preservation, missing fields, unsupported schema versions, source path access, and the public package surface. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Framework](../../../../framework/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up Opaque-reference import is intended for trusted project importers, not as a general App-defined serialization extension. Video Marker is the first legacy importer scheduled to consume the overload."},{"title":"Local validation uses one focused route and CI owns full coverage","url":"history/records/2026/07/LK-20260721-local-validation-routing.html","kind":"history","section":"history","keywords":"LK-20260721-local-validation-routing 2026-07-21 150 test compatible Test routing Continuous Integration","text":"Local validation uses one focused route and CI owns full coverage id: LK-20260721-local-validation-routing date: 2026-07-21 sequence: 150 type: test compatibility: compatible scope: Test routing scope: Continuous Integration Context The repository exposed both `changedFast` and a conservative `changed` task. For documentation, agent guidance, and CI workflow changes they selected the same complete project suite, so running both locally duplicated work that the cross-platform CI already repeated more comprehensively. Decision and rationale Keep one local changed-file task, `changedFast`, for a focused pre-push check. PR and main-push CI remain the complete validation gate because they run the full headless and hidden-GUI suites on every supported platform. Changes Removed the public conservative `changed` task and its runner entry. Guidance, templates, workflows, and CI helpers now route to their owning documentation or CI guardrails instead of the complete project suite. Broad GUI candidate selection remains reduced to representative workflows for the local route. User and data impact App behavior, saved projects, analysis results, and laboratory data do not change. Contributors receive faster local feedback while the same complete platform coverage remains mandatory before delivery. Compatibility and migration Use `buildtool changedFast` for the local pre-push checkpoint. The retired `buildtool changed` command is intentionally unavailable; use CI results for complete validation rather than substituting another local broad run. Validation Build-task catalog and route-contract tests verify the single public local route and its owner-specific selections. The changed-file gate exercises the selected guardrails, while CI continues to run full headless and hidden-GUI coverage. Evidence The CI workflow invokes `headless` and `gui` on Linux, macOS, and Windows. The validation planner records a reason for every selected local guardrail. Known limitations and follow-up Local `changedFast` is intentionally not a substitute for CI or manual App review. If a new shared surface lacks an owning test route, add that route rather than broadening unrelated local checks."},{"title":"MATLAB-compatible image conversion API","url":"history/records/2026/07/LK-20260713-matlab-compatible-image-conversion.html","kind":"history","section":"history","keywords":"LK-20260713-matlab-compatible-image-conversion 2026-07-13 47 refactor breaking labkit.image labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app MATLAB-compatible image conversion API","text":"MATLAB-compatible image conversion API id: LK-20260713-matlab-compatible-image-conversion date: 2026-07-13 sequence: 47 type: refactor compatibility: breaking component: `labkit.image` | `1.2.0 -> 2.0.0` component: `labkit_DICPostprocess_app` | `1.3.5 -> 1.3.6` component: `labkit_BatchImageCrop_app` | `1.6.7 -> 1.6.8` component: `labkit_CurvatureMeasurement_app` | `1.3.4 -> 1.3.5` component: `labkit_FocusStack_app` | `1.4.8 -> 1.4.9` component: `labkit_ImageEnhance_app` | `1.5.7 -> 1.5.8` component: `labkit_ImageMatch_app` | `1.5.7 -> 1.5.8` scope: MATLAB-compatible image conversion API Context The `toDouble`, `toLuma`, and `toRgbDouble` names combined class conversion, channel shaping, and clipping in ways that differed from familiar MATLAB APIs. Decision and rationale Use MATLAB-compatible names and call contracts for replacement functions, and keep orthogonal RGB shaping explicit so users do not need to learn a composite LabKit normalization rule. Changes Added base-MATLAB `labkit.image.im2double` and `labkit.image.rgb2gray`. Kept channel shaping in `ensureRgb` and made clipping explicit at call sites. Removed the ambiguous conversion helpers and centralized Rec.601 ownership. User and data impact Base-MATLAB users receive familiar image conversion behavior without hidden toolbox requirements. Existing app image results retain their intended ranges and channel shapes through explicit pipelines. Compatibility and migration This is a breaking facade rename. External callers replace removed helper names with `im2double`, `rgb2gray`, and `ensureRgb` as separately needed. Validation Image facade, downstream app, toolbox-shadow, and base-MATLAB ownership tests cover class conversion, luma values, and representative workflows. Evidence The current API contracts are documented in the [Image Library](../../../../libraries/image/README.md). Commit `e3f71c2d` aligned the conversion functions with MATLAB call behavior. Known limitations and follow-up The compatibility layer intentionally covers the LabKit-used MATLAB contracts, not every Image Processing Toolbox function. labkit.image.im2double Convert image data to double using MATLAB's im2double contract. labkit.image.rgb2gray Convert RGB data using MATLAB's rgb2gray call contract."},{"title":"Managed image interactions and diagnostic tracing","url":"history/records/2026/06/LK-20260604-managed-image-interactions.html","kind":"history","section":"history","keywords":"LK-20260604-managed-image-interactions 2026-06-04 4 feat compatible historical project evolution","text":"Managed image interactions and diagnostic tracing id: LK-20260604-managed-image-interactions date: 2026-06-04 sequence: 4 type: feat compatibility: compatible scope: historical project evolution Context Image and ECG apps had made the workbench broader, but they also revealed a class of GUI failures that did not appear in parser or calculation tests. An anchor editor could re-enter a callback, a scale bar could lose ownership, an axes popout could stop reflecting the source, and a long operation could leave controls in an ambiguous state. Decision and rationale Move repeatable interaction mechanics into the UI foundation while leaving image-specific geometry and workflow state in each app. Give long-running actions an explicit busy-state guard, make image axes and tools managed resources, and record callback progress in diagnostic traces that could be inspected after a failure. Changes Added axes popout support and corrected image aspect and menu refresh behavior. Added shared UI control helpers and a reusable ECG peak detector facade. Added the Focus Stack app with automatic and manual image selection. Improved anchor insertion, curvature controls, and scale-bar ownership. Added a busy-state guard to prevent overlapping actions. Introduced a managed image-axes runtime, layered UI facades, and app debug trace logging. Split large app entry points into app-owned private workflow helpers rather than widening the public framework API. User and data impact Users gained Focus Stack and more predictable image editing. Popout views, anchors, scale bars, and long operations provided clearer feedback and were less likely to leave an app in a broken intermediate state. Debug traces made it possible to distinguish a slow or failed callback from an unresponsive window. The changes affected UI state and presentation; they did not intentionally alter existing scientific results. Compatibility and migration Apps gradually moved from direct axes and interaction setup to the managed UI facades. App-private geometry helpers remained private, so the framework did not promise a public API for every interaction detail. Validation The period added public API option documentation, app-private helper tests, interaction regression coverage, and focused checks for UI layout ownership. No single historical validation command covers the complete stage. Evidence Axes popout and control helpers `1e9022c4` through `2136009f`. ECG detector and filtering work `e8450652` through `adb7dfbf`. Focus Stack `08518e91`, `cc4cdf92`. Busy guard `ac36bd54`. Image interaction and scale-bar work `f58b8559` through `5304fa20`. Managed axes, traces, and layered UI facades `27bdbdd8`, `e86e4ea3`, `da0663f1`, `c4cae633`. Known limitations and follow-up The interaction layer was still evolving and the test infrastructure mixed custom runners with newer MATLAB tests. The v1.0 stabilization pass addressed test entry points, package ownership, and remaining compatibility aliases."},{"title":"Managed scientific and conversion constants","url":"history/records/2026/07/LK-20260713-managed-calculation-constants.html","kind":"history","section":"history","keywords":"LK-20260713-managed-calculation-constants 2026-07-13 48 refactor compatible labkit.dta labkit.rhs labkit.biosignal labkit_ChronoOverlay_app labkit_CSC_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Managed scientific and conversion constants","text":"Managed scientific and conversion constants id: LK-20260713-managed-calculation-constants date: 2026-07-13 sequence: 48 type: refactor compatibility: compatible component: `labkit.dta` | `2.0.0 -> 2.0.1` component: `labkit.rhs` | `1.0.0 -> 1.0.1` component: `labkit.biosignal` | `1.0.0 -> 1.0.1` component: `labkit_ChronoOverlay_app` | `1.3.5 -> 1.3.6` component: `labkit_CSC_app` | `1.3.9 -> 1.3.10` component: `labkit_NerveResponseAnalysis_app` | `1.3.4 -> 1.3.5` component: `labkit_ResponseReviewStats_app` | `1.3.4 -> 1.3.5` scope: Managed scientific and conversion constants Context Scientific coefficients, device-format gains, unit conversions, and numeric tolerances were sometimes left as unexplained literals or repeated across callers, making origin and change impact difficult to audit. Decision and rationale Give each semantic calculation constant one named owner and a nearby source or purpose comment, while exempting ordinary indices and explicit UI geometry that do not encode scientific meaning. Changes Named and documented Rec.601, sRGB/CIE, DTA/RHS gains, SI conversions, tolerances, and empirical policies across facades and apps. Centralized repeated CSC charge-density, CIC display-unit, and curvature tolerance contracts. Added repository-wide magic-number and rectangle-interaction guardrails. User and data impact Calculation results are preserved, but future changes now expose the constant's meaning and provenance instead of silently editing an unexplained literal. Compatibility and migration No user migration is required. Public function call contracts and output schemas are unchanged by this record. Validation `MagicNumberGovernanceTest`, rectangle governance, module compatibility tests, and affected app tests cover centralized ownership and preserved numeric behavior. Evidence Source comments use the `Constant:` marker and guardrail diagnostics name the unmanaged file and line. Commit `125338c0` introduced the calculation-constant checks and the corresponding source annotations. Known limitations and follow-up The scanner targets semantically suspicious precision and notation; review is still required for simple integers or short decimals whose meaning is hidden."},{"title":"Migration helper cleanup","url":"history/records/2026/06/LK-20260630-migration-helper-cleanup.html","kind":"history","section":"history","keywords":"LK-20260630-migration-helper-cleanup 2026-06-30 23 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_ImageEnhance_app labkit_RHSPreview_app labkit_RHSPreview_app historical project evolution","text":"Migration helper cleanup id: LK-20260630-migration-helper-cleanup date: 2026-06-30 sequence: 23 type: refactor compatibility: compatible component: `labkit.ui` | `3.2.8 -> 3.2.9` component: `labkit_DICPostprocess_app` | `1.2.3 -> 1.2.4` component: `labkit_BatchImageCrop_app` | `1.3.7 -> 1.3.8` component: `labkit_BatchImageCrop_app` | `1.3.8 -> 1.3.9` component: `labkit_ImageEnhance_app` | `1.3.4 -> 1.3.5` component: `labkit_RHSPreview_app` | `1.2.2 -> 1.2.3` component: `labkit_RHSPreview_app` | `1.2.3 -> 1.2.4` scope: historical project evolution Context Several apps still split one small decision across multiple temporary helper files created during earlier package migrations. That made simple state and preview behavior harder to follow without creating a reusable API. Decision and rationale Consolidate related values behind one clearly named operation and delete pass-through helpers whose only purpose was reducing file length. Preserve the visible app behavior while making each calculation or state summary traceable from its caller. Changes DIC Post, Batch Crop, and RHS Preview patch bumped. Retired migration helper debt. Consolidated RHS preview window bounds, Batch Crop scale state, and Image Enhance export helpers. User and data impact No workflow or result format changed. The cleanup reduced internal indirection in Image Enhance export, Batch Crop scale summaries, RHS Preview window bounds, and a small set of DIC/electrochem helpers. Compatibility and migration The consolidated helpers preserved the app-facing state and result structures. No project or export format changed as the duplicate implementations were removed. Validation Commits `7f73b71b`, `e3349af6`, `733fb951`, `98a2b02c`, and `391540a7` added or updated focused tests for each consolidated operation. Evidence Main commits `7f73b71b`, `e3349af6`, `733fb951`, `98a2b02c`, and `391540a7`. Known limitations and follow-up This was a behavior-preserving cleanup. Later workflow-first packages replaced some of the package names shown in the historical commits."},{"title":"Minimal App contract and current authoring manuals","url":"history/records/2026/07/LK-20260716-minimal-app-contract.html","kind":"history","section":"history","keywords":"LK-20260716-minimal-app-contract 2026-07-16 115 docs compatible labkit-project App authoring Runtime V2 contract guardrails","text":"Minimal App contract and current authoring manuals id: LK-20260716-minimal-app-contract date: 2026-07-16 sequence: 115 type: docs compatibility: compatible component: `labkit-project` scope: App authoring scope: Runtime V2 contract guardrails Context Runtime V2 already supplied default project, session, action, presenter, and startup capabilities, but the complete-App tutorial and several current manuals still taught the retired split metadata and generic lifecycle package shape. The App structure guardrail also prohibited those files only when an App already owned `projectSpec.m`, leaving static Apps unprotected. Decision and rationale Make progressive capability the documented and tested authoring contract. A static App consists of a thin entrypoint, one definition, and one data-only layout. Actions, presentation, durable project state, transient reconstruction, and startup are added independently only when the product needs them. Changes Rewrote the complete-App tutorial around the three-file starting point and one `projectSpec.m` create/validate/migrate entry. Updated architecture, framework, private-App, library, testing, release, and history manuals plus scoped App rules and the App-builder skill to use definition-owned metadata and progressive optional capabilities. Added a hidden-GUI launch test whose definition omits every optional Runtime V2 component. Corrected the tutorial's numeric field declaration after the real launch test exposed its implicit text-control mismatch. Made the App package guardrail reject retired metadata, lifecycle, state, startup, and per-version migration files for every App shape. User and data impact App behavior and scientific results do not change. A developer can begin with three source files and add each larger architectural concept only when a real workflow requires it. Public and private App documentation now teach the same contract. Compatibility and migration This change documents and protects the already migrated Runtime V2 structure. It does not alter saved project payloads or remove the temporary low-level legacy project-migration reader used by framework tests. Validation Focused contract and GUI tests cover both the forbidden retired structures and an actual launch from the minimal definition. A documentation contract checks the tutorial, scoped App rules, and App-builder skill together. Documentation consistency checks and a regenerated site validate navigation and history metadata. Evidence [Build a Complete App](../../../../development/build-apps/complete-app.md) gives the progressive file-by-file tutorial. [Architecture](../../../../development/build-apps/architecture.md) defines the current package shape and ownership boundaries. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) specifies every optional component and callback. Known limitations and follow-up The minimal contract proves framework defaults, not that every complex App has already reached its lowest maintainable complexity. App-by-App workflow and framework-boundary audits continue separately."},{"title":"Minimal App definitions","url":"history/records/2026/07/LK-20260716-minimal-app-definition.html","kind":"history","section":"history","keywords":"LK-20260716-minimal-app-definition 2026-07-16 76 feat compatible labkit.ui App authoring Runtime V2 definition defaults","text":"Minimal App definitions id: LK-20260716-minimal-app-definition date: 2026-07-16 sequence: 76 type: feat compatibility: compatible component: `labkit.ui` | `7.0.0 -> 7.1.0` scope: App authoring scope: Runtime V2 definition defaults Context Runtime V2 already normalized missing project and session buckets, but every definition still had to provide project create/validate callbacks, at least one action, and a presenter. A static App therefore needed lifecycle files and placeholder functions that owned no behavior. Decision and rationale Make lifecycle components proportional to product behavior. Every App still declares a stable ID, title, and semantic layout. The framework supplies empty canonical state and no-op dynamic behavior until the App opts into durable data, transient cache, actions, or presentation. Changes Made `Project`, `Actions`, and `Present` optional in `labkit.ui.runtime.define`; `CreateSession` was already optional. Added a framework-owned version-1 empty project specification and empty presenter model. Allowed an empty action registry when no layout event or startup phase references an action. Added a GUI contract test that launches a definition containing only `Id`, `Title`, and `Layout` and verifies both canonical state roots. Rewrote App-development entry guidance around capability tiers instead of a fixed list of placeholder files. User and data impact Existing Apps behave unchanged. A new static App does not need `createProject.m`, `createSession.m`, `validateProject.m`, `definitionActions.m`, or a presenter. Those components are introduced only when the App gains corresponding behavior. Compatibility and migration The change is additive within UI 7. Existing complete definitions remain valid. Saved project and source schemas do not change. Validation The minimal-definition GUI test exercises real launch, canonical project and session creation, layout construction, empty presentation, and readiness. Definition validation and public documentation guardrails cover both minimal and full definitions. Evidence [App Framework](../../../../framework/README.md) introduces the minimal definition. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) lists required and optional definition components. [App Development](../../../../development/build-apps/app-development.md) maps files to capabilities instead of treating them as universal boilerplate. Known limitations and follow-up Apps that own a project schema still use explicit project specification. The next lifecycle cleanup consolidates each App's project factory, validator, and ordered migration steps behind one `projectSpec.m` entry point rather than scattered version-step files."},{"title":"Multi-app launcher packages","url":"history/records/2026/07/LK-20260709-multi-app-launcher-packages.html","kind":"history","section":"history","keywords":"LK-20260709-multi-app-launcher-packages 2026-07-09 42 feat compatible labkit_launcher Multi-app launcher packages","text":"Multi-app launcher packages id: LK-20260709-multi-app-launcher-packages date: 2026-07-09 sequence: 42 type: feat compatibility: compatible component: `labkit_launcher` | `1.2.7 -> 1.3.0` scope: Multi-app launcher packages Context The deployment tool could package one app, but laboratory workflows often used a small related set. Creating one ZIP per app duplicated the shared LabKit runtime, while manually merging ZIP files risked inconsistent entry files and manifests. The launcher's selected row also could not double as a multi-select without making Open and Debug ambiguous. Decision and rationale Add a separate Package checkbox to each launcher row. Keep the ordinary row selection for Open and Debug, and let checked rows produce one bundle with one entry file per app and a manifest describing the complete package. Changes `labkit_launcher` `1.2.7 -> 1.3.0` Project deployment tooling, multi-app bundle support. Added an independent `Package` checkbox column to the launcher app table so users can choose multiple apps without changing the row selected for Open or Debug. `Package Checked` and `Checked P-code` now create one zip containing every checked app, one direct entry file per app, and a multi-app manifest. Kept single-app package names, result fields, and manifest schema compatible when only one app is supplied to `packageLabKitApp`. User and data impact Users could distribute a related group of apps in one source or P-code ZIP without including the rest of the workbench. Opening and debugging still acted on the highlighted app, so preparing a package did not change normal launcher navigation. Compatibility and migration Existing direct calls that package one app continue to produce the original single-app package contract. Validation The commit expanded deployment unit tests, launcher packaging GUI coverage, selection helpers, changed-file routing, and build-task efficiency checks. The exact historical command was not recorded. Evidence Mainline commit `8a23a52`. Known limitations and follow-up Bundles intentionally included selected apps and their LabKit dependencies, not tests, full documentation, or unrelated private workspaces. P-code packages still required a compatible MATLAB runtime."},{"title":"Nerve Response Analysis uses fixed source identities","url":"history/records/2026/07/LK-20260716-nerve-response-structure.html","kind":"history","section":"history","keywords":"LK-20260716-nerve-response-structure 2026-07-16 107 refactor compatible labkit_NerveResponseAnalysis_app Neurophysiology App structure","text":"Nerve Response Analysis uses fixed source identities id: LK-20260716-nerve-response-structure date: 2026-07-16 sequence: 107 type: refactor compatibility: compatible component: `labkit_NerveResponseAnalysis_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context Nerve Response Analysis split one project schema across generic lifecycle files and kept two additional lifecycle helpers to filter and replace source records by role. Product metadata remained in separate requirement/version functions, while session and presentation code read nested portable-reference fields directly. Decision and rationale Use `filterRecord` and `protocol` as the two stable App-owned source IDs and matching roles. Runtime V2 already supports semantic path lookup by ID and an injected upsert service, so role-filter wrappers duplicate framework behavior without adding scientific meaning. Keep event detection, recording analysis, CAP measurement, presentation, and result writing in their existing concrete workflow packages. Changes Consolidated command identity, display metadata, version, requirements, project, session, layout, actions, presenter, renderer, and debug capability through the single definition. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved parsed JSON and transient workflow reconstruction to root `createSession`. Replaced App-owned role filtering/replacement helpers with `sourcePaths` and the injected `upsertSource` service. Strengthened validation so the two source IDs are unique, supported, and match their declared roles. Removed the generic lifecycle package and separate requirement/version files. User and data impact Filter/protocol selection, run limits, event and train detection, CAP metrics, issue handling, previews, output-folder defaults, JSON export, manifest output, reset, and project reopening are unchanged. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still combine their separate filter and protocol records into the canonical source collection. Runtime V2 invokes the single migration entry and owns iteration. Current version-2 files already written with the documented fixed IDs load without data transformation. Validation Focused unit tests cover migration, source identity validation, event trains, differential/common-mode calculations, CAP metrics, filter labels, and the legacy keep-column input. The hidden GUI workflow covers filter selection, analysis, counts/issues presentation, JSON and manifest export, project save, reset, reopen, and reanalysis. Evidence [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not establish that a selected protocol, stimulation source, or timing window is physiologically correct. RHS Preview still uses the older split lifecycle structure and requires its own independent convergence review."},{"title":"One project migration entry per App","url":"history/records/2026/07/LK-20260716-single-project-migration-entry.html","kind":"history","section":"history","keywords":"LK-20260716-single-project-migration-entry 2026-07-16 81 feat compatible labkit.ui Runtime V2 projects App authoring","text":"One project migration entry per App id: LK-20260716-single-project-migration-entry date: 2026-07-16 sequence: 81 type: feat compatibility: compatible component: `labkit.ui` | `7.2.1 -> 7.3.0` scope: Runtime V2 projects scope: App authoring Context Runtime V2 already applied project migrations sequentially, but each App had to expose a cell array containing every historical version-step function. Schema growth therefore increased files and wiring even though the framework owned the actual migration loop. Decision and rationale Give each persistent App one migration entry. `Migrate(project,fromVersion)` upgrades exactly one step; Runtime V2 supplies each missing version in order, validates every intermediate payload, and continues to the current version. Changes Added the scalar `Project.Migrate` callback. Kept per-step serialization validation, newer-version rejection, atomic load behavior, final project validation, session rebuild, and dirty upgrade state unchanged. Converted the framework's version-1-to-3 round-trip fixture to one callback with two version cases. Retained `Project.Migrations` only as a temporary bridge for existing Apps. User and data impact Saved projects behave as before. App maintainers can keep create, validate, and every schema transition as local functions in one `projectSpec.m` instead of maintaining a growing migration-file registry. Compatibility and migration The contract is additive within UI 7. Existing Apps continue to load during family-sized migration. An App must not declare both migration forms. Validation The Runtime V2 project test opens a version-1 payload in a version-3 App, executes both steps through one callback, verifies final data and source relinking, saves the current payload version, and checks failure atomicity. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents the project declaration and loop. [App Development](../../../../development/build-apps/app-development.md) describes the single project file. Known limitations and follow-up Existing Apps still need their lifecycle functions consolidated into `projectSpec.m`. The legacy migration-array bridge will be removed after the last family is migrated."},{"title":"Output folder prompts","url":"history/records/2026/06/LK-20260630-output-folder-prompts.html","kind":"history","section":"history","keywords":"LK-20260630-output-folder-prompts 2026-06-30 20 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Output folder prompts","text":"Output folder prompts id: LK-20260630-output-folder-prompts date: 2026-06-30 sequence: 20 type: feat compatibility: compatible component: `labkit.ui` | `3.2.5 -> 3.2.6` component: `labkit_DICPostprocess_app` | `1.2.0 -> 1.2.1` component: `labkit_DICPreprocess_app` | `1.2.0 -> 1.2.1` component: `labkit_BatchImageCrop_app` | `1.3.3 -> 1.3.4` component: `labkit_FocusStack_app` | `1.2.1 -> 1.2.2` component: `labkit_ImageEnhance_app` | `1.3.1 -> 1.3.2` component: `labkit_ImageMatch_app` | `1.3.1 -> 1.3.2` component: `labkit_NerveResponseAnalysis_app` | `1.2.1 -> 1.2.3` component: `labkit_ResponseReviewStats_app` | `1.2.1 -> 1.2.2` scope: Output folder prompts Context Exporting from different apps opened `uigetdir` directly, chose starting folders differently, and could not substitute a noninteractive chooser in a test. The repeated dialog code also made cancellation handling inconsistent. Decision and rationale Provide one output-folder prompt that selects a safe default, returns an explicit cancellation flag, remembers a successful folder, and accepts an injected chooser for tests. Apps would still decide when an output folder was needed and what they wrote there. Changes `labkit.ui` `3.2.5 -> 3.2.6` DIC apps, Batch Crop, Focus Stack, Image Enhance/Match, Nerve Response, and Response Review patch bumped. Added `promptOutputFolder`. Migrated output-folder prompts with chooser injection and safe defaults. User and data impact Output dialogs began in a useful folder and cancellation returned cleanly to the app. The selected folder was remembered as a preference; no output was created until the owning app performed its export. Compatibility and migration Existing output locations and exported file formats remained valid. Apps moved from direct folder dialogs to the shared prompt without changing what they wrote after selection. Validation Commit `c5055b98` added `AppHookHelpersTest` coverage for defaults, cancellation, successful selection, and chooser injection, then updated the affected app compatibility checks. Evidence Main commit `c5055b98`. Known limitations and follow-up Runtime V2 later moved dialog access into injected services, preserving the same separation between app decisions and platform dialog mechanics."},{"title":"Owner-minimal validation routing compiles canonical test groups","url":"history/records/2026/07/LK-20260722-owner-minimal-validation-routing.html","kind":"history","section":"history","keywords":"LK-20260722-owner-minimal-validation-routing 2026-07-22 157 test compatible Test routing Test ownership","text":"Owner-minimal validation routing compiles canonical test groups id: LK-20260722-owner-minimal-validation-routing date: 2026-07-22 sequence: 157 type: test compatibility: compatible scope: Test routing scope: Test ownership Context Changed-file validation could widen an App capability change to a family-level test folder, depend on implicit contract discovery, or execute the same test through overlapping folder routes. The migration ledger recorded the required zero-debt replacement: explicit App owners, framework consumer closure, and one execution of each selected official test. Decision and rationale Treat route selection and execution as separate concerns. Preserve every semantic owner route with its reason, discover the matching official tests, then union canonical identities into one non-GUI and one hidden-GUI execution group. This keeps coverage reviewable without paying for duplicate execution. Changes Moved App-specific tests into explicit `appContract`, `workbench`, source capability, `smoke`, and `isolatedPath` owners; moved cross-App audits to project ownership. Removed family-root and overlap-based route suppression. Added canonical test-union compilation, deterministic feature-tagged smoke selection, direct facade-consumer routing, and safe all-smoke fallback when required feature metadata is absent. Preserved physical suite-folder case during selector discovery and made an isolated App sample derive the repository root from the shared test setup, so owner paths behave consistently on Linux, macOS, and Windows. Focused ordinary App-manual, history, and library-documentation routes while retaining complete contracts for renderer and policy changes. Added dynamic guards for every public App's owned contracts and smoke proof, valid source-capability scopes, unique canonical execution, and focused documentation routes. Test classes also reject migrated test-shaped methods stranded in non-discoverable static-private blocks. User and data impact No App behavior, saved project, scientific result, or export changes. Local `changedFast` feedback is smaller and deterministic; CI remains the complete headless and hidden-GUI validation gate. Compatibility and migration No saved-data migration is required. The active test-routing migration ledger is retired; future routing changes must extend the durable ownership rules and their guardrails rather than restore compatibility fallbacks. Validation Focused build-routing guardrails, App ownership contracts, and moved cross-App contracts. Same-machine MATLAB R2025a before/after benchmark for a representative Figure Studio source-capability change. One final `buildtool changedFast` run for the completed diff. Evidence The planner prints all semantic routes and the canonical execution count, including the number of repeated selections removed. The final local gate and CI provide the executable evidence for the exact merged diff. For a change to the Figure Studio `sourceAxes/copyToPreview` capability, the pre-migration planner at the retained mainline baseline selected 46 test executions across three runner groups: 45 unique tests plus one repeated isolated-path contract. Those groups took 120.396 seconds after 5.284 seconds of planning. The migrated planner retained four explicit semantic routes, selected 11 unique tests with no repeated execution, and compiled them into one headless and one hidden-GUI group. Those groups took 51.134 seconds after 6.212 seconds of planning. Test execution time fell by 69.262 seconds (57.5%) while retaining the source-capability unit tests, the owning App's isolated path contract, its launch smoke test, and the relevant source-axes GUI tests. Known limitations and follow-up Hidden GUI smoke tests validate bounded launch/layout behavior, not native dialogs, pointer feel, visual quality, or scientific interpretation; those remain manual review responsibilities."},{"title":"Preview-area per-axis wheel zoom","url":"history/records/2026/07/LK-20260709-preview-area-per-axis-wheel-zoom.html","kind":"history","section":"history","keywords":"LK-20260709-preview-area-per-axis-wheel-zoom 2026-07-09 44 feat compatible labkit.ui Preview-area per-axis wheel zoom","text":"Preview-area per-axis wheel zoom id: LK-20260709-preview-area-per-axis-wheel-zoom date: 2026-07-09 sequence: 44 type: feat compatibility: compatible component: `labkit.ui` | `5.0.3 -> 5.0.4` scope: Preview-area per-axis wheel zoom Context The preview area applied the same two-dimensional wheel zoom to every registered axes. That was appropriate for an image or main plot, but awkward for a narrow histogram or color-scale axes: horizontal zoom changed a dimension whose layout was meant to remain fixed. Decision and rationale Let a preview-area definition choose `xy`, `x`, or `y` wheel zoom for each axes. Preserve `xy` as the default so existing apps keep their behavior, while side axes can opt into only the dimension that carries data. Changes `labkit.ui` `5.0.3 -> 5.0.4` Added a `scrollZoomAxes` preview-area layout option so apps can declare whether each preview axis should mouse-wheel zoom in `xy`, `x`, or `y`. Preview-area side axes can now remain horizontally stable while still allowing app-selected vertical wheel zoom. User and data impact A histogram or temperature scale could remain horizontally stable while still supporting vertical exploration. Main images and plots continued to zoom in both dimensions unless their app explicitly chose another mode. Compatibility and migration Existing preview areas keep default `xy` wheel zoom unless they opt into another per-axis setting. Validation The commit extended UI layout unit tests and axes-workbench GUI coverage for the default and per-axis modes. The exact historical command was not recorded. Evidence Mainline commit `3c143eb`. Known limitations and follow-up The option controlled wheel navigation only. Toolbar zoom, pan, linked axes, and app-specific limit policies remained separate concerns."},{"title":"Profiling and validation speedups","url":"history/records/2026/07/LK-20260702-profiling-and-validation-speedups.html","kind":"history","section":"history","keywords":"LK-20260702-profiling-and-validation-speedups 2026-07-02 30 ci compatible labkit_launcher labkit_launcher labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_ECGPrint_app Profiling and validation speedups","text":"Profiling and validation speedups id: LK-20260702-profiling-and-validation-speedups date: 2026-07-02 sequence: 30 type: ci compatibility: compatible component: `labkit_launcher` | `1.2.0 -> 1.2.1` component: `labkit_launcher` | `1.2.1 -> 1.2.2` component: `labkit.ui` | `3.4.0 -> 3.4.1` component: `labkit.ui` | `3.4.1 -> 3.4.2` component: `labkit_BatchImageCrop_app` | `1.6.0 -> 1.6.1` component: `labkit_ECGPrint_app` | `1.3.0 -> 1.3.1` scope: Profiling and validation speedups Context Startup and file-selection complaints could not be resolved reliably from wall clock impressions alone. At the same time, focused changes paid avoidable test discovery cost, GUI tests waited longer than the behavior required, and Batch Crop read every selected image before the user requested a preview or export. Decision and rationale Add a profiler that records launcher, startup, callback, and close costs with source attribution. Use its evidence to remove repeated UI updates and eager image reads. Route changed-file validation to the smallest owning suites and bound GUI waits without weakening their behavioral assertions. Changes `labkit_launcher` `1.2.0 -> 1.2.2` `labkit.ui` `3.4.0 -> 3.4.2` `labkit_BatchImageCrop_app` `1.6.0 -> 1.6.1` `labkit_ECGPrint_app` `1.3.0 -> 1.3.1` Added LabKit profiling and build-managed test routing to the launcher. Reduced GUI profiling overhead and deferred Batch Crop image reads until preview/export. Compressed validation runtime with bounded GUI waits. User and data impact Maintainers could launch a profiled target and receive a summarized report instead of interpreting MATLAB's raw profile table. Selecting many Batch Crop files returned sooner because images were decoded only for the current preview or final export. Routine validation also completed with less discovery and wait overhead. Compatibility and migration Profiling and test routing were maintainer tools. Batch Crop kept its task and export schemas; selected images were simply decoded later in the workflow. Validation The profiling tool received a dedicated unit suite. Later commits added launcher profiler coverage, deferred-read tests, debug-trace mirroring checks, routing guardrails, and bounded GUI-wait tests. Commit `25912c54` records successful `buildtool changed`, integration validation, and remote MATLAB tests. Evidence Main commits `c07dfc0a`, `74025fee`, `eadcca82`, `25912c54`, and `fcfc36d8`. Known limitations and follow-up These changes reduced measured work but did not eliminate the white window shown before all startup setup completed. The following startup pass changed when windows were painted and when scroll navigation was installed."},{"title":"Project restore distinguishes missing sources from damaged sources","url":"history/records/2026/07/LK-20260717-strict-project-session-restore.html","kind":"history","section":"history","keywords":"LK-20260717-strict-project-session-restore 2026-07-17 125 fix compatible labkit.ui labkit_FigureStudio_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageMatch_app labkit_ImageEnhance_app labkit_NerveResponseAnalysis_app Runtime project restore App session reconstruction","text":"Project restore distinguishes missing sources from damaged sources id: LK-20260717-strict-project-session-restore date: 2026-07-17 sequence: 125 type: fix compatibility: compatible component: `labkit.ui` | `7.4.6 -> 7.4.7` component: `labkit_FigureStudio_app` | `0.2.8 -> 0.2.9` component: `labkit_FLIRThermal_app` | `1.4.6 -> 1.4.7` component: `labkit_FocusStack_app` | `1.5.5 -> 1.5.6` component: `labkit_ImageMatch_app` | `1.6.6 -> 1.6.7` component: `labkit_ImageEnhance_app` | `1.6.6 -> 1.6.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.6 -> 1.4.7` scope: Runtime project restore scope: App session reconstruction Context Runtime already resolved missing required source paths and offered interactive relinking before it rebuilt an App session. Several session factories then caught every decoder exception and returned an empty cache. A file that still existed but was damaged, unsupported, or exposed a programming error could therefore look like an unresolved source or an empty project. Decision and rationale Keep path resolution and cancellation in Runtime, and make reconstruction of an existing source strict. A session factory may accept an explicitly absent optional source, but it must not reinterpret an exception from an existing file. Runtime wraps reconstruction failures with `inputs.sources` IDs, roles, and filenames, records the exception in diagnostics, and preserves the prior state and presentation. Changes Removed broad exception recovery from Figure Studio, Focus Stack, Image Match, Image Enhance, FLIR Thermal, and Nerve Response Analysis session reconstruction. Made FLIR restore use strict thermal decoding while retaining skip-and-report behavior for interactive batch import. Added Runtime diagnostic context and a field-specific `ProjectSessionRestoreFailed` error without replacing the decoder cause. Added atomic corrupt-source, diagnostic-delivery, cross-App factory, and no-broad-catch regression coverage. User and data impact Missing required files still open the relink workflow, and cancelling it leaves the current project untouched. Existing corrupt or unsupported files now stop the load with the relevant project source identities and filenames instead of showing an empty preview. The prior document remains usable. App developers can leave ordinary decoder failures uncaught in `createSession.m`; Runtime owns the user-visible load failure and diagnostic record. Compatibility and migration No saved-project migration is required. Project envelopes and source-reference formats are unchanged. This change only corrects failure handling while reconstructing transient state. Validation Focused validation covers successful project round trips, missing-source relink, cancellation rollback, existing corrupt source rollback, diagnostic delivery, all affected session factories, and the repository guardrail that forbids broad catches in `createSession.m`. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md#session-actions-presentation-and-renderers) [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Focus Stack](../../../../apps/image-measurement/focus-stack/README.md) [Image Match](../../../../apps/image-measurement/image-match/README.md) [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) Known limitations and follow-up Automated hidden GUI tests verify state rollback and semantic error delivery; they do not replace manual review of native dialog wording or damaged third-party file variants."},{"title":"Protected image enhancement workflows","url":"history/records/2026/06/LK-20260629-protected-image-enhancement-workflows.html","kind":"history","section":"history","keywords":"LK-20260629-protected-image-enhancement-workflows 2026-06-29 16 feat compatible labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"Protected image enhancement workflows id: LK-20260629-protected-image-enhancement-workflows date: 2026-06-29 sequence: 16 type: feat compatibility: compatible component: `labkit_ImageEnhance_app` | `1.2.2 -> 1.3.0` component: `labkit_ImageMatch_app` | `1.2.1 -> 1.3.0` scope: historical project evolution Context The existing appearance adjustments could change a subject together with its background. Image Enhance and Image Match needed modes that protected subject detail while correcting bright backgrounds and tone. Decision and rationale Add subject-preserving enhancement and protected tone matching as explicit methods with their own parameters, descriptions, and tests. Keep these methods inside the image apps because they encode workflow-specific decisions rather than generic image primitives. Changes Image Enhance `1.2.2 -> 1.3.0` Image Match `1.2.1 -> 1.3.0` Added Subject-preserving enhance to Image Enhance. Added Protected tone to Image Match. Added calculation and GUI coverage for both methods. User and data impact Users could brighten or normalize a background with less change to the main subject. The new methods were optional; existing histories and other matching methods retained their previous behavior. Compatibility and migration Existing enhancement steps and Image Match inputs remained valid. The new protected methods were additional choices and did not rewrite saved source images. Validation Commit `1768dd57` added focused Image Enhance and Image Match unit tests and updated both GUI workflow tests. Evidence Main commit `1768dd57`. Known limitations and follow-up The protected methods reduce unwanted subject changes but cannot infer semantic foreground perfectly. Their result still requires visual review."},{"title":"Public API help is complete, discoverable, and behavior checked","url":"history/records/2026/07/LK-20260717-complete-public-api-help-contract.html","kind":"history","section":"history","keywords":"LK-20260717-complete-public-api-help-contract 2026-07-17 127 docs compatible labkit.biosignal labkit.dta labkit.rhs labkit.thermal labkit.image labkit.ui labkit_DICPreprocess_app labkit_DICPostprocess_app labkit_CSC_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Public MATLAB help Generated API reference Documentation contract tests","text":"Public API help is complete, discoverable, and behavior checked id: LK-20260717-complete-public-api-help-contract date: 2026-07-17 sequence: 127 type: docs compatibility: compatible component: `labkit.biosignal` | `1.0.2 -> 1.0.3` component: `labkit.dta` | `2.0.2 -> 2.0.3` component: `labkit.rhs` | `1.0.2 -> 1.0.3` component: `labkit.thermal` | `1.1.1 -> 1.1.2` component: `labkit.image` | `2.0.1 -> 2.0.2` component: `labkit.ui` | `7.5.0 -> 7.5.1` component: `labkit_DICPreprocess_app` | `1.5.7 -> 1.5.8` component: `labkit_DICPostprocess_app` | `1.4.6 -> 1.4.7` component: `labkit_CSC_app` | `1.4.7 -> 1.4.8` component: `labkit_BatchImageCrop_app` | `1.7.6 -> 1.7.7` component: `labkit_FLIRThermal_app` | `1.4.7 -> 1.4.8` component: `labkit_ImageMatch_app` | `1.6.7 -> 1.6.8` component: `labkit_VideoMarker_app` | `1.5.6 -> 1.5.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.7 -> 1.4.8` component: `labkit_ResponseReviewStats_app` | `1.4.6 -> 1.4.7` scope: Public MATLAB help scope: Generated API reference scope: Documentation contract tests Context LabKit's generated function pages came from MATLAB help, but the contract test covered a hand-maintained subset of packages and only checked broad section presence. A public function, implemented option, default, legal value, failure mode, or related API could therefore be omitted while the documentation suite still passed. Several App calculation pages exposed exactly those gaps. Decision and rationale Treat the callable public surface as discoverable source truth. The contract now scans every non-private function below `+labkit` plus each App API declared in the API catalog. It derives standard option reads from implementation code and requires those fields to have documented values and defaults, while required options remain explicitly distinguishable from optional ones. Every public function also states failure behavior and a `See also` relationship. This keeps a generated page useful for standalone MATLAB callers instead of merely repeating a short summary. Changes Completed help for the Contract, Biosignal, DTA, RHS, Thermal, Image, UI, and cataloged App calculation surfaces. Added exact option/default/legal-value checks, package-wide discovery, failure-behavior checks, and related-API checks to the documentation contract. Added a synthetic regression proving the guard detects an undocumented option, an omitted default, a vague value domain, missing failure behavior, and a missing related-API link. Made `labkit.image.resizeToFit` reject unsupported interpolation method names instead of silently treating them as bilinear. Linked owning library and App manuals to the detailed generated reference. User and data impact Readers can now determine valid calls, defaults, option domains, outputs, empty-result behavior, errors, and adjacent functions from the generated page for a public symbol. Developers adding a public function or a standard option read receive a focused contract failure when the corresponding help is incomplete. Compatibility and migration No saved project or data migration is required. Existing valid calls are unchanged. A call to `labkit.image.resizeToFit` with a method other than `\"bilinear\"` or `\"nearest\"` now raises `labkit:image:InvalidResizeMethod`; such values were never part of the documented contract. Validation The exact `PublicApiDocumentationContractTest` suite passes all eight tests, including execution of cataloged examples and the new synthetic defect regression. Image facade unit tests cover both supported resize methods and the unsupported-method error. Generated-site consistency and changed-scope validation are run after this record and its component manuals are rendered. Evidence [Public API index](../../../../reference/README.md) [LabKit App Framework](../../../../framework/README.md) [App catalog](../../../../apps/README.md) [Documentation maintenance](../../../../development/maintain-and-release/documentation.md) Known limitations and follow-up Implementation-derived option discovery intentionally recognizes the repository's standard option access patterns; unusual dynamic field access still requires review. Private helpers remain outside the public-page contract, and interactive `Typical Call:` sketches are not executed as file-independent examples. labkit.image.resizeToFit Resize an image to fit within maximum row/column limits."},{"title":"Public library help contracts and reference validation","url":"history/records/2026/07/LK-20260716-public-api-help-contracts.html","kind":"history","section":"history","keywords":"LK-20260716-public-api-help-contracts 2026-07-16 62 docs compatible labkit.biosignal labkit.dta labkit.image labkit.rhs labkit.thermal public MATLAB help generated API reference documentation contract tests","text":"Public library help contracts and reference validation id: LK-20260716-public-api-help-contracts date: 2026-07-16 sequence: 62 type: docs compatibility: compatible component: `labkit.biosignal` | `1.0.1 -> 1.0.2` component: `labkit.dta` | `2.0.1 -> 2.0.2` component: `labkit.image` | `2.0.0 -> 2.0.1` component: `labkit.rhs` | `1.0.1 -> 1.0.2` component: `labkit.thermal` | `1.1.0 -> 1.1.1` scope: public MATLAB help scope: generated API reference scope: documentation contract tests Context The public library reference identified functions and basic syntax but did not consistently explain option values, units, returned structures, failure behavior, or the distinction between executable examples and calls that need a user-supplied file. Readers still had to inspect implementation files to use several GUI-free APIs correctly. Decision and rationale Treat each public MATLAB help block as the source-level call contract and render that contract into one API page per function. Detailed library validation is kept separate from project-navigation validation so both remain focused and within the repository's effective-code-line budget. The patch versions record a compatible documentation-contract improvement. Function signatures, calculations, returned data, and error identifiers are unchanged. Changes Expanded public help with syntax, inputs, outputs, option names and legal values, defaults, units, errors, examples or typical calls, and related APIs. Distinguished self-contained `Example:` sections from interactive or file-dependent `Typical Call:` sections. Added generated-reference checks for thermal conversion and rendering, DTA schemas and pulse modes, and RHS lazy reads and waveform units. Corrected stale biosignal documentation links in package guidance and an implementation contract. Made label-ownership and package-boundary scans ignore prose-only substring collisions while continuing to reject literals and forbidden MATLAB tokens in executable code. User and data impact Users can discover the supported call contract through both `help` and the generated HTML reference without launching an app. No file format, saved project, calculation, or exported value changes. Compatibility and migration This is a patch-level compatible change. Existing calls require no migration. Validation Documentation consistency checks validate generated sections and executable examples. Project guardrails validate version/history ownership, literal ownership, package boundaries, and effective code-line budgets. Evidence Public help blocks and generated API pages are the maintained contract. Known limitations and follow-up Interactive calls and calls requiring laboratory files remain `Typical Call:` sketches until the repository provides synthetic, non-sensitive fixtures that make them executable examples."},{"title":"RHS Preview separates source roles from reference mechanics","url":"history/records/2026/07/LK-20260716-rhs-preview-structure.html","kind":"history","section":"history","keywords":"LK-20260716-rhs-preview-structure 2026-07-16 108 refactor compatible labkit_RHSPreview_app Neurophysiology App structure","text":"RHS Preview separates source roles from reference mechanics id: LK-20260716-rhs-preview-structure date: 2026-07-16 sequence: 108 type: refactor compatibility: compatible component: `labkit_RHSPreview_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context RHS Preview split one durable project across generic lifecycle files and used two additional lifecycle helpers to select or replace records by role. It also constructed variable filter-source IDs itself and read Runtime-owned reference fields in session, actions, and presentation code. Product metadata remained in separate requirement/version functions. Decision and rationale Keep the three App-owned roles because they express real workflow meaning: one preview recording, one optional protocol, and an ordered collection of filter recordings. Use Runtime V2 for portable path access, fixed-source upserts, and stable reconciliation of the variable collection. Keep only the small App-owned role-to-path selection policy under `sourceFiles`; do not introduce a new framework public role API for one App-specific collection shape. Changes Consolidated command identity, display metadata, version, requirements, project, session, layout, actions, presenter, renderer, and debug capability through the single definition. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved indexing, initial preview reads, filter discovery, and transient view reconstruction to root `createSession`. Added `sourceFiles.pathsForRole` to select App-owned source roles while delegating reference decoding to `labkit.ui.runtime.sourcePaths`. Replaced fixed-source construction with `upsertSource` and variable filter ID construction with `reconcileSources`, preserving filter order and stable identities across rediscovery. Removed the generic lifecycle package and separate requirement/version files. User and data impact RHS indexing, lazy waveform windows, channel selection, managed ROI/scroll interaction, protocol editing, multi-file filter labels, refresh/removal, exports, manifests, reset, and project reopening retain their behavior. New filter selections use Runtime-generated stable IDs; those IDs are internal project identities and do not change exported recording paths or labels. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still combine their separate recording, protocol, and filter source fields before validation. Existing version-2 source IDs and ordering are preserved when their resolved files remain selected. Runtime V2 owns migration iteration and later saves remain version 2. Validation Focused unit tests cover source migration, role-path ordering, summary/detail models, channel-role drafts, filter labels, and preview bounds. The hidden GUI workflow covers indexing and drawing a synthetic RHS file, channel tables, two-file filter discovery, protocol/filter export, manifests, project save, reset, reopen, and reconstruction of both preview and filter sources. Evidence [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not assess native file-dialog feel, physical channel-role correctness, or waveform interpretation. The remaining special startup path and repository guidance still require separate convergence review."},{"title":"Release validation gate and GUI CI hardening","url":"history/records/2026/07/LK-20260708-release-validation-gate-and-gui-ci-hardening.html","kind":"history","section":"history","keywords":"LK-20260708-release-validation-gate-and-gui-ci-hardening 2026-07-08 40 ci compatible historical project evolution","text":"Release validation gate and GUI CI hardening id: LK-20260708-release-validation-gate-and-gui-ci-hardening date: 2026-07-08 sequence: 40 type: ci compatibility: compatible scope: historical project evolution Context Release-candidate tags did not yet require one explicit summary gate over the headless, coverage, and GUI jobs. Some GUI assertions also depended on exact pixel ordering or timing that varied across CI display backends. Decision and rationale Require every release test job before publication, while testing semantic grid structure instead of platform-specific pixel rounding. Increase the shared GUI idle allowance so slower hosted displays can finish registered UI work. Changes Project validation workflow, no component version change. Release candidate tags now run the full MATLAB test workflow gate before publication: headless tests, coverage, GUI tests, and a release summary gate. GUI layout tests now assert structural grid contracts instead of platform-dependent flattened pixel ordering or width comparisons. Shared GUI test idle waiting allows slower CI display backends more time to finish registered UI work. User and data impact Published release candidates gained a single pass/fail validation signal. Application behavior and data formats did not change; the GUI suite became less sensitive to harmless platform layout differences. Compatibility and migration No known manual migration. Validation Commit `f359518` updated CI policy tests, declarative UI tests, launcher GUI tests, and shared wait behavior. Evidence Mainline commit `f359518`. Known limitations and follow-up The gate covers automated checks only. Interactive workflow feel and scientific visual review remain outside hosted CI."},{"title":"Release validation includes Base MATLAB compatibility","url":"history/records/2026/07/LK-20260717-release-validation-hardening.html","kind":"history","section":"history","keywords":"LK-20260717-release-validation-hardening 2026-07-17 129 ci compatible scheduled and release-candidate validation","text":"Release validation includes Base MATLAB compatibility id: LK-20260717-release-validation-hardening date: 2026-07-17 sequence: 129 type: ci compatibility: compatible scope: scheduled and release-candidate validation Context LabKit already exposed `buildtool baseMatlab`, but GitHub Actions did not run the broad MATLAB product-ownership analysis. A release candidate could therefore pass headless, coverage, and GUI jobs while the explicit Base MATLAB compatibility gate remained a local-only check. Decision and rationale Run the existing public `baseMatlab` task in scheduled and manually dispatched workflows, and require it before a validated release tag can be created. Ordinary pull requests and main pushes retain the smaller headless gate because the static call scan and representative fallback workflows already run there. Changes Added a dedicated Base MATLAB compatibility job with JUnit, active-test, log, and artifact reporting. Added that job to the release-test dependency gate. Corrected the CI contract test so it discovers actual `tasks:` values and checks them against the executable build-task catalog. Updated artifact upload jobs to the current Node.js 24 action generation. User and data impact No App behavior or saved data changes. A release tag is now blocked when MATLAB resolves production source to an undeclared MathWorks product. Compatibility and migration The public `buildtool baseMatlab` command is unchanged. GitHub-hosted runners meet the action runtime requirement; no self-hosted runner migration is introduced. Validation The CI policy contract verifies job scope, public-task ownership, diagnostics, timeouts, parent checkout depth, and release-gate dependencies. The Base MATLAB task remains covered by its own dependency guardrail. Evidence `.github/workflows/matlab-tests.yml` is the executable release gate; `tests/cases/contract/project/ci/CiValidationPolicyGuardrailTest.m` protects its routing and dependency structure. Known limitations and follow-up The product-ownership analysis depends on MATLAB's dependency report and cannot replace manual scientific or interactive App validation."},{"title":"Response Review Stats uses one project contract","url":"history/records/2026/07/LK-20260716-response-review-structure.html","kind":"history","section":"history","keywords":"LK-20260716-response-review-structure 2026-07-16 105 refactor compatible labkit_ResponseReviewStats_app Neurophysiology App structure","text":"Response Review Stats uses one project contract id: LK-20260716-response-review-structure date: 2026-07-16 sequence: 105 type: refactor compatibility: compatible component: `labkit_ResponseReviewStats_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context Response Review Stats kept project creation, validation, its one source-schema migration, and session reconstruction in four generic lifecycle files. Session and presentation code also read the Runtime's nested source path directly, and the reset action depended on the generic lifecycle package. Decision and rationale Use the standard compact Runtime V2 structure: one definition, one project contract, and one root session factory. Metric alignment, measurement, summaries, source parsing, exports, and presentation already had clear owners and remain unchanged. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderer, and debug capability in `definition.m`. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec.m`. Moved transient metric/alignment reconstruction to root `createSession.m` and reused it for the reset action. Replaced direct portable-reference reads with semantic `sourcePaths` lookup. Removed the generic lifecycle package and separate requirement/version files. User and data impact Analysis JSON and segment CSV loading, automatic window recomputation, aligned preview, metric summaries, reset, output-folder choice, CSV export, and result manifest behavior are unchanged. Developers now see the entire durable schema history through one entry instead of four lifecycle files. Compatibility and migration Durable schema version 2 is unchanged. Version-1 projects still move their singular source to the canonical source collection before validation and the next save. Validation Focused tests cover source migration, segment parsing, time-grid alignment, measurement, pair-group summaries, and default-grid selection. The hidden GUI workflow loads representative metrics, presents them, and exports the CSV and manifest through real Runtime callbacks. Evidence [Response Review And Stats](../../../../apps/neurophysiology/response-review-stats/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) Known limitations and follow-up Automated checks do not validate biological interpretation of a chosen baseline/noise window. The larger RHS Preview and Nerve Response Analysis Apps still require independent ownership review."},{"title":"Runtime V2 lifecycle ownership across the app fleet","url":"history/records/2026/07/LK-20260715-runtime-v2-app-migration.html","kind":"history","section":"history","keywords":"LK-20260715-runtime-v2-app-migration 2026-07-15 59 refactor breaking labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app `docs/` `docs/ui-runtime-redesign.md` runtime migration guidance","text":"Runtime V2 lifecycle ownership across the app fleet id: LK-20260715-runtime-v2-app-migration date: 2026-07-15 sequence: 59 type: refactor compatibility: breaking component: `labkit.ui` | `5.2.0 -> 6.0.0` component: `labkit_DICPostprocess_app` | `1.3.6 -> 1.4.0` component: `labkit_DICPreprocess_app` | `1.4.0 -> 1.5.0` component: `labkit_ChronoOverlay_app` | `1.3.6 -> 1.4.0` component: `labkit_CIC_app` | `1.3.8 -> 1.4.0` component: `labkit_CSC_app` | `1.3.10 -> 1.4.0` component: `labkit_EIS_app` | `1.3.4 -> 1.4.0` component: `labkit_VTResistance_app` | `1.3.8 -> 1.4.0` component: `labkit_GaitAnalysis_app` | `1.0.0 -> 1.1.0` component: `labkit_BatchImageCrop_app` | `1.6.8 -> 1.7.0` component: `labkit_CurvatureMeasurement_app` | `1.3.5 -> 1.4.0` component: `labkit_FLIRThermal_app` | `1.3.0 -> 1.4.0` component: `labkit_FocusStack_app` | `1.4.9 -> 1.5.0` component: `labkit_ImageEnhance_app` | `1.5.8 -> 1.6.0` component: `labkit_ImageMatch_app` | `1.5.8 -> 1.6.0` component: `labkit_VideoMarker_app` | `1.2.0 -> 1.3.0` component: `labkit_FigureStudio_app` | `0.1.5 -> 0.2.0` component: `labkit_NerveResponseAnalysis_app` | `1.3.5 -> 1.4.0` component: `labkit_ResponseReviewStats_app` | `1.3.5 -> 1.4.0` component: `labkit_RHSPreview_app` | `1.3.4 -> 1.4.0` component: `labkit_ECGPrint_app` | `1.3.5 -> 1.4.0` scope: `docs/` scope: `docs/ui-runtime-redesign.md` scope: runtime migration guidance Context App lifecycle, callback ordering, UI-derived state, project persistence, graphics resources, and result packaging were implemented repeatedly across apps. Migrating only the lifecycle names would have preserved the main author cost: each app would still need to understand raw controls, figure callbacks, and multiple competing state copies. Decision and rationale Make Runtime V2 the only write path and give the framework ownership of launch, startup, the FIFO event queue, atomic state commits, deterministic presentation, dialogs, interaction resources, project persistence, and result manifests. Apps retain explicit project/session/presentation/result contracts and all domain calculations, workflow decisions, plots, schemas, and exports. This deliberately optimizes ownership and learning cost rather than total app line count. Explicit app contracts can add code in a large scientific app, but they replace hidden closure state and callback plumbing with one inspectable model. The remaining 36 public UI functions have distinct reviewed contracts; the earlier 32-function planning target was not forced through vague APIs. Changes Migrated all twenty public apps to Runtime V2 definitions, standard launch, canonical project/session state, semantic events, pure presentation, and managed resources. Standardized current project writes and result manifests while retaining app-owned payload migrations and existing output files. Retired Runtime V1 writes and removed the public control-registry mutation, standalone editor/runtime, preview mutation, dialog, title, and dispatch compatibility surfaces. Kept supported V1 snapshots and named legacy app projects as read-only import formats; subsequent saves use the current project codec. Updated app requirements to `labkit.ui >=6 <7` and made UI 6 the breaking public facade boundary. Replaced Video Marker's optional `vision.PointTracker` branch with one deterministic app-owned multiscale patch matcher, retaining confidence, constant-velocity fallback, subpixel coordinates, and forward-cache behavior without Computer Vision Toolbox. Added traceable temporary MathWorks-product debt declarations: rapid app work must ship a base-MATLAB fallback plus deterministic and Toolbox-parity tests, and product analysis must continue to see the dependency until replacement. Re-audited already migrated apps, removed five dead or presenter-only helper files, and updated helper-quality classification from retired role packages to the current workflow-first lifecycle/state/result/UI boundaries. Corrected Runtime V2 interaction event construction so paired-anchor cell payloads remain one scalar semantic event. DIC Preprocess point matching now accepts alternating reference/moving points, enables alignment after two complete pairs, and completes the in-workbench rigid registration flow. Hardened the same scalar-envelope invariant for app-managed Runtime V2 resources and `runBusy` callback capture, including legitimate cell-form MATLAB callbacks and cell-valued resource payloads. Made project replacement invalidate the prior presentation cache before the fresh session is rendered, so opening an unchanged project still rebuilds app-owned preview graphics and other ephemeral visual resources. Corrected shared file-event index decoding for R2025a string results, so CIC and the other multi-file V2 apps can select and remove imported items. Stopped presentation commits from rerunning unchanged preview requests, and made DIC point-label updates preserve image handles and zoom viewports. Routed anchor-editor wheel input to the shared image zoom implementation without the retired wrapper's same-name recursion. Deferred CIC and VT batch DTA decoding until a file becomes visible or the batch is exported, so multi-file selection no longer blocks on every source. Let Gait Analysis read current Video Marker project/recovery envelopes and legacy Video/Image Marker autosaves directly as pose-coordinate inputs. Added interaction-mode subtitles for curve anchors, point marking, paired anchors, scale references, and fixed point slots; restored axes context menus after renderer resets; and promoted screenshot/project commands to top-level window entries. Moved Video Marker's Session controls to the top of the Video page, added an `Open MAT` project shortcut, and made New setup explicitly cancel, save, or discard before clearing the current project. Reorganized human documentation into getting-started, app workflow, public API, development, and focused-guide layers. Split app authoring from the user catalog, added the missing contract API reference, and made a project guardrail verify that every public `+labkit` function remains indexed under its owning facade. User and data impact App entrypoint names, scientific calculations, workflow decisions, plots, and existing export filenames remain stable. Current projects have a consistent save/reopen path, external sources use portable references, and result exports include a standard manifest. Runtime errors and modal interactions now pass through framework services, which also makes hidden validation deterministic. Compatibility and migration The migrated app versions require `labkit.ui >=6 <7`; they must not be copied into a UI 5 checkout. Supported V1 snapshots and documented legacy projects remain importable but are not written again. After import, save a new current project if continued editing or recovery is required. Validation Focused Runtime V2, project, interaction-hub, DIC point-matching, CIC, Figure Studio, app-boundary, and public-surface tests passed. The latest repair checkpoint additionally passed CIC/VT GUI workflows (2/2), Gait/app compatibility tests (21/21), Curvature and Video Marker GUI methods (6/6), focused framework menu/interaction tests (8/8), and three targeted interaction-hint methods (3/3). A real local legacy Video Marker autosave was read successfully without copying it into the repository. The documentation hierarchy checkpoint passed relative-link validation and 44/44 focused documentation, app-structure, build-task, and history guardrails. The Phase-6 `buildtool changedFast` checkpoint passed 15 framework GUI tests, 284 headless tests with one environment-assumption skip, and six representative GUI workflows. The final broad gate and any manual pointer or visual checks are reported with the merge handoff rather than embedded as mutable history. Video Marker replacement tests exercise integer and subpixel translation, flat-patch fallback, repeated-input determinism, prediction caching, and coordinate parity with `vision.PointTracker` when that product is installed. `buildtool baseMatlab` confirms the production source resolves only to MATLAB. Evidence `4454ca30` introduced the Runtime V2 kernel; the app migrations then ran from Chrono Overlay (`5fe76ee4`) through Figure Studio (`21936e8d`). `1ef46bfd` removed the retired UI runtime, and `8e109ff4` completed the framework contracts used by the migrated apps. The commits listed in the migration sequence above preserve the individual app checkpoints; this record explains their shared architectural result. Known limitations and follow-up Explicit domain contracts mean that large apps do not necessarily have fewer production lines. Further extraction is justified only by repeated mechanics, not by file-size targets. Pointer feel, drag ergonomics, and scientific visual judgment still require the documented manual app review before merge."},{"title":"Runtime diagnostics and workflow repair converge on one product path","url":"history/records/2026/07/LK-20260726-runtime-diagnostics-and-workflow-repair.html","kind":"history","section":"history","keywords":"LK-20260726-runtime-diagnostics-and-workflow-repair 2026-07-26 159 feat breaking labkit_launcher labkit.app labkit.dta labkit.image labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app Always-on session diagnostics and semantic App events Clean startup and explicit synthetic-input generation Launcher repair boundary and preserved product interaction Batch Crop native ROI interaction and preview resolution Explicit DTA units and EIS impedance display units","text":"Runtime diagnostics and workflow repair converge on one product path id: LK-20260726-runtime-diagnostics-and-workflow-repair date: 2026-07-26 sequence: 159 type: feat compatibility: breaking component: `labkit_launcher` | `1.6.0 -> 1.7.0` component: `labkit.app` | `1.2.4 -> 2.0.0` component: `labkit.dta` | `2.0.3 -> 3.0.0` component: `labkit.image` | `2.0.2 -> 2.0.3` component: `labkit_DICPostprocess_app` | `1.5.1 -> 1.6.0` component: `labkit_DICPreprocess_app` | `1.6.1 -> 1.7.0` component: `labkit_ChronoOverlay_app` | `1.5.1 -> 1.6.0` component: `labkit_CIC_app` | `1.5.1 -> 1.6.0` component: `labkit_CSC_app` | `1.5.1 -> 1.6.0` component: `labkit_EIS_app` | `1.5.2 -> 1.6.0` component: `labkit_VTResistance_app` | `1.5.1 -> 1.6.0` component: `labkit_GaitAnalysis_app` | `2.1.1 -> 2.2.0` component: `labkit_BatchImageCrop_app` | `1.8.1 -> 1.9.0` component: `labkit_CurvatureMeasurement_app` | `1.5.1 -> 1.6.0` component: `labkit_FLIRThermal_app` | `1.5.1 -> 1.6.0` component: `labkit_FocusStack_app` | `1.6.1 -> 1.7.0` component: `labkit_ImageEnhance_app` | `1.7.1 -> 1.8.0` component: `labkit_ImageMatch_app` | `1.7.1 -> 1.8.0` component: `labkit_VideoMarker_app` | `1.6.1 -> 1.7.0` component: `labkit_FigureStudio_app` | `0.6.5 -> 0.7.0` component: `labkit_NerveResponseAnalysis_app` | `1.5.1 -> 1.6.0` component: `labkit_ResponseReviewStats_app` | `1.5.1 -> 1.6.0` component: `labkit_RHSPreview_app` | `1.5.1 -> 1.6.0` component: `labkit_TTestWizard_app` | `1.2.0 -> 1.3.0` component: `labkit_ECGPrint_app` | `1.5.1 -> 1.6.0` scope: Always-on session diagnostics and semantic App events scope: Clean startup and explicit synthetic-input generation scope: Launcher repair boundary and preserved product interaction scope: Batch Crop native ROI interaction and preview resolution scope: Explicit DTA units and EIS impedance display units Context Debug launching had combined synthetic data, diagnostic persistence, and App startup into one mode, while ordinary incidents often left too little useful history. App-owned Log tabs and status/error adapters competed with a partial framework recorder. Batch Crop also exposed the practical consequence of testing construction without native interaction: its ROI could be visible yet fail to respond, and ordinary images were unnecessarily downsampled. The root Launcher had simultaneously accumulated installed-product behavior, repair implementation, and maintenance-tool logic in one rescue file. DTA inputs also carried ambiguous unit inference into downstream electrochemistry workflows. Decision and rationale Every normal App launch now owns one clean Runtime session with an always-on, privacy-bounded event journal. Runtime instruments framework operations; Apps add stable semantic events through `CallbackContext.log`. Trace capture, session viewing, and bundle export are runtime tools, while synthetic input generation is a separate explicit developer action that never loads its output or changes the open project. The public App SDK advances to 2.0 because launch diagnostic options and the old status, error, checkpoint, count, recorder, and debug-sample contracts were removed rather than kept as misleading compatibility aliases. Each migrated App requires `labkit.app >=2 <3`. The root Launcher remains a small self-contained repair entry. The installed launcher keeps the established catalog, status, App information, double-click launch, version browsing, and visual identity, while independent maintenance operations live under `tools/`. Changes Added canonical session events, durable bounded journals, active-session leases, degradation reporting, operation/result versus rollback semantics, a live session viewer, trace control, and diagnostic bundle export. Migrated all public Apps to semantic user/developer events, removed duplicate Log tabs, and exposed explicit anonymous synthetic-input generation from the ordinary Tools menu. Kept default launches data-free and action-free; hidden GUI tests now own their visibility fixture instead of relying on a test tag. Restored Batch Crop ROI body drag, center drag, and click-to-place behavior through managed native interaction, while preserving the viewport and using full-resolution previews unless an explicit high-resolution budget applies. Made DTA item units explicit, rejected unknown pulse modes, and added EIS impedance display choices from milliohms through megohms with kilohms as the default. Split Launcher rescue, installed composition, version management, cleanup, and documentation responsibilities without removing product capabilities. User and data impact Users can diagnose a problem after it occurs in an ordinary session, export a sanitized history, and deliberately generate anonymous reproduction inputs without restarting in a special mode. App status remains concise, while developer detail is available through Diagnostics. Existing scientific source files are not rewritten. Batch Crop keeps source resolution for ordinary images and provides responsive native ROI editing. EIS changes only display and export scaling selected by the user; calculations retain canonical ohms. Launcher repair remains explicit and never downloads or replaces files merely because it was opened. Compatibility and migration `labkit.app` 2.0 intentionally removes the former launch/debug and diagnostic adapter APIs. App source using those APIs must migrate to `BuildSyntheticSample` and semantic `CallbackContext.log` events. Saved App project schemas and their declared migrations are unchanged. `labkit.dta` 3.0 replaces ambiguous numeric unit inference with explicit unit metadata. Consumers must use declared canonical values. `labkit.image` 2.0.3 removes library-owned default megapixel policy; Apps choose preview budgets according to their own workflow. Validation Focused framework suites cover event schema, RNG preservation, operation outcomes, multi-process leases, privacy allowlists, projection degradation, viewer behavior, bundle export, Runtime instrumentation, and repository architecture. All 63 public hidden native App smoke cases pass. Every migrated App family has focused workflow coverage, including Batch Crop native ROI and preview-resolution contracts, EIS units, DTA parity, Launcher dispatch/repair, and synthetic-input state neutrality. Evidence The `debug-repair` branch contains purpose-based implementation and focused-test commits. Final evidence is the branch-review validation result, pull-request CI, merged-main CI, and developer-led manual checks recorded in the pull request. Known limitations and follow-up Hidden GUI tests do not establish pointer feel, dialog usability, visual quality, or the usefulness and privacy of exported incident bundles. Those remain explicit manual acceptance gates before merge."},{"title":"Runtime exposes GUI-free source-record creation","url":"history/records/2026/07/LK-20260716-gui-free-source-records.html","kind":"history","section":"history","keywords":"LK-20260716-gui-free-source-records 2026-07-16 100 feat compatible labkit.ui Runtime V2 sources App project migration","text":"Runtime exposes GUI-free source-record creation id: LK-20260716-gui-free-source-records date: 2026-07-16 sequence: 100 type: feat compatibility: compatible component: `labkit.ui` | `7.4.1 -> 7.4.2` scope: Runtime V2 sources scope: App project migration Context Runtime V2 owned the portable-reference schema and exposed a stable path accessor, but source-record creation was available only through callback-bound project services. GUI-free project migrations and legacy importers therefore could not create canonical sources without copying private reference fields. Batch Crop and Video Marker both contained such schema copies. Decision and rationale Expose the canonical source record, not the portable-reference implementation. `labkit.ui.runtime.sourceRecord` accepts stable App identity, semantic role, path, and required status, then delegates the nested representation to the Runtime. The injected callback service uses the same factory. Changes Added the GUI-free `labkit.ui.runtime.sourceRecord` factory with validation and complete MATLAB help. Routed `services.project.sourceRecord`, `upsertSource`, and `reconcileSources` through the same public contract. Extended source tests to cover factory output and invalid inputs. Updated the framework API map and portable-source guidance. User and data impact User project behavior is unchanged. App authors can now implement project migrations and legacy imports without opening a UI callback or constructing runtime-owned reference fields. Current file paths remain available only through `sourcePaths`. Compatibility and migration The addition is compatible within UI 7. Existing source records and injected services retain their shape and behavior. Apps should replace copied portable reference construction when their project/import code is migrated. Validation Focused Runtime source tests cover empty arrays, canonical creation, resolved path lookup, ordered/optional ID lookup, invalid references, and invalid factory arguments. Package-surface, version, documentation, and history guardrails protect the new public contract. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Framework](../../../../framework/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up Existing Apps that still copy or read portable-reference fields must migrate to `sourceRecord` and `sourcePaths` before the legacy source boundary is fully retired."},{"title":"Runtime owns portable source-reference details","url":"history/records/2026/07/LK-20260716-source-path-accessor.html","kind":"history","section":"history","keywords":"LK-20260716-source-path-accessor 2026-07-16 91 feat compatible labkit.ui Runtime source boundary App maintenance cost","text":"Runtime owns portable source-reference details id: LK-20260716-source-path-accessor date: 2026-07-16 sequence: 91 type: feat compatibility: compatible component: `labkit.ui` | `7.3.0 -> 7.4.0` scope: Runtime source boundary scope: App maintenance cost Context Runtime V2 owned source creation, rebasing, relinking, and validation, but an App still had to read `reference.originalPath` whenever it loaded or displayed a source. This exposed the nested persistence representation throughout session factories, actions, presenters, and workflow packages. Several Apps then added their own identical path loops or ID lookup functions. Decision and rationale Keep source `id`, `role`, and `required` as stable App-facing project data, but treat the nested portable reference as a runtime-owned value. Add one pure `labkit.ui.runtime.sourcePaths` accessor that works before UI construction and therefore serves session creation as well as actions and presentation. Passing UI-bound injected services into `CreateSession` was rejected: it would couple otherwise pure state reconstruction to figure, queue, dialog, and resource-service construction merely to read a path. Changes Added `sourcePaths(sources)` for paths in source order. Added `sourcePaths(sources,ids)` for strict lookup in requested ID order. Defined empty, malformed-reference, and unknown-ID behavior in executable public help and unit tests. Stopped documenting portable-reference fields as an App read contract. Added the accessor to the guarded public UI runtime surface. User and data impact Saved projects and file resolution behave unchanged. App code can now read, compare, load, and present source paths without knowing how a portable reference stores its current or relative location. Compatibility and migration The addition is compatible within UI 7. Existing direct field reads continue to work temporarily, but tracked Apps will move to the accessor during their family-by-family Runtime V2 consolidation. No project migration is required. Validation Unit tests cover source order, requested ID order, empty results, missing IDs, and invalid runtime references. Package-surface and documentation guardrails cover the new public contract. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Complete App Tutorial](../../../../development/build-apps/complete-app.md) Known limitations and follow-up Existing App-local path loops remain until their owning App commits are migrated and behavior-tested. Once all consumers use this accessor, a contract guard will prevent production Apps from reading portable-reference fields."},{"title":"Runtime uses one source contract for launch, migration, and layout","url":"history/records/2026/07/LK-20260717-retired-runtime-compatibility.html","kind":"history","section":"history","keywords":"LK-20260717-retired-runtime-compatibility 2026-07-17 126 refactor breaking labkit.ui Runtime launch contract Project payload migration Semantic layout validation","text":"Runtime uses one source contract for launch, migration, and layout id: LK-20260717-retired-runtime-compatibility date: 2026-07-17 sequence: 126 type: refactor compatibility: breaking component: `labkit.ui` | `7.4.7 -> 7.5.0` scope: Runtime launch contract scope: Project payload migration scope: Semantic layout validation Context UI Runtime had already introduced one-definition App launch, one version-aware `Project.Migrate` callback, and presenter-owned managed interactions. Transitional branches still accepted separate requirements and version factories, ordered `Project.Migrations` cells, and a `toolPanel` layout kind whose public constructor no longer existed. No public or accepted private App used those branches; framework tests and stale guidance were their only remaining consumers. Decision and rationale Keep one writable source contract for each capability. `launch` receives one definition factory and reads product metadata and requirements from that definition. Runtime calls the App's single `Migrate(project,fromVersion)` callback for each missing payload step. Layout sections contain supported semantic controls, while presenter interaction specs own axes tools. This removes source-level compatibility that could hide an incomplete App migration. Read-only support for current saved project envelopes, declared legacy MAT imports, and older supported payload versions remains intact. Changes Removed requirements/version factory dispatch from `runtime.launch` and added a direct diagnostic for the retired call form. Rejected `Project.Migrations` and removed its project-reader fallback. Removed `toolPanel` validation, sizing, building, diagnostics, and current manual guidance. Converted Runtime tests to complete single definitions and added regression checks for both retired contracts. User and data impact Current Apps and their project files behave the same. App developers have one place to review identity, version, requirements, layout, and optional runtime capabilities. Source code still using a three-factory launch or `Project.Migrations` now fails immediately with migration guidance instead of silently taking a legacy path. Compatibility and migration This is a source-contract cleanup within UI 7. Call `labkit.ui.runtime.launch(@app.definition,varargin{:})`, move product metadata and `Requirements` into `definition.m`, and replace migration callback cells with one callback that switches on `fromVersion`. No saved-data rewrite is required. Validation Focused Runtime GUI tests cover single-definition launch, rejection of the retired launch form, ordered project migration and atomic load, rejection of `Project.Migrations`, managed interactions, and startup progress. Contract tests cover App layout structure and public surface drift. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) [App development](../../../../development/build-apps/app-development.md) [Public API reference](../../../../reference/README.md) Known limitations and follow-up Historical records retain their original descriptions of the transitional APIs. They document how the architecture evolved and are not current usage guidance."},{"title":"Runtime-only P-code app packages","url":"history/records/2026/07/LK-20260708-runtime-only-p-code-app-packages.html","kind":"history","section":"history","keywords":"LK-20260708-runtime-only-p-code-app-packages 2026-07-08 41 refactor compatible historical project evolution","text":"Runtime-only P-code app packages id: LK-20260708-runtime-only-p-code-app-packages date: 2026-07-08 sequence: 41 type: refactor compatibility: compatible scope: historical project evolution Context Single-app P-code packages included a P-coded copy of the full launcher even though their purpose was to run one protected app. That exposed maintenance and packaging actions that were meaningful only in a source checkout. Decision and rationale Make P-code output a minimal runtime package with a direct app entry script. Keep the full launcher in source and source-package distributions, where its installation, profiling, and packaging tools are available. Changes Project deployment tooling, no component version change. `Package P-code` now creates a runtime-only single-app package instead of shipping a P-coded LabKit launcher and launcher maintenance tools. P-code package manifests and README instructions point users to the direct `run_` entry file. P-code packaging no longer requires `labkit_launcher.m` or `labkit_launcher.p` to exist in the package root being used as the runtime source. User and data impact Recipients of a P-code package start the protected app with `run_` and no longer see unrelated launcher maintenance actions. The protected app and its data formats are unchanged. Compatibility and migration Users of P-code packages should run `run_` from the unzipped package instead of `labkit_launcher`. Source packages still include and support the launcher. Validation Commit `75f63f1` expanded `PackageLabKitAppToolTest` for runtime-only package contents and updated the generated package instructions. Evidence Mainline commit `75f63f1`. Known limitations and follow-up P-code packages intentionally omit source-oriented launcher features. Users who need installation or packaging tools should use a source or source-package distribution."},{"title":"Runtime-owned project shape validation","url":"history/records/2026/07/LK-20260716-runtime-owned-project-shape-validation.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-owned-project-shape-validation 2026-07-16 117 refactor compatible labkit.ui labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app Runtime V2 project validation electrochemistry App maintenance cost","text":"Runtime-owned project shape validation id: LK-20260716-runtime-owned-project-shape-validation date: 2026-07-16 sequence: 117 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.4 -> 7.4.5` component: `labkit_ChronoOverlay_app` | `1.4.4 -> 1.4.5` component: `labkit_CIC_app` | `1.4.4 -> 1.4.5` component: `labkit_CSC_app` | `1.4.4 -> 1.4.5` component: `labkit_EIS_app` | `1.4.4 -> 1.4.5` component: `labkit_VTResistance_app` | `1.4.4 -> 1.4.5` scope: Runtime V2 project validation scope: electrochemistry App maintenance cost Context Runtime already validated canonical project buckets and portable source records after actions committed. During project restore, however, the App validator ran before that framework validation. Every persistent App consequently repeated the same bucket and source-record checks so malformed loaded projects would fail safely. Decision and rationale Validate framework-owned project structure before invoking App-owned project validation on both initial state and project restore. App validators should describe only their own schema and scientific rules. This gives canonical shape and source records one owner while retaining strict validation at the load boundary. Changes Added one private Runtime project-shape validator shared by restore and semantic state validation. Runtime now rejects a malformed loaded payload before its App validator can inspect domain fields. Removed 67 net lines of repeated canonical bucket and source-record checks from the five electrochemistry `projectSpec.m` files. Kept each App's legal choices, numeric bounds, result fields, and other domain-specific validation unchanged. User and data impact Valid projects and scientific results are unchanged. Malformed projects now receive the framework's consistent invalid-state error for canonical structure failures instead of an App-specific bucket or source error. No payload migration is required. Compatibility and migration The accepted project schema is unchanged. This is a validation-ownership change, not a data-format change, and current or older supported project versions continue through the same migration callbacks. Validation The Runtime project GUI test loads a payload missing a canonical bucket and verifies atomic rejection before App validation. Focused electrochemistry GUI tests exercise load, analysis, plotting, and export paths with the reduced App validators. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the framework and App validation boundary. [App Development](../../../../development/build-apps/app-development.md) tells App authors which validation remains in `projectSpec.m`. Each affected [Electrochemistry App](../../../../apps/electrochemistry/README.md) manual identifies its domain-owned validator responsibilities. Known limitations and follow-up Other App families still repeat some canonical structure checks. They will move to the same boundary only after their role and cross-field constraints are separated from the generic checks and covered by focused tests."},{"title":"Runtime-owned session defaults across App families","url":"history/records/2026/07/LK-20260716-runtime-owned-session-defaults.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-owned-session-defaults 2026-07-16 116 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Runtime V2 session construction App maintenance cost","text":"Runtime-owned session defaults across App families id: LK-20260716-runtime-owned-session-defaults date: 2026-07-16 sequence: 116 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.3 -> 7.4.4` component: `labkit_DICPostprocess_app` | `1.4.3 -> 1.4.4` component: `labkit_DICPreprocess_app` | `1.5.4 -> 1.5.5` component: `labkit_GaitAnalysis_app` | `2.0.4 -> 2.0.5` component: `labkit_BatchImageCrop_app` | `1.7.3 -> 1.7.4` component: `labkit_CurvatureMeasurement_app` | `1.4.3 -> 1.4.4` component: `labkit_FLIRThermal_app` | `1.4.3 -> 1.4.4` component: `labkit_FocusStack_app` | `1.5.2 -> 1.5.3` component: `labkit_ImageEnhance_app` | `1.6.3 -> 1.6.4` component: `labkit_ImageMatch_app` | `1.6.3 -> 1.6.4` component: `labkit_VideoMarker_app` | `1.5.2 -> 1.5.3` component: `labkit_FigureStudio_app` | `0.2.5 -> 0.2.6` component: `labkit_NerveResponseAnalysis_app` | `1.4.3 -> 1.4.4` component: `labkit_ResponseReviewStats_app` | `1.4.3 -> 1.4.4` component: `labkit_RHSPreview_app` | `1.4.3 -> 1.4.4` component: `labkit_ECGPrint_app` | `1.4.3 -> 1.4.4` scope: Runtime V2 session construction scope: App maintenance cost Context Runtime V2 already adds the canonical `selection`, `workflow`, `view`, and `cache` session buckets after an App factory returns. Its workflow service also creates `logLines` on the first log operation. Fifteen App factories still repeated empty buckets or initialized that framework-owned log, making their state declarations longer and obscuring which transient fields were genuinely App-specific. Decision and rationale Treat canonical session shape and workflow-log initialization as Runtime control-plane responsibilities. An App session factory now returns only the selection, workflow, view, and cache fields that express its own workflow. This preserves explicit App state while removing framework boilerplate. Changes Removed empty canonical session buckets from affected App factories. Removed every App-owned `logLines` initializer; the injected workflow service remains the only writer and lazy initializer. Removed Image Enhance's direct log-panel presenter binding; Runtime commits the workflow log to every declared log panel. Added `services.project.newState()` and moved full-project reset actions onto that Runtime-owned creation and normalization path. Added a fleet-wide structure guard that rejects empty canonical buckets and direct workflow-log initialization in `createSession.m`. Updated every affected App manual and patch version. User and data impact There is no workflow, scientific, persistence, or saved-data change. Runtime normalization produces the same complete in-memory session before validation, presentation, or the first callback. Existing project files remain compatible. Compatibility and migration The change is source-compatible within Runtime V2 and requires no project payload migration. Direct tests of an App's raw session factory should inspect only App-owned fields; tests that need canonical empty buckets should create state through Runtime. Validation The fleet structure guard covers every public App factory. Focused App unit and hidden-GUI suites verify restored state, initial presentation, workflow logs, and representative source-backed sessions across the affected families. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines canonical session normalization and workflow logging. [App Development](../../../../development/build-apps/app-development.md) requires `createSession.m` to rebuild only App-specific transient data. Each affected App manual describes its remaining session-owned fields. Known limitations and follow-up Most factories still perform necessary source decoding or reconstruction. They should not be removed merely to reduce file count. Further simplification requires evidence that a repeated reconstruction pattern is domain-neutral and belongs behind a stable Runtime service."},{"title":"Searchable MATLAB-generated documentation site and launcher workflow groups","url":"history/records/2026/07/LK-20260715-documentation-site.html","kind":"history","section":"history","keywords":"LK-20260715-documentation-site 2026-07-15 60 feat compatible labkit_launcher `docs/` `site/` `tools/docs/` `buildfile.m`","text":"Searchable MATLAB-generated documentation site and launcher workflow groups id: LK-20260715-documentation-site date: 2026-07-15 sequence: 60 type: feat compatibility: compatible component: `labkit_launcher` | `1.4.0 -> 1.5.0` scope: `docs/` scope: `site/` scope: `tools/docs/` scope: `buildfile.m` Context The documentation had become a set of large Markdown pages with no generated site, global search, code-entity pages, or enforced relationship between the public MATLAB surface and its reference material. Launcher actions were also presented as a mostly flat list, making routine app launch, installation, maintenance, profiling, and packaging appear equally related. Decision and rationale Keep authoring in tracked Markdown, structured navigation/catalog metadata, and MATLAB help blocks, then use one repository-owned MATLAB compiler to emit the tracked static site. Bind every non-private `labkit.*` API and each explicitly cataloged app-owned scientific API to its source declaration. Keep private helpers out of detailed reference pages. Group launcher actions by user intent and make documentation regeneration a first-level maintenance action. This follows the separation used by MATLAB and Qt documentation while keeping the build offline and dependency-free. Changes Added a MATLAB-only static documentation compiler, responsive site chrome, global client-side search, source links, related-API links, and deterministic generated-tree comparison. Added structured page and app-owned API catalogs. Generated reference now covers reusable library functions plus explicitly supported GUI-free app calculation entry points, while rejecting `private/` paths. Added public documentation build and consistency tasks and project guardrails for catalog coverage, search visibility, and generated output. Reorganized launcher buttons into Run Apps, Versions and Install, Development and Maintenance, and Package and Publish groups. Added Update Documentation to the launcher. It rebuilds `site/`, opens the generated home page, and reports narrative-page, API-page, file, and output location details. User and data impact Users can browse and search documentation without a MATLAB documentation server, move between related public functions, and regenerate the same site after source changes. Existing app data, projects, calculations, and exports are unchanged. Launcher maintenance actions are easier to distinguish from normal app launch and installation tasks. Compatibility and migration The feature is additive. Existing Markdown links remain valid in the source repository. `site/` is generated and must not be edited manually. Source-only or P-code app packages that omit documentation tools show the launcher action disabled with an explanatory tooltip. Validation The compiler generated 20 narrative pages, 133 public API pages, and 157 tracked files. Focused documentation/build guardrails passed after verifying a byte-for-byte rebuild. Hidden-GUI launcher layout, documentation generation, and missing-tool behavior tests passed 3/3. Evidence `tools/docs/renderLabKitDocs.m` and `docs/site.json` define the deterministic build and navigation contract. `docs/catalogs/api.json` defines the supported app-owned public API surface. Initial searchable site and launcher integration `bdeba572`. Per-app documentation publication `82734a47`. Later information-architecture rebuild `8b55e2b9`. Known limitations and follow-up The first generated site still contains transitional narrative pages that must be split into app-family, per-app, framework, and focused API guides. Cataloged scientific APIs need richer standalone examples and algorithm/unit sections before the documentation rewrite is complete."},{"title":"Session diagnostics expose actions and teardown failures","url":"history/records/2026/07/LK-20260726-actionable-session-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260726-actionable-session-diagnostics 2026-07-26 161 fix compatible labkit.app Session log filter and action terminology Runtime close and resource-cleanup diagnostics","text":"Session diagnostics expose actions and teardown failures id: LK-20260726-actionable-session-diagnostics date: 2026-07-26 sequence: 161 type: fix compatibility: compatible component: `labkit.app` | `2.0.0 -> 2.0.1` scope: Session log filter and action terminology scope: Runtime close and resource-cleanup diagnostics Context The live session viewer exposed internal `Audience` and `Root` terminology without explaining their meaning. Root-action choices were bare `op-*` identifiers, so users could not tell which click or workflow event a correlated call chain represented. The TRACE-disabled notice also implied that ordinary lifecycle evidence might be missing even though only TRACE-severity records were disabled. Runtime callback and startup failures were instrumented, but application resource cleanup ran before recorder close without its own operation boundary. A cleanup exception could therefore prevent orderly adapter and journal teardown while leaving no canonical lifecycle failure. Decision and rationale Present diagnostic concepts in user-task language while retaining stable machine identifiers underneath. Reader intent becomes **View**, root correlation becomes **Action**, and each action choice combines time, a safe semantic message or callback alias, and its `op-*` identifier. Treat Runtime close as an ordinary instrumented lifecycle operation. Attempt independent resource and native cleanup even after one fails, persist the terminal close result, close the journal, and only then return the original or combined cleanup exception. Changes Replaced the viewer's Audience wording with Useful, Everything, User workflow, and Developer details choices plus explanatory tooltips. Replaced Root wording with Action and generated readable correlation labels without changing the underlying root-action filter value. Clarified that TRACE-off sessions still capture DEBUG lifecycle boundaries and every warning or error. Added `runtime.close.started`, `runtime.close.completed`, and `runtime.close.failed` operation records. Made sibling cleanup continue after a resource or adapter failure and guaranteed recorder close before the cleanup exception is returned. User and data impact Users can select a recognizable action instead of guessing which `op-*` value belongs to an incident. Default views remain concise, while earlier DEBUG callback boundaries remain available without having enabled TRACE. Scientific state, project data, App callbacks, and saved schemas are unchanged. Diagnostic records remain sanitized and use the existing bounded journal. Compatibility and migration This is a compatible App SDK patch. Public App-author APIs and the event schema are unchanged. The projection adds a private display-label field, and existing root-action identifiers remain stable for filtering and exported bundles. Validation Focused projection specifications verify audience filtering, readable action labels, and accurate TRACE limitations. Hidden-GUI viewer specifications verify the visible terminology and separate label/value behavior. Runtime logging specifications inject an application-resource cleanup failure and require both the failed close operation and orderly session close in memory and the durable journal. Evidence The focused framework results, generated documentation check, changed-file gate, and exact main-push CI run are the delivery evidence. Known limitations and follow-up No logger can synthesize a failed terminal record for native events that never reach Runtime, exceptions deliberately swallowed without logging, or a process that hangs or terminates abruptly. The last retained boundary and journal lease state remain the evidence in those cases."},{"title":"Shared image facade","url":"history/records/2026/06/LK-20260630-shared-image-facade.html","kind":"history","section":"history","keywords":"LK-20260630-shared-image-facade 2026-06-30 24 feat compatible labkit.image labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"Shared image facade id: LK-20260630-shared-image-facade date: 2026-06-30 sequence: 24 type: feat compatibility: compatible component: `labkit.image` | `new -> 1.0.0` component: `labkit_BatchImageCrop_app` | `1.3.9 -> 1.4.0` component: `labkit_CurvatureMeasurement_app` | `1.2.3 -> 1.2.4` component: `labkit_FocusStack_app` | `1.2.5 -> 1.3.0` component: `labkit_ImageEnhance_app` | `1.3.5 -> 1.4.0` component: `labkit_ImageMatch_app` | `1.3.5 -> 1.4.0` scope: historical project evolution Context Batch Crop, Focus Stack, Image Enhance, and Image Match each carried similar code for supported extensions, path normalization, image reads, preview sizing, and basic enhancement. Small differences between those copies produced inconsistent file and display behavior. Decision and rationale Introduce `labkit.image` for GUI-free operations with neutral meaning, then move the image apps to that shared API. Keep crop geometry, focus fusion, matching, protected enhancement, and export schemas in their respective apps. Changes `labkit.image` `1.0.0` Batch Crop, Curvature, Focus Stack, Image Enhance, and Image Match advanced within their image-facade adoption lines. Added a GUI-free image facade for file input, display normalization, basic processing, and preview support. Adopted that facade across image-measurement apps. User and data impact Supported image selection, display-name handling, preview normalization, and common enhancement primitives became consistent across the migrated apps. Scientific app outputs and source files did not change format. Compatibility and migration Image apps kept their entry points and exports while adopting the shared operations. App code that called the retired local helpers needed to use the image facade or the owning app operation. Validation Commit `7023e87e` added a dedicated `LabKitImageFacadeTest` suite and updated app compatibility, package-boundary, public-surface, and focused image-app tests. Evidence Main commit `7023e87e`. Known limitations and follow-up The first facade release covered common MATLAB image formats and basic processing only. Thermal decoding and workflow-specific algorithms remained outside `labkit.image`."},{"title":"Single-click DIC rigid point matching","url":"history/records/2026/07/LK-20260713-dic-rigid-point-editor.html","kind":"history","section":"history","keywords":"LK-20260713-dic-rigid-point-editor 2026-07-13 50 feat compatible labkit.ui labkit_DICPreprocess_app Single-click DIC rigid point matching","text":"Single-click DIC rigid point matching id: LK-20260713-dic-rigid-point-editor date: 2026-07-13 sequence: 50 type: feat compatibility: compatible component: `labkit.ui` | `5.0.4 -> 5.1.0` component: `labkit_DICPreprocess_app` | `1.3.6 -> 1.4.0` scope: Single-click DIC rigid point matching Context DIC manual rigid matching had draggable points but maintained a separate pointer implementation and required a less consistent placement workflow than the ROI-center anchors used by related reconstruction workflows. Decision and rationale Extend the existing app-neutral anchor editor with a discrete point mode, then keep moving/fixed pair order, numbering, minimum pair count, and rigid-fit policy inside DIC. Changes Added `mode=\"points\"`: one blank click appends a point, dragging refines it, no connecting curve is drawn, and deletion remains under explicit controls. Migrated the DIC modal to two shared point-mode editors while preserving ordered moving/fixed pairs, labels, undo, cancel, and acceptance rules. Retained toolbox-free image display and rigid alignment behavior. User and data impact Feature placement now follows the same direct click-and-drag model as Imager ROI anchors. Point coordinates and the resulting rigid transform keep their existing N-by-2 pixel-coordinate contract. Compatibility and migration The default anchor-editor curve mode is unchanged. DIC exports and transform math are unchanged; this is an additive interaction improvement. Validation UI anchor-editor tests cover discrete point append and no-path behavior. The DIC GUI workflow covers toolbox-free modal cancellation and app launch wiring. Evidence Primary sources are `labkit.ui.interaction.anchorEditor` and `dic_preprocess.userInterface.selectRigidPointPairs`. Commit `392a073e` introduced their shared DIC rigid-point interaction. Known limitations and follow-up Automated hidden-GUI tests cannot judge pointing ergonomics; final interaction feel still requires a short manual placement-and-drag check."},{"title":"Single-definition App product contract","url":"history/records/2026/07/LK-20260716-single-definition-contract.html","kind":"history","section":"history","keywords":"LK-20260716-single-definition-contract 2026-07-16 77 feat compatible labkit.ui App authoring Runtime V2 launch metadata","text":"Single-definition App product contract id: LK-20260716-single-definition-contract date: 2026-07-16 sequence: 77 type: feat compatibility: compatible component: `labkit.ui` | `7.1.0 -> 7.2.0` scope: App authoring scope: Runtime V2 launch metadata Context Every App repeated product metadata and facade requirements in `version.m` and `requirements.m`, while `definition.m` separately described the product's runtime behavior. Thin entrypoints had to join all three factories, and the launcher and release guardrails treated the duplicate files as separate facts. Decision and rationale Make `definition.m` the one App product contract. Product identity, version, requirements, layout, and optional runtime capabilities now have one owner. The public entrypoint can delegate through `launch(@definition, varargin{:})`. Changes Added `Command`, optional `DisplayName`, `Family`, `AppVersion`, `Updated`, and `Requirements` fields to `labkit.ui.runtime.define`. Added single-definition launch and lightweight `version` and `requirements` request handling. Kept the previous three-factory launch form only as a temporary bridge while existing Apps move in reviewed family-sized changes. Extended the minimal real-GUI launch test to obtain all metadata from the same definition. User and data impact App users see no workflow or scientific change. App authors no longer need to keep three product declarations synchronized after their App is migrated. Compatibility and migration The addition is compatible within UI 7. Existing definitions continue to run during the migration window. Each App will move its exact metadata and facade ranges into `definition.m` before its redundant files are removed. The bridge is not a permanent supported architecture. Validation Focused runtime tests exercise normal launch plus `version` and `requirements` requests from one minimal definition. Documentation, facade-version, and App contract tests protect the metadata shape during the family migrations. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents the single definition and field meanings. [App Development](../../../../development/build-apps/app-development.md) shows the reduced static App file set. Known limitations and follow-up Existing Apps, launcher static discovery, release version guards, tutorials, and the App-builder skill still need family-by-family migration. The legacy launch bridge is removed only after those consumers no longer use it."},{"title":"Source-adjacent Video Marker autosave","url":"history/records/2026/07/LK-20260716-source-adjacent-video-autosave.html","kind":"history","section":"history","keywords":"LK-20260716-source-adjacent-video-autosave 2026-07-16 69 fix compatible labkit.ui labkit_VideoMarker_app Runtime V2 project recovery Video Marker autosave location","text":"Source-adjacent Video Marker autosave id: LK-20260716-source-adjacent-video-autosave date: 2026-07-16 sequence: 69 type: fix compatibility: compatible component: `labkit.ui` | `6.0.3 -> 6.0.4` component: `labkit_VideoMarker_app` | `1.4.0 -> 1.4.1` scope: Runtime V2 project recovery scope: Video Marker autosave location Context The first explicit Runtime V2 autosave action wrote only the framework's hidden recovery generation. Video Marker already had a product contract for a visible, stable autosave beside the source video, and users select those MAT files directly in downstream workflows such as Gait Analysis. Decision and rationale Keep generic debounced recovery framework-owned, but let an app provide a deterministic autosave destination when that location is part of its workflow. Video Marker derives the destination from the source video and never asks the user to choose it. Changes Added `services.project.saveAutosave(state,filepath)` without changing named project ownership or dirty status. Restored the visible `Video Marker Autosaves` folder and stable `.video_marker.autosave.mat` filename. Enabled **Save autosave** only after a video is open and report write failures through the standard diagnostics and app-parented alert services. User and data impact Clicking **Save autosave** immediately overwrites the source video's current autosave using the current `labkitProject` envelope. It opens no location dialog. The file remains easy to locate, copy, and load into Gait Analysis. Compatibility and migration The filename and source-adjacent folder match the earlier Video Marker autosave convention. Generic Runtime recovery remains available independently. Validation The focused Video Marker GUI test opens a synthetic video, verifies the button is enabled, writes the expected source-adjacent MAT, and confirms the runtime still has no named project path. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the exact no-dialog destination. [Runtime and lifecycle](../../../../framework/guides/runtime.md) distinguishes generic recovery from an app-targeted autosave. Known limitations and follow-up The source folder must be writable. A write failure leaves the current project untouched and is shown to the user."},{"title":"Startup responsiveness","url":"history/records/2026/07/LK-20260702-startup-responsiveness.html","kind":"history","section":"history","keywords":"LK-20260702-startup-responsiveness 2026-07-02 31 perf compatible labkit_launcher labkit.ui Startup responsiveness","text":"Startup responsiveness id: LK-20260702-startup-responsiveness date: 2026-07-02 sequence: 31 type: perf compatibility: compatible component: `labkit_launcher` | `1.2.2 -> 1.2.3` component: `labkit.ui` | `3.4.2 -> 3.4.4` scope: Startup responsiveness Context Profiling showed that a substantial part of perceived startup delay occurred after MATLAB had created the window but before it allowed the operating system to paint it. Launcher discovery and preview scroll setup also ran before their results were needed, leaving users with a blank or apparently frozen window. Decision and rationale Paint the launcher and app shell as soon as their basic structure exists, then defer discovery and preview navigation setup until the UI can remain responsive. Preserve the same controls and app catalog once initialization finishes. Changes `labkit_launcher` `1.2.2 -> 1.2.3` `labkit.ui` `3.4.2 -> 3.4.4` Painted launcher and app windows earlier. Deferred launcher app discovery and lazy preview scroll setup. User and data impact The window appeared and responded earlier, making initialization visible instead of presenting a long white-screen interval. App discovery and scroll navigation still completed automatically; the change moved their timing rather than removing those capabilities. Compatibility and migration App definitions and saved data were unchanged. The launcher and runtime altered only when discovery and interaction setup occurred during startup. Validation Launcher profiler assertions checked the earlier paint point, while UI axes layout tests covered deferred scroll navigation in `7d4ef11e`. Evidence Main commit `7d4ef11e`. Known limitations and follow-up Earlier painting improved perceived responsiveness, but it also made progress communication important. Later runtime work introduced explicit startup stages and lifecycle-managed readiness reporting."},{"title":"Structured documentation history","url":"history/records/2026/07/LK-20260715-component-owned-history.html","kind":"history","section":"history","keywords":"LK-20260715-component-owned-history 2026-07-15 61 docs breaking `docs/history/records/` `tools/docs/` `tools/release/` `CHANGELOG.md`","text":"Structured documentation history id: LK-20260715-component-owned-history date: 2026-07-15 sequence: 61 type: docs compatibility: breaking scope: `docs/history/records/` scope: `tools/docs/` scope: `tools/release/` scope: `CHANGELOG.md` Context Project evolution was stored in one 3,571-line root changelog. App, framework, library, validation, launcher, and release decisions were mixed together, while the new documentation site already had component pages capable of presenting the relevant history in context. Decision and rationale Store each change explanation once as an independent Markdown page in the chronological `docs/history/records///` tree. Let the component metadata, rather than the file's physical folder, associate a record with each affected app, framework, or library. The documentation compiler then generates both component-specific history lists and a searchable project-wide index. Changes Split the existing recorded history into one Markdown file per Change ID and placed every public record under one chronological source tree. Removed the root aggregate changelog, duplicated current-version lookup, and the dedicated release-history parser. Replaced the launcher's custom version-history window with a direct link to the selected App's generated Documentation and History page. Routed version governance through owned component docs, distributed history, generated-site consistency, and source version metadata. User and data impact Users see instructions, APIs, scientific context, and relevant changes from one app or library page. Source readers can find every public history record under `docs/history/records/`. Search still reaches the complete historical record. No project files, calculations, or experimental data formats change. Compatibility and migration Links that targeted the old root changelog must move to the generated Project History page or a component page. The old parser and launcher history mode are intentionally removed rather than maintained as compatibility aliases. Validation The documentation compiler validates unique history identities, required metadata, source pages, generated links, search indexing, and deterministic output. Project release guardrails validate the distributed record set and component aggregation. Evidence `docs/history/README.md` defines the human entry point. `tools/docs/private/loadLabKitDocumentation.m` discovers history pages. `DocumentationHistoryGuardrailTest` protects the distributed model. Known limitations and follow-up Some older records retain the wording used when their changes were originally made. Component metadata, rather than folder placement, determines where each record is shown."},{"title":"Structured evolution records","url":"history/records/2026/07/LK-20260713-structured-evolution-records.html","kind":"history","section":"history","keywords":"LK-20260713-structured-evolution-records 2026-07-13 52 docs compatible repository changelog and release governance","text":"Structured evolution records id: LK-20260713-structured-evolution-records date: 2026-07-13 sequence: 52 type: docs compatibility: compatible scope: repository changelog and release governance Context The previous `Unreleased` and `Pending` model mixed delivery state with historical meaning and required records to be moved after merge. Its prose was useful to humans but had no reliable machine contract. Decision and rationale Use stable, status-free records with a small parseable metadata header and required explanatory sections. Git branches, PRs, tags, and evidence identify delivery state without duplicating it in the changelog. Changes Added schema-v1 metadata for Change ID, date, type, compatibility, component introductions and version transitions, and unversioned repository scopes. Added a base-MATLAB parser and release guardrail for schema integrity. Normalized every historical entry into the same schema-v1 record contract. User and data impact Users and maintainers can search one durable ID and obtain the reason, impact, compatibility, validation, and evidence for a change without reconstructing it from commit messages. No scientific data or runtime behavior changes. Compatibility and migration Existing links to `CHANGELOG.md` remain valid. Automation that searched the old `Unreleased` or `Version History` headings must use the parser and stable Change IDs. Validation `ChangelogGuardrailTest` parses every schema-v1 record and checks current version lookup coverage and release-policy documentation. Evidence The parser lives at `tools/release/parseLabKitChangelog.m`; the normalized historical baseline was checked against the dated mainline log and release tags. Known limitations and follow-up Some older records do not name exact test commands because that evidence was not recorded at the time; they state that limitation instead of inventing it."},{"title":"T-Test Wizard adds table selection and first-versus-each comparisons","url":"history/records/2026/07/LK-20260718-ttest-wizard-and-table-workspace.html","kind":"history","section":"history","keywords":"LK-20260718-ttest-wizard-and-table-workspace 2026-07-19 135 feat compatible labkit_TTestWizard_app labkit.ui Statistics CSV exchange App Framework presentation","text":"T-Test Wizard adds table selection and first-versus-each comparisons id: LK-20260718-ttest-wizard-and-table-workspace date: 2026-07-19 sequence: 135 type: feat compatibility: compatible component: `labkit_TTestWizard_app` | `new -> 1.0.0` component: `labkit.ui` | `7.5.1 -> 7.6.0` scope: Statistics scope: CSV exchange scope: App Framework presentation Context Laboratory comparison data appeared as manually maintained workbook blocks, same-shape files, and rectangular App exports containing identifiers beside numeric metrics. Schema-first mapping made unfamiliar sources harder to use. The required workflow also included several treatments compared with one control, while the first implementation boundary held only A and B. Runtime presentation could update table values but not their spreadsheet headers or collapse unused workspace panels. An App therefore could not show an unfamiliar table faithfully while giving each wizard step the full workspace. Decision and rationale Display CSV, TSV, XLSX, and XLS sources as visible cell grids. Let the user append each numeric selection as an ordered durable group. The first group is the explicit reference; one chosen t-test specification is then applied to every later group versus that first group. Keep Data, Test/Plot, and Export controls short. Put Data and Plot in user-selectable right-side workspace pages, merge result review with the test, and keep completed comparisons as immutable snapshots. Plot controls consume those snapshots and cannot recalculate the result family. Keep CSV as a low-constraint rectangular interchange boundary. Known producers may add conveniences later, but visible cell selection remains the fallback. Changes Added the Statistics family and T-Test Wizard 1.0.0. Added direct visible-cell selection from CSV, TSV, XLSX, and XLS, including worksheet selection, numeric-text acceptance, mixed-cell feedback, and groups captured from different sources. Added an ordered `Group`/`Value` observation editor, manual paste/editing, existing-group capture choices, first-appearance reference ordering, and batch reassignment or deletion of selected observation rows. Added layered spreadsheet-header suggestions for new group names and coalesced completed-selection delivery so range dragging does not repeatedly refresh the App. Added first-versus-each Welch, pooled equal-variance, and paired t-tests with two-sided and directional alternatives. Added Base MATLAB Student-t probabilities and intervals, stable per-result failure statuses, and immutable result snapshots. Added reference-style bar and box plots with pastel groups, explicit numeric axes, standard x-axis tick labels, boxed grid-free axes, and stacked `NS/*/**/***/****` brackets. Added multi-column group CSV and multi-row result CSV exports. Added project schema version 2 migration from durable A/B vectors to two ordered groups. Extended Runtime presentation so result tables can update headers and hidden rows collapse. Coalesced rapid table-selection events before App presentation and replaced stale standalone file-context titles instead of appending duplicate suffixes. Preserved tick labels, limits, and graphics stacking order when plots are popped into standalone figures, keeping error bars above their bars, and added a copied-figure control for switching X-axis group labels between angled and horizontal presentation. Responsive popout margins keep enlarged ticks, axis labels, and titles inside the standalone window. Extended `labkit.ui.layout.workspace` to accept two or more tab pages without adding a parallel workspace-specific public constructor. User and data impact Users can select any number of groups from an unfamiliar readable table without defining a schema or removing unrelated text. Running creates one comparison row for every group after the first. Paired testing follows displayed row order for each comparison with the first group. The App exports ordinary CSVs instead of requiring another program to decode a project MAT file. Imported sources are not modified. Compatibility and migration The App and Statistics family are additive. Version-1 T-Test Wizard projects migrate A to group 1 and B to group 2, preserving labels, values, and source metadata. An earlier completed A/B result remains a one-row result family. `labkit.ui` remains compatible with `>=7 <8`. Existing presentation structs that omit dynamic table headers and visibility behave as before. No multiple-comparison correction is silently introduced. This preserves the meaning of each requested t-test and makes the limitation explicit. Validation Unit tests cover mixed-cell extraction, source preservation, Welch/pooled/ paired reference values, first-versus-each ordering, result-family identity, and multi-group CSV round trips. A focused framework GUI check proves rapid selections commit only the final range and repeated file-context presentation does not duplicate plot titles. A hidden App GUI workflow covers three captured ranges, batch group changes, two comparisons with the first group, equal-height source/editor tables, native Data/Plot pages, compact stale-plot messaging, bar/box selection, boxed grid-free rendering, and both exports. Framework GUI tests cover native workspace pages, invalid page composition, dynamic table headers, missing-cell display, full-width previews, collapsed hidden rows, and invalid presentation diagnostics. Representative private workbook structures informed the visual and workflow requirements without copying paths, filenames, identifiers, or values into the repository. Evidence [T-Test Wizard](../../../../apps/statistics/ttest-wizard/README.md) [Statistics Apps](../../../../apps/statistics/README.md) [Simple Scientific CSV Exchange](../../../../development/data-and-designs/scientific-csv-interchange.md) [T-Test Wizard design](../../../../development/data-and-designs/group-comparison-app-design.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Version 1.0.0 does not infer experimental units, independence, pairing, exclusions, or groups from filenames. It does not apply multiple-comparison correction, ANOVA, nonparametric tests, regression, or mixed modeling. Automatic numeric-column suggestions remain a possible convenience only after real use demonstrates a stable need."},{"title":"T-Test plots gain truthful limits and an explicit view reset","url":"history/records/2026/07/LK-20260720-ttest-plot-viewport-and-density.html","kind":"history","section":"history","keywords":"LK-20260720-ttest-plot-viewport-and-density 2026-07-20 143 feat compatible labkit_TTestWizard_app Statistics Apps App Framework","text":"T-Test plots gain truthful limits and an explicit view reset id: LK-20260720-ttest-plot-viewport-and-density date: 2026-07-20 sequence: 143 type: feat compatibility: compatible component: `labkit_TTestWizard_app` | `1.1.1 -> 1.2.0` scope: Statistics Apps scope: App Framework Context T-Test Wizard used the absolute data magnitude as a minimum padding span, so box plots with a narrow nonzero range could receive excessive empty space. Bar-chart zoom could move the zero baseline away from the y-axis edge, and the App had no visible action for returning to the fitted view. Two short status summaries also occupied detail-panel height. Decision and rationale Fit limits from the graphics that carry statistical meaning: distribution or bar extents, optional observations, standard-deviation error bars, and significance brackets. Keep zero at the appropriate edge for one-sided bar data because truncated bar baselines distort magnitude; mixed-sign data must instead show zero inside the axis. Keep box plots distribution-fitted. Expose an App-owned reset action backed by the framework's general transient plot-view revision. Use the framework's line-count hint for short summaries rather than adding T-Test-specific pixel sizes. Changes Corrected the fitted span and padding used by bar, box, and annotation geometry. Kept zero at the lower or upper y-limit for positive or negative bar plots across programmatic, toolbar, and wheel-driven limit changes. Added **Reset plot view** without recalculating tests or changing project data. Refined box fill, outlines, whiskers, observation overlays, bar opacity, error-bar caps, and outward ticks. Reduced **What will run** and **Result family** to two-line summary panels with the framework's summary reading size. User and data impact Project schema 2, completed comparison values, CSV outputs, formulas, and test choices are unchanged. T-Test Wizard 1.2.0 requires `labkit.app` 1.2 or later for compact summaries and one-shot plot viewport revisions. Existing projects open without migration. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation The T-Test hidden-GUI workflow checks fitted limits, zero-edge persistence, box-plot independence, compact summary sizing, and explicit view reset. Framework contract tests cover plot viewport preservation and revision resets. Documentation rendering and history consistency cover the versioned change. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led visual review should compare box, bar, error-bar, and bracket appearance at typical desktop sizes and with real scientific label lengths."},{"title":"Test catalog cutover replaces the legacy test tree","url":"history/records/2026/07/LK-20260723-test-catalog-cutover.html","kind":"history","section":"history","keywords":"LK-20260723-test-catalog-cutover 2026-07-23 158 test breaking Test catalog Test authoring Cross-platform validation","text":"Test catalog cutover replaces the legacy test tree id: LK-20260723-test-catalog-cutover date: 2026-07-23 sequence: 158 type: test compatibility: breaking scope: Test catalog scope: Test authoring scope: Cross-platform validation Context The former test tree mixed stage folders, route wrappers, shared helpers, and executor policy. Authors had to infer where a specification belonged and which selector would exercise it. On Windows, an additional case-sensitive path comparison could reject valid catalog specifications before any behavior ran. Decision and rationale Make production ownership, behavioral contract, and execution environment the only selection model. Keep framework mechanics private in `labkittest`; keep only genuinely cross-owner synthetic data in a named fixture package. A test path comparison must be case-insensitive because Windows resolves an existing path with filesystem casing that need not match the configured root spelling. Changes Replaced the old runner and `tests/cases` tree with exact catalog specifications under `tests/specs`. Added semantic authoring and execution entry points: `explain`, `createSpec`, and `run`. Removed unreferenced legacy GUI, App-contract, repository, and assertion helpers from the former `tests/shared` bucket. Moved the remaining cross-owner synthetic DTA, thermal, RHS, chrono, and gait inputs into `tests/+testfixtures` and qualified every consumer. Normalized catalog filesystem paths before ownership comparison and added a Windows regression for differently cased specification-root input. Updated the current testing manual, scoped test rules, migration ledger, and generated documentation site. User and data impact No App behavior, saved project, scientific calculation, export schema, or laboratory data changes. Test authors gain a path-free insertion workflow and machine-readable run artifacts. All fixtures remain synthetic. Compatibility and migration This intentionally removes the unsupported legacy test folders, runner, and selector model. Contributors must use `tests/specs`, `labkittest` semantic selectors, and `tests/+testfixtures` where a synthetic input is genuinely cross-owner. No data migration is required. Validation Catalog/build regression: 23 tests passed. DTA, RHS, and thermal synthetic-fixture regressions: 8 tests passed. Complete headless catalog: 202 tests passed. Documentation render and `docsCheck`: 372 generated files current. PR CI was inspected; Windows separator handling and the shell-sensitive clean-checkpoint revision syntax are covered by catalog regressions and require the updated commit to run remotely. POSIX CI also exposed an interactive Git pager inherited by MATLAB; planner queries now use `git --no-pager` so the progress watchdog can never stall on a pager. Its case-sensitive filesystem also verified that semantic owner matching must resolve the physical specification-directory spelling exactly. Evidence The catalog writes exact identities and semantic reasons into each run's `plan.json`; the final headless `summary.json` records 202 passed identities with no failures or incomplete tests. The PR workflow independently executes headless and hidden-GUI profiles on Linux, macOS, and Windows. Known limitations and follow-up Hidden-GUI checks remain structural and do not replace manual validation of native dialogs, visual quality, pointer behavior, real-data suitability, or scientific interpretation. The PR must receive green cross-platform CI before merge."},{"title":"Thermal facade and FLIR app","url":"history/records/2026/07/LK-20260701-thermal-facade-and-flir-app.html","kind":"history","section":"history","keywords":"LK-20260701-thermal-facade-and-flir-app 2026-07-01 26 feat compatible labkit.thermal labkit.ui labkit_FLIRThermal_app Thermal facade and FLIR app","text":"Thermal facade and FLIR app id: LK-20260701-thermal-facade-and-flir-app date: 2026-07-01 sequence: 26 type: feat compatibility: compatible component: `labkit.thermal` | `new -> 1.0.0` component: `labkit.ui` | `3.2.9 -> 3.2.10` component: `labkit_FLIRThermal_app` | `new -> 1.0.0` scope: Thermal facade and FLIR app Context FLIR radiometric JPEGs contain both a visible preview and embedded thermal data plus camera metadata. Treating them as ordinary images loses the temperature meaning, while placing all parsing and conversion code inside one GUI would make the scientific operations unusable from MATLAB scripts. Decision and rationale Create a thermal library for reading radiometric files, converting raw sensor values to temperature, and rendering temperature images. Build the FLIR Thermal Postprocess app on those public operations, keeping file lists, range controls, measurement state, and export flow in the app. Changes `labkit.thermal` `1.0.0` `labkit.ui` `3.2.9 -> 3.2.10` `labkit_FLIRThermal_app` `1.0.0` Added the thermal facade. Added the FLIR Thermal Postprocess app. User and data impact Users gained a FLIR app with file browsing, temperature display, range control, summary values, and export-oriented views. The same thermal functions could be called without opening the GUI, so scripts could inspect temperature matrices and metadata directly. Compatibility and migration The thermal facade and FLIR app were additive. Existing image apps and files were unchanged; scripts could begin using `labkit.thermal` without a project- file migration. Validation The change added thermal-facade and app-operation unit suites, a FLIR layout suite, package-surface checks, and a synthetic radiometric JPEG writer. No laboratory image was committed as a fixture. Evidence Main commit `977c9457`. Known limitations and follow-up Camera-specific metadata support depended on the formats represented by the synthetic and available local samples. Later changes refined display ranges, preview cost, and point and ROI measurement tools."},{"title":"Tool-panel hosts and image app fixes","url":"history/records/2026/06/LK-20260629-tool-panel-hosts-and-image-app-fixes.html","kind":"history","section":"history","keywords":"LK-20260629-tool-panel-hosts-and-image-app-fixes 2026-06-29 18 fix compatible labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_ImageEnhance_app labkit_ImageMatch_app Tool-panel hosts and image app fixes","text":"Tool-panel hosts and image app fixes id: LK-20260629-tool-panel-hosts-and-image-app-fixes date: 2026-06-29 sequence: 18 type: fix compatibility: compatible component: `labkit.ui` | `3.2.0 -> 3.2.2` component: `labkit.ui` | `3.2.2 -> 3.2.3` component: `labkit_BatchImageCrop_app` | `1.3.2 -> 1.3.3` component: `labkit_CurvatureMeasurement_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.3.0 -> 1.3.1` component: `labkit_ImageMatch_app` | `1.3.0 -> 1.3.1` scope: Tool-panel hosts and image app fixes Context Reusable interaction tools were being attached to generic empty sections, while several image apps had separate problems with file-entry identity, reported output size, and White ROI updates. Decision and rationale Add a semantic `toolPanel` host for composed tools and fix the image-app behaviors at their actual data owners. Normalize file entries and regenerate ids deterministically so presentation updates do not change source identity. Changes `labkit.ui` `3.2.0 -> 3.2.3` Batch Crop, Curvature, Image Enhance, and Image Match patch bumped where layouts or image-app behavior changed. Hardened file-panel entry normalization and deterministic ID regeneration. Fixed output-size reporting and White ROI responsiveness. Added semantic `toolPanel` hosts. User and data impact White ROI controls responded to the selected image, Batch Crop reported output dimensions consistently, and reusable tools occupied a stable named layout area. Existing source and result formats were unchanged. Compatibility and migration Existing file entries and image results remained readable. Deterministic ids were regenerated in memory, and the new layout host did not alter saved data. Validation The listed commits updated file-panel, tool-host, and focused image-app tests. Exact historical commands were not recorded. Evidence Main commits `f2189aef`, `77084fbe`, and `871739cd`. Known limitations and follow-up The first `toolPanel` API was later replaced by the current semantic layout and managed-interaction model."},{"title":"Traceable FLIR temperature calibration","url":"history/records/2026/07/LK-20260713-flir-calibration-provenance.html","kind":"history","section":"history","keywords":"LK-20260713-flir-calibration-provenance 2026-07-13 49 feat compatible labkit.thermal labkit_FLIRThermal_app Traceable FLIR temperature calibration","text":"Traceable FLIR temperature calibration id: LK-20260713-flir-calibration-provenance date: 2026-07-13 sequence: 49 type: feat compatibility: compatible component: `labkit.thermal` | `1.0.0 -> 1.1.0` component: `labkit_FLIRThermal_app` | `1.2.8 -> 1.3.0` scope: Traceable FLIR temperature calibration Context A successful Celsius conversion did not reveal whether emissivity and environmental values came from the file or from model defaults, which could make an absolute temperature appear more certain than its metadata justified. Decision and rationale Preserve conversion provenance with the thermal record and show fallback use in the app, rather than silently treating every parameter as measured. Changes Added optional conversion diagnostics with correction mode, defaulted fields, parameter sources, and fallback status. Stored diagnostics in thermal record metadata and surfaced warnings in FLIR file status and details. Documented embedded calibration requirements and environmental fallbacks. User and data impact Users can distinguish radiometric values based entirely on embedded metadata from values affected by fallback assumptions before interpreting temperatures. Compatibility and migration Existing one-output conversion calls remain valid. The diagnostics output and record metadata are additive; app workflows need no migration. Validation Thermal facade and FLIR app tests cover metadata sources, fallback reporting, and one-output compatibility. Evidence The model and provenance contract are documented in `docs/api/thermal.md`; branch checkpoint `7391e293` carries the implementation before the final squash merge. Known limitations and follow-up Diagnostics describe source and fallback use; they do not estimate a physical uncertainty interval for a particular camera, surface, or environment."},{"title":"Traceable and base-MATLAB CI validation","url":"history/records/2026/07/LK-20260713-traceable-base-matlab-ci.html","kind":"history","section":"history","keywords":"LK-20260713-traceable-base-matlab-ci 2026-07-13 51 ci compatible GitHub Actions and MATLAB validation routing","text":"Traceable and base-MATLAB CI validation id: LK-20260713-traceable-base-matlab-ci date: 2026-07-13 sequence: 51 type: ci compatibility: compatible scope: GitHub Actions and MATLAB validation routing Context CI failures and stalls could end without enough information to identify the active test, and ordinary success on a toolbox-rich development machine did not prove that base-MATLAB users could run representative workflows. Decision and rationale Make the existing official runner publish progress and active-test state, and add a distinct compatibility gate that combines static calls, product ownership, and toolbox-shadowed behavior. Changes Added per-test progress, heartbeat, active-test, timeout-summary, and artifact publication behavior to CI. Added `buildtool baseMatlab` and representative toolbox-shadow workflows. Improved changed-file routing to target direct consumers while retaining explicit owners such as the launcher GUI suite. Corrected result aggregation so assumption-filtered tests remain visible as skipped without being misreported as shard failures. User and data impact Base-MATLAB compatibility is now an explicit supported path, and failed or stalled CI runs provide enough state to identify the last active test. Runtime scientific outputs are not changed by this record. Compatibility and migration Existing build tasks remain available. Maintainers can add `baseMatlab` to local validation without installing or uninstalling toolboxes. Validation CI policy, build-task framework, changed-routing, toolbox dependency, and representative workflow tests guard the new behavior. Evidence The command matrix is in `docs/development/maintain-and-release/testing.md`; CI uploads official runner logs and active-test artifacts. Commit `28ff8edb` made MATLAB failures traceable, `37bd7fd5` enforced Base MATLAB compatibility, and `2c9b8792` preserved launcher validation ownership. Known limitations and follow-up Shadow tests cover known dependency risks and cannot simulate every licensed toolbox combination; MATLAB product-ownership analysis remains the broad check."},{"title":"UI 5 facade redesign, app migration, and plot refresh","url":"history/records/2026/07/LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh.html","kind":"history","section":"history","keywords":"LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh 2026-07-06 38 refactor breaking labkit_launcher labkit.ui labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app UI 5 facade redesign, app migration, and plot refresh","text":"UI 5 facade redesign, app migration, and plot refresh id: LK-20260706-ui-5-facade-redesign-app-migration-and-plot-refresh date: 2026-07-06 sequence: 38 type: refactor compatibility: breaking component: `labkit_launcher` | `1.2.3 -> 1.2.4` component: `labkit.ui` | `4.2.0 -> 4.2.1` component: `labkit.ui` | `4.2.1 -> 5.0.0` component: `labkit_DICPostprocess_app` | `1.3.3 -> 1.3.4` component: `labkit_DICPreprocess_app` | `1.3.3 -> 1.3.4` component: `labkit_ChronoOverlay_app` | `1.3.3 -> 1.3.5` component: `labkit_CIC_app` | `1.3.5 -> 1.3.7` component: `labkit_CSC_app` | `1.3.7 -> 1.3.9` component: `labkit_EIS_app` | `1.3.3 -> 1.3.4` component: `labkit_VTResistance_app` | `1.3.5 -> 1.3.7` component: `labkit_BatchImageCrop_app` | `1.6.5 -> 1.6.6` component: `labkit_CurvatureMeasurement_app` | `1.3.3 -> 1.3.4` component: `labkit_FLIRThermal_app` | `1.2.7 -> 1.2.8` component: `labkit_FocusStack_app` | `1.4.4 -> 1.4.5` component: `labkit_ImageEnhance_app` | `1.5.4 -> 1.5.5` component: `labkit_ImageMatch_app` | `1.5.4 -> 1.5.5` component: `labkit_FigureStudio_app` | `new -> 0.1.0` component: `labkit_FigureStudio_app` | `0.1.0 -> 0.1.1` component: `labkit_FigureStudio_app` | `0.1.1 -> 0.1.2` component: `labkit_FigureStudio_app` | `0.1.2 -> 0.1.4` component: `labkit_NerveResponseAnalysis_app` | `1.3.3 -> 1.3.4` component: `labkit_ResponseReviewStats_app` | `1.3.3 -> 1.3.4` component: `labkit_RHSPreview_app` | `1.3.3 -> 1.3.4` component: `labkit_ECGPrint_app` | `1.3.4 -> 1.3.5` scope: UI 5 facade redesign, app migration, and plot refresh Context The declarative runtime had made app structure more consistent, but its public packages still reflected the order in which features had been extracted: `app`, `spec`, `view`, `tool`, and `diag` mixed lifecycle, layout, plotting, interaction, and diagnostics. App authors often had to know implementation history to guess where an operation belonged. Plot behavior exposed the cost of that ambiguity. Apps independently cleared axes, fitted limits, translated image coordinates, registered previews, and handled empty states. After removing or changing a file, an electrochem plot could retain limits, legends, or annotations from the previous selection. Launcher operations also needed visible progress, and the growing collection of popout-figure actions no longer fit comfortably in every app. Figure cleanup, style adjustment, data export, and reconstruction were substantial enough to justify their own workflow. Decision and rationale Name the UI facade by responsibility: runtime, layout, controls, plots, interactions, and debugging. Move reusable axes mechanics into the plot layer, then migrate every supported app in one breaking version so old and new package names cannot coexist indefinitely. Give long launcher operations one busy and progress model, and move advanced figure work into Figure Studio rather than expanding the app shell further. Changes `labkit_launcher` `1.2.3 -> 1.2.4` `labkit.ui` `4.2.0 -> 5.0.0` `labkit_FigureStudio_app` `0.1.0 -> 0.1.4` `labkit_ChronoOverlay_app` `1.3.3 -> 1.3.5` `labkit_CIC_app` `1.3.5 -> 1.3.7` `labkit_CSC_app` `1.3.7 -> 1.3.9` `labkit_EIS_app` `1.3.3 -> 1.3.4` `labkit_VTResistance_app` `1.3.5 -> 1.3.7` `labkit_DICPreprocess_app` `1.3.3 -> 1.3.4` `labkit_DICPostprocess_app` `1.3.3 -> 1.3.4` `labkit_BatchImageCrop_app` `1.6.5 -> 1.6.6` `labkit_CurvatureMeasurement_app` `1.3.3 -> 1.3.4` `labkit_FLIRThermal_app` `1.2.7 -> 1.2.8` `labkit_FocusStack_app` `1.4.4 -> 1.4.5` `labkit_ImageEnhance_app` `1.5.4 -> 1.5.5` `labkit_ImageMatch_app` `1.5.4 -> 1.5.5` `labkit_RHSPreview_app` `1.3.3 -> 1.3.4` `labkit_NerveResponseAnalysis_app` `1.3.3 -> 1.3.4` `labkit_ResponseReviewStats_app` `1.3.3 -> 1.3.4` `labkit_ECGPrint_app` `1.3.4 -> 1.3.5` Reorganized the UI facade into `labkit.ui.runtime`, `layout`, `control`, `plot`, `interaction`, and `debug` so app authors can find lifecycle, data-only layout, control update, plot-area, pointer/tooling, and diagnostic APIs by responsibility. Replaced the old app/spec/view/tool/diag UI paths with UI 5 names and moved every app to `definition(..., \"Layout\", @buildWorkbenchLayout)` plus `labkit.ui.layout.*`, `labkit.ui.control.*`, `labkit.ui.plot.*`, `labkit.ui.interaction.*`, and `labkit.ui.debug.*`. Added framework-owned plot helpers for registered axes lookup, clearing, empty-state messages, fitted limits, canvas framing, image preview redraw, and data/fraction coordinate conversion. Reset electrochem plot axes and legends when files are cleared, removed, or redrawn so old ranges, markers, shaded windows, and annotations do not remain after the file list changes. Refit CIC, Chrono Overlay, CSC all-cycle, and VT Resistance redraws to the current plotted data instead of preserving stale manual zoom limits. Staggered CIC Emc/Ema marker labels with readable white backgrounds so key extrema labels are less likely to be hidden by voltage-step or window annotations. Added visible busy/progress feedback for launcher actions that can wait on file scans, artifact cleanup, app startup, profiling, GitHub version lookup, or update/install work. Added launcher and version-manager busy gates so repeated clicks do not start overlapping synchronous operations. Replaced top workbench utility buttons with native window utility menus. Changed workbench plot popout/copy/save actions to operate on every registered preview axes in a multi-axes app. Replaced icon-only popout figure tools with visible text buttons for font, plotted-line, axes, grid, and Studio handoff controls. Added the LabKit Core Figure Studio app for opening MATLAB `.fig` files, switching between the measured LabKit single-panel style and the imported FIG defaults, tuning font/line/grid parameters, and exporting visible graphics data packages with reconstruction scripts. Normalized imported FIG axes before applying Studio canvas and style constraints so source layout/aspect metadata and file-selection titles cannot collapse the preview. Centered the managed preview canvas in the app preview grid so styled FIG labels and plot content render in the visible canvas instead of the corner cell. Added a `labkit.ui.plot.fitCanvas` canvas-frame helper so fixed-size preview axes use the framework-owned preview grid policy instead of app-owned row/column layout code. User and data impact App plots now followed the current file selection: clearing or changing data also cleared stale ranges, legends, markers, and annotations. CIC kept Emc and Ema labels readable on dense traces, and multi-axes apps offered one consistent set of plot utilities. Launcher progress distinguished a long synchronous operation from a frozen MATLAB session and rejected repeated clicks while that operation was active. Figure Studio added a separate place to open a `.fig`, compare the imported style with a LabKit single-panel style, adjust visible graphics, and export the axes data with a reconstruction script. These operations worked on figure presentation and visible graphics; they did not replace the scientific export owned by the source app. For app authors, the breaking rename made API discovery more direct. Lifecycle code used `labkit.ui.runtime`, layout descriptions used `labkit.ui.layout`, and plot or interaction code no longer depended on a broad historical bucket. Compatibility and migration Breaking UI facade migration: app code must use the UI 5 package paths and require `labkit.ui >=5 <6`. Validation Commit `78f4827e` migrated 383 source and test files and added focused plot- helper and neurophysiology layout coverage. Launcher progress received its own GUI suite in `edbc79d8`. Figure Studio added GUI and result-export suites in `4c841d6f`, followed by source-axes import tests in `7edd619f`. Exact local commands for the combined release were not recorded. Evidence UI 5 facade and app migration `78f4827e`. Launcher progress and plot utilities `edbc79d8`. Figure Studio `4c841d6f` and source-axes cleanup `8aeee1f1`. FIG import normalization `7edd619f`. Known limitations and follow-up UI 5 clarified package ownership, but app code still performed much of its own lifecycle and rendering coordination. Runtime V2 later took explicit ownership of startup, callbacks, presenters, injected services, and serializable state."},{"title":"UI 7 public runtime boundary","url":"history/records/2026/07/LK-20260716-ui7-public-runtime-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-ui7-public-runtime-boundary 2026-07-16 75 refactor breaking labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app public API ownership Runtime V2 busy and persistence mechanics","text":"UI 7 public runtime boundary id: LK-20260716-ui7-public-runtime-boundary date: 2026-07-16 sequence: 75 type: refactor compatibility: breaking component: `labkit.ui` | `6.0.5 -> 7.0.0` component: `labkit_DICPostprocess_app` | `1.4.0 -> 1.4.1` component: `labkit_DICPreprocess_app` | `1.5.0 -> 1.5.1` component: `labkit_ChronoOverlay_app` | `1.4.0 -> 1.4.1` component: `labkit_CIC_app` | `1.4.0 -> 1.4.1` component: `labkit_CSC_app` | `1.4.0 -> 1.4.1` component: `labkit_EIS_app` | `1.4.0 -> 1.4.1` component: `labkit_VTResistance_app` | `1.4.0 -> 1.4.1` component: `labkit_GaitAnalysis_app` | `2.0.1 -> 2.0.2` component: `labkit_BatchImageCrop_app` | `1.7.0 -> 1.7.1` component: `labkit_CurvatureMeasurement_app` | `1.4.1 -> 1.4.2` component: `labkit_FLIRThermal_app` | `1.4.0 -> 1.4.1` component: `labkit_FocusStack_app` | `1.5.0 -> 1.5.1` component: `labkit_ImageEnhance_app` | `1.6.0 -> 1.6.1` component: `labkit_ImageMatch_app` | `1.6.0 -> 1.6.1` component: `labkit_VideoMarker_app` | `1.5.0 -> 1.5.1` component: `labkit_FigureStudio_app` | `0.2.0 -> 0.2.1` component: `labkit_NerveResponseAnalysis_app` | `1.4.1 -> 1.4.2` component: `labkit_ResponseReviewStats_app` | `1.4.1 -> 1.4.2` component: `labkit_RHSPreview_app` | `1.4.1 -> 1.4.2` component: `labkit_ECGPrint_app` | `1.4.1 -> 1.4.2` scope: public API ownership scope: Runtime V2 busy and persistence mechanics Context The UI 6 runtime exposed three implementation-level helpers alongside its App lifecycle: direct busy-state execution and the creation and resolution of portable file-reference structs. Production Apps did not need those entry points. Semantic callbacks already owned busy transactions, while injected project services and the save/load boundary owned source references. Keeping the helpers public created two competing ways to perform the same framework-owned work. It also made tests exercise a direct busy mode that no production App used. Decision and rationale UI 7 keeps lifecycle and semantic construction public while moving callback transactions and reference serialization into the runtime implementation. `labkit.ui.runtime.create` remains public: it has a distinct supported purpose for callers that already own a complete declarative layout and need only the workbench shell, without project lifecycle or action orchestration. Changes Removed `labkit.ui.runtime.runBusy` from the public facade. Layout actions and other semantic callbacks now reach one private transaction helper. Removed `createPortableFileReference` and `resolvePortableFileReference` from the public facade. Runtime save, load, recovery, and relink code continue to use those algorithms privately. Removed the unused direct-busy behavior that froze callbacks and graphics hit testing. Semantic transactions preserve callback-owned interaction changes. Updated all tracked Apps to declare the UI 7 compatibility range without changing their domain behavior or project payload versions. Replaced tests that fabricated references through a public helper with project-level save/move/load coverage or explicit legacy fixtures. User and data impact App users see no workflow or saved-data change. App developers initiate work through semantic actions and create source records through `services.project.sourceRecord`; the runtime owns the corresponding busy and serialization mechanics. Code that called the three removed UI 6 functions must move to those lifecycle contracts before adopting UI 7. Compatibility and migration The change is a deliberate public API break and therefore increments the UI facade major version. Every tracked App now requires `labkit.ui >=7 <8`. Runtime V2 project envelopes, App payload versions, and portable-reference field names are unchanged, so saved projects require no data migration. Validation Validation covers the exact UI package surface, semantic busy transactions, Runtime V2 project save/move/load and relink behavior, affected App migrations, App requirement/version consistency, generated API removal, and documentation history contracts. Evidence [UI Framework](../../../../framework/README.md) maps the supported public packages and runtime entry points. [Runtime and Data Model](../../../../framework/guides/runtime.md) explains semantic transactions, injected services, and portable source behavior. Known limitations and follow-up This boundary change does not redesign the lower-level declarative workbench builder. `labkit.ui.runtime.create` remains supported and will be judged by its own layout-only contract rather than by production App call count."},{"title":"UI diagnostics and release v3.0.0","url":"history/records/2026/06/LK-20260629-ui-diagnostics-and-release-v3-0-0.html","kind":"history","section":"history","keywords":"LK-20260629-ui-diagnostics-and-release-v3-0-0 2026-06-29 17 feat compatible labkit_launcher labkit.ui UI diagnostics and release v3.0.0","text":"UI diagnostics and release v3.0.0 id: LK-20260629-ui-diagnostics-and-release-v3-0-0 date: 2026-06-29 sequence: 17 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.2 -> 1.1.3` component: `labkit.ui` | `3.1.3 -> 3.2.0` scope: UI diagnostics and release v3.0.0 Context The diagnostic work completed during the 3.x UI cycle needed a stable release checkpoint, clearer validation documentation, and CI behavior that would not run duplicate workflows for the same change. Decision and rationale Publish the UI diagnostic improvements as `v3.0.0`, document how they are validated, and avoid redundant CI triggers. Keep the release compatible with the app data produced by the preceding 2.x workbench releases. Changes Release tag `v3.0.0` `labkit.ui` `3.1.3 -> 3.2.0` `labkit_launcher` `1.1.2 -> 1.1.3` Improved UI diagnostics and validation documentation. Published the v3.0.0 release line around UI diagnostics, validation docs, and duplicate CI avoidance. User and data impact Users gained a named release to install or restore, while failure reports contained better callback context. Project and scientific result formats did not change in this release record. Compatibility and migration The v3.0.0 release retained the preceding app data formats. Users could update or roll back the workbench without converting saved scientific results. Validation Commit `21eff4dc` carried the diagnostic and validation changes; tag commit `349a7549` published `v3.0.0`. Exact historical local commands were not recorded. Evidence Main commits `21eff4dc` and release tag commit `349a7549`. Known limitations and follow-up This record marks the release boundary; individual diagnostic mechanics are described in their earlier component history records."},{"title":"UI groups migration","url":"history/records/2026/07/LK-20260703-ui-groups-migration.html","kind":"history","section":"history","keywords":"LK-20260703-ui-groups-migration 2026-07-03 34 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app UI groups migration","text":"UI groups migration id: LK-20260703-ui-groups-migration date: 2026-07-03 sequence: 34 type: refactor compatibility: compatible component: `labkit.ui` | `3.4.5 -> 4.0.0` component: `labkit_DICPostprocess_app` | `1.3.1 -> 1.3.2` component: `labkit_DICPreprocess_app` | `1.3.1 -> 1.3.2` component: `labkit_ChronoOverlay_app` | `1.3.1 -> 1.3.2` component: `labkit_CIC_app` | `1.3.3 -> 1.3.4` component: `labkit_CSC_app` | `1.3.3 -> 1.3.4` component: `labkit_EIS_app` | `1.3.1 -> 1.3.2` component: `labkit_VTResistance_app` | `1.3.3 -> 1.3.4` component: `labkit_BatchImageCrop_app` | `1.6.3 -> 1.6.4` component: `labkit_CurvatureMeasurement_app` | `1.3.1 -> 1.3.2` component: `labkit_FLIRThermal_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.4.2 -> 1.4.3` component: `labkit_ImageEnhance_app` | `1.5.2 -> 1.5.3` component: `labkit_ImageMatch_app` | `1.5.2 -> 1.5.3` component: `labkit_NerveResponseAnalysis_app` | `1.3.1 -> 1.3.2` component: `labkit_ResponseReviewStats_app` | `1.3.1 -> 1.3.2` component: `labkit_RHSPreview_app` | `1.3.1 -> 1.3.2` component: `labkit_ECGPrint_app` | `1.3.2 -> 1.3.3` scope: UI groups migration Context The declarative runtime described actions as a mostly flat list with optional group hints. Apps with several phases could not reliably express which controls belonged together, and layout code had to infer more structure than the app had actually declared. Decision and rationale Make groups the primary unit of control layout. Each group would carry its title and controls, giving the runtime enough structure to render related actions together without learning app-specific labels or workflow rules. Changes `labkit.ui` `3.4.5 -> 4.0.0` All supported apps received patch bumps. Replaced action groups with UI groups. Moved the reusable UI contract into the 4.x line. User and data impact Apps retained their actions, but related controls appeared in explicit visual sections. App definitions became easier to scan because their grouping matched the panels users saw. Compatibility and migration App workflow definitions had to align with the new grouped UI contract. Validation The migration updated UI specification tests, basic-control and workbench GUI tests, app package structure checks, and all supported app definitions. The exact historical command was not recorded. Evidence Main commit `e81243a3`. Known limitations and follow-up This was a breaking definition-format change and established the UI 4.x line. UI 5 later renamed facade packages by responsibility but retained explicit groups as the layout model."},{"title":"UI utility snapshots and popout tools","url":"history/records/2026/07/LK-20260704-ui-utility-snapshots-and-popout-tools.html","kind":"history","section":"history","keywords":"LK-20260704-ui-utility-snapshots-and-popout-tools 2026-07-04 37 feat compatible labkit.ui UI utility snapshots and popout tools","text":"UI utility snapshots and popout tools id: LK-20260704-ui-utility-snapshots-and-popout-tools date: 2026-07-04 sequence: 37 type: feat compatibility: compatible component: `labkit.ui` | `4.1.0 -> 4.2.0` scope: UI utility snapshots and popout tools Context Users could tune controls and plots during a session, but restoring that work required repeating the settings manually. Popout axes were useful for visual inspection, yet copying data or recreating the plot in a script still required ad hoc commands. Decision and rationale Give the runtime a serializable snapshot contract for app state and expose save and load as workbench utilities. Extend popout axes with explicit styling, image copy, data export, and reconstruction-script actions derived from the graphics objects already present on the axes. Changes `labkit.ui` `4.1.0 -> 4.2.0` Added UI state snapshot save/load APIs. Added workbench utility controls. Improved axes popout export and copy tools. User and data impact Users could save a supported app state and restore it later from the workbench. Popout plots could be restyled, copied, exported as visible graphics data, or used to generate a MATLAB reconstruction script without changing the original app result. Compatibility and migration Apps that did not declare serializable state continued to run without snapshot support. Existing app data and figure files were not converted automatically. Validation The commit expanded app-runtime and axes-workbench GUI suites and added package- surface checks for the snapshot APIs. The exact historical command was not recorded. Evidence Main commit `0155cd12`. Known limitations and follow-up Snapshots covered state declared serializable by the app; they were not raw MATLAB workspace dumps. Figure Studio later provided a dedicated workflow for more extensive figure cleanup and export."},{"title":"VT Resistance consolidates its project contract","url":"history/records/2026/07/LK-20260716-vt-resistance-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-vt-resistance-project-spec 2026-07-16 90 refactor compatible labkit_VTResistance_app Electrochemistry Project lifecycle","text":"VT Resistance consolidates its project contract id: LK-20260716-vt-resistance-project-spec date: 2026-07-16 sequence: 90 type: refactor compatibility: compatible component: `labkit_VTResistance_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context VT Resistance split static product metadata across three files and its first-version project across a generic lifecycle package. Its session factory also encodes a real performance policy by loading only the first preview source. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because lazy DTA decoding and analysis is transient reconstruction, not lifecycle boilerplate. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved lazy preview restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Added a direct project/session contract assertion to the VT export suite. Kept pulse detection, steady windows, voltage definitions, resistance formulas, plots, and export schemas unchanged. User and data impact Launch, batch loading, selection, calculation, save/load, plotting, and CSV exports behave unchanged. The durable contract and lazy reconstruction policy are now explicit and adjacent. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing VT Resistance projects require no migration. Validation Unit tests cover project/session boundaries, pulse detection, window policies, raw and baseline-corrected resistance, failed rows, and CSV output. The hidden GUI workflow covers real launch, selection, calculation, save/load, plots, and export. Evidence [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The lazy session still reaches into portable-reference internals. The shared source-path boundary will replace this across all Apps in one framework change."},{"title":"VT Resistance removes source and workflow storage boilerplate","url":"history/records/2026/07/LK-20260716-vt-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-vt-runtime-source-reconciliation 2026-07-16 114 refactor compatible labkit_VTResistance_app Electrochemistry App structure Runtime adoption","text":"VT Resistance removes source and workflow storage boilerplate id: LK-20260716-vt-runtime-source-reconciliation date: 2026-07-16 sequence: 114 type: refactor compatibility: compatible component: `labkit_VTResistance_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context VT Resistance deliberately keeps batch source registration lazy, decoding only the selected preview until export. The App nevertheless generated and appended portable source records itself and wrote analysis messages directly into `session.workflow.logLines`. Its session factory consequently repeated empty workflow and view buckets owned by Runtime V2. Decision and rationale Keep lazy ordered path registration, selection, resistance analysis, plots, and exports in VT Resistance. Delegate the conversion from that ordered path set to stable portable records to `services.project.reconcileSources`. Runtime preserves retained identities and allocates new unique identities. Route analysis messages through `services.workflow.log` and return only App-specific selection and cache state from `createSession`. Changes Removed local source-ID generation, record append, and registration lookup. Reconciled ordered source paths after add, removal, and clear operations. Routed resistance-analysis messages through the Runtime workflow service. Removed empty workflow and view session boilerplate. Added GUI checks for Runtime-supplied buckets and stable, unique source IDs across batch add, save, reopen, removal, and re-addition. Advanced the VT Resistance App version to 1.4.4. User and data impact Lazy loading, current selection, resistance values, plots, logs, export columns, and project files retain their behavior. Retained file identities are stable and newly added files cannot collide with them. Scientific calculations and exported numeric values are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing portable source records retain their IDs when reopened. Validation The VT Resistance hidden GUI workflow covers three-source lazy registration, selection, whole-batch recomputation, export, save, clear, reopen, remove, re-add, and source identity. Focused resistance/export and repository contract tests cover the surrounding calculation and product behavior. Evidence [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up The broader App inventory must still be scanned for local source-record construction that represents migrations or semantic fixed slots rather than replaceable Runtime bookkeeping."},{"title":"Validated runtime identity contracts","url":"history/records/2026/07/LK-20260716-runtime-identity-contracts.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-identity-contracts 2026-07-16 65 fix compatible labkit.ui runtime definitions layouts and presentations projects and result manifests","text":"Validated runtime identity contracts id: LK-20260716-runtime-identity-contracts date: 2026-07-16 sequence: 65 type: fix compatibility: compatible component: `labkit.ui` | `6.0.0 -> 6.0.1` scope: runtime definitions scope: layouts and presentations scope: projects and result manifests Context Runtime ids connect app definitions, layout controls, actions, axes, interactions, durable source records, result outputs, and managed resources. Several registries previously accepted duplicate ids or dangling references until a callback ran. Recovery directory names also used MATLAB identifier normalization, which is not an injective mapping and could make two distinct app ids share storage. Decision and rationale Keep semantic ids developer-owned, but validate each namespace at the earliest transaction boundary. App ids now have one documented portable syntax and a collision-free filesystem encoding. References from layouts, presentations, and interactions must resolve before the runtime changes visible handles or managed resources. Same-scope resource replacement remains intentional: a resource id is an idempotent ownership slot, while resources that must coexist use distinct ids. Changes Rejected invalid app ids, duplicate preview-axis ids, duplicate durable source ids, and duplicate result-output ids. Rejected layout, interaction, and presentation references to unknown actions, controls, targets, axes, or renderers before committing UI changes. Encoded app recovery storage with the exact app id while retaining read-only discovery of recovery files written under the earlier normalized folder. Added a repository contract test requiring literal public app ids to be globally unique. User and data impact Valid current apps and saved projects behave as before. Configuration mistakes now fail with a specific runtime error instead of overwriting another object or appearing later as a broken callback. Existing recovery files remain discoverable and new recovery files cannot collide through name normalization. Compatibility and migration This patch is compatible for documented definitions. A private app with an invalid app id, duplicate semantic ids, or a dangling reference must correct that declaration. Its app id should otherwise remain unchanged because the id is part of saved-project and recovery compatibility. Validation Focused unit, contract, and hidden-GUI runtime tests cover invalid definitions, duplicate namespaces, dangling references, result manifests, and recovery storage behavior. Evidence `labkit.ui.runtime.define` documents the public identity and resource rules. Runtime validation rejects invalid identity graphs before commit. The app identity contract scans every public app definition. Known limitations and follow-up Identity validation protects one runtime and the public app catalog. External private-app catalogs remain responsible for running the same contract test in their own workspace."},{"title":"Version metadata baseline","url":"history/records/2026/06/LK-20260623-version-metadata-baseline.html","kind":"history","section":"history","keywords":"LK-20260623-version-metadata-baseline 2026-06-23 10 feat compatible labkit_launcher labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Version metadata baseline","text":"Version metadata baseline id: LK-20260623-version-metadata-baseline date: 2026-06-23 sequence: 10 type: feat compatibility: compatible component: `labkit_launcher` | `new -> 1.0.0` component: `labkit.ui` | `2.1.0 -> 2.2.0` component: `labkit_DICPostprocess_app` | `new -> 1.0.0` component: `labkit_DICPreprocess_app` | `new -> 1.0.0` component: `labkit_ChronoOverlay_app` | `new -> 1.0.0` component: `labkit_CIC_app` | `new -> 1.0.0` component: `labkit_CSC_app` | `new -> 1.0.0` component: `labkit_EIS_app` | `new -> 1.0.0` component: `labkit_VTResistance_app` | `new -> 1.0.0` component: `labkit_BatchImageCrop_app` | `new -> 1.0.0` component: `labkit_CurvatureMeasurement_app` | `new -> 1.0.0` component: `labkit_FocusStack_app` | `new -> 1.0.0` component: `labkit_ImageEnhance_app` | `new -> 1.0.0` component: `labkit_ImageMatch_app` | `new -> 1.0.0` component: `labkit_NerveResponseAnalysis_app` | `new -> 1.0.0` component: `labkit_ResponseReviewStats_app` | `new -> 1.0.0` component: `labkit_RHSPreview_app` | `new -> 1.0.0` component: `labkit_ECGPrint_app` | `new -> 1.0.0` scope: Version metadata baseline Context The repository had release tags and package contracts, but the launcher and individual apps did not display their own component versions. Decision and rationale Add lightweight `version` requests to every app and the launcher, show those versions in titles and the launcher catalog, and validate their format so a debug report can identify the exact component being run. Changes Release tag `v2.4.0` `labkit_launcher` `1.0.0` All supported apps `1.0.0` `labkit.ui` `2.1.0 -> 2.2.0` Added app and launcher version metadata. Added versioned titles, lightweight version requests, launcher catalog version display, and version guardrails. User and data impact Users and maintainers could read the launcher and app version without inspecting Git history. The change added metadata only and did not modify saved data or numerical results. Compatibility and migration Version requests and titles were additive. Existing app commands, inputs, and saved results continued to work without conversion. Validation Commit `d70c2607` added version-request, title, catalog, and format checks. Release tag `v2.4.0` identifies the shipped baseline. Evidence Main commit `d70c2607`. Known limitations and follow-up These component versions describe displayed app/library builds; compatibility between reusable packages is handled separately by `labkit.contract`."},{"title":"Video Marker app","url":"history/records/2026/07/LK-20260713-video-marker-app.html","kind":"history","section":"history","keywords":"LK-20260713-video-marker-app 2026-07-13 54 feat compatible labkit_VideoMarker_app Video Marker app","text":"Video Marker app id: LK-20260713-video-marker-app date: 2026-07-13 sequence: 54 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `new -> 1.0.0` scope: Video Marker app Context Image measurement workflows needed a generic video annotation tool for ordered 2D keypoints and skeleton connections. The app belongs with image measurement tools because it uses the same axes interaction, scale calibration, and export style, but the source media and frame navigation are video-specific. Decision and rationale Add a standalone Video Marker app instead of extending an existing image-only app or adding a public video facade. The first version keeps marker semantics app-local, uses existing UI interaction tools for ordered points and scale calibration, and avoids training, inference, multi-subject tracking, or per-point missing-state semantics. Changes Added `labkit_VideoMarker_app` under Image Measurement with video loading, indexed frame navigation, ordered keypoint placement, skeleton overlays, and previous-confirmed-frame inheritance as draft annotations. Added a self-describing marker CSV that stores pixel coordinates for every frame and can be imported back into the app for overlay or editing. Added a separate coordinate CSV export for continuous confirmed frame ranges, with selectable pixel or calibrated units, top-left or first-point origin, and up/down Y-axis convention. Added project MAT save/load and debug sample-pack coverage for synthetic video marker assets. User and data impact Users can manually annotate video frames without depending on a model-training workflow. Marker CSV remains the editable round-trip format; coordinate CSV is plain plotting data and does not mutate the saved marker annotations. Compatibility and migration The app is additive and does not change existing image measurement apps or public LabKit facades. No existing project files or exports require migration. Validation `VideoMarkerTest` covers skeleton parsing, frame inheritance, marker CSV round trip, coordinate transforms, and project save/load. `GuiLayoutVideoMarkerTest` covers the hidden GUI launch and semantic control contract. Both accompanied the app in commit `b41f5254`. Evidence Initial Video Marker app `b41f5254`. Known limitations and follow-up The first version is manual and single-subject. It intentionally does not perform automatic interpolation, optical flow, model inference, multi-camera synchronization, 3D annotation, or per-point missing/out-of-frame labeling."},{"title":"Video Marker concentrates project history behind one contract","url":"history/records/2026/07/LK-20260716-video-marker-structure.html","kind":"history","section":"history","keywords":"LK-20260716-video-marker-structure 2026-07-16 103 refactor compatible labkit_VideoMarker_app labkit_GaitAnalysis_app Image Measurement App structure","text":"Video Marker concentrates project history behind one contract id: LK-20260716-video-marker-structure date: 2026-07-16 sequence: 103 type: refactor compatibility: compatible component: `labkit_VideoMarker_app` | `1.5.1 -> 1.5.2` component: `labkit_GaitAnalysis_app` | `2.0.2 -> 2.0.3` scope: Image Measurement scope: App structure Context Video Marker had already separated skeleton, annotations, tracking, video IO, export, and UI capabilities, but its Runtime contract remained spread across seven generic lifecycle files plus separate requirement and version files. A path lookup wrapper also inspected the Runtime's nested portable-reference fields, and the legacy importer constructed that schema itself. Decision and rationale Keep the App's real persistence requirements—schema migration, old MAT import, and current-frame resume—but expose them together through one `projectSpec.m`. Keep decoded-video reconstruction in root `createSession.m`. Use the Runtime's source factory and accessor instead of App-owned reference adapters. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderers, and debug capability in `definition.m`. Concentrated create, validate, migrate, legacy import, resume creation, and resume application behind one project-spec entry. Replaced the ordered migration-cell declaration with the Runtime's single migration callback. Replaced App-side source-reference construction and nested-field lookup with `sourceRecord` opaque import and `sourcePaths` semantic lookup. Removed the generic lifecycle package, source-path wrapper, and separate requirement/version files. Updated Gait's synthetic Video Marker fixture to construct the current payload through the project contract rather than a retired lifecycle file. User and data impact Skeleton setup, continuous point marking, prediction, calibration, coordinate export, explicit autosave, save-before-new confirmation, recovery, source relinking, and old MAT upgrade retain their behavior. The saved current-frame resume remains navigation convenience; durable coordinates and provenance are unchanged. Developers now find all project-history policy in one file and do not need to learn the Runtime's portable-reference representation. Compatibility and migration Current schema version 2 and the recognized old `videoMarkerProject` variable are unchanged. Version-1 payloads still derive durable video metadata, and old portable references retain their relative path. New saves continue to use the current `labkitProject` envelope. Validation Focused unit tests cover skeleton editing, annotation provenance, deterministic motion tracking, Toolbox parity, CSV and coordinate semantics, metadata, migration, legacy import, validation, and resume. Five hidden GUI workflows cover launch, continuous marking, viewport preservation, explicit autosave, new-project choices, named save/load, recovery, and old-format upgrade. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) Known limitations and follow-up Automated GUI checks do not establish tracking validity for every recording or replace manual judgment of point placement and prediction quality. Remaining Apps with generic lifecycle packages will be reviewed independently."},{"title":"Video Marker predictive frame navigation","url":"history/records/2026/07/LK-20260714-video-marker-predictive-navigation.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-predictive-navigation 2026-07-14 58 feat compatible labkit_VideoMarker_app labkit.ui `docs/development/build-apps/architecture.md` `docs/api/ui.md` runtime dependency policy","text":"Video Marker predictive frame navigation id: LK-20260714-video-marker-predictive-navigation date: 2026-07-14 sequence: 58 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `1.1.0 -> 1.2.0` component: `labkit.ui` | `5.1.1 -> 5.2.0` scope: `docs/development/build-apps/architecture.md` scope: `docs/api/ui.md` scope: runtime dependency policy Context The first tracking implementation exposed separate interpolation and previous-frame buttons. Its integer grayscale block search could only model a small translation, and coordinate interpolation did not follow the actual trajectory of articulated points. Users still had to request every estimate manually. Decision and rationale Make prediction an automatic part of forward navigation. Use a mature pyramidal Kanade-Lucas-Tomasi tracker with forward/backward rejection for short-range point propagation, and treat every complete human edit as a new anchor. This matches the app's correction-oriented workflow without claiming unattended long-term or occlusion tracking. Keep the implementation MATLAB-local. Project policy now forbids incidental third-party runtimes, Python environments, model downloads, and first-run network installation; additional dependencies require an explicit architecture decision. Changes Removed the user-facing Track and Interpolate actions. Added automatic forward prediction for adjacent frames and forward jumps. Replaced integer block matching with cropped four-level pyramidal KLT, forward/backward reliability checks, subpixel coordinates, and a constant-velocity fallback for rejected points. Added manual/predicted frame provenance and per-point confidence to the recoverable project payload while preserving marker CSV coordinates. Added reusable portable external-file references to `labkit.ui.runtime` and used them for both Video Marker projects and video-adjacent autosaves. Added a reusable field-labeled manual relinking fallback for malformed or unresolved references; app imports retain file-format validation ownership. Project and autosave payloads now prefer a path relative to the MAT file, fall back to the original path or same-folder filename, and offer video relinking when no automatic candidate exists. Made manual edits immutable anchors that reset subsequent prediction; only predicted drafts may be refreshed by the tracker. Added the repository runtime-dependency boundary to the root constitution, app rules, and human architecture documentation. User and data impact After users mark a complete frame, moving forward presents editable predicted points automatically. Correcting those points establishes a fresh anchor. Older projects are upgraded in memory with provenance defaults; marker and coordinate CSV formats are unchanged. Project/video directory trees can move between users, cloud-drive roots, and operating systems without editing paths. Compatibility and migration Existing Video Marker projects and autosaves remain readable. New project payloads contain additive provenance and confidence fields. Systems without the MATLAB KLT implementation retain coordinates through the motion fallback and remain manually editable; no third-party installation is attempted. Validation Synthetic tests cover subpixel translated-texture tracking, provenance upgrade, and marker CSV round trips. Hidden GUI tests cover automatic prediction on forward navigation and the absence of manual assist buttons. Local untracked evaluation used adjacent frames from a representative video across marker, eye, ear, nose, and toe textures; repository fixtures contain no real sample paths or pixels. Evidence Predictive marking and portable file references `6bfd74c8`. Known limitations and follow-up KLT is a short-range tracker. Low-texture, occluded, or strongly deforming points can be rejected and require human correction; that correction is the next anchor rather than an error state."},{"title":"Video Marker startup validation","url":"history/records/2026/07/LK-20260714-video-marker-startup-fix.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-startup-fix 2026-07-14 56 fix compatible labkit_VideoMarker_app `tests/shared/guiTestHelpers.m`","text":"Video Marker startup validation id: LK-20260714-video-marker-startup-fix date: 2026-07-14 sequence: 56 type: fix compatibility: compatible component: `labkit_VideoMarker_app` | `1.0.0 -> 1.0.1` scope: `tests/shared/guiTestHelpers.m` Context Video Marker declared its preview axes as `video` but reset an empty preview through the nonexistent `raw` axes id. The runtime correctly converted that deferred startup exception into a visible failure status, but the structural GUI test continued because it checked controls before asserting that startup had completed successfully. Decision and rationale Use the declared `video` axes id in the app and make the shared standard workbench assertion wait for the startup lifecycle. A startup failure now fails the GUI test with the runtime diagnostic instead of leaving the test green after only verifying that the shell was constructed. Changes Corrected the empty-preview reset to target `videoAxes/video`. Added a shared startup-success assertion to standard workbench GUI checks. Added a synthetic regression proving deferred startup failures are rejected. User and data impact Video Marker opens normally before a video is selected. This change does not alter marker projects, coordinate exports, annotations, or measurement data. Compatibility and migration The fix is compatible with existing Video Marker projects and exported files. No user migration is required. Validation The Video Marker hidden GUI test covers the corrected initial preview path. The reusable declarative UI GUI test covers startup-failure detection in the shared helper. Both regressions were added in the focused startup repair commits below. Evidence Video Marker startup repair `48319203`. Focused GUI startup-lifecycle regression split `c3c6ce06`. Known limitations and follow-up Automated startup checks validate lifecycle completion and diagnostics; they do not replace manual review of video rendering or point-drag interaction."},{"title":"Video Marker visual skeleton setup and continuous marking","url":"history/records/2026/07/LK-20260714-video-marker-continuous-marking.html","kind":"history","section":"history","keywords":"LK-20260714-video-marker-continuous-marking 2026-07-14 57 feat compatible labkit.ui labkit_VideoMarker_app Video Marker visual skeleton setup and continuous marking","text":"Video Marker visual skeleton setup and continuous marking id: LK-20260714-video-marker-continuous-marking date: 2026-07-14 sequence: 57 type: feat compatibility: compatible component: `labkit.ui` | `5.1.0 -> 5.1.1` component: `labkit_VideoMarker_app` | `1.0.1 -> 1.1.0` scope: Video Marker visual skeleton setup and continuous marking Context Video Marker required comma-separated keypoint names, manually encoded edge text, a start/finish edit toggle, and a separate confirm action. Those controls made skeleton setup error-prone and added modes that did not match the direct click-and-drag interaction already supported by the point editor. Decision and rationale Make skeleton definition the first explicit step, represent keypoints and connections as structured controls, and keep point editing active for the whole video session. Frame navigation is the save boundary; complete frames confirm automatically and inherited frames remain drafts until edited. Changes Replaced long skeleton strings with an editable ordered-keypoint table, mutually filtered connection endpoint selectors, and add/remove/reorder actions. Added editable skeleton presets led by the legacy iliac-to-foot five-point leg chain, while keeping blank custom construction available. Added a connect-in-order shortcut that fills all adjacent point connections without removing other user-defined edges. Added the domain-neutral `labkit.ui.control.setItems` API for dynamically updating selectable control choices without firing app callbacks. Added `labkit.ui.plot.replaceOverlay` so image apps can replace one named overlay layer without clearing peer graphics, view limits, or callbacks. Added hidden-test-safe `labkit.ui.runtime.confirm` for app-owned two-choice recovery and confirmation workflows. Removed start/finish and confirm controls; blank clicks add points in order, dragging refines them, and frame changes save and inherit coordinates. Made point edits refresh only the skeleton overlay so zoom, wheel handling, and the active continuous-marking session survive each placed point. Preserved the current X/Y viewport across frame navigation so users can mark the same zoomed ROI through consecutive frames. Added draft-only marking assists for temporal interpolation between nearest confirmed frames and lightweight local block matching from the immediately previous confirmed frame. Added atomic change-driven project-compatible autosave in a visible subfolder beside the source video, plus matching-video restore/start-new choices. Added a new-setup action that explicitly clears the current session before a different skeleton is declared. User and data impact Skeleton setup is visible and validated before video loading. Existing marker CSV, coordinate CSV, and project MAT schemas remain unchanged. Point order is still the coordinate-column identity contract. Compatibility and migration Existing marker CSV and project files remain readable. The workflow changes are UI-only; users no longer press Apply skeleton, Start point edit, or Confirm frame. Video Marker now requires `labkit.ui >=5.1.1 <6`. Validation Unit tests cover skeleton presets, edits, and edge remapping. Hidden GUI workflow tests cover preset application, table-based setup, mutually filtered connection choices, automatic editor activation, consecutive point placement with preserved zoom/scroll callbacks, complete-frame confirmation, frame inheritance, and draft-state preservation. UI framework tests cover dynamic selectable items. Evidence Continuous-marking and skeleton workflow redesign `bcba1833`. Known limitations and follow-up Automated hidden-GUI tests verify callbacks and state transitions but cannot judge visual density or pointer ergonomics; those still need a short manual review with a representative video."},{"title":"v1.0: app-owned packages and a standard test platform","url":"history/records/2026/06/LK-20260606-v1-foundation.html","kind":"history","section":"history","keywords":"LK-20260606-v1-foundation 2026-06-06 5 refactor compatible historical project evolution","text":"v1.0: app-owned packages and a standard test platform id: LK-20260606-v1-foundation date: 2026-06-06 sequence: 5 type: refactor compatibility: compatible scope: historical project evolution Context The pre-release work had produced many useful apps and library functions, but the repository still carried large entry points, custom test routing, UI compatibility aliases, and helper ownership that was difficult to explain. Releasing that state as a stable foundation required more than assigning a version number. Decision and rationale Make each app package responsible for its runner, callbacks, presenters, and private workflow helpers. Use MATLAB's official test platform as the common runner, retain focused GUI gesture tests where automation was meaningful, and remove compatibility abstractions that no longer represented supported code. Changes Added the official MATLAB test-platform skeleton and migrated project guardrails and app suites to it. Decomposed image, DIC, electrochem, and ECG entry points into app-owned packages and private workflow helpers. Added GUI gesture coverage and CI test artifacts. Centralized the build-task catalog and separated scheduled coverage from faster routine checks. Added Batch Image Crop with a base-MATLAB rotation path. Completed DIC package migration and removed toolbox-only unit dependencies from its tested paths. Removed obsolete UI compatibility aliases and specialized dual-plot shell APIs in favor of structured form specifications and app-owned plot controls. User and data impact The v1.0 foundation provided a coherent set of named apps backed by repeatable tests. Batch Image Crop joined the image family, while existing electrochem, DIC, curvature, focus, and ECG workflows retained their app entry points. The release concentrated on ownership and validation. It did not introduce a single shared project-file format or combine the apps into one monolithic GUI. Compatibility and migration Repository code using removed UI aliases or dispatch routers needed to call the supported app and UI contracts. User-facing app entry points remained the intended launch mechanism. Validation MATLAB unit suites, architecture guardrails, GUI gesture tests, build-task checks, CI JUnit summaries, and scheduled coverage were present by the end of the stage. Tag `v1.0` points to `0bb83a6e`. Evidence Test-platform rewrite `7ad36886` through `83c8fcc1`. Build entry points and task catalog `12aa66af` through `7cc209ab`. Batch Image Crop `0731f6f0`, `5a76eb31`. App-owned package migrations `c4ae5074` through `76146c3d`. UI API cleanup `65a8c65d` through `0f428eb8`. v1.0 tag target `0bb83a6e`. Known limitations and follow-up v1.0 still required users to know individual app commands and used the first generation of the UI runtime. The next major release added a launcher and migrated apps to UI 2.0."},{"title":"v2.0: launcher, image apps, and UI 2.0","url":"history/records/2026/06/LK-20260615-v2-launcher-and-ui2.html","kind":"history","section":"history","keywords":"LK-20260615-v2-launcher-and-ui2 2026-06-15 6 feat compatible historical project evolution","text":"v2.0: launcher, image apps, and UI 2.0 id: LK-20260615-v2-launcher-and-ui2 date: 2026-06-15 sequence: 6 type: feat compatibility: compatible scope: historical project evolution Context The first release had stable app commands, but discovery was poor: users had to know which command to run. Apps also used a mixture of first-generation UI patterns, which made startup, resizing, debug launch, and preview behavior less consistent than the underlying package boundaries. Decision and rationale Add a launcher as the visible entrance to the workbench and migrate the app families to a second-generation UI contract. Preserve separate app entry points and app-owned workflows; the launcher would discover and start them rather than turn them into screens of one large application. Changes Added the LabKit launcher and localized MATLAB project metadata. Added Image Enhance and Image Match workflows and semantic tab resizing. Supplied a base-MATLAB path for image matching instead of requiring an Image Processing Toolbox implementation. Migrated LabKit apps to UI 2.0 and added a starter template for new apps. Stabilized responsive layout behavior and default text-panel sizing. Completed cold-start migration, simplified debug launch, and persisted debug artifacts for later inspection. Refined DIC Postprocess overlay export at the release boundary. User and data impact Users could browse and start supported apps from one launcher while retaining the option to call an app directly. Image users gained enhancement and matching workflows. Debug sessions left evidence that could be inspected after a startup or callback problem. Existing scientific file formats and app-specific saved data remained owned by their apps. Compatibility and migration App implementations moved to UI 2.0, but the release preserved the model of separate named app entry points. Code that depended on first-generation UI implementation details needed to adopt the new runtime contracts. Validation The release used the reorganized MATLAB suite, GUI layout regressions, cold- start checks, and focused DIC overlay tests. Tag `v2.0` points to `4bc7343f`. Evidence Launcher `7c825ef8`. Image apps and base-MATLAB matching `5cdc31e2`, `c838635c`. UI 2.0 migration `c2658605`. Starter app and layout stabilization `6c8b57ab`, `78ca39e0`. Startup and debug work `90c8869f` through `2153bd08`. v2.0 tag target `4bc7343f`. Known limitations and follow-up The launcher did not yet update itself, and large file panels and image tools still exposed inconsistent busy, zoom, and path-selection behavior. Those cross-app concerns became the focus of the 2.1 through 2.3 releases."},{"title":"v2.2 to v2.3.1: self-contained launch and image workflow refinement","url":"history/records/2026/06/LK-20260621-v2-2-v2-3-image-workflows.html","kind":"history","section":"history","keywords":"LK-20260621-v2-2-v2-3-image-workflows 2026-06-21 8 feat compatible historical project evolution","text":"v2.2 to v2.3.1: self-contained launch and image workflow refinement id: LK-20260621-v2-2-v2-3-image-workflows date: 2026-06-21 sequence: 8 type: feat compatibility: compatible scope: historical project evolution Context The launcher and updater existed, but installation still assumed repository context. At the same time, ordinary image work exposed small but costly interaction problems: selecting several files, preserving zoom while placing a marker, padding a crop without shifting its source coordinates, or returning to duplicate-crop mode after editing scale. Decision and rationale Make the launcher usable as a self-contained entry artifact and refine the high-frequency image workflows in place. These releases favored fewer steps and stable spatial state over adding more general-purpose dialogs or toolbars. Changes Made the launcher self-contained and added an update button backed by the release updater. Appended file-panel selections and added multi-file choose-and-load behavior. Improved Image Match speed and separated its reference input from moving images. Preserved Batch Crop source coordinates when padding, reduced padding seams, and refined the padding workflow. Added physical scale mode to Batch Crop and restored scale and duplicate-task interactions after edits. Preserved image-tool zoom while placing or editing overlays. Hardened image-tool ownership and changed the README to present the launcher as the primary user entry. User and data impact Users could begin from a compact launcher, select several files in one action, and edit image overlays without losing their current view. Batch Crop gained physical scaling while retaining the relationship between the visible crop and the source image. Padding and scale metadata affected exported crop interpretation, so regression work explicitly protected source coordinates and resumed interaction modes. Compatibility and migration The changes were compatible additions and fixes to the v2 workflows. Release tags identify the incremental baselines: `v2.2.0` at `c904baca`, `v2.3.0` at `f2ed23c2`, and `v2.3.1` at `83d03e7a`. Validation Focused tests covered path-list cleanup, Batch Crop scale and padding contracts, preview interactions, and image-tool ownership. The exact command for each historical commit was not recorded. Evidence Self-contained launcher and updater button `a1938db8`, `c904baca`. File selection and Image Match work `5aa10909`, `471b79c2`, `ebdbf9db`. Batch Crop padding `54c8fe43` through `f2ed23c2`. Zoom and physical scale `632191b9` through `2b9690fc`. Ownership hardening and primary launcher guidance `8328248a`, `83d03e7a`. Known limitations and follow-up Some previews still repeated expensive processing, EIS log-axis navigation was fragile, and the release process did not yet prove that downloadable artifacts matched their tags. v2.3.2 and v2.3.3 addressed those issues."},{"title":"v2.3.2 and v2.3.3: preview performance and release contracts","url":"history/records/2026/06/LK-20260623-v2-3-performance-and-release-contracts.html","kind":"history","section":"history","keywords":"LK-20260623-v2-3-performance-and-release-contracts 2026-06-23 9 fix compatible historical project evolution","text":"v2.3.2 and v2.3.3: preview performance and release contracts id: LK-20260623-v2-3-performance-and-release-contracts date: 2026-06-23 sequence: 9 type: fix compatibility: compatible scope: historical project evolution Context By v2.3.1, app discovery and image interaction were substantially better, but users could still experience redundant preview processing and unstable axes limits. Separately, a working release download was not sufficient evidence that the downloaded launcher came from the intended tag. Decision and rationale Separate preview computation from rendering so an interaction does not repeat scientific or image processing unnecessarily. Stabilize axes behavior at the app boundary, then make release assets reproducible and add facade contract checks that describe which library calls apps may rely on. Changes Stabilized EIS zoom on logarithmic axes and reset plot limits when coordinate choices changed. Reduced repeated image preview processing and separated preview preparation from display updates. Documented the boundary between preview state and exported results. Added pixel-radius controls for scale previews. Required release assets to be reproduced from their Git tag. Improved GitHub onboarding and issue templates. Added facade contract checks and hid windows created by automated GUI tests. User and data impact Image apps responded more quickly when controls changed without requiring new processing. EIS navigation became more predictable, and scale previews exposed their pixel radius directly. Release users gained a stronger link between the published tag and the launcher they downloaded. The preview/export separation clarified that a faster display refresh must not silently change exported numerical or spatial results. Compatibility and migration The changes corrected existing v2 behavior without introducing a new user data format. Tags `v2.3.2` and `v2.3.3` point to `29669ca6` and `a7e7dfb1`, respectively. Validation Focused EIS and image-preview tests accompanied the fixes. Release guidance added tag-derived asset checks, and facade tests checked supported app-to- library calls. Automated GUI windows were hidden to reduce test interference. Evidence EIS axes fixes `6c8e6540`, `579bc56a`. Preview processing separation `cc65e844`, `2369ffd3`. Preview/export contract and scale controls `d0d46f6e`, `29669ca6`. Reproducible release and onboarding `1832f46a` through `0c5243a9`. Facade and GUI-test contracts `a25b79f9`, `20ea41b8`. Known limitations and follow-up Component-specific version metadata was introduced only after these releases, so the early record relies on repository tags and commit evidence. Later history pages can name both the repository release and the affected component version."},{"title":"VT Resistance","url":"apps/electrochemistry/vt-resistance.html","kind":"app","section":"apps","keywords":"apps electrochemistry vt resistance VT Resistance","text":"VT Resistance VT Resistance estimates cathodic and anodic steady resistance from a biphasic voltage transient and reports the mean of their absolute values. Requirements And Launch The app requires a chrono DTA curve with valid time, voltage, and current columns. labkit_VTResistance_app Inputs And Batch Behavior Add one or more chrono `.DTA` files. The transient session decodes and analyzes the registered batch so shared setting changes update every result together. No electrode-area normalization is performed because the reported quantity is electrical resistance in ohms. Saved projects preserve source order and portable identity through removal, later additions, and reopen. Basic Workflow Add files and select a representative transient. Choose pulse detection, steady-window policy, and voltage definition. Inspect detected phase windows and current/voltage traces. Review cathodic, anodic, and average resistance. Export the batch result CSV. Analysis Parameters Parameter Default Alternatives Pulse detection Metadata first, then auto Metadata only; Auto from Im only Steady window Full pulse median Center 60% median Resistance voltage Baseline-corrected dV/I Raw Vf/I The center-60% policy uses the interval from 20% to 80% of each detected phase to reduce onset and offset transients. Plot marker and shading choices affect display only. Calculation Semantics After finite-sample filtering and pulse detection, the app calculates median current and voltage in the chosen cathodic and anodic steady windows. The cathodic baseline is the median pre-pulse voltage and the anodic baseline is the median post-pulse voltage, with explicit finite fallbacks. Baseline-corrected mode uses: R_cath = (V_cath,steady - V_cath,baseline) / I_cath,steady R_anod = (V_anod,steady - V_anod,baseline) / I_anod,steady R_average = mean(abs(R_cath), abs(R_anod)) Raw mode replaces each voltage difference with its steady absolute voltage. Division by a missing, non-finite, or effectively zero current returns `NaN`; the app does not invent a finite resistance. Output Schema The CSV contains source identity, steady currents and voltages, baseline voltages and windows, raw and baseline-corrected resistance fields, selected resistance values, absolute cathodic/anodic resistance, average resistance, pulse-detection method, and status. A result manifest records the common batch parameters and output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message); options = struct( ... \"pulseMode\", \"Metadata first, then auto\", ... \"windowMode\", \"Full pulse median\", ... \"voltageMode\", \"Baseline-corrected dV/I\"); result = vt_resistance.analysisRun.computeResistance(item, options); assert(result.ok, result.message); Errors And Limitations At least five finite `T`, `Vf`, and `Im` samples are required. Each selected steady window must contain at least two samples. Resistance is invalid when steady current is zero or non-finite. The result is a transient-derived estimate and does not replace an impedance model or compensate wiring/contact resistance automatically. Related Topics [Electrochemistry family](../README.md) [CIC](../cic/README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md) vt_resistance.analysisRun.computeResistance Estimate cathodic and anodic resistance from a VT pulse."},{"title":"Gait Apps","url":"apps/gait/index.html","kind":"app family","section":"apps","keywords":"apps gait Gait Apps","text":"Gait Apps Gait apps turn frame-ordered landmark coordinates into auditable kinematic tables. Annotation stays in the image or video marker workflow; this family owns role mapping, coordinate calibration, temporal interpretation, gait metrics, quality checks, and result export. Choose An App Task App Place and revise landmarks on video frames [Video Marker](../image-measurement/video-marker/README.md) Convert completed landmarks into gait metrics [Gait Analysis](gait-analysis/README.md) Data Flow The current Video Marker records one ordered set of named points per frame and saves coordinates, skeleton, timing, and calibration in its project. Gait Analysis validates that project and normalizes the saved payload to a frame-by-point-by-two coordinate array. The user maps point names to iliac, hip, knee, ankle, and foot roles. The app smooths coordinates, calculates joint angles and segment lengths, detects steps, applies quality rules, and builds export tables. Gait Analysis reads current Video Marker project and autosave files directly. It never rewrites the annotation project and does not accept generic MAT or coordinate-table substitutes that lack the required source metadata. Use The Calculation Without The App The app-owned calculation entry point is `gait_analysis.analysisRun.computeGait`. It accepts normalized pose data from `gait_analysis.sourceFiles.readPoseFile` and the same option structure used by the app. See the [Gait Analysis manual](gait-analysis/README.md) for the data shape, defaults, outputs, and example. Related Documentation [Image Measurement apps](../image-measurement/README.md) [App catalog](../README.md) [App Framework](../../framework/README.md) gait_analysis.analysisRun.computeGait Calculate joint, segment, swing, and step-quality measurements. gait_analysis.sourceFiles.readPoseFile Read one current Video Marker MAT project for gait analysis."},{"title":"Gait Analysis","url":"apps/gait/gait-analysis.html","kind":"app","section":"apps","keywords":"apps gait gait analysis Gait Analysis","text":"Gait Analysis Gait Analysis 2 converts a current Video Marker project into independently segmented treadmill swing steps, per-frame kinematics, per-step gait parameters, visual step reports, and reproducible CSV outputs. Loading and analysis are deliberately separate: loading first shows the complete tracked skeleton trajectory; analysis then enables one-step-at-a-time review. Open Gait Analysis From the LabKit launcher, select **Gait Analysis** and choose **Open**. From a source checkout, run: labkit_GaitAnalysis_app Input Contract The only file input is a current Video Marker `labkitProject` MAT document or autosave. The MAT file is the analysis source of truth and the cross-App data contract; Gait Analysis does not call the Video Marker package at runtime. The saved fields are described by [Video Marker project and session state](../../image-measurement/video-marker/README.md#project-and-session-state). Gait Analysis reads: frames-by-points-by-2 pixel coordinates; point IDs, point names, and skeleton edges; frame annotation status and source when present; video frame count, frame rate, duration, width, and height; scale calibration and physical unit when calibrated. The app does not reopen the original video to obtain scientific metadata. Video Marker payloads older than the embedded-video-metadata contract must be opened and saved with the current Video Marker before analysis. Gait Analysis rejects generic coordinate tables and arbitrary MAT variables because they do not jointly own timing, skeleton, calibration, and annotation provenance. `computeGait` still accepts an in-memory normalized pose for deterministic tests and programmatic calculations. The Video Marker document is stored as a portable project source. Older Gait Analysis projects are upgraded on load. Coordinates use image convention: the origin is at the upper left and Y increases downward. The skeleton preview preserves that convention. Angle and length time series use conventional plot axes. Project And Session State The pose source, analysis options, computed tables/events, and export record are saved. Decoded pose data and the currently selected step are reconstructed after load. Projects from the older stride-naming contract invalidate results whose scientific meaning changed, so rerun the analysis before export. Two-Stage Workflow 1. Load And Inspect All Trajectories Choose **Open Video Marker MAT**. Before any analysis runs, the workspace overlays every recorded skeleton and each named point trajectory. This view is intended to expose gross tracking failures, missing landmarks, unexpected skeleton order, and coordinate orientation before derived numbers are produced. The source summary reports frame count, point count, embedded frame rate, source format, and unit. Role fields are populated from matching source point names; `iliac_crest` is accepted for the iliac role. 2. Analyze And Review One Step Choose **Run analysis**. Select a row in the step table or use **Previous step** and **Next step**. The workspace then shows only that step: all skeleton poses from lift-off through landing, with point trajectories; hip, knee, and ankle angle traces; iliac-hip, hip-knee, knee-ankle, and ankle-foot length traces. The skeleton plot annotates swing duration, step length, iliac/hip/knee/ankle/ foot translations, and each joint's minimum, maximum, and range of motion. This is the app version of the earlier per-step gait figure; the figures no longer need to be generated as an intermediate image set merely to inspect each step. Step Segmentation The default treadmill detector operates on the smoothed foot X coordinate. A lift-off candidate is a local maximum whose prominence is at least the configured threshold and whose height is at least the configured mean-minus-sigma standard-deviation floor. Candidates closer than the minimum interval retain the higher peak. Every accepted lift-off is independently paired with the lowest subsequent foot X value before the next lift-off, or before the recording ends. That following minimum is the landing event. This pairing is important: a completed last swing remains a valid step even when the recording ends before another lift-off. Earlier contact-to-contact implementations lost that final step. These are image-kinematic treadmill events, not force-plate contact measurements. When a later lift-off exists, the app additionally derives cycle time, stance time, cadence, and duty factor. Those values are legitimately unavailable for the final independently complete swing if no next lift-off was recorded. Parameters Parameter Default Meaning Iliac, hip, knee, ankle, foot point Matching role name Exact, case-insensitive source point assigned to each anatomical role. Frame rate `0` until supplied by the source Used only when the source has no usable per-frame time vector. Current Video Marker projects populate it automatically. Pixels per unit `1` Pixel density for physical outputs. A valid Video Marker calibration replaces it automatically. Unit name `px` Physical unit label associated with pixels per unit. First-frame first point as origin Off Shifts scaled coordinate exports; raw pixel coordinates remain unchanged. Smooth window 5 frames Centered finite-value mean applied independently to each point and axis. Minimum foot-X prominence 20 source units Minimum lift-off peak prominence inherited from the legacy treadmill workflow. Peak-height sigma 2 Requires a lift-off peak to be at least mean foot X minus this many standard deviations, matching the legacy treadmill detector. Minimum interval 0.2 s Minimum time between lift-off peaks; converted to frames from source timing. Minimum swing 3 frames Minimum accepted inclusive lift-off-to-landing span and timing-free separation fallback. Maximum swing 300 frames Maximum accepted inclusive lift-off-to-landing span. Minimum step length 1 output unit Minimum two-dimensional foot endpoint displacement. Maximum hip translation 1,000,000 output units Upper endpoint-displacement QC rule; the default normally leaves it inactive. Changing any parameter invalidates the previous result. Repeating a run with the same source and parameters is skipped when its deterministic fingerprint is unchanged. Calculations For each frame the app calculates unsigned 0-180 degree hip, knee, and ankle angles from adjacent segment vectors and four Euclidean segment lengths. A nonfinite or zero-length vector produces `NaN`; the app does not invent an angle. For each lift-off-to-landing step it calculates: swing duration; two-dimensional foot endpoint displacement, reported as step length; two-dimensional endpoint translation for iliac, hip, knee, ankle, and foot; minimum, maximum, and range of motion for hip, knee, and ankle; cycle time, stance time, cadence per minute, and duty factor when the next lift-off exists; validity and a concrete rejection reason for duration, step length, or hip translation rules. The former scripts sometimes called foot displacement `stride_length` even though only one active swing was measured. Version 2 uses `step_length` so the column name matches the calculation. Outputs **Export CSV set** writes: `_frames.csv`: frame/time, step membership, event flags, three joint angles, four segment lengths, and scaled point coordinates; `_coordinates.csv`: raw pixel and scaled/origin-shifted coordinates; `_steps.csv`: event boundaries, timing, cadence/duty factor, step length, five point translations, joint extrema/ROM, validity and reason; `_summary.csv`: source geometry, counts, valid-step means, and global joint extrema; `_gait.labkit.json`: provenance manifest for inputs, parameters, outputs, and valid-step count. Use Without The GUI pose = gait_analysis.sourceFiles.readPoseFile(\"marker-project.mat\"); opts = gait_analysis.analysisRun.optionsForPose( ... pose, gait_analysis.analysisRun.defaultOptions()); result = gait_analysis.analysisRun.computeGait(pose, opts); writetable(result.stepTable, \"steps.csv\"); `computeGait` performs no UI or file writes. Its result contains `frameTable`, `coordinateTable`, `stepTable`, `summaryTable`, paired `events`, normalized `options`, `ok`, and `message`. Limitations Kinematic lift-off and landing are not substitutes for force or pressure measurements. The default detector reflects lateral treadmill motion and assumes the foot travels toward increasing X before returning toward decreasing X. Smoothing and prominence affect event boundaries and must be reported with scientific results. Tracking confidence and manual/predicted frame status are preserved from Video Marker but are not yet automatic step-exclusion rules. Group statistics and between-animal inference are outside this single- recording app; exported step tables are the boundary for that workflow. Related Functions And Apps `gait_analysis.sourceFiles.readPoseFile` `gait_analysis.analysisRun.defaultOptions` `gait_analysis.analysisRun.optionsForPose` `gait_analysis.analysisRun.computeGait` [Video Marker](../../image-measurement/video-marker/README.md) [Gait apps](../README.md) gait_analysis.analysisRun.computeGait Calculate joint, segment, swing, and step-quality measurements. gait_analysis.sourceFiles.readPoseFile Read one current Video Marker MAT project for gait analysis."},{"title":"Image Measurement Apps","url":"apps/image-measurement/index.html","kind":"app family","section":"apps","keywords":"apps image measurement Image Measurement Apps","text":"Image Measurement Apps The Image Measurement family turns image and video data into repeatable crops, calibrated measurements, thermal readings, fused images, appearance pipelines, and landmark coordinates. Scientific and workflow-specific choices remain app-owned; generic image IO and primitives come from `labkit.image` and `labkit.thermal`. Choose An App Goal App Main result Apply repeatable crop geometry to many images [Batch Image Crop](batch-crop/README.md) same-size crop images and manifest Measure a fitted circular arc and traced length [Curvature Measurement](curvature/README.md) radius, curvature, length, overlay Decode and measure radiometric FLIR images [FLIR Thermal](flir-thermal/README.md) Celsius matrix, readings, rendered image Fuse multiple focal planes [Focus Stack](focus-stack/README.md) all-in-focus image and depth index map Apply an ordered enhancement pipeline [Image Enhance](image-enhance/README.md) enhanced images and processing history Transfer appearance from a reference image [Image Match](image-match/README.md) matched images and match history Mark ordered landmarks across video [Video Marker](video-marker/README.md) recoverable project and coordinate tables Shared Interaction Conventions Managed points, rectangles, scale references, and overlays remain editable without resetting the current zoom. A new source or project may start at a home view; ordinary marker placement, dragging, ROI resizing, and annotation refresh preserve the user's viewport. Canvas subtitles and action labels describe the active interaction mode. Image coordinates use `[x y]` with `x` as column and `y` as row. Rectangle geometry uses `[x y width height]` unless a linked API states that it accepts two corners. Calibrated measurements always retain the pixel-domain source geometry needed for audit. Numeric And Display Boundaries Display palettes, axes zoom, overlay opacity, marker appearance, and preview downsampling do not change scientific arrays. Crop geometry, scale calibration, thermal conversion, fusion parameters, enhancement steps, and coordinate transforms do affect output and are stored in project state or result manifests. Related Modules [Image Library](../../libraries/image/README.md) [Thermal Library](../../libraries/thermal/README.md) [App Framework interactions](../../framework/README.md) [All Apps](../README.md)"},{"title":"Batch Image Crop","url":"apps/image-measurement/batch-crop.html","kind":"app","section":"apps","keywords":"apps image measurement batch crop Batch Image Crop","text":"Batch Image Crop Batch Image Crop defines one crop task per image, previews rotation and edge-continuous padding, and exports repeatable same-size crops in pixel or physical-scale mode. Requirements And Launch labkit_BatchImageCrop_app Inputs Use **Add images or folder** to load supported image files. A folder selection loads supported files from that folder. Each list row stores its own crop center, rotation, padding, and optional scale calibration. **Duplicate image** creates another task for the same source so multiple ROIs can be exported without loading duplicate files. Basic Workflow Load images and select a task. Set crop width and height or switch to physical mode. Click anywhere in the preview to place the crop center. Drag the center marker or anywhere inside the highlighted ROI to move it, or enter center X/Y. Set rotation and padding. Calibrate physical scale when required. Duplicate tasks for additional ROIs. Choose format and output folder, then export. The preview rectangle remains draggable by either its center marker or its interior; a click without dragging sets its center at that preview location, including inside the rectangle. Change crop width and height with their controls. Editing the ROI updates the selected task immediately; switching tasks restores that task's geometry. ROI refreshes preserve axes zoom. Pixel Mode Default crop dimensions are 1024 by 1024 pixels. Rotation defaults to 0 degrees and padding to 0%. Center controls use source-image coordinates. When crop geometry crosses the rotated image boundary, edge-continuous padding supplies pixels so every exported crop retains the requested dimensions. Physical Mode And Scale Physical mode specifies width and height in the selected unit (default `um`). Each source image requires a valid pixels-per-unit calibration. Measure a known reference line, enter its physical length, and verify the displayed pixels/unit. `targetPixelsPerUnit=0` selects an automatic common output scale; a positive value requests an explicit scale. The max-upsample warning defaults to 15%. It warns when a task would require substantial interpolation; it does not silently change the requested physical geometry. Crop and calibration units are converted explicitly. Scale Bar The scale-bar length defaults to 100 selected units, at Bottom right, in Black. Measure the reference first, then place the bar. The bar is an export overlay; its placement does not change crop geometry or scale calculations. Outputs Exports support PNG, TIFF, and JPEG. Each task produces one image at the planned output dimensions. The crop manifest records source, task identity, center, rotation, padding, source/output scale, requested physical geometry, format, and output filename. A second LabKit result JSON records project-wide parameters and identifies each output file. Project And Recovery Saved projects preserve one independent task per list row, so duplicated tasks can resolve to the same image while retaining separate crop geometry and calibration. Crop dimensions, physical-scale settings, output format, output folder, and scale-bar choices are also saved. Image pixels and preview caches are reconstructed from the sources after load rather than embedded in the project. Older projects are upgraded on open while preserving each task's center, rotation, padding, and calibration. If a source moved, the standard relinking flow asks for its new location. Use Without The GUI calibration = labkit.app.interaction.scaleCalibration(20, 10, \"um\"); items = struct(\"scaleCalibration\", calibration); physicalOptions = struct( ... \"physicalWidth\", 5, \"physicalHeight\", 3, ... \"scaleUnit\", \"um\", \"targetPixelsPerUnit\", 4); plan = batch_crop.cropGeometry.scalePlan(items, physicalOptions); imageData = reshape(uint8(1:100), 10, 10); cropOptions = struct(\"centerXY\", [5 5]); cropped = batch_crop.cropGeometry.cropScaledImage( ... imageData, cropOptions, plan); For pixel-domain work use `batch_crop.cropGeometry.cropImage`. Open the linked API pages for exact plan fields, interpolation, and padding behavior. Function Reference The generated crop-geometry pages document pixel coordinates, rounding, padding, scaling plans, per-image option fields, defaults, output metadata, failure behavior, and related APIs. Start with [`cropScaledImage`](../../../reference/api/batch_crop/cropGeometry/cropScaledImage.html) when reproducing a physical-scale export outside the GUI. Errors And Limitations Physical export requires a finite positive calibration for every task. Large upsampling cannot restore information absent from the source. Rotation and scale resampling can soften high-frequency detail. JPEG is lossy; use PNG or TIFF for quantitative downstream image analysis. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) batch_crop.cropGeometry.cropImage Rotate and crop an image at a fixed pixel size. batch_crop.cropGeometry.cropScaledImage Crop one calibrated image to a common physical output scale. batch_crop.cropGeometry.scalePlan Plan equal-size physical crops across calibrated images."},{"title":"Curvature Measurement","url":"apps/image-measurement/curvature.html","kind":"app","section":"apps","keywords":"apps image measurement curvature Curvature Measurement","text":"Curvature Measurement Curvature Measurement fits a circle to an ordered image curve, reports radius and curvature, measures traced arc length, and supports pixel-to-physical scale calibration. Requirements And Launch labkit_CurvatureMeasurement_app Basic Workflow The **Files + Analysis** tab contains the complete workflow: Choose an image in the **Image** section. Use **Measure reference pixels**, or enter **Reference pixels** directly, then enter the known reference length and unit. Configure and place the optional display scale bar. Use **Start curve edit** and place ordered anchors along the feature. Drag anchors to refine the trace; undo or clear as needed, then finish curve editing. Choose the densification settings, fit the circle, and measure curve length. Export the result CSV and overlay PNG. The edit buttons change to **Finish curve edit** or **Finish reference edit** while their managed interaction is active. Curve and reference edits are mutually exclusive. Anchor edits and result overlays preserve the current axes zoom. The **Summary + Results** tab reports curve length, radius, curvature, RMSE, fit center, and pixels per selected unit. **Details** explains the next valid step before a result exists and reports the current measurement afterward. **Tools > Diagnostics > Open Session Log...** records file, edit, fit, calibration, export, and runtime actions without consuming a workflow tab. The chosen image is saved as a portable project source. Older projects are upgraded on load without changing the curve, calibration, or result meaning. Curve Editing Curve points are ordered by placement. **Undo last point** removes the newest anchor and **Clear curve** removes the complete trace. Neighboring duplicate points are removed before numeric fitting. At least three distinct points are required for a circle fit; length requires at least two. Fit Parameters And Semantics **Densify before circle fit** is enabled by default with 300 dense points. Densification interpolates along the ordered trace before the least-squares circle fit; it changes sampling density, not the manually defined path. **Show dense fit points** is presentation-only. For fitted radius `r`: curvature = 1 / r Pixel-domain radius and curvature are always available. With calibration, radius and traced length are converted to the selected physical unit and curvature uses its reciprocal unit. Curve length is the sum of distances along the ordered polyline/densified curve specified by the API options; it is not the circumference of the fitted circle. Scale Calibration And Bar Measure the pixel distance spanning a known reference, enter its physical length, and choose `m`, `cm`, `mm`, `um`, or `nm`. Calibration stores reference pixels, reference length, unit, and pixels per unit. Scale-bar length defaults to 100 units; position defaults to Bottom right and color to Black. Placing or moving the display bar does not modify the fit. Outputs The CSV records point count, fit settings, center, radius, curvature, traced length, calibration, units, and status. The overlay PNG contains the source image, ordered curve, optional dense samples, fit residuals, fitted circle, center, and configured scale bar when available. Each export writes its standard result manifest with source and parameter provenance. Use Without The GUI points = [100 80; 130 58; 165 52; 200 66; 225 95]; fit = curvature.analysisRun.computeCurvatureFit(points, ... struct(\"densify\", true, \"densePointCount\", 300)); lengthResult = curvature.analysisRun.computeCurveLength(points, struct()); Errors And Limitations Collinear or nearly collinear points do not define a stable finite circle. Calibration uncertainty propagates directly to physical radius and length. A circle fit summarizes a curved segment; it does not prove the feature is truly circular. Inspect residuals and the overlay. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) curvature.analysisRun.computeCurvatureFit Fit a circle and curvature to traced image points. curvature.analysisRun.computeCurveLength Measure the polyline length of traced image points."},{"title":"FLIR Thermal","url":"apps/image-measurement/flir-thermal.html","kind":"app","section":"apps","keywords":"apps image measurement flir thermal FLIR Thermal","text":"FLIR Thermal FLIR Thermal decodes radiometric FLIR JPEG/RJPEG files, displays calibrated temperature maps, measures rectangular ROI hot/cold/mean values, and exports rendered images with Celsius data. Requirements And Launch A `.jpg`, `.jpeg`, or `.rjpg` extension alone is insufficient; the file must contain a readable FLIR raw thermal record and calibration metadata. labkit_FLIRThermal_app Inputs And Navigation Use **Add FLIR files or folder** to load one or more radiometric images. The selected row is the current image; Previous/Next change selection without discarding per-image display range or measurement annotations. Source files are read-only. Basic Workflow Load radiometric files and inspect the decoded min/max/metadata summary. Choose a palette and color mapping. Set a range preset, per-image range, or shared group range. Choose ROI hot spot, ROI cold spot, or ROI mean, then drag its rectangle. Optionally click the image for an independent manual point reading. Review the numeric results and markers. Export the current image or the full batch. Placing or dragging a reading ROI and refreshing its marker preserves the current zoom. The reading is recalculated from the thermal matrix, not from screen colors. Temperature Conversion `labkit.thermal.readFile` extracts the raw sensor matrix and converts it to Celsius. Environment-corrected conversion uses Planck constants plus available emissivity, object distance, atmospheric/window transmission, reflected temperature, and related metadata. Missing environmental fields may use documented defaults and are listed in `record.metadata.temperatureConversion`; required Planck constants are never invented. Absolute temperature accuracy depends on camera calibration and environmental assumptions. Preserve conversion diagnostics with reported measurements. Display Controls Palettes are Turbo, Iron, Hot, Parula, and Gray. Mapping modes are Linear, Log, and Gamma; gamma defaults to 2.2 and is limited to 0.1-5. Display ranges are stored per image. Actions can set each image independently, apply one shared group range, fit the current image, or round configured bounds. Palette, mapping, gamma, and color limits affect only mapping into display colors. They do not transform the Celsius matrix or exported temperature values. ROI Readings The rectangular ROI includes both boundary pixels after its two corners are rounded and clamped to the thermal matrix. Hot and cold readings report value and `[x y]` location. Mean ignores non-finite pixels and also records ROI geometry and finite pixel count. The marker is a display annotation; moving it does not change source data. Outputs Current or batch export writes PNG, TIFF, or JPEG rendered thermal images, matching color scale graphics, Celsius CSV matrices, measurement values, a batch CSV summary, and the standard `flir_thermal.labkit.json` result manifest. The clean image export excludes interactive toolbar chrome. Numeric temperature outputs remain Celsius regardless of palette or mapping mode. Project And State Saved projects keep portable source references, display parameters, export settings, and lightweight per-image ranges and readings. Raw sensor matrices and decoded Celsius matrices are loaded again from the selected source when a project opens. Missing source files use the standard relinking flow rather than relying on a local absolute path. An existing source that is no longer a readable radiometric file aborts the restore and preserves the current document. Batch import may still report and skip rejected selections before they become project sources. An empty launch does not choose an output directory. Adding files establishes the source-adjacent default; **Choose folder** remains available before export. Use Without The GUI record = labkit.thermal.readFile(\"capture.rjpg\"); temperatureC = record.temperatureC; point = flir_thermal.analysisRun.pointTemperatureReading( ... temperatureC, [120 80]); [hot, cold, meanReading] = ... flir_thermal.analysisRun.roiTemperatureMeanReading( ... temperatureC, [90 60], [169 129]); [imageHot, imageCold] = ... flir_thermal.analysisRun.extremeTemperatureReadings(temperatureC); Function Reference The generated pages for [`pointTemperatureReading`](../../../reference/api/flir_thermal/analysisRun/pointTemperatureReading.html) and [`extremeTemperatureReadings`](../../../reference/api/flir_thermal/analysisRun/extremeTemperatureReadings.html) specify coordinate order, clamping, nonfinite/empty behavior, returned fields, conversion failures, and related measurement APIs. Errors And Limitations Non-radiometric JPEG files are rejected even when their extension is valid. Coordinates refer to the decoded thermal matrix, not a separately scaled visible-light image. An ROI containing no finite temperature pixels cannot return a finite mean. JPEG export is visual and lossy; keep Celsius data for quantitative work. Related Topics [Thermal Library](../../../libraries/thermal/README.md) [Image Measurement family](../README.md) [API Reference](../../../reference/README.md) flir_thermal.analysisRun.extremeTemperatureReadings Locate finite global thermal extrema. flir_thermal.analysisRun.pointTemperatureReading Read the nearest calibrated thermal pixel. flir_thermal.analysisRun.roiTemperatureMeanReading Measure an inclusive rectangular thermal ROI."},{"title":"Focus Stack","url":"apps/image-measurement/focus-stack.html","kind":"app","section":"apps","keywords":"apps image measurement focus stack Focus Stack","text":"Focus Stack Focus Stack fuses at least two focal planes into one all-in-focus image using multilevel Laplacian focus evidence and exports a focus-depth index map. Requirements And Launch Inputs should show the same field of view at different focus positions. labkit_FocusStack_app Inputs Add selected image files or choose a folder. Remove blurred, displaced, or otherwise invalid frames before running. The first image defines working geometry; differently sized inputs are resized and the result records how many images required resizing. Basic Workflow Load at least two focal planes. Remove frames that do not belong to the stack. Start with the Balanced preset. Enable auto-registration when the stack has small rigid camera motion. Tune detail scale, blend radius, and uncertain blend only when artifacts are visible. Run the stack and inspect both fused image and focus map. Export fused PNG, focus-map PNG, and summary CSV. Fusion Parameters Parameter Default Meaning Preset Balanced coordinated starting values; Crisp favors fine detail, Smooth favors seam suppression, Noisy favors grain tolerance Auto-register off register images to the middle image before fusion Detail scale 31 px odd local window for focus-energy evidence Blend radius 4 px smooth focus-selection weights across boundaries Uncertain blend 5% blend competing planes where focus confidence is low The public calculation API also accepts `minConfidence` (default 0.05) and `pyramidLevels` (default 4, bounded by image geometry). Algorithm Images are normalized to grayscale or RGB double data on common geometry. A Gaussian/Laplacian pyramid is built for each plane. Local squared Laplacian detail energy chooses the strongest plane at each level. Confidence is the relative separation between the best and second-best evidence. High-confidence regions use the winning plane. Low-confidence regions blend normalized evidence weights; zero-evidence regions use equal weights. The weights are spatially smoothed by the level-scaled blend radius and the fused pyramid is reconstructed and clamped to `[0,1]`. The focus index is the winning input plane at full resolution. Focus coverage is the fraction of pixels assigned to each plane. The algorithm does not correct parallax, deformation, moving objects, illumination changes, or severe registration error. Outputs fused all-in-focus image; integer focus-depth index map; confidence map and per-plane focus coverage in the run result; summary CSV with geometry, parameters, confidence, coverage, registration, and resize information; result provenance manifest. Use Without The GUI images = {imread(\"z01.png\"), imread(\"z02.png\"), imread(\"z03.png\")}; options = struct(\"focusWindow\", 31, \"smoothRadius\", 4, ... \"minConfidence\", 0.05, \"pyramidLevels\", 4); result = focus_stack.analysisRun.computeFocusStack(images, options); assert(result.ok, result.message); imwrite(result.fused, \"stacked.png\"); Errors And Limitations Fewer than two images is an error. Reopening a saved stack is strict: an existing image that cannot be decoded aborts restore and leaves the current document unchanged. `minConfidence` must be finite and between 0 and 1. Resizing changes sampling and should not substitute for consistent capture. Auto-registration should be reviewed; a wrong registration can create a sharp-looking but geometrically false composite. Related Topics [Image Measurement family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) focus_stack.analysisRun.computeFocusStack Fuse a stack of images with different focus planes."},{"title":"Image Enhance","url":"apps/image-measurement/image-enhance.html","kind":"app","section":"apps","keywords":"apps image measurement image enhance Image Enhance","text":"Image Enhance Image Enhance builds an ordered, reversible processing history for one image or a batch and exports the resulting images with the exact step sequence. Requirements And Launch All processing uses MATLAB and repository-owned code. labkit_ImageEnhance_app Inputs And Batch Mode Add supported image files or a folder. In **Batch shared processing** mode, one shared step history is applied to every source. When batch mode is off, each image keeps a separate history and optional white ROI. Selecting another image updates the preview without recalculating unrelated files. Basic Workflow Load images and select a representative source. Decide whether steps are shared across the batch. Choose a tool and set its controls. For White ROI calibration, draw the white-background ROI first. Apply the tool to history and inspect Enhanced, Original, or Before | After. Undo or reset history as needed. Choose output format/folder and export. Each **Apply tool** action saves one processing step. Panner changes preview the pending tool without changing the saved history until **Apply tool** is chosen. ROI creation and overlay refresh preserve the current zoom. Tools And Defaults Tool Primary control Secondary control Brightness/contrast Brightness 0% Contrast 15% Local contrast Clarity 30% Radius 12 px Sharpen Sharpen 35% Radius 1.5 px Hue/saturation Hue 0 degrees Saturation 10% White balance Strength 100% Temperature 0% White ROI calibration Strength 100% White target 92% Subject-preserving enhance Strength 70% Background target 90% Processing converts input to RGB double in `[0,1]`, applies steps in table order, and clamps the final image. Brightness, local contrast, sharpening, hue/saturation, and gray-world white balance call the linked `labkit.image` primitives. White ROI calibration requires a valid rectangle for every affected image. It builds a softened background mask from the rectangle and adjusts luminance toward the target while limiting subject changes. Subject-preserving enhance estimates a low-saturation bright background automatically and uses a whole image fallback only when no usable background support is found. History And Reproducibility The history table records step kind and settings in execution order. **Undo history** removes the latest applied step; **Reset history** returns to the normalized original. Source images are not modified. Shared and per-image histories are stored separately in project state so switching batch mode does not ambiguously merge them. Outputs Export supports PNG, TIFF, and JPEG. Each output is rendered from the source plus its effective stored pipeline, not from a downsampled preview. The export manifest records source, ordered steps, ROI geometry, batch mode, format, and output filename. A LabKit result JSON records the complete output set and the project parameters used to create it. Use Without The GUI steps = [ ... image_enhance.analysisRun.makeStep(\"Brightness/contrast\", 0, 15), ... image_enhance.analysisRun.makeStep(\"Local contrast\", 30, 12), ... image_enhance.analysisRun.makeStep(\"Sharpen\", 35, 1.5)]; output = image_enhance.analysisRun.applyPipeline(imread(\"source.png\"), steps); imwrite(output{1}, \"enhanced.png\"); Project And State Saved projects keep portable source references, shared and per-image step histories, white-reference ROIs, export settings, and compact result metadata. Full-size pixels and downsampled previews are loaded again from the selected source when a project opens rather than embedded in the project. An empty launch does not choose an output directory. Adding images establishes the source-adjacent default; **Choose folder** remains available before export. Errors And Limitations Unknown step labels are rejected. Reopening a project with an existing unreadable image aborts restore and preserves the current document instead of displaying an empty preview. White ROI calibration fails when a required image has no valid ROI. Enhancement changes pixel values and can invalidate quantitative intensity analysis; retain the source and manifest. JPEG export adds lossy compression after processing. Related Topics [Image Match](../image-match/README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) image_enhance.analysisRun.applyPipeline Replay an ordered non-destructive image enhancement history."},{"title":"LabKit Apps","url":"apps/index.html","kind":"overview","section":"apps","keywords":"apps LabKit Apps","text":"LabKit Apps LabKit apps are independent MATLAB tools for complete laboratory workflows. Choose an app by the result you need, then use its app page for supported inputs, controls, interaction behavior, calculations, outputs, recovery, and programmatic APIs. Start An App Open `labkit_launcher`, select one app, and choose **Open**. The launcher's **Documentation and History** action opens the selected app page. Source-checkout users can also call an app command after adding LabKit to the MATLAB path: labkit_launcher % Or, after repository path setup: labkit_CIC_app See [Getting Started](../getting-started/README.md) for installation, updating, version selection, and source-checkout setup. The [LabKit Launcher manual](labkit-core/launcher/README.md) documents its complete interactive and programmatic surface. Choose An App Task App Input Principal output Discover, install, launch, diagnose, and package LabKit apps [LabKit Launcher](labkit-core/launcher/README.md) Installed or source LabKit tree Running app, maintenance report, or deployment ZIP Register, crop, and mask image pairs for DIC [DIC Preprocess](dic/dic-preprocess/README.md) Reference and moving images Aligned images, crops, mask Render and summarize Ncorr strain results [DIC Postprocess](dic/dic-postprocess/README.md) Ncorr MAT, reference image, mask Strain overlays and summary CSV Overlay chrono voltage and current traces [Chrono Overlay](electrochemistry/chrono-overlay/README.md) Chrono DTA Plot and CSV Measure charge-injection capacity [CIC](electrochemistry/cic/README.md) Chrono DTA CIC result table and CSV Compare cyclic and time-domain CSC [CSC](electrochemistry/csc/README.md) CV/CT DTA Per-cycle CSC and CV data Inspect impedance curves [EIS](electrochemistry/eis/README.md) EIS DTA Nyquist/Bode plots and CSV Estimate voltage-transient resistance [VT Resistance](electrochemistry/vt-resistance/README.md) Chrono DTA Resistance table and CSV Convert tracked points into gait metrics [Gait Analysis](gait/gait-analysis/README.md) Current Video Marker project MAT Frame, step, coordinate, and summary tables Crop image batches at repeatable geometry [Batch Image Crop](image-measurement/batch-crop/README.md) Image files Same-size crops and manifest Measure curve radius, curvature, and length [Curvature Measurement](image-measurement/curvature/README.md) Image Overlay and measurement CSV Decode and measure radiometric images [FLIR Thermal](image-measurement/flir-thermal/README.md) FLIR radiometric image Temperature data, measurements, rendered image Fuse focal planes [Focus Stack](image-measurement/focus-stack/README.md) Aligned image stack Fused image and focus map Apply a repeatable enhancement pipeline [Image Enhance](image-measurement/image-enhance/README.md) Image files Enhanced images and manifest Match appearance to a reference image [Image Match](image-measurement/image-match/README.md) Source and reference images Matched images and manifest Annotate ordered landmarks across video [Video Marker](image-measurement/video-marker/README.md) Video Project MAT and coordinate CSV Restyle figures and export visible plot data [Figure Studio](labkit-core/figure-studio/README.md) FIG or popout axes Styled figure and plot data Inspect RHS recordings and define channels [RHS Preview](neurophysiology/rhs-preview/README.md) RHS file or folder Preview, protocol JSON, filter record Detect trains and measure neural responses [Nerve Response Analysis](neurophysiology/nerve-response-analysis/README.md) Filter record and protocol JSON Analysis JSON and CAP metrics Review aligned responses and statistics [Response Review and Stats](neurophysiology/response-review-stats/README.md) Analysis JSON or segment CSV Metrics CSV and summary Compare multiple groups with the first using t-tests and one mean/SD plot [T-Test Wizard](statistics/ttest-wizard/README.md) CSV, TSV, workbook, or entered values Result family, CSVs, and comparison plot Inspect ECG and measure segment SNR [ECG Print](wearable/ecg-print/README.md) MAT or delimited table Segment SNR CSV and waveform image Browse By Family [DIC](dic/README.md) - preparation and postprocessing around a DIC solver. [Electrochemistry](electrochemistry/README.md) - DTA-based chrono, CV/CT, impedance, charge, and resistance workflows. [Gait](gait/README.md) - pose-coordinate analysis and gait metrics. [Image Measurement](image-measurement/README.md) - calibrated image, thermal, annotation, crop, fusion, and appearance workflows. [LabKit Core](labkit-core/README.md) - the launcher and general MATLAB graphics tools. [Neurophysiology](neurophysiology/README.md) - RHS inspection, response analysis, and review. [Statistics](statistics/README.md) - explicit first-versus-each t-tests and result-based group plotting. [Wearable](wearable/README.md) - wearable biosignal workflows. How To Read An App Page Concrete app pages use a common MATLAB-style order: purpose, requirements, and launch command; supported inputs and the shortest successful workflow; controls and interaction behavior; calculation or algorithm semantics, units, and assumptions; outputs, project files, autosave, and recovery behavior; GUI-free MATLAB examples and public app-owned APIs; errors, limitations, troubleshooting, related topics, and history. The app page documents stable user-visible behavior. Exact callable syntax and data shapes live on the linked API reference pages. Internal callbacks and private implementation helpers are intentionally omitted. Shared framework contracts are documented once in the [App Framework](../framework/README.md), not repeated in every App page. An App page mentions shared behavior only when that App changes it or when the behavior is necessary to complete the App's workflow. Common App Behavior The App Framework owns lifecycle, busy state, file selection, state snapshots, screenshot actions, plot tools, and managed interactions. Apps own scientific choices, workflow-specific defaults, result schemas, and exports. See the [App Framework](../framework/README.md) for behavior shared across apps. Every App opens as a clean project. Use **Tools > Diagnostics** to inspect the current session history, enable future trace capture, or export a diagnostic bundle after a problem. Apps with declared sample generation expose **Tools > Developer Tools > Generate Synthetic Inputs...**; generation writes anonymous inputs without loading them or changing the open project. The [runtime guide](../framework/guides/runtime.md) defines these shared contracts. Action and input-selection buttons provide concise hover help. The shared **Tools** menu contains plot, screenshot, and project-state actions when the corresponding capability is available. Input data and exported results should remain outside the replaceable LabKit runtime folder. Apps do not overwrite source files unless an app page states an explicit in-place operation. Programmatic Use Important scientific and deterministic app operations can be called without opening the GUI. Each app page identifies its supported app-owned functions and links them to the generated [API Reference](../reference/README.md). Reusable file parsers and generic processing functions live in the public `labkit.*` libraries. MATLAB must be able to see both the repository root and the owning app root. The launcher prepares these paths automatically. In a source checkout, add them explicitly before calling an app package: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(repoRoot) addpath(fullfile(repoRoot, \"apps\", \"electrochem\", \"cic\")) help cic.analysisRun.computeCIC For another app, replace the final app path with the directory that directly contains its `+package` folder. Do not add every repository subfolder with `genpath`; app roots are independent and may contain same-named private or development files that should not become global MATLAB commands. Related Documentation [Getting Started](../getting-started/README.md) [LabKit Launcher](labkit-core/launcher/README.md) [App Framework](../framework/README.md) [API Reference](../reference/README.md) [App Development](../development/build-apps/app-development.md) [Component History](../history/README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics."},{"title":"Image Match","url":"apps/image-measurement/image-match.html","kind":"app","section":"apps","keywords":"apps image measurement image match Image Match","text":"Image Match Image Match transfers tone and color statistics from one reference image to one or more source images while preserving each source image's geometry. Requirements And Launch The app performs appearance matching only; it does not geometrically register images. labkit_ImageMatch_app Inputs Choose one reference image and add source images or a source folder. The reference supplies appearance statistics and is not exported as a matched source. All images are normalized to RGB double data in `[0,1]`; output retains the source height and width. Basic Workflow Choose the reference and source images. Select a matching method. Set overall, tone, and color strength. Apply the match to history. Inspect Matched, Original, and Before | After views. Undo/reset history or add another step. Export matched images. Changing method or strength previews the pending match; **Apply match** saves the settings as the next step in the image's processing history. Matching Methods Method Behavior Balanced robust white-point match followed by Lab-style tone/color transfer White balance match robust bright-neutral RGB white points Tone only quantile-match Lab lightness only Protected tone constrained luminance/background adjustment with shadow, highlight, and saturation guards Lab style quantile-match lightness and covariance-match Lab chroma Histogram independently quantile-match all three Lab channels Overall strength blends the matched result with the original. Tone and color strength separately blend lightness and chroma behavior where the selected method supports them. All three default to 100%. Robust statistics and covariance regularization handle flat or nearly single-color images, but a visually dissimilar reference can still produce an unhelpful result. Matching reproduces distributions, not semantic lighting or camera calibration. History And Outputs Each source is recomputed from its original plus the ordered match history. Undo removes the latest step and reset removes all steps. Export supports PNG, TIFF, and JPEG and writes an image manifest containing reference/source identity, method, strengths, history, format, and output filenames. Source and reference files are never overwritten. Use Without The GUI source = imread(\"source.png\"); reference = imread(\"reference.png\"); step = image_match.analysisRun.makeStep(\"Balanced\", 100, 100, 100); matched = image_match.analysisRun.applyMatch(source, reference, step); % Apply a stored sequence: outputs = image_match.analysisRun.applyPipeline({source}, step, reference); output = outputs{1}; Function Reference The generated pages for [`applyMatch`](../../../reference/api/image_match/analysisRun/applyMatch.html) and [`applyPipeline`](../../../reference/api/image_match/analysisRun/applyPipeline.html) document supported step fields and defaults, normalization and output geometry, empty-input behavior, failures, examples, and related APIs. Errors And Limitations An empty reference returns normalized source data in the calculation API, but the app requires a reference before enabling match actions. Reopening a project with an existing unreadable reference or source image aborts restore and preserves the current document. Matching never corrects position, scale, rotation, perspective, or parallax. Histogram and Lab matching can shift scientifically meaningful pixel values; retain original images for quantitative analysis. JPEG export is lossy. Related Topics [Image Enhance](../image-enhance/README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) image_match.analysisRun.applyMatch Transfer reference appearance statistics to one image. image_match.analysisRun.applyPipeline Replay ordered reference-match steps over source images."},{"title":"Video Marker","url":"apps/image-measurement/video-marker.html","kind":"app","section":"apps","keywords":"apps image measurement video marker Video Marker","text":"Video Marker Video Marker defines an ordered landmark skeleton, records coordinates across video frames, predicts forward positions between manual anchors, and saves a portable project with an explicit source-adjacent autosave copy. Requirements And Launch The app declares compatibility with `labkit.app` 1.x and uses image functions shipped with the same workbench. Video decoding uses MATLAB's available video support. Predictive navigation is implemented in repository-owned MATLAB code; no model weights or third-party runtime package are downloaded. labkit_VideoMarker_app Start Or Open A Project The **Session** panel appears first in the Video tab. **Open MAT** uses the same loader as the window's top-level Load State action and accepts an explicit project or compatible autosave. **Save autosave** immediately updates `Video Marker Autosaves/.video_marker.autosave.mat` beside the source video without asking for a path and without turning that recovery copy into the named project file. **New setup** offers Cancel, Save and start new, or Discard and start new; it never silently clears the current session. For a new project, choose a preset or add keypoints, edit their names, reorder them, and define connections. **Connect in order** links every adjacent pair. At least one keypoint is required before opening a new annotation session. The skeleton is locked after the video opens so coordinate column meaning remains stable. Marking Workflow Define the ordered skeleton and optional scale calibration. Open the video. Click blank image locations to place the next missing keypoint in order. Drag an existing marker to refine it. Use Undo last point or Clear frame points when needed. Move to the next frame; the current frame is saved automatically. Correct predicted frames periodically and export or save the project. Point marking remains active while a video is open. A complete edited frame is a manual anchor. Moving forward predicts every ordered point using cropped multiscale patch matching. The tracker compares mean-centered local patches by normalized correlation, refines accepted matches to subpixel coordinates, and reports confidence for each point. A rejected point retains its bounded motion- prior estimate. Predicted frames remain editable drafts. Editing a complete frame makes it a new manual anchor for later prediction. Jumping forward propagates through intermediate frames and does not overwrite existing manual anchors. Frame navigation and marker refresh preserve the current zoom. Opening a new video or project starts at its own home view. Scale And Coordinate Export Scale calibration stores a measured pixel distance, known length, and unit. The coordinate export options are: units: `pixels` or `calibrated_physical`; origin: `top_left_pixel_center` or `first_point`; Y axis: `up` or `down`; inclusive start and end frame. Marker CSV is the round-trip editing format. Coordinate CSV is the plotting-oriented table after optional calibration, origin shift, and Y-axis conversion. Raw pixel coordinates remain available in the project. Autosave, Recovery, And Portability **Save autosave** writes atomically to `Video Marker Autosaves` beside the source video. It is an explicit action, not a background timer. Autosave and named project MAT files contain the same project data: frame count, frame rate, duration, image dimensions, skeleton edges, annotation status/source, calibration, and durable coordinates. The video itself remains an external portable source and can be relinked without discarding annotations. When a project tree moves between folders, users, or operating systems, the relative reference is tried first. If no candidate exists, the app asks the user to locate the video without discarding skeleton or annotations. A compatible old Video Marker project or autosave opens through **Open MAT** or **Tools > Project State > Load State...**. Project And Session State Durable state consists of video metadata, the portable video source, skeleton, frame annotations and provenance, calibration, export parameters, and output manifest paths. The video reader, decoded frame, current interaction, selection, preview graphics, and frame cache are transient session resources. Only the current frame number is saved as navigation convenience; annotations remain the authoritative scientific data. Older compatible project and autosave formats are upgraded when opened. Save the upgraded project before moving it into another workflow. Outputs explicit project MAT and visible autosave MAT; marker CSV for round-trip editing; coordinate CSV for analysis and plotting; output manifests recording coordinate options and file roles. CSV dialogs start in a source-adjacent `video_marker` output folder. Result manifests are written beside the chosen CSV. Diagnostics And Synthetic Inputs Use **Tools > Diagnostics** to inspect the live session log, enable trace capture, or export a diagnostic bundle after a problem occurs. Use **Tools > Developer Tools > Generate Synthetic Inputs...** to create a synthetic video, a valid marking project, and declared marker/coordinate output targets without including user filenames or laboratory data. Generation does not load those inputs into the running App. Use Without The GUI previousFrame = peaks(61); nextFrame = circshift(previousFrame, [-2 3]); [nextPoints, confidence] = video_marker.motionEstimate.trackPoints( ... previousFrame, nextFrame, [31 31]); skeleton = video_marker.skeletonDefinition.fromText( ... \"hip, knee\", \"hip-knee\"); annotations = video_marker.frameAnnotations.emptyAnnotations(2, 2); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 1, [11 21; 31 41], \"confirmed\"); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 2, [13 25; 35 45], \"confirmed\"); videoInfo = struct(\"frameRate\", 10); options = video_marker.coordinateExport.options( ... \"startFrame\", 1, \"endFrame\", 2, \"unitMode\", \"pixels\", ... \"originMode\", \"first_point\", \"yAxisMode\", \"up\"); coordinateTable = video_marker.coordinateExport.buildTable( ... annotations, skeleton, videoInfo, struct(), options); Function Reference Use the generated [`buildTable`](../../../reference/api/video_marker/coordinateExport/buildTable.html) page for exact frame-range option types, defaults, coordinate transforms, calibration requirements, output columns, failures, and related marker APIs. The [`trackPoints`](../../../reference/api/video_marker/motionEstimate/trackPoints.html) page documents the repository-owned prediction contract separately. Errors And Limitations Prediction is an editing aid, not unattended long-duration tracking. Occlusion, blur, repeated texture, large deformation, and leaving the frame can invalidate patch matches; inspect and correct drafts. Calibrated export requires a valid positive scale calibration. Changing skeleton order after annotation would change coordinate meaning and is therefore prevented once a video session begins. Related Topics [Gait Analysis](../../gait/gait-analysis/README.md) [Image Measurement family](../README.md) [API Reference](../../../reference/README.md) video_marker.coordinateExport.buildTable Convert confirmed video annotations to a coordinate table. video_marker.motionEstimate.trackPoints Track ordered image points between two consecutive video frames."},{"title":"LabKit Core Apps","url":"apps/labkit-core/index.html","kind":"app family","section":"apps","keywords":"apps labkit core LabKit Core Apps","text":"LabKit Core Apps Core apps provide the LabKit workbench entry point and cross-domain tools that are useful to many workflows but do not own a scientific measurement or experiment-specific formula. Choose An App Task App Discover, install, launch, diagnose, and package apps [LabKit Launcher](launcher/README.md) Restyle a MATLAB figure and export its visible graphics [Figure Studio](figure-studio/README.md) Workbench Entry Point The launcher is documented here because users operate it as the first LabKit application. Its implementation remains a self-contained root file so it can repair a damaged installation without depending on the app framework. Figure Handoff Figure Studio can open tracked `.fig` files or receive a plot sent from a LabKit plot context menu. The lightweight framework popout is intended for inspection; Figure Studio provides the complete workflow for controlled canvas and style settings, raster/vector export, and a data-plus-recreation-script package. Figure Studio processes one selected axes at a time. A mixed FIG exposes its subplots as separate panels; Studio does not turn the composite layout into a single plot. It does not rerun the calculation that produced the original figure and does not claim that displayed graphics are a lossless replacement for the original scientific dataset. Related Documentation [Figure Studio manual](figure-studio/README.md) [LabKit Launcher manual](launcher/README.md) [App Framework](../../framework/README.md) [Architecture](../../development/build-apps/architecture.md)"},{"title":"Figure Studio","url":"apps/labkit-core/figure-studio.html","kind":"app","section":"apps","keywords":"apps labkit core figure studio Figure Studio","text":"Figure Studio Figure Studio restyles MATLAB figures, exports presentation copies, and extracts supported visible graphics into a portable data package. It changes presentation properties, not the calculation that produced the plot. Open Figure Studio From the launcher, select **Figure Studio** and choose **Open**. From a source checkout, run: labkit_FigureStudio_app A LabKit plot can also send its current axes to Figure Studio through the plot context menu. That handoff embeds a serializable plot snapshot in the project and immediately applies the **LabKit figure** preset. The source style remains available through **FIG default**. Project And Handoff A plot-context-menu handoff creates the same editable project state as loading a FIG file. Saved projects retain portable FIG sources, embedded plot snapshots, the selected subplot panel, style settings, and canvas settings. Decoded source graphics are rebuilt after load, and the default output folder follows the restored source. Load And Select Figures On **Figures**, choose **Add FIG files or scan folder**. The source list accepts MATLAB `.fig` files and keeps one current selection. Selecting another source opens its native graphics without rerunning the analysis. A mixed FIG is listed as ordered **Subplot panel** choices (top-left to bottom-right, with a title where one exists). Select one panel to preview, restyle, recalculate, and export exactly that axes; Studio never combines multiple panels into one output. The plot-context-menu handoff already represents one axes, so it provides one panel. **Clear figures** removes all sources from the project. For a source using **FIG default**, Figure Studio adopts its font, semantic line widths, annotations, legend, grid, and axes appearance. Selecting **LabKit figure** applies the calibrated single-panel publication hierarchy while retaining the source legend placement. Switching a text preset never changes the selected plot-frame width, aspect, or export scale. Style And Canvas Controls Group Controls Text Style preset, all/title/axis-label/tick/annotation font size, X tick-label angle, grid alpha, grid visibility Lines + Boundaries data, uncertainty, main-graphic boundary, reference-line, and axes line widths Legend source/on/off display, location, font size, columns, and border Plot frame reference, 6:5, 4:3, 16:9, 1:1, 3:2, or source aspect; a fixed plot width or source size; export supersampling; top/right frame visibility Changing **All font** updates title, axis-label, tick, annotation, and legend sizes together. Individual controls then provide category-level refinement. **LabKit figure** uses a calibrated 900-by-725 px reference **plot frame**: 45 pt title, axis-label, tick, annotation, and legend text; 6.5 pt data strokes; 2 pt uncertainty strokes; and 1.5 pt boundary, reference, and axes strokes. The default uses a complete top/right frame with no grid and no legend box. Legend samples use the reference's long line tokens. These editable baselines come from normalized pixel measurements across all nine panels of the maintained 3-by-3 visual reference, not from a single imported FIG. The configured width and aspect always describe the inside of the axes frame. Figure Studio calculates the enclosing figure's outer margins from the current title, labels, ticks, legend, and visible annotations, so changing a long label cannot silently shrink the data region. Empty ruler text is ignored, including the zero-area placeholders MATLAB exposes on logarithmic axes. Choose **Source size** or one of 640, 720, 900, 960, 1200, 1237, 1364, 1600, or 2400 px; an aspect choice sets the paired plot-frame height. The workbench preview is a real interactive axes, not a raster image. When its allotted screen area changes, Studio reflows only the display text and strokes; project settings and export proportions remain unchanged. This calibration is validated on both 72-PPI Linux renderers and 96-PPI desktop MATLAB so display-density differences do not restyle exports. **Export x** is raster supersampling only. **Top/right frame** only shows or hides those two axes edges; it never changes the plot-frame size. Font controls use 0.5 pt steps and line controls use 0.1 pt steps. The preset styles every existing category but does not create or move a legend by default, because either action can cover data. Use the Legend panel for those explicit layout changes. **X tick labels** retains the source angle, makes labels horizontal, or rotates them 45 degrees. PNG and JPG resolution is `300 * Export x`, with a minimum of 72 dpi. SVG uses vector content. **FIG default** records the source plot-frame ratio as its reference, so reopening a source does not rescale its original typography. If limits are stale after copying or editing, use **Recalculate X/Y limits** to fit the visible graphics, update the interactive viewport, and refresh the four limit inputs. The X/Y minimum and maximum controls accept values within the finite visible-data extent expanded by exactly 50% on both sides; they do not change the source calculation. Quick Exports The **Figures** tab provides: **Save FIG** for an editable MATLAB figure; **PNG** and **JPG** for raster output; **SVG** for vector output. Each quick export also writes a LabKit result manifest beside the selected file. The manifest records the source, style parameters, output identity, and summary metadata. Export A Data Package On **Export**, select an output folder and choose **Export data + script**. Figure Studio creates a package containing supported visible data, style and axes metadata, and a MATLAB recreation script. For preview, editable FIG export, and image export, Studio first copies the selected native axes hierarchy when MATLAB permits the parent transition. This preserves MATLAB graphics that can be copied into the target axes, including ordinary grouped charts such as `boxplot`. Native child stacking is retained, so lines, markers, and text drawn over an image remain above that image in both the interactive preview and exports. UIAxes sources are rebuilt into conventional export axes from the portable snapshot, retaining the displayed scientific axis exponents as well as visible data. The portable data package separately recognizes visible line, bar, error-bar, area, scatter, image, surface, patch, rectangle, text, and constant-line objects. Existing legend text, visibility, placement, orientation, column count, font, interpreter, and border are retained. Explicit axis ticks, tick labels, label rotations, axis locations, and tick geometry are retained as visible presentation metadata. Visible graphics with hidden handles, such as error bars or significance brackets intentionally excluded from legends, are also retained without adding legend entries. Portable-package unsupported graphics are skipped and reported as warnings; the original FIG remains the authoritative source for object types that cannot be represented in that portable package. The package represents displayed graphics in axes child order. It is intended for audit, handoff, and plot recreation, not for reconstructing hidden source data or rerunning the original analysis. Programmatic Data Extraction fig = openfig(\"result.fig\", \"invisible\"); cleanup = onCleanup(@() close(fig)); ax = findobj(fig, \"Type\", \"axes\", \"-depth\", 1); plotData = figure_studio.resultFiles.extractAxesData(ax(1)); `plotData.axes` contains titles, labels, ticks, tick labels, scales, directions, limits, grid and aspect settings, color order, font settings, and colormap when available. `plotData.objects` contains the graphics type, display name, coordinate/color data, style fields, and object metadata. `plotData.warnings` lists skipped objects. Errors And Limitations A FIG must contain a readable axes to enter the editing workflow. Reopening a project with an existing but damaged or unsupported FIG stops the restore and preserves the currently open document; it is not presented as an empty figure source. Invisible objects are not exported. A selected single axes is copied natively when MATLAB supports that parent transition. The portable data package remains deliberately narrower and is not a promise of pixel-identical reconstruction of every MATLAB chart class. Callbacks, application data, custom classes, and analysis provenance inside the source figure are not treated as portable scientific data. Some object-specific rendering semantics cannot be reproduced from a plain graphics snapshot. Related Functions And Documentation `figure_studio.resultFiles.extractAxesData` `figure_studio.resultFiles.exportAxesPackage` `figure_studio.resultFiles.createStyledFigure` [LabKit Core apps](../README.md) [Plotting framework](../../../framework/README.md)"},{"title":"LabKit Launcher","url":"apps/labkit-core/launcher/index.html","kind":"app manual","section":"apps","keywords":"apps labkit core launcher LabKit Launcher","text":"LabKit Launcher The LabKit Launcher is the installed workbench entry point. It discovers apps, prepares their MATLAB paths, checks requirements, starts App SDK sessions, manages installed versions, opens app documentation, and exposes source-checkout maintenance tools. It is intentionally self-contained so a single surviving `labkit_launcher.m` can repair an incomplete ZIP installation. Start The Launcher labkit_launcher The window paints before app discovery completes. Its status area first reports that the app list is loading, then shows the selected app, integrity problems, tool availability, or the active maintenance operation. Launcher Window Group Action Behavior Run Apps **Open Selected App** Checks the selected app requirements, adds the app root, and calls its App SDK entrypoint without retired runtime launch arguments. Run Apps **Refresh App List** Repeats public and configured private-app discovery without restarting the launcher. Run Apps **Documentation and History** Opens the generated manual for the selected app. Versions and Install **Latest** Installs the current `main` branch archive. Versions and Install **Release** Installs the latest stable GitHub release. Versions and Install **Versions** Opens the release, tag, and commit selector for deliberate upgrade or rollback. Development and Maintenance **Update Documentation** Regenerates `site/` with the repository-owned documentation compiler. Development and Maintenance **Run Code Analyzer** Scans the checkout and writes JSON and HTML Code Analyzer reports. Development and Maintenance **Profile Selected App** Starts the selected app under the MATLAB profiler and saves its report when the app closes. Development and Maintenance **Clean Artifacts** Removes ignored generated reports under `artifacts/`; it does not delete app projects or exported laboratory results. Package and Publish **Package Checked** Creates one source ZIP containing all checked apps and their runtime support. Package and Publish **Checked P-code** Creates a runtime-only ZIP with MATLAB source encoded as P-code. Double-clicking an app row is equivalent to selecting it and opening it normally. The checkbox column controls package membership; ordinary launch selection does not change the checked set. When startup begins, the launcher immediately disables its App table and actions, changes the open button to **Starting App...**, shows a wait pointer, and reports two explicit stages: preparing the selected App path, then initializing its window through the named entry point. This feedback is painted before the potentially slow App entry point runs. Duplicate clicks are ignored while startup is active. Completion restores the controls and reports the opened command; failure reports the failing identifier and message, with repair guidance only for structural installation failures. Every launch uses the same clean App path. Use the App's **Tools > Diagnostics** menu to inspect its live session log, enable trace capture, or export a diagnostic ZIP after a problem occurs. Apps that declare a synthetic input pack expose **Tools > Developer Tools > Generate Synthetic Inputs...**. Generation writes anonymous fixture files and a manifest into a new folder but does not load them or mutate the running project. Programmatic Calls The launcher exposes a small non-GUI surface: fig = labkit_launcher; apps = labkit_launcher(\"list\"); info = labkit_launcher(\"version\"); page = labkit_launcher(\"documentation\", \"labkit_CIC_app\"); Call Result `labkit_launcher` Opens the window; optionally returns its figure handle. `labkit_launcher(\"list\")` Returns the discovered app catalog as a table without opening the launcher window. `labkit_launcher(\"version\")` Returns launcher name, display name, semantic version, and update date. `labkit_launcher(\"documentation\", appCommand)` Returns the local generated HTML page for one discovered app command. Unsupported modes, empty documentation commands, extra inputs, and more than one requested GUI output raise `labkit_launcher:InvalidInput` or `labkit_launcher:TooManyOutputs` errors. Documentation lookup uses the discovered public App folder and the unique path-conventional manual at `docs/apps///README.md`. It does not require a separately maintained App catalog. App Discovery Public apps are discovered from `apps/**/labkit_*_app.m`. Each entry point delegates to one `definition.m`, which supplies display metadata, family, command, version, requirements, layout, and optional capabilities. The launcher reads version and update metadata from that definition without starting the App. Local private apps may be discovered from `private_apps/apps/` or paths named by `LABKIT_PRIVATE_APP_ROOTS`; their repositories and manuals remain private. The launcher adds only the paths needed for the selected app. It does not use `genpath` to expose every app helper globally. Refresh the app list after adding, removing, or updating an app while the launcher remains open. Installation And Recovery The downloaded `labkit_launcher.m` can begin by itself. When no installed Launcher is available, it opens a focused install/repair window with: an editable installation folder and native **Browse...** action; a clear new-installation, existing-installation, or unsafe-target result; **Latest stable release** (recommended), a GitHub-backed selector of published stable versions, and **Current main branch** (development); honest preparation, download, package-validation, installation, and outcome stages with current-action or error details; and confirmation before replacing an existing installation. The target is checked before any network request. A new install may use a nonexistent folder whose parent exists, an empty folder, or a folder containing only the standalone launcher and ordinary OS metadata. Repair accepts an existing LabKit installation. Filesystem roots, Git checkouts, files, missing parents, and non-LabKit folders containing unrelated content are rejected. After LabKit is installed, **Latest**, **Release**, and **Versions** in the full Launcher provide the richer source-checkout version workflow. Every downloaded archive is validated before replacement. Existing repairs preserve a recovery copy transactionally and retain known local workspace folders when required. Keep experimental data and exports outside the runtime folder because installed code is replaceable. Installed apps run offline unless their own inputs use a network location. Version discovery and update actions require network access. Source and P-code deployment packages have different contents; see [App Deployment Packages](../../../development/tools/deployment.md). Maintenance Tools Maintenance buttons are enabled only when their corresponding source-checkout tool is available. The same operations can be called directly from MATLAB: [Code Analyzer Reports](../../../development/tools/codecheck.md) [App Deployment Packages](../../../development/tools/deployment.md) [Documentation Build Tools](../../../development/tools/documentation.md) [Performance Profiling](../../../development/tools/profiling.md) These tools produce ignored artifacts. They are not app runtime APIs and do not alter scientific data. Development History The launcher began as the visible entry point for UI 2.0, then gained self-contained repair updates, version selection, private-app discovery, profiling and packaging actions, early startup feedback, and direct app documentation links. The generated **Change history** below lists every record whose metadata names `labkit_launcher` in linear sequence. Key milestones: [v2.0 launcher and UI 2.0](../../../history/records/2026/06/LK-20260615-v2-launcher-and-ui2.md) [Self-contained launch and image workflow refinement](../../../history/records/2026/06/LK-20260621-v2-2-v2-3-image-workflows.md) [Launcher manager and stale callback repair](../../../history/records/2026/06/LK-20260626-launcher-manager-and-stale-callback-fix.md) [Profiling and validation speedups](../../../history/records/2026/07/LK-20260702-profiling-and-validation-speedups.md) [Searchable documentation and launcher workflow groups](../../../history/records/2026/07/LK-20260715-documentation-site.md) Related Documentation [Getting Started](../../../getting-started/README.md) [LabKit Apps](../../README.md) [LabKit Core Apps](../README.md) [Architecture](../../../development/build-apps/architecture.md) [Private Apps](../../../development/maintain-and-release/private-apps.md) [Release Process](../../../development/maintain-and-release/release.md)"},{"title":"Neurophysiology Apps","url":"apps/neurophysiology/index.html","kind":"app family","section":"apps","keywords":"apps neurophysiology Neurophysiology Apps","text":"Neurophysiology Apps The neurophysiology apps cover three stages of an Intan RHS workflow: checking recordings, measuring stimulus-evoked responses, and reviewing the resulting measurements. Choose An App What you want to do App Check recording metadata and short waveform windows; assign channel roles [RHS Preview](rhs-preview/README.md) Find stimulation events and measure compound action potentials [Nerve Response Analysis](nerve-response-analysis/README.md) Recalculate or summarize measurements from an analysis or segment table [Response Review and Stats](response-review-stats/README.md) Recommended Workflow Open a representative recording in RHS Preview. Confirm the channels and inspect a short portion of the waveform. Save a protocol draft that records channel roles. If you are working with a set of recordings, label the files and save a filter record. Open the filter record in Nerve Response Analysis, add the protocol, and run the analysis. Export the analysis as JSON. This file contains the events, stimulus trains, response measurements, and any issues found while reading the recordings. Open that JSON in Response Review and Stats to inspect and export the measurement table. A compatible segment CSV can be used instead when you need to recalculate measurements from waveforms. Each stage saves its own parameters with the results. Keep those settings with any reported latency, amplitude, or signal-to-noise values. Use RHS Data In MATLAB Code The [RHS library](../../libraries/rhs/README.md) provides functions for reading recording metadata and selected waveform windows without opening an app. The individual app manuals show the calculation functions used for event detection, response measurement, alignment, and summary statistics. Related Documentation [RHS library](../../libraries/rhs/README.md) [Biosignal library](../../libraries/biosignal/README.md) [App catalog](../README.md)"},{"title":"Nerve Response Analysis","url":"apps/neurophysiology/nerve-response-analysis.html","kind":"app","section":"apps","keywords":"apps neurophysiology nerve response analysis Nerve Response Analysis","text":"Nerve Response Analysis Nerve Response Analysis reads the recording list prepared in RHS Preview, finds stimulation events, groups them into trains, and measures compound action potential responses in the assigned channels. Open Nerve Response Analysis From the launcher, select **Nerve Response Analysis** and choose **Open**. From a source checkout, run: labkit_NerveResponseAnalysis_app Analyze A Recording Set On **Setup**, choose the filter JSON created by RHS Preview. On **Protocol**, choose the protocol JSON that assigns channel roles. A protocol is optional, but strongly recommended. Set **Max recordings** or **Max duration** only when you want a shorter test run. A value of zero means no limit. Choose **Analyze Filtered Files**. On **Review**, inspect the counts and switch the plot between **Counts** and **Issues**. On **Export**, choose an output folder and then **Export Analysis**. Changing the filter, protocol, or limits clears the previous analysis so that an export cannot silently use outdated settings. The filter record and optional protocol are saved as distinct portable project sources. Older projects with separate source fields are upgraded on load. Project And Session State The durable project stores portable references for the filter record and optional protocol, the two run limits, and the last export paths. Parsed JSON, analysis tables, issue details, preview selection, log messages, and output-folder convenience are transient session state. Opening a project reparses its JSON sources but does not persist or silently reuse old analysis tables; choose **Analyze Filtered Files** to calculate them again. If an existing selected JSON file is malformed, project restore stops and preserves the current document. An absent optional protocol remains valid. What The Analysis Does For each readable recording, the app selects an event source from the protocol and recording metadata, reads the required waveform, and finds sharp changes using the absolute first difference. A robust noise estimate sets the default detection threshold. Nearby peaks are reduced according to the minimum event spacing, then grouped into trains by their time gaps and train rules. For every event and response channel, the app measures a pre-event baseline, baseline noise, positive and negative peaks, peak-to-peak amplitude, peak time, latency, and SNR. The response search starts after the pulse-blanking interval to avoid measuring the stimulation artifact itself. Detection and measurement settings are scientific choices. Confirm that the event source and timing shift match the physical stimulus before interpreting latency. Output Files **Export Analysis** writes: `nerve_response_analysis.json`, containing source information, the protocol, events, trains, response measurements, and issues; `nerve_response_analysis.labkit.json`, containing the selected inputs, run limits, output identity, and result counts. An unreadable recording or missing channel is recorded in the issues table so that other recordings can still complete. Detect Events In MATLAB Code sampleRate = 10000; timeSec = (0:1/sampleRate:0.12).'; eventSignal = zeros(size(timeSec)); pulseTimes = 0.03 + (0:4).' * 0.01; eventSignal(round(pulseTimes * sampleRate) + 1) = 5; eventOptions = struct( ... \"sourceId\", \"stim\", ... \"stimShiftSec\", 0, ... \"train\", struct(\"minDetectedPulses\", 5)); [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, eventSignal, eventOptions); `timeSec` and `eventSignal` must be equal-length numeric vectors. Options can set the threshold multiplier, minimum score, minimum peak spacing, train gap, minimum pulse count, maximum train duration, isolation rule, and reported stimulus-time shift. With no candidates, the function returns empty tables. Measure CAP Features In MATLAB Code metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, responseSignal, events.timeSec, metricOptions); `metricOptions` can set `baselineWindowSec`, `blankingAfterPulseSec`, and `searchEndAfterPulseSec`. The output contains one row per event. If the search window contains no samples, that row is kept with status `noSamples`. To process an RHS file or an entire filter record, use `nerve_response_analysis.analysisRun.analyzeRecording` or `nerve_response_analysis.analysisRun.analyzeSession`. Function Reference The generated pages for [`detectEventTrains`](../../../reference/api/nerve_response_analysis/analysisRun/detectEventTrains.html), [`measureCapMetrics`](../../../reference/api/nerve_response_analysis/analysisRun/measureCapMetrics.html), and [`analyzeSession`](../../../reference/api/nerve_response_analysis/analysisRun/analyzeSession.html) document protocol nesting, direct option defaults and legal values, table schemas, units, partial-recording failure policy, and related APIs. Errors And Limitations Time and signal vectors must have the same length. A detection peak marks an electrical transition; it is not automatically the physical onset of the delivered stimulus. Default timing windows are starting points, not universal physiological constants. Missing events produce empty results rather than fabricated measurements. Review the issues table before comparing counts across recordings. Related Functions And Apps `nerve_response_analysis.analysisRun.analyzeSession` `nerve_response_analysis.analysisRun.detectEventTrains` `nerve_response_analysis.analysisRun.measureCapMetrics` [RHS Preview](../rhs-preview/README.md) [Response Review and Stats](../response-review-stats/README.md) nerve_response_analysis.analysisRun.analyzeRecording Analyze one RHS recording according to a protocol. nerve_response_analysis.analysisRun.analyzeSession Analyze accepted RHS recordings and combine their results. nerve_response_analysis.analysisRun.detectEventTrains Detect pulse candidates and group them into trains. nerve_response_analysis.analysisRun.measureCapMetrics Measure event-locked CAP response features."},{"title":"Response Review And Stats","url":"apps/neurophysiology/response-review-stats.html","kind":"app","section":"apps","keywords":"apps neurophysiology response review stats Response Review And Stats","text":"Response Review And Stats Response Review and Stats opens a Nerve Response Analysis result or a segment table, displays the measurements, and exports a clean CSV for review or downstream statistics. Segment tables can also be aligned and measured again with new baseline and noise windows. Open Response Review And Stats From the launcher, select **Response Review Stats** and choose **Open**. From a source checkout, run: labkit_ResponseReviewStats_app Review An Analysis On **Setup**, choose an analysis JSON or segment CSV. The app loads the measurement rows immediately. If the input contains waveform segments, set the **Baseline** and **Noise** windows. Both ranges are expressed in seconds relative to alignment time. Choose **Refresh Metrics** when you want to reload the source explicitly. On **Review**, inspect the summary and details. Switch the plot between **Summary** and **Aligned**. On **Export**, choose a folder and select **Export Metrics**. Changing either time window recalculates a loaded segment input automatically. For an analysis JSON that already contains measurements, the app reads those measurements rather than recreating unavailable waveform data. Accepted Inputs JSON exported by Nerve Response Analysis. The app reads its `metrics` records. CSV that can be interpreted as waveform segments with time and values. When segments have different time grids, the app interpolates them onto a shared grid. If no interval is specified in code, it uses the median positive source interval. If the timestamps cannot provide one, it uses 0.0001 seconds. When no window is specified, the grid spans the intersection of the time ranges after each segment's alignment-time shift. Samples outside an aligned segment's original range remain `NaN`. The selected input is saved as a portable project source. Older projects are upgraded on load. Project And Session State The project saves the source, baseline/noise windows, and last export reference. Metric tables, aligned waveforms, and summaries are recalculated from that source after load. Measurements And Summary For each aligned segment, the calculation subtracts the baseline mean and reports positive and negative peaks, peak-to-peak amplitude, noise RMS, and SNR. The summary groups rows by `pairId` when available; otherwise it uses the segment name. Counts are included so exclusions and missing rows remain visible. Output Files **Export Metrics** writes: `response_review_metrics.csv`, one row per measurement; `response_review_metrics.labkit.json`, recording the input, time windows, output identity, row count, and group count. Align Segments In MATLAB Code localTime = (-0.003:0.001:0.012).'; firstValues = zeros(size(localTime)); firstValues(localTime == 0.004) = 2; firstValues(localTime == 0.008) = -1; secondValues = 0.8 * firstValues; segments = struct( ... \"timeSec\", {10 + localTime, 20 + localTime}, ... \"values\", {firstValues, secondValues}, ... \"name\", {\"first\", \"second\"}, ... \"alignTimeSec\", {10, 20}); options = struct( ... \"sampleIntervalSec\", 0.001, ... \"windowSec\", [-0.003 0.012], ... \"baselineWindowSec\", [-0.003 -0.001], ... \"noiseWindowSec\", [-0.003 -0.001], ... \"measurementWindowSec\", [0 0.010]); aligned = response_review_stats.analysisRun.alignSegments(segments, options); metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, options); summary = response_review_stats.analysisRun.summarizeMetrics(metrics); Each segment supplies `timeSec`, `values`, and `name`. An optional `alignTimeSec` shifts that segment to the common zero. `aligned.values` is a samples-by-segments matrix. Function Reference The generated pages for [`alignSegments`](../../../reference/api/response_review_stats/analysisRun/alignSegments.html) and [`measureAlignedSegments`](../../../reference/api/response_review_stats/analysisRun/measureAlignedSegments.html) define time-window option shapes and defaults, interpolation and empty-sample behavior, output table columns, failures, examples, and related APIs. Errors And Limitations Baseline and noise windows must overlap the available data to produce finite measurements. Interpolation makes grids comparable but cannot restore timing precision or bandwidth that is absent from the source. An empty input returns an empty result rather than a synthetic summary. Keep the chosen windows with any exported amplitude or SNR values. Related Functions And Apps `response_review_stats.analysisRun.loadMetrics` `response_review_stats.analysisRun.alignSegments` `response_review_stats.analysisRun.measureAlignedSegments` `response_review_stats.analysisRun.summarizeMetrics` [Nerve Response Analysis](../nerve-response-analysis/README.md) response_review_stats.analysisRun.alignSegments Interpolate segment traces to a shared time grid. response_review_stats.analysisRun.measureAlignedSegments Measure peak-to-peak, noise, and SNR per segment. response_review_stats.analysisRun.summarizeMetrics Compute grouped metric counts and finite means."},{"title":"RHS Preview","url":"apps/neurophysiology/rhs-preview.html","kind":"app","section":"apps","keywords":"apps neurophysiology rhs preview RHS Preview","text":"RHS Preview RHS Preview lets you inspect an Intan RHS recording without loading the entire waveform into memory. Use it to check channels, move through short waveform windows, choose the channels to plot, and prepare protocol or file-filter JSON for later analysis. Open RHS Preview From the LabKit launcher, select **RHS Preview** and choose **Open**. From a source checkout, run: labkit_RHSPreview_app Preview A Recording On **Setup**, choose one `.rhs` file. The app reads the header, lists the amplifier channels, selects up to eight channels, and displays a short waveform window. Drag **Pan** to move the window through the recording. Scroll over the plot to change the displayed duration around the pointer. Edit the plotted interval on the graph when you need to mark a smaller region. Choose **Zoom to ROI** to make that interval the current window. Choose **Refresh Preview** to reread the current window from disk. Only the displayed window is decoded. This keeps navigation practical for long recordings and does not change the source file. Choose Channels And Roles On **Protocol**, the channel table contains four columns: Column Purpose Plot Show or hide this channel in the stacked preview Role Assign its purpose in the experiment Label Enter a readable name for plots and later analysis Channel Intan channel name read from the recording You may open an existing protocol JSON before editing the table. Choose **Save Protocol Draft** to save the current assignments. A protocol is recommended for response analysis because it makes the stimulation, reference, and response channels explicit. Prepare A File Filter Use **Add RHS files or folder** to collect recordings for a later analysis. The file table lets you assign a label and comment to each recording. Remove individual entries or clear the list as needed, then choose **Save Filter Record**. The filter record identifies the selected files and their labels. It does not copy the RHS recordings or contain decoded waveforms. Saved Project Sources The preview recording, optional protocol, and filter recordings are saved as distinct portable project sources. Older projects that stored these sources separately are combined automatically on load. Project And Session State The durable project stores portable references for one preview recording, one optional protocol, and an ordered collection of filter recordings. It also stores preview settings, channel-role drafts, manual filter labels/comments, and compact export records. Header indices, decoded preview windows, table presentation state, current ROI and window position, status text, and log messages are transient session data. They are reconstructed from the project sources when a project is opened. Review Recording Information The **Review** tab shows the indexed duration, channel counts, current window, selected channels, and recent action. The preview plot uses a separate vertical band for each selected channel so traces do not obscure one another. Read An RHS Window Without The App [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); assert(status.ok, status.message) options = struct( ... \"family\", \"amplifier\", ... \"channels\", [\"A-000\", \"A-001\"], ... \"timeRangeSec\", [1.0 1.5]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", options); assert(status.ok, status.message) `indexFile` reads recording metadata. `readWindow` returns the selected time range together with the sample rate and channel information needed to interpret the waveform matrix. Errors And Limitations RHS Preview currently displays amplifier channels. A channel listed in a protocol must exist in the selected recording. A window is limited to the indexed recording duration; requests outside that range are clamped. Short reads reduce memory and waiting time, but repeated navigation still requires disk access. The app is for inspection and preparation. Use Nerve Response Analysis for stimulus detection and CAP measurements. Related Functions And Apps `labkit.rhs.indexFile` `labkit.rhs.readWindow` `rhs_preview.analysisRun.readPreviewWindow` [Nerve Response Analysis](../nerve-response-analysis/README.md) [RHS library](../../../libraries/rhs/README.md) rhs_preview.analysisRun.readPreviewWindow Read the requested RHS preview window from app state."},{"title":"Statistics Apps","url":"apps/statistics/index.html","kind":"app family","section":"apps","keywords":"apps statistics Statistics Apps","text":"Statistics Apps Statistics Apps start from explicit observations selected or entered by the user. They keep data preparation, hypothesis testing, completed results, and figure design as separate steps so a visual change cannot silently alter a test. Choose An App What you want to do App Main result Select two or more groups, compare each later group with the first, and draw a mean/SD plot [T-Test Wizard](ttest-wizard/README.md) ordered t-test family, group CSV, result CSV, and plot Table And CSV Boundary An input file does not need a LabKit schema. T-Test Wizard first shows CSV, TSV, XLSX, and XLS sources as spreadsheet cells, then lets the user choose the numeric ranges that have scientific meaning. The first captured group is the reference and every later group is tested against it. For exchange between Apps and scripts, prefer the [Simple Scientific CSV Exchange](../../development/data-and-designs/scientific-csv-interchange.md): one header row, one rectangular table, readable labels, and blank cells for missing values. This is a lowest-layer table contract, not a universal experiment schema. Related Documentation [T-Test Wizard manual](ttest-wizard/README.md) [Simple Scientific CSV Exchange](../../development/data-and-designs/scientific-csv-interchange.md) [T-Test Wizard design](../../development/data-and-designs/group-comparison-app-design.md) [All Apps](../README.md)"},{"title":"Digital Image Correlation Apps","url":"apps/dic/index.html","kind":"app family","section":"apps","keywords":"apps dic Digital Image Correlation Apps","text":"Digital Image Correlation Apps The DIC family prepares optical image pairs before correlation and turns Ncorr strain results into presentation images and summary measurements afterward. LabKit does not implement the correlation solver itself. Apps In This Family Stage App Use it to Main result Before correlation [DIC Preprocess](dic-preprocess/README.md) register a moving image to a reference, apply identical crop geometry, and build a mask prepared image pair and binary ROI mask After correlation [DIC Postprocess](dic-postprocess/README.md) map EXX/EYY strain fields onto the reference image and summarize the valid ROI overlay PNGs and strain summary CSV End-To-End Workflow Use DIC Preprocess to align each moving image with the reference. Apply a shared crop and save the prepared pair. Create and save the ROI mask. Run correlation in Ncorr using the prepared images and mask. Load the Ncorr MAT result, matching reference image, and mask into DIC Postprocess. Generate overlays, inspect valid coverage, and export the summary. The two apps deliberately exchange ordinary image and MAT files rather than a private in-memory object. This keeps the external solver boundary explicit and makes each stage independently repeatable. Coordinate And Image Conventions Interactive points and rectangles use MATLAB image coordinates: `x` is the column coordinate and `y` is the row coordinate. Registration and crop operations preserve array class and color channels where the operation permits. Masks are logical images aligned with the displayed reference domain. DIC Preprocess owns geometric edits. DIC Postprocess may resize strain and mask arrays to the reference-image domain for rendering, but it does not alter the original Ncorr MAT file. Related Modules [Image Library](../../libraries/image/README.md) [App Framework interactions](../../framework/README.md) [All Apps](../README.md)"},{"title":"T-Test Wizard","url":"apps/statistics/ttest-wizard.html","kind":"app","section":"apps","keywords":"apps statistics ttest wizard T-Test Wizard","text":"T-Test Wizard T-Test Wizard captures two or more numeric groups, compares every group after the first with the first group, and draws one publication-oriented mean and standard-deviation plot. The first group is always the reference group. Open T-Test Wizard From the LabKit launcher, select **T-Test Wizard** and choose **Open**. From a source checkout, run: labkit_TTestWizard_app The App requires Base MATLAB and the LabKit App Framework. It does not require Statistics and Machine Learning Toolbox. Workflow The left controls use three short pages: **Data** — open a table, choose where selected values go, or clear data. **Test & Plot** — choose one t-test, run all comparisons, review result rows, and adjust plot presentation. **Export** — save all group observations and result rows as separate CSVs. The right workspace has two user-selectable pages: **Data** keeps the opened source table above the editable analysis table, each using half of the workspace. **Plot** gives the completed statistical figure the full workspace. Changing data or test settings does not silently recalculate the plot. A compact status beside the plot controls reports **OUT OF DATE** until **Run / refresh comparisons** is used again. The short **What will run** and **Result family** summaries stay compact so the result table and plot controls receive most of the vertical space. Add Groups From A Table The Data step accepts `.csv`, `.tsv`, `.xlsx`, and `.xls`. Workbook worksheets can be selected from the dropdown. Headers and labels remain visible in the source grid. For each group: select its numeric cells; leave **Source selection to** at `(new group)`, or choose an existing group; choose **Add selected source cells**; repeat for every additional group. Finite numeric cells and numeric text are accepted. Blank and label cells are skipped with a visible count. `NaN` and infinities block that selection until they are excluded or corrected. Opening another file replaces the source grid but keeps all groups already captured. For a new group, the App proposes a name from the nearest useful header above the selection. It follows merged-style headings to the left and combines up to two useful levels, such as `Treatment A - Metric Y`. Dates, row numbers, and numeric summary cells are not used as names. The proposed label remains editable in the analysis table. The lower analysis table uses one row per observation: Column Meaning Group Editable category label; repeated labels belong to one group Value One finite numeric observation Type or paste directly into either column. Blank rows are available for manual entry without a source file. Group order follows the first appearance of each label in this table; the first group is the reference. To change the reference, reorder the corresponding rows and edit any cell so the new order is committed. The interface deliberately does not expose an unexplained `N` column. To relabel many observations at once, select their rows in the analysis table, choose an existing group under **Change selected rows to**, and use **Change selected rows**. Selection is committed when the completed table selection changes, so dragging across a range does not repeatedly rebuild the App while the pointer is still moving. Use **Delete selected rows** to remove only the selected observations; if every observation in a group is deleted, that empty group is removed. **Clear all data** remains the separate full-reset action. Choose The Tests One selected method is applied to every comparison: Choice Calculation and use Independent t-test — Welch Independent groups without an equal-variance assumption; default Independent t-test — equal variances Pooled-variance independent test Paired t-test Row-by-row differences between the first group and each later group The Question dropdown offers two-sided, first greater than comparison, or first less than comparison alternatives. Difference direction is always: first/reference group minus comparison group Alpha defaults to `0.05`. No multiple-comparison correction is applied. The App reports each requested t-test exactly as configured; whether correction, ANOVA, a mixed model, or another method is scientifically appropriate remains the user's responsibility. Every group needs at least two finite observations. Paired testing additionally requires each comparison group to have the same length as the first group. Pairing follows displayed row order and is never inferred from a filename, header, or equal length. Results **Run / refresh comparisons** creates `N - 1` immutable result snapshots for `N` groups. Each row reports: comparison and reference labels; sample counts; group means; reference-minus-comparison difference; t statistic, degrees of freedom, and exact p-value; `NS`, `*`, `**`, `***`, or `****`; stable success or failure status. The thresholds used for the shorthand plot labels are: Label Rule `NS` `p >= alpha` `*` `p < alpha` `**` `p < 0.01` `***` `p < 0.001` `****` `p < 0.0001` The exact p-value remains available in the result table and CSV. Editing groups or test settings afterward does not rewrite the completed family; rerun to create a new family. Plot Style The plot uses the copied completed-result observations, not live editor values. Choose **Mean bars with SD** or **Box plot** without recalculating the tests. Both styles use numeric y-axis ticks, ordinary horizontal x-axis tick labels, and the same annotation geometry. Figure Studio can adjust typography and label presentation after the plot is popped out. The bar visual contract is: one pastel bar per group; bar height equal to the group mean; dark, capped sample-standard-deviation error bars; a zero-anchored y-axis for all-positive or all-negative bars, including after wheel or toolbar zoom; black boxed axes on a white background; Helvetica labels and no grid; stacked brackets from the first bar to every later successful comparison; significance shorthand on each bracket. The first four colors are soft green, orange, blue, and yellow; the palette cycles for additional groups. The box style uses the same group colors with subtler fill, dark outlines, whiskers, and medians. When individual values are shown, the box chart's own outlier markers are suppressed so observations are not drawn twice. Optional controls overlay individual values, hide SD bars on bar plots, hide brackets, and edit the title or y-axis label. Initial limits include bars or boxes, visible individual values, error bars, and significance brackets. **Reset plot view** recomputes that fitted viewport without rerunning the tests or changing project data. Switching between bar and box styles also accepts the new style's fitted limits; ordinary redraws otherwise preserve the user's current zoom. Use **Plot > Pop out all plots** for a standalone MATLAB figure. The popout can be passed to [Figure Studio](../../labkit-core/figure-studio/README.md) for further visual styling without recalculating the tests. CSV Outputs **Export group data CSV** writes one group per column and pads unequal groups with blank cells: Row,Reference,Treatment 1,Treatment 2 1,1.2,1.8,1.1 2,1.4,1.7,1.2 **Export comparison results CSV** writes one row per later group. Columns are: Test, Alternative, Alpha, Reference Group, Comparison Group, N Reference, N Comparison, N Pairs, Mean Reference, SD Reference, Mean Comparison, SD Comparison, Difference Reference-Comparison, SE, T, DF, P, CI Lower, CI Upper, Significance, Status, Message The imported source is never modified or overwritten. Programmatic Use Run a complete first-versus-each family without opening the GUI: groups = struct( ... \"label\", {\"Reference\", \"Treatment 1\", \"Treatment 2\"}, ... \"values\", {[1.2 1.4 1.3], [1.8 1.7 2.0], [1.1 1.2 1.4]}); options = struct( ... \"method\", \"welch\", ... \"alternative\", \"two_sided\", ... \"alpha\", 0.05); results = ttest_wizard.testRun.runGroupTTests(groups, options); [`runGroupTTests`](../../../reference/api/ttest_wizard/testRun/runGroupTTests.html) returns one canonical [`runTTest`](../../../reference/api/ttest_wizard/testRun/runTTest.html) result per later group. Project Recovery Project schema version 2 stores ordered groups, test settings, plot settings, completed comparison results, and source references. Version-1 A/B projects migrate by preserving A as the first group and B as the second group. Assumptions And Limitations Observations must share a meaningful measurement unit. Independent tests assume independence within and between groups. Paired tests use the displayed row order for every first-versus-group pair. The App does not infer experimental units, exclusions, independence, biological replication, or the correct statistical model. No multiple-comparison correction, ANOVA, nonparametric test, regression, or mixed model is performed. Plot shorthand does not replace the exact p-value or establish scientific importance. Related Documentation [Statistics Apps](../README.md) [T-Test Wizard design](../../../development/data-and-designs/group-comparison-app-design.md) [Simple Scientific CSV Exchange](../../../development/data-and-designs/scientific-csv-interchange.md) [Figure Studio](../../labkit-core/figure-studio/README.md) [App Framework](../../../framework/README.md) ttest_wizard.testRun.runGroupTTests Compare every group after the first with the first group. ttest_wizard.testRun.runTTest Run one independent Welch, pooled, or paired t-test."},{"title":"Wearable Apps","url":"apps/wearable/index.html","kind":"app family","section":"apps","keywords":"apps wearable Wearable Apps","text":"Wearable Apps Wearable apps read physiological recordings, preserve their time and channel information, and provide focused analysis and reporting workflows. Choose An App What you want to do App Filter ECG, detect beats, inspect segment quality, and export a printable waveform [ECG Print](ecg-print/README.md) Use Biosignal Functions Directly The [Biosignal library](../../libraries/biosignal/README.md) provides the same building blocks used by ECG Print: recording import, channel selection, filtering, peak detection, event-centered segments, template construction, and segment measurements. Use those functions when you need a script or batch workflow rather than the app. Related Documentation [ECG Print manual](ecg-print/README.md) [Biosignal library](../../libraries/biosignal/README.md) [App catalog](../README.md)"},{"title":"ECG Print","url":"apps/wearable/ecg-print.html","kind":"app","section":"apps","keywords":"apps wearable ecg print ECG Print","text":"ECG Print ECG Print reads a wearable recording, filters one channel, detects beats, builds event-centered segments and a representative template, and reports signal quality over time. It can export the segment measurements and a printable waveform image. Open ECG Print From the LabKit launcher, select **ECG Print** and choose **Open**. From a source checkout, run: labkit_ECGPrint_app Supported Inputs Choose **Open recording** to load a MAT, CSV, TXT, or TSV biosignal file. MAT files can contain a supported timetable or recording structure. Delimited text files can be detected automatically or parsed with the controls under **Import Parsing**. For a difficult text file, choose **Preview file header**, then set: the line containing column names; whether a header is present; the time column and its unit; the signal columns; a fallback sample rate when time values are unavailable. Choose **Parse / refresh file** after changing import settings. The app reports the detected channels and lets you select one for analysis. The recording is saved as a portable project source. Older projects are upgraded on load. Project And Session State ECG Print stores only durable workflow state in its project file: the portable recording source; import, channel, ROI, filter, detector, segment, template, and view settings; the compact last-analysis summary and export records. Decoded recordings, signal arrays, events, segments, templates, measurements, header previews, and plot models are transient session data. They are rebuilt from the recording and durable parameters when a project is opened. This keeps saved projects portable and avoids duplicating large waveform caches. Analyze ECG Open and parse a recording. Select a channel and, if needed, set the start and end of the time region. When the end is not greater than the start, the full signal is used. Set the bandpass range and peak method. Set minimum peak distance, segment half-window, template count, and smoothing count. Choose **Analyze current ROI**. Review the four plots: waveform and peaks, template-noise RMS, template SNR, and the template with either a residual band or individual segments. The filter is applied to the full selected channel before the time region is cropped. This reduces boundary artifacts at the region edges. The **Files + Analysis** tab keeps the workflow in five ordered sections: **Recording**, **Import Parsing**, **Channel + ROI**, **Signal Processing + SNR**, and **Exports**. Bounded numeric settings use paired spinner-and-slider controls. **Summary + Results** contains the analysis summary and file-header preview. **Tools > Diagnostics > Open Session Log...** records the current workflow and earlier runtime context. The **ECG Preview** workspace keeps four vertically stacked time-series axes available on every tab. Analysis Parameters Parameter Default Meaning Fallback sample rate 2000 Hz Used only when import cannot derive time spacing Bandpass 0.5 to 40 Hz Applied before peak detection; the upper cutoff is kept below 45% of sample rate Peak method QRS streaming QRS streaming, Pan-Tompkins, or local peaks Peak distance 0.28 s Minimum interval between accepted detections Segment half-window 0.7 s Data retained before and after each event Template top N 30 Number of best segments used to build the template Smooth beats 15 Smoothing span used in exported per-segment trends Template plot Template + residual band Alternative view is template plus individual segments Peak polarity is selected automatically. The default detector threshold is 2.8 standard deviations inside the app calculation. Output Files **Export segment SNR CSV** writes `ecg_segment_snr.csv` and a matching `ecg_segment_snr.labkit.json` manifest. The CSV contains per-segment measurements and smoothed trends. **Export waveform PNG** writes `ecg_waveform.png` and `ecg_waveform.labkit.json`. The manifest records the source, import and analysis settings, output identity, event count, segment count, and summary. Analyze A Signal In MATLAB Code [recording, status] = labkit.biosignal.readRecording(\"ecg.csv\"); assert(status.ok, status.message) signal = labkit.biosignal.getChannel(recording, 1); cache = struct(\"signal\", signal); parameters = struct( ... \"lowCut\", 0.5, ... \"highCut\", 40, ... \"roiStart\", 0, ... \"roiEnd\", 0, ... \"peakMethod\", \"QRS streaming\", ... \"peakDistance\", 0.28, ... \"segmentWindow\", 0.7, ... \"templateTopN\", 30); cache = ecg_print.analysisRun.analyzeSignal(cache, parameters); The returned cache contains the working and filtered signals, detected events, segments, template, and measurements. For a more customized pipeline, call `labkit.biosignal.filterSignal`, `detectEcgPeaks`, `segmentByEvents`, `buildTemplate`, and `measureSegments` directly. Errors And Limitations Time or a valid sample rate is required to interpret filter frequencies and event spacing. Automatic parsing should be checked when a text file has long preambles, unusual headers, or mixed metadata rows. A failed **Parse / refresh file** keeps the selected source and header preview available so import settings can be corrected in place. Automatic peak polarity and thresholds still require visual review when morphology or noise changes within a recording. Filtering and detection settings must accompany any reported heart rate or signal-quality result. Related Functions And Documentation `ecg_print.analysisRun.analyzeSignal` `labkit.biosignal.readRecording` `labkit.biosignal.detectEcgPeaks` `labkit.biosignal.measureSegments` [Biosignal library](../../../libraries/biosignal/README.md) ecg_print.analysisRun.analyzeSignal Rebuild ECG analysis products from one decoded signal cache."},{"title":"Development","url":"development/index.html","kind":"overview","section":"development","keywords":"development Development","text":"Development Use these pages for source changes and maintenance. App users normally need only [Getting started](../getting-started/README.md) and the [app guide](../apps/README.md). Read By Task Task Page Understand repository and package ownership [Architecture](build-apps/architecture.md) Create or refactor an app [App development](build-apps/app-development.md) and [complete app tutorial](build-apps/complete-app.md) Exchange simple scientific tables or gradually improve an App CSV export [Simple scientific CSV exchange](data-and-designs/scientific-csv-interchange.md) Develop the proposed cell-selection-based t-test App [T-Test Wizard design](data-and-designs/group-comparison-app-design.md) Work with an ignored private app repository [Private apps](maintain-and-release/private-apps.md) Select tests, GUI checks, or profiling [Testing](maintain-and-release/testing.md) Call source-checkout packaging, profiling, codecheck, or documentation tools [Maintainer tools](tools/README.md) Maintain the documentation sources and generated site [Documentation system](maintain-and-release/documentation.md) Choose versions, tags, and release evidence [Release process](maintain-and-release/release.md) Reusable facade calls are documented separately in the [public API reference](../reference/README.md). Launcher behavior belongs with the [LabKit Core apps](../apps/labkit-core/README.md), not in maintainer-only development pages."},{"title":"App Development","url":"development/build-apps/app-development.html","kind":"guide","section":"development","keywords":"development build apps app development App Development","text":"App Development LabKit apps are first-class deliverables. Each app owns its scientific workflow, state, plots, result schema, and exports; the reusable framework owns lifecycle and domain-neutral UI mechanics. Create An App Start from the LabKit app template rather than copying a complete neighboring app. Use the smallest nearby app only as a workflow example. A static App begins with only the product entrypoint, definition, and layout it actually uses: apps///labkit__app.m apps///+/definition.m apps///+/+workbench/buildLayout.m `labkit.app.Definition` supplies empty project/session and default presentation behavior when optional components are omitted. Bind real business callbacks directly from the layout. Add `+workbench/present.m` for derived visible state, `createSession.m` for transient decoded/cache state, and `projectSpec.m` only when the App owns durable data. That single project file contains local create, validate, and migrate functions. Its migrate callback exists only after a saved project schema has actually changed and only for the older payload versions the App explicitly supports; Runtime owns the version loop. Current saves always write the current payload version. Use an exact declared `LegacyImports` variable adapter only for real pre-envelope user files, never as a shape-guessing fallback. Runtime and App architecture names remain versionless. Put facade/App compatibility in the existing version and requirement metadata, and put saved payload numbers only in `projectSpec` migration logic. Do not create version-named packages, files, functions, types, tests, or manual sections. The project validator owns only App-specific requirements: domain fields, legal choices and ranges, cross-field relationships, source roles, and scientific invariants. Runtime validates the five canonical project buckets and standard portable source records before the callback runs. Do not repeat those framework checks in each App. Create additional packages only for concrete workflows that need them, for example `+sourceFiles`, `+analysisRun`, `+resultFiles`, `+cropGeometry`, or `+thermalFrames`. Avoid generic buckets such as `+actions`, `+ops`, `+io`, `+helpers`, and `+utils`. Define The Runtime Contract `definition.m` returns one immutable `labkit.app.Definition`. It is the App's single product contract and names the public command, stable ID, display metadata, App version, compatible LabKit facades, and workbench. Project schema, session factory, presenter, post-layout start callback, and debug sample are opt-in capabilities. Callbacks and renderers are owned directly by their layout nodes. The complete field tables, callback signatures, canonical project/session buckets, presenter shape, and renderer contract are documented in [Runtime and Lifecycle](../../framework/guides/runtime.md#definition-component-contract). For a complete file-by-file implementation, follow [Build a Complete App](complete-app.md). The framework owns: startup, readiness, and busy state queued callback dispatch and rollback project save/load and recovery managed interactions and resources debug tracing and result manifests The app owns durable `state.project`, transient `state.session`, semantic callbacks, presentation models, and scientific behavior. Build The Workbench `+workbench/buildLayout.m` returns a data-only `labkit.app.layout.*` tree. Keep tabs, sections, and workspace pages in the same order users see them. For a complex App, compose capability-owned layout fragments instead of flattening every control into this file. It must not create graphics handles, read files, run calculations, mutate state, or schedule startup work. `+workbench/present.m` is the pure assembly bridge from canonical state to a complete `labkit.app.view.Snapshot`. Compose feature-owned fragments with `Snapshot.include`. Renderers live with the capability they draw, receive only axes and a prepared model, and do not own workflow decisions. Use the complete runtime value only at this assembly bridge and at callbacks referenced directly by layout signals. Name it `applicationState`, unpack `project` and `session` at the top, and call feature presenters with the exact values they display. A feature presenter should look like `present(results, selection, displayOptions)`, not `present(state)`. A direct callback is the transaction adapter. Its signature is `(applicationState, callbackContext)` for a button or `(applicationState, typedEventValue, callbackContext)` for a value-bearing signal. It may perform short, visible state mutation in workflow order, then delegate calculations and writes through explicit inputs. Do not pass the complete state or callback context into a generic second action layer. Name Workflow Code Use concrete verb-object names such as: +sourceFiles/readSourceFiles.m +analysisRun/collectAnalysisOptions.m +analysisRun/computeAnalysisResults.m +resultFiles/writeResultFiles.m Keep callback-local glue local when extracting it would hide state mutation or workflow order. Extract a helper when it owns a stable calculation, data shape, file boundary, state transition, or reusable interaction contract. File length alone is not an extraction reason. Data Loading And Task Lifecycle Register large file selections with the least data needed for the first useful preview. Decode the selected file on demand and defer full-batch work until the user runs or exports when the workflow permits it. Apps with preview, edit, run, or export stages should make dirty state and the last successful task explicit. Pure helpers build deterministic task snapshots and calculations; result writers receive explicit task data rather than reading UI handles. Cross-App Data Contracts Apps exchange saved, documented data contracts; production and debug code do not call a sibling App package. A consumer owns its parser and error language, so it can launch with only the framework and its own App root on the MATLAB path. A producer owns serialization and schema validation. Keep one producer-consumer integration test that invokes both Apps and proves the current saved format remains compatible; keep consumer unit and debug fixtures independent of the producer package so the shared test path cannot hide a runtime dependency. Ownership Check Keep these in the app: accepted formats, domain defaults, units, and thresholds formulas and scientific interpretation plots, labels, result columns, and export schemas workflow ordering, user messages, and logs Move code into `+labkit` only when it is domain-neutral, independent of app state, directly testable, used broadly, and makes the public API clearer. See [Architecture](architecture.md#reusable-extraction-rule). Version And Requirements `definition.m` owns supported LabKit facade ranges together with the App command, display name, family, semantic version, and last change date. There is no second metadata registry. App behavior changes update this definition, the App documentation, and component-owned history in the same coherent change. Validation Use focused app tests while editing, then follow the stable gates in [Testing](../maintain-and-release/testing.md). Automated GUI tests cover launch, layout, callbacks, debug plumbing, and synthetic workflows; visual quality and manual interaction feel still require a human MATLAB check. Related Reference [App catalog and workflows](../../apps/README.md) [App Framework](../../framework/README.md) [Architecture](architecture.md) [Testing](../maintain-and-release/testing.md) labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot."},{"title":"Architecture","url":"development/build-apps/architecture.html","kind":"guide","section":"development","keywords":"development build apps architecture Architecture","text":"Architecture [Development index](../README.md) | [Public API index](../../reference/README.md) | [LabKit Launcher](../../apps/labkit-core/launcher/README.md) | [Maintainer Tools](../tools/README.md) LabKit is an app-first MATLAB workbench. Apps are the deliverables; `+labkit` is the small reusable foundation they share. Project Model apps/ workflow-specific GUI apps and app-owned helpers +labkit/ reusable UI, image, thermal, DTA, RHS, and biosignal facades tests/ behavior tests, project contracts, GUI checks, shared helpers, and runner code docs/ human-facing usage, API, architecture, and validation docs tools/ maintainer diagnostics, deployment packagers, and report generators .github/scripts/ GitHub Actions-only helper scripts The repository root has no generic `scripts/` bucket. Keep automation beside its single consumer; promote it to a shared owner only when several stable consumers genuinely need the same operation. Apps should remain independently launchable. The reusable library should grow only when a helper is domain-neutral, app-facing, tested, and useful beyond one workflow. Runtime Dependency Boundary LabKit apps run from MATLAB and repository-owned code. Production apps and facades do not create Python or Conda environments, install third-party runtime packages, download model weights, or require a network connection on first use. This keeps source checkouts, offline packages, and restored lab systems reproducible. During rapid development, an app may temporarily accelerate a capability with a MathWorks Toolbox only when it also ships a repository-owned base-MATLAB implementation with comparable user-visible behavior. The app must remain usable without the product, and automated tests must exercise that fallback. When values feed scientific interpretation, branching, exports, or later calculations, identical inputs must be idempotent: pure calculations reproduce the same app-consumed values, while safely repeated stateful operations do not compound state or side effects. A parity test must compare app-consumed outputs against the Toolbox reference within a documented tolerance. Visual similarity is not sufficient evidence. Record each temporary Toolbox dependency with its source function, MathWorks product, responsible app, fallback tests, idempotency/parity evidence, and replacement plan. Dependency reports continue to show the Toolbox call while that debt is open. The debt closes when the repository implementation replaces the Toolbox branch and the branch is removed. Adding any third-party runtime remains an architecture and deployment decision requiring explicit approval; it is not an ordinary app-local implementation choice. Version Ownership Facade and App compatibility is expressed by the existing version metadata and dependency-requirement components. Saved-data schemas keep explicit numeric versions only inside their persistence and migration contracts. Release and history records preserve the versions needed as evidence. Architecture concepts themselves are versionless. Packages, folders, files, functions, classes, type tags, protocol names, tests, and current manuals use stable semantic names. An incompatible redesign replaces the owning contract and updates version metadata; it does not create a second version-named architecture. Runtime Entrypoints Users normally start with: labkit_launcher The launcher discovers public `apps/**/labkit_*_app.m` entry points. Public app command names are stable user entry points, for example `labkit_CIC_app`, `labkit_DICPreprocess_app`, `labkit_ECGPrint_app`, and `labkit_RHSPreview_app`. Source checkouts may also keep local private apps under an ignored `private_apps/apps/` workspace or roots named by `LABKIT_PRIVATE_APP_ROOTS`. Any developer can create that local workspace for their own private apps. The launcher can list and launch those apps with `Visibility` set to `private`, but public releases and CI checks cover only the public `apps/` tree. Keep each private workspace as a separate private Git repository rather than mixing private app files into the public repo history. The public structure guide is [private-apps.md](../maintain-and-release/private-apps.md); private app documentation belongs in the private workspace. The launcher keeps update, discovery, and repair logic self-contained: it uses native MATLAB UI and local helper functions so users can repair a damaged zip install even if packages, apps, docs, or scripts have been deleted. It configures the MATLAB path for app entry points. MATLAB desktop project metadata belongs to each developer's local workspace. Tools under `tools/` are source-checkout support utilities rather than app runtime APIs. The launcher may call a small, explicit subset for maintenance and deployment actions, such as profiling a selected app or packaging a single app for offline deployment. Single-app deployment packages include the launcher and only those launcher-needed tool folders, not the whole source checkout. Direct syntax, options, outputs, and artifact behavior are documented in [Maintainer Tools](../tools/README.md). Ownership Boundaries Area Owns App entry point Public launch name delegated to one runtime definition, including lightweight requirements/version/debug requests. App package App definition, workflow state, command handlers, presenters, calculations, summaries, exports, and app-local helpers. `labkit.app` App definition and launch, semantic layout and view snapshots, typed events, callback capabilities, projects, diagnostics, results, interactions, and private native runtime lifecycle. `labkit.image` GUI-free image file IO, display normalization, resizing, mean filtering, and basic enhancement primitives. `labkit.thermal` GUI-free thermal source-file parsing, raw thermal matrices, embedded calibration metadata, raw-to-temperature conversion, and thermal colormap rendering. `labkit.dta` GUI-free Gamry DTA discovery, loading, parsed curves, and pulse helpers. `labkit.biosignal` GUI-free recording import, channel extraction, filtering, events, segments, templates, and measurements. `labkit.rhs` GUI-free Intan RHS discovery, header parsing, block indexing, and lazy waveform window reads. Apps own experiment-specific vocabulary, thresholds, protocol roles, plots, result schemas, export formats, alerts, and log wording. Reusable facades own domain-neutral mechanics that multiple apps can share. App Package Shape The target app shape is workflow-first. Each app keeps one MATLAB package under its app folder: apps///labkit__app.m apps///+/definition.m apps///+/+workbench/buildLayout.m Those three files are a complete static App. Add only the capabilities the product needs: projectSpec.m durable create/validate/migrate contract createSession.m transient App-specific reconstruction +workbench/present.m dynamic snapshot assembly +/layoutSection.m feature-owned controls +/present.m feature-owned snapshot fragment +/draw.m feature-owned plot renderer `definition.m` is the single product contract. It owns identity, display metadata, App version, facade requirements, layout, and references to any optional capabilities. `projectSpec.m` is the one durable-schema entry and keeps its create, validate, and version-aware migrate functions local. Runtime owns the migration loop. Do not add separate `requirements.m`, `version.m`, generic `+appLifecycle` or `+appState` packages, or per-version migration files. Add workflow packages only when the App has that user-facing capability: +sourceFiles/ choosing, reading, validating, and previewing source data +analysisRun/ collecting options, computing results, and showing results +resultFiles/ choosing output folders, writing files, and summarizing exports +cropGeometry/ app-owned crop geometry operations +thermalFrames/ app-owned thermal frame queues and display choices +syntheticInputs/ app-owned clean-room synthetic input generation Create only the packages the app needs. Names should describe a workflow or domain capability that changes together, not a broad technical phase. Avoid generic `+actions`, `+renderers`, `+ops`, `+io`, `+ui`, `+userInterface`, `+view`, `+export`, `+helpers`, and `+utils` packages for new app code. Avoid fixed `+app` namespaces, family-level `private/` helpers, `*Workflow.m` string dispatchers, and `+core/dispatch.m` routers. `+state`, `+actions`, `+ui`, `+view`, `+ops`, `+io`, and `+export` packages were retired with the workflow-first migration. Current app work should follow the workflow-first shape. App SDK Boundary App GUIs use the explicit `labkit.app` SDK: Layer App-facing API Definition `labkit.app.Definition` owns identity, requirements, optional project/session boundaries, workbench, presentation, and launch. Layout `labkit.app.layout.*` owns semantic controls, containers, workspace pages, direct callbacks, bindings, and plot renderers. View `labkit.app.view.Snapshot` owns complete derived visible state and prepared renderer models. Callback boundary Typed `labkit.app.event.*` values and sealed, specifically named `labkit.app.CallbackContext` operations. Optional contracts `labkit.app.project.*`, `result.*`, and `dialog.*`. Reusable facades publish MATLAB-native contract versions through their `version()` APIs. Apps declare required facade ranges in the `Requirements` field of `definition.m`, and `labkit.contract` checks those ranges in tests and at launch. This is a same-repo maintenance guardrail; routine users still update LabKit as one repository. Apps publish `AppVersion` and `Updated` metadata from the same definition for the launcher and window title. App versions are not dependency constraints and do not belong in `labkit.contract`. Project guardrails check `X.Y.Z` format and require versioned code changes to increase the corresponding definition, launcher, or facade version. The [release guide](../maintain-and-release/release.md) explains how to select and publish the next version. Image workflows may use `labkit.image` for generic image file filters, source image reads, display-name normalization, RGB double conversion, preview-size fitting, mean filtering, basic enhancement primitives, and image writes. Apps still own processing step semantics, ROI/background policy, matching formulas, crop geometry, focus-stack algorithms, DIC behavior, export schemas, and user workflow text. Thermal workflows may use `labkit.thermal` for radiometric source reads, embedded calibration metadata, raw thermal matrices, Celsius conversion, and linear thermal palette rendering. Apps still own file queues, display-range defaults, log/gamma display-mapping controls, export manifests, colorbar placement, overlay-removal workflow wording, measurements, and user-facing decisions. Generic image IO and filters stay in `labkit.image`; thermal file parsing and raw-to-temperature mechanics stay in `labkit.thermal`. `definition.m` returns the app runtime contract. Layout nodes own concrete callbacks and renderers, so Apps maintain no parallel registries. The framework compiles the static graph, builds the shell, owns the transactional event queue, persistence, resources, diagnostics, and private native adapter. `+workbench/buildLayout.m` should read as the product's user workflow. Capability packages own their controls, state transitions, prepared view models, rendering, alerts, and wording. `+workbench/present.m` extracts exact state inputs and composes those feature fragments; it is not a second monolithic implementation of the App. The state funnel The runtime commits one canonical value containing `project` and `session`, but that value is an SDK transaction envelope, not the App's domain model. Keep it at the edge: layout signal -> capability action(applicationState, event, callbackContext) -> calculation(exact scientific inputs) -> writer(exact result and destination inputs) runtime presentation -> workbench.present(applicationState) -> capability.present(exact visible inputs) -> renderer(axes, prepared model) Only `createSession`, `workbench.present`, `OnStart`, and a function bound directly to a layout signal accept the complete envelope. Those functions name it `applicationState`, expose the typed event and `callbackContext` in their signatures, and unpack it immediately. Feature presenters, renderers, calculations, writers, and local helpers receive named narrow inputs. Do not introduce a second App object, service bag, callback registry, or feature-wide context type to hide these inputs. If a calculation needs five scientific values, list those five values. If several always travel together and form a stable app-owned concept, give that concept a semantic struct and validate it at its owner. Reusable Extraction Rule A helper may move into `+labkit` only when all of these are true: It can be named without experiment-specific vocabulary. It does not encode app-specific units, thresholds, plots, result columns, or export schemas. It does not read or mutate app state. It is independently testable. At least two real apps use it, or one facade clearly needs it. It makes the app-facing API clearer rather than broader and vaguer. If those conditions are not met, keep the helper app-local. Extraction Quality Line budgets are maintainability backstops, not architecture goals. A smaller file is only better when the responsibilities are clearer. Extracted helpers should own a coherent behavior contract: a stable data shape, an explicit side effect, a GUI-free calculation, an export boundary, a display model, or a reusable app-facing framework mechanism. Trivial label formatters, single boolean checks, constant lists, and one-call facade wrappers can remain local, nested, or inline when that makes the workflow easier to follow. There is no minimum useful helper length. Small public facades, factories, filters, defaults, and test-facing helpers are valid when the name protects a real contract. Conversely, a long extracted helper is not reusable merely because it moved out of `run.m`; it must improve ownership, testability, or the app-facing API. When reviewing helper organization, prefer evidence from the helper's boundary role, call sites, tests, side effects, and ownership over raw line count. Repository line budgets count effective MATLAB code lines: blank lines and comment-only documentation do not consume the budget. Physical line counts remain useful diagnostic context, but they must not discourage complete function help. Validation Boundary The default automated validation boundary is: buildtool headless This covers project contracts, reusable facade behavior, and non-GUI app helper behavior. GUI checks cover launch, layout, callback wiring, trace plumbing, reusable tool lifecycle, and hidden synthetic app workflows. Manual MATLAB review is still required for full interactive workflow feel. labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot."},{"title":"Build A Complete App","url":"development/build-apps/complete-app.html","kind":"guide","section":"development","keywords":"development build apps complete app Build A Complete App","text":"Build A Complete App This guide builds a small trace viewer with the production `labkit.app` contract. The example keeps durable settings in a project, rebuilds transient file data in a session, draws a plot, and exports a result. Each file has one visible responsibility. File Shape apps/examples/trace_viewer/ |-- labkit_TraceViewer_app.m `-- +trace_viewer/ |-- definition.m |-- projectSpec.m |-- createSession.m |-- +workbench/ | |-- buildLayout.m | `-- present.m |-- +sourceTrace/ | |-- readTrace.m | |-- layoutSection.m | |-- workspacePlot.m | |-- present.m | `-- draw.m `-- +resultFiles/ |-- layoutSection.m `-- exportTrace.m There is no handler table, renderer registry, or `+userInterface` bucket. `+workbench` is the product assembly boundary. Capability packages own their part of the user workflow. 1. Thin Entrypoint function varargout = labkit_TraceViewer_app(varargin) [varargout{1:nargout}] = trace_viewer.definition().launch(varargin{:}); end The entrypoint does not build state, controls, services, or figures. 2. One App Definition function app = definition() app = labkit.app.Definition( ... Entrypoint=\"labkit_TraceViewer_app\", ... AppId=\"examples.trace-viewer\", ... Title=\"Trace Viewer\", ... Family=\"Examples\", ... AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... ProjectSchema=trace_viewer.projectSpec(), ... CreateSession=@trace_viewer.createSession, ... Workbench=trace_viewer.workbench.buildLayout(), ... PresentWorkbench=@trace_viewer.workbench.present); end Definition is a readable inventory, not an execution script. It validates the static layout, direct callback signatures, plot renderers, target IDs, project schema, and presentation contract before native UI mutation. 3. Durable Project function schema = projectSpec() schema = labkit.app.project.Schema( ... Version=1, ... Create=@createProject, ... Validate=@validateProject); end function project = createProject() project = struct( ... \"inputs\", struct(\"sources\", struct([])), ... \"parameters\", struct(\"gain\", 1), ... \"results\", struct(\"lastExport\", [])); end function accepted = validateProject(project) accepted = isstruct(project) && isscalar(project) && ... isfield(project, \"inputs\") && isfield(project.inputs, \"sources\") && ... isfield(project, \"parameters\") && ... isnumeric(project.parameters.gain) && ... isscalar(project.parameters.gain) && ... isfinite(project.parameters.gain); end The project contains only durable App meaning. External files are portable source records owned by the runtime, not raw paths. 4. Transient Session function session = createSession(project, callbackContext) paths = callbackContext.resolveSourcePaths(project.inputs.sources); trace = struct(\"x\", [], \"y\", []); if ~isempty(paths) trace = trace_viewer.sourceTrace.readTrace(paths(1)); end session = struct(\"trace\", trace); end `createSession(project,callbackContext)` is the one reconstruction boundary. The runtime calls it after project restore and file-list source changes. Session data is reconstructible and is not written into the project envelope. 5. Product Layout function layout = buildLayout() controls = { ... trace_viewer.sourceTrace.layoutSection(), ... trace_viewer.resultFiles.layoutSection()}; workspace = labkit.app.layout.workspace( ... trace_viewer.sourceTrace.workspacePlot()); layout = labkit.app.layout.workbench( ... controls, Workspace=workspace); end The assembly reads in user order. A complex App can use tabs and workspace pages here without flattening every feature into one file. The source capability owns its controls: function section = layoutSection() section = labkit.app.layout.section(\"sourceTrace\", \"Trace\", { ... labkit.app.layout.fileList(\"traceFiles\", ... Label=\"Trace file\", ... SelectionMode=\"single\", ... Bind=\"project.inputs.sources\", ... SourceRole=\"trace\", ... SourceIdPrefix=\"trace\"), ... labkit.app.layout.slider(\"gain\", ... Label=\"Gain\", Limits=[0.1 10], ... Bind=\"project.parameters.gain\")}); end Standard fields and file lists need no App callback. Their bindings update canonical state transactionally. The result capability binds a real business action directly: function section = layoutSection() section = labkit.app.layout.section(\"resultFiles\", \"Results\", { ... labkit.app.layout.button(\"exportTrace\", ... \"Export trace\", @trace_viewer.resultFiles.exportTrace, ... Tooltip=\"Export the calibrated trace and its sampling metadata.\")}); end 6. Complete View Snapshot function view = present(applicationState) view = labkit.app.view.Snapshot(); view = view.include(trace_viewer.sourceTrace.present( ... applicationState.session.trace, ... applicationState.project.parameters.gain)); view = view.enabled(\"exportTrace\", ... ~isempty(applicationState.session.trace.x)); end `+workbench/present.m` is a short assembly boundary. It extracts exact inputs and composes feature fragments with `Snapshot.include`; it does not perform IO or calculation. function view = present(trace, gain) model = struct(\"x\", trace.x, \"y\", trace.y .* gain); view = labkit.app.view.Snapshot().renderPlot(\"tracePlot\", model); end 7. Feature-Owned Renderer function node = workspacePlot() node = labkit.app.layout.plotArea( ... \"tracePlot\", @trace_viewer.sourceTrace.draw); end function draw(axesById, model) ax = axesById.main; cla(ax); plot(ax, model.x, model.y); xlabel(ax, \"Time (s)\"); ylabel(ax, \"Signal\"); grid(ax, \"on\"); end The plot area references the concrete renderer. The model carries prepared App meaning; drawing does not read project state or dispatch workflow actions. 8. Direct Business Callback function applicationState = exportTrace( ... applicationState, callbackContext) trace = applicationState.session.trace; gain = applicationState.project.parameters.gain; choice = callbackContext.chooseOutputFile( ... {\"*.csv\", \"CSV files\"}, \"\"); if choice.Cancelled return; end tableValue = table(trace.x(:), trace.y(:) .* gain, ... VariableNames=[\"Time\", \"Signal\"]); writetable(tableValue, choice.Value); applicationState.project.results.lastExport = string(choice.Value); end The callback signature exposes its runtime boundary. For larger exports, delegate table construction and result packaging to functions that accept only the trace, gain, and destination they require. Validation Test readers, calculations, project validation, snapshot fragments, renderers, and exports directly with synthetic values. Construct `definition()` in a headless test to validate the whole static contract. Run the App's bounded hidden-GUI workflow after smaller tests are stable; native dialogs and visual quality still require developer-led interactive validation. Before merge, update the App version, owning manual, structured component history, generated documentation site, and the final branch validation gates. Related Documentation [App Development](app-development.md) [Architecture](architecture.md) [LabKit App SDK](../../framework/README.md) [Testing](../maintain-and-release/testing.md) labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.slider Add a bounded numeric slider. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.project.Schema Declare one durable App project contract. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"T-Test Wizard Design","url":"development/data-and-designs/group-comparison-app-design.html","kind":"guide","section":"development","keywords":"development data and designs group comparison app design T-Test Wizard Design","text":"T-Test Wizard Design [Development index](../README.md) | [App manual](../../apps/statistics/ttest-wizard/README.md) | [App development](../build-apps/app-development.md) | [Simple scientific CSV exchange](scientific-csv-interchange.md) Product Boundary T-Test Wizard answers: Given two or more ordered numeric groups, what are the configured t-test results for every later group versus the first, and how should that completed family be visualized? The App owns visible group selection, order, test settings, result rows, plot meaning, export schemas, and failure wording. It does not choose the scientifically correct test, infer independence or pairing, apply automatic multiple-comparison correction, or replace ANOVA and repeated-measures models. Field Value Display name T-Test Wizard Command `labkit_TTestWizard_app` App ID `ttest_wizard` Family Statistics Version 1.0.0 No new `+labkit` facade is involved. The multi-group policy stays App-local. Workflow Select or enter groups -> order groups with reference first -> choose one test specification -> run group 2..N versus group 1 -> review result family -> draw bar or box comparison plot -> export group and result CSVs The left controls are Data, Test & Plot, Export, and Log. The right workspace has native Data and Plot pages. Data stacks the opened source table and editable analysis table at equal height; Plot gives the figure the full workspace. A compact freshness field stays beside the plot controls. Result rows stay beside the test controls, removing a result-only navigation step. Data Boundary The source grid accepts readable CSV, TSV, XLSX, and XLS tables. Selection is schema-free: finite numeric cells and numeric text are copied in visible spreadsheet order; blanks and labels are skipped with counts; nonfinite numeric cells block the selected range. Each capture appends one durable group: group.label group.values group.sourceDisplayName group.sheet group.cellAddresses Only `label` and `values` participate in calculation identity. Source metadata supports traceability and remains App-owned. A source can be replaced without discarding captured groups. The ordered group editor has `Group` and `Value` columns with one observation per row. Repeated labels form one group, and first label appearance defines group order. The first group is the reference. Blank rows support manual entry. The capture dropdown contains `(new group)` plus current labels, so a source selection can create a group or append to an existing one without an unexplained count column. For new groups, the App scans upward from the selected numeric cells and combines at most two nonnumeric header levels. Looking left on the same header row supports merged-style spreadsheet headings. Dates, numeric summaries, and row numbers are excluded. The result is only an editable suggestion, not a calculation identity inferred from the source. The editor supports batch group changes and deletion of selected observation rows. Deleting the final observation removes the empty group; clearing all data remains a distinct action. Table selection is delivered after the completed selection changes so pointer dragging does not trigger repeated presentation commits. Statistical Contract `runTTest` remains the two-vector primitive. `runGroupTTests` calls it once for each later group: for k = 2:numel(groups) results(k - 1) = runTTest( ... groups(1).values, groups(k).values, optionsForTheseLabels); end Common rules: difference direction is reference minus comparison; result order matches group order after the reference; the same method, alternative, and alpha apply to the family; Welch uses Welch-Satterthwaite degrees of freedom; pooled testing uses the selected equal-variance model; paired testing analyzes displayed reference(k)-comparison(k) differences; paired length is checked independently for each later group; Student-t probabilities and confidence intervals use the Base MATLAB implementation already owned by the App; each comparison preserves stable failure status instead of fabricating a number; no p-value correction is applied. The completed result family stores copies of the reference vector and each comparison vector. Plot callbacks consume those copies and cannot recalculate the tests. Result And Plot Model Each result row includes the method, alternative, alpha, labels, sample counts, means, SDs, reference-minus-comparison estimate and interval, standard error, t statistic, degrees of freedom, exact p-value, status, and message. Significance shorthand is presentation metadata: p < 0.0001 -> **** p < 0.001 -> *** p < 0.01 -> ** p < alpha -> * otherwise -> NS The plot contract follows the supplied laboratory reference figure: white axes and canvas; black box and ticks, no grid; explicit numeric y ticks and ordinary horizontal x-axis tick labels; modest default typography intended for later Figure Studio adjustment; either one pastel mean bar with sample-SD error bar or one colored box per group; black edge, cap, and annotation strokes; stacked brackets from x=1 to x=2..N; shorthand label centered above each bracket; optional deterministic raw-value overlay. The first four colors are: #9DD39C #F5C38A #A9D8E8 #FFF89A They cycle deterministically for additional groups. Plot limits reserve space for every successful bracket. Failed comparisons remain visible as group bars but do not receive a misleading significance annotation. Export Contract The data CSV has `Row` followed by one uniquely named column per group. Shorter groups are padded with blank cells. The result CSV has one row per later group and columns for: method, alternative, alpha, reference label, comparison label, sample counts, means and SDs, difference and interval, SE, t, df, p, shorthand, status, message CSV remains the portable boundary. The project MAT file is for recovery, not the only recoverable copy. Persistence And Migration Project schema version 2 replaces: inputs.vectorA inputs.vectorB with: inputs.groups Migration from version 1 preserves vector A as group 1 and vector B as group 2, including labels, values, and source metadata. A not-run scalar result becomes an empty result family; an existing completed A/B result remains the one-row family for those migrated groups. Validation Unit evidence covers: table-cell extraction and source preservation; Welch, pooled, paired, and directional reference values; ordered first-versus-each result generation; result-family identity after group mutation; multi-column group CSV and multi-row result CSV; stable individual failure statuses. Hidden GUI evidence covers: capture of three selections; ordered group state; two comparisons against the first group; equal-height source/editor tables and native Data/Plot workspace pages; stale-plot messaging after a manual data edit; boxed, grid-free rendered axes; group and result exports. Manual validation remains required for: native dialogs; large and irregular workbooks; group-table editing feel; long label wrapping; visual bracket spacing with many groups; publication suitability and scientific interpretation. Private laboratory files may be used only as local evidence. No real paths, filenames, identifiers, timestamps, or recognizable values enter tracked fixtures or documentation."},{"title":"Simple Scientific CSV Exchange","url":"development/data-and-designs/scientific-csv-interchange.html","kind":"guide","section":"development","keywords":"development data and designs scientific csv interchange Simple Scientific CSV Exchange","text":"Simple Scientific CSV Exchange [Development index](../README.md) | [T-Test Wizard](../../apps/statistics/ttest-wizard/README.md) | [T-Test Wizard design](group-comparison-app-design.md) | [App development](../build-apps/app-development.md) | [Architecture](../build-apps/architecture.md) Status And Purpose This page defines the minimum CSV exchange contract for LabKit Apps. It guides new exports and gradual improvement of existing exports; adopting it does not change another released App contract by itself. The contract is intentionally a table format, not a universal scientific data model. It provides a dependable lowest layer when two Apps do not share a richer schema: a person can type or repair it in Excel or a text editor; a script can read it without LabKit; an App can display it without first understanding its scientific meaning; different experiments may use different column names and row meanings; consumers can fall back to explicit column or cell-range selection; an App may add useful columns without asking the common format for permission. There are no required `Schema`, `Dataset`, `SampleID`, `Group`, `PairID`, `Include`, or `Note` columns. There is no required filename pattern or JSON sidecar. Repeating such fields in every row would make the lowest layer harder to write without making an unfamiliar experiment unambiguous. The Boundary Between Source And Exchange Data A laboratory source does not need to comply with this contract before LabKit can open it. Source workbooks may contain repeated blocks, dates between sections, formulas, summary rows, blank separators, inconsistent filenames, and several tables on one sheet. Existing App CSVs may mix text identifiers with numeric result columns. A compatible reader first opens these files as cell grids. It then lets the user select the cells or columns that matter. Exchange data is the simple rectangular table saved after that selection or after an App has produced a well-defined table. This distinction allows a fast, human-friendly experimental worksheet to remain useful without forcing every downstream App to reverse-engineer its layout. Existing file -> open cell grid -> select data -> copy values -> simple CSV Minimum File Contract A conforming exchange CSV has: UTF-8 text; comma delimiters; exactly one nonempty header row; a unique, nonempty name for every column; one rectangular table and no second header or table below it; ordinary CSV quoting for text containing commas, quotes, or line breaks; `.` as the decimal separator and no thousands separators in numeric cells; empty cells for missing values; evaluated values rather than spreadsheet formulas; no merged cells, color-dependent meaning, preamble, or comment lines. Text and numeric columns may coexist. A file is not invalid merely because it contains text, blanks, status messages, or columns that a particular analysis does not use. Headers are user-facing labels. Writers should keep them short and stable. When a unit is useful, the recommended spelling is: Charge [mC] Time [s] Resistance [ohm] The bracketed unit is a readable convention, not a prerequisite for opening the file. A generic reader preserves headers exactly and does not silently infer scientific roles from them. Three Useful Table Shapes The following shapes are examples of the same contract, not separate schemas. Apps may use other rectangular tables when their row meaning is documented. Vector Table A vector table places one numeric vector in each column. It is the preferred exchange shape for t-tests and other small comparisons: Condition A,Condition B 1.2,1.7 1.4,1.8 1.3,2.0 Independent vectors may have different lengths. Shorter columns end with blank cells: Condition A,Condition B 1.2,1.7 1.4,1.8 ,2.0 For a paired analysis, values on the same visible row form a candidate pair only after the user selects a paired test. The CSV does not force one statistical interpretation. An optional first column may give readable row labels: Row,Before,After 1,2.1,2.5 2,2.0,2.4 3,2.2,2.6 The same file can still be analyzed as two independent vectors. `Row` is an ordinary column, not a reserved identity system. Record Table A record table places one result, sample, cycle, image, segment, or other producer-defined record on each row. Text columns describe the row and numeric columns contain measurements: Source,Cycle,Charge [mC],Duration [s],Status sample_a,1,2.4,10,ok sample_a,2,2.5,10,ok sample_b,1,2.9,10,ok This is already a useful cross-App table. A statistics App can display it and the user can select cells from `Charge [mC]`. It does not need a conversion to a repeated `Metric,Value` representation first. Series Table A time series or curve is also a normal table: Time [s],Signal A [mV],Signal B [mV] 0.00,0.12,0.09 0.01,0.18,0.13 0.02,0.14,0.11 The exchange layer preserves it. Whether curve points are valid observations for a t-test is a scientific decision made by the user or the consuming App, not by the CSV reader. Reading Unknown Files A LabKit table reader follows a compatibility ladder: Read the file without requiring known headers. Display the table with spreadsheet row and column coordinates. If an obvious numeric column or vector exists, offer it as a convenience. Always allow the user to select or revise the exact cells. Copy the confirmed values into the App's own state. Offer export as a simple vector or record table. Automatic recognition is optional assistance. It must never be the only route through the App. The reader must not show a file-level error such as “file contains nonnumeric text” merely because some cells are labels. When a selected range contains mixed cells, report the actual selection in plain language, for example: 12 cells selected: 10 numbers will be used; 1 blank and 1 label will be skipped. The numeric preview is authoritative. The user can change the selection before continuing. Cell Selection Rules For a numeric vector: a single row or single column is the clearest selection; disjoint cells may be accepted when their order is shown in the preview; finite numeric cells are copied in visible row-major order; blanks and text are counted and shown, not silently mistaken for zero; `NaN`, positive infinity, and negative infinity are rejected as observations; the source is never overwritten during extraction. A paired test displays the copied A and B vectors side by side and pairs them by their displayed order. Unequal lengths block the paired test but do not block an independent test. Standard Export From A Two-Vector Tool A two-vector App writes the data actually used in a compact table: Row,Condition A,Condition B 1,1.2,1.7 2,1.4,1.8 3,1.3,2.0 It may omit `Row` when no row labels are useful. It pads unequal independent vectors with blanks. It writes labels chosen by the user as headers and units in those headers when known. Statistical results belong in a separate CSV because a completed test and its input observations have different row meanings: Test,Alternative,N A,N B,Mean A,Mean B,Difference A-B,T,DF,P Welch independent,Two-sided,3,3,1.3,1.833333,-0.533333,-5.237,3.812,0.0077 The values above are synthetic. Repository examples and fixtures must not contain real laboratory filenames, paths, identifiers, timestamps, or recognizable measurements. Result writers may add effect sizes, confidence intervals, warnings, or App-specific fields. They document those columns as that App's export contract; the common CSV layer does not standardize every statistical result. Manual Experimental Recording When a simple table fits the experiment, use it directly: put one stable header in each column; put raw numeric observations in ordinary cells; use one vector per column or one record per row; use blank cells for values not recorded; keep mean, SD, SEM, p-values, and notes outside the raw numeric range or in a separate table; start a new file when that is easier than making one very wide table. When repeated visual blocks are faster during acquisition, keep the block layout. The user can later select the relevant raw cells and export a simple table. The exchange contract must not make experimental entry slower merely to simplify one future importer. Several same-shape CSV files are also valid. A consuming App may open them one at a time, combine them, or let the user select one vector from each. Meaning encoded in filenames can be offered as editable text, but a filename pattern is never required and never silently becomes a group or pair assignment. Existing App Exports An existing CSV is already compatible with the lowest layer when it has one header row and one rectangular table. Migration does not require renaming all columns or expanding each numeric cell into a long observation row. Use these migration rules: Document what one row represents. Keep released column names stable unless a normal compatibility change is justified. Split genuinely different tables—raw data, summaries, test results, and logs—into separate CSVs when practical. Use readable headers and include units where they are known. Avoid absolute local paths in portable output. A basename or user label is usually enough. Preserve useful text and status columns; do not delete them merely to make a file “numeric.” Add an optional companion vector table only when it materially simplifies a common downstream workflow. A generic consumer reads the table and exposes manual selection. A producer-specific adapter is justified only when it removes repeated work and its scientific meaning is stable. The adapter still shows its proposed selection before use. No public `+labkit` facade is justified by this design alone. Reading, selection, and writing remain App-owned until several Apps demonstrate a stable, domain-neutral implementation. What This Contract Deliberately Does Not Encode The lowest layer does not claim to determine: which rows are statistically independent; whether equal-length vectors are paired; whether cycles or segments are samples or repeated measurements; whether a numeric column is raw data, a feature, or a summary; which group is control; which hypothesis test is appropriate; whether a filename token is a subject, condition, channel, or date. Those decisions require experimental context. A richer App export may record them, but an unknown CSV remains usable because the fallback is visible cell selection rather than rejection. Validation Expectations Synthetic tests for a compatible reader and writer cover: vector tables with equal and unequal column lengths; record tables containing both text and numeric columns; series tables; quoted headers and text; blank cells and fully blank trailing rows; duplicate or empty headers; mixed selections with a visible accepted/skipped count; nonfinite numeric values; CSV write/read round trips preserving column order and displayed values; extraction from two different source files; paired preview order and unequal-length blocking; source files that do not match any known App profile. Manual checks use representative private laboratory files without copying their names, paths, identifiers, or values into the repository. Related Guidance [T-Test Wizard Design](group-comparison-app-design.md) [T-Test Wizard](../../apps/statistics/ttest-wizard/README.md) [App Development](../build-apps/app-development.md) [Architecture](../build-apps/architecture.md) [Testing](../maintain-and-release/testing.md) [Documentation System](../maintain-and-release/documentation.md)"},{"title":"Documentation Architecture","url":"development/maintain-and-release/documentation.html","kind":"guide","section":"development","keywords":"development maintain and release documentation Documentation Architecture","text":"Documentation Architecture This page defines how LabKit documentation is authored, organized, rendered, and validated. It is a maintainer reference; readers looking for an app or a MATLAB function should start at the [documentation home](../../README.md). Design Goals LabKit documentation must support four distinct reading tasks: learn a workflow from a complete example; complete a specific task without reading architecture material; understand why a framework or scientific operation behaves as it does; look up exact MATLAB syntax, argument rules, fields, units, and errors. The organization follows the successful division visible in MATLAB and Qt documentation: product and module landing pages orient the reader, task pages teach workflows, concept pages explain behavior, and one page per callable entity provides exact reference. A single long narrative must not try to serve all four purposes. Source Tree `docs/` contains only author-maintained, human-readable documentation data: docs/ README.md getting-started/ apps/ /README.md //README.md //.md /history/ framework/ README.md .md history/ libraries/ /README.md /.md /history/ reference/README.md development/ build-apps/ data-and-designs/ maintain-and-release/ tools/ README.md .md history/README.md Executable renderer code and synthetic-asset generators live under `tools/docs/`. Generated HTML, CSS, JavaScript, and search data live under `site/`. No executable `.m` file belongs under `docs/`, and no generated file under `site/` is edited by hand. Launcher documentation follows the user-facing app map under `docs/apps/labkit-core/launcher/`. Direct-call references for maintainer tools follow their development ownership under `docs/development/tools/`; executable implementations remain under the repository `tools/` tree. Page Types Product landing page The documentation home answers what LabKit is, who each major section is for, and where a reader should begin. It is a routing page, not a project history, launcher manual, or API dump. Tutorial A tutorial leads a new reader through one complete, successful result. It states prerequisites, sample/input expectations, ordered actions, the expected result, and the next relevant topic. Tutorials may teach one path and defer alternatives. How-to guide A how-to guide begins with a concrete goal and gives the shortest reliable procedure. It includes prerequisites, commands or actions, verification, and failure recovery. Background explanation is linked, not repeated. App family page A family page explains the shared experimental domain, compares sibling apps, shows how their files or outputs connect, and links the relevant reusable libraries. It must not contain detailed instructions for one app. App manual Every public app owns one manual at `docs/apps///README.md`. Its normal section order is: purpose and applicability; requirements and launch; supported inputs and file interpretation; a shortest successful workflow; controls and interaction modes; parameters, defaults, legal values, and units; calculations, algorithms, and scientific assumptions; outputs and result schemas; project state, autosave, recovery, and overwrite behavior; use without the GUI; errors, limitations, troubleshooting, related topics, and history. Sections that genuinely do not apply may be omitted. Applicable information must not be hidden in an app catalog, family overview, changelog, or source comment. Complex apps add focused topic pages beside the manual rather than turning the manual into an unstructured notebook. Framework module page The framework landing page presents ownership and module relationships. Topic pages then document lifecycle, definition, callbacks, state transactions, busy behavior, file selection, persistence, layout, plotting, interactions, debugging, and extension rules. Each topic identifies: the problem and owning package; the observable behavior guaranteed to apps; the normal call sequence; public types or functions grouped by role; examples, failure behavior, and related topics. Private runtime mechanics are explained only when necessary to understand a public behavior; private functions do not receive reference pages. Library module page Each `labkit.*` facade has a module landing page comparable to a MATLAB toolbox or Qt module page. It states scope, dependencies, data model, grouped public functions, common call sequences, numerical/scientific semantics, compatibility, errors, examples, and related apps. Exact per-function syntax is linked to generated reference pages. Function reference page Every public function has one generated page. The MATLAB help block immediately after its declaration is the only authoring source and documents, as applicable: all supported syntax forms; concise description and observable behavior; each input, name-value option, and struct field; defaults, legal values, units, shape, type, and empty-value behavior; each output and its fields; algorithms, scientific definitions, and numerical assumptions; thrown errors or status-return failure behavior; a copyable example; related functions and the owning app or module. Function reference must not promise an overload, field, or default that the current implementation and tests do not support. History record History is component-owned. A record explains what changed, why it matters, compatibility, affected components, and validation evidence. It is not an instruction manual and must link to the current page that describes the final behavior. The global history page is only an index. The [history record format](../../history/record-format.md) requires a global positive-integer `sequence`; values are unique and contiguous, and a new record normally takes the previous maximum plus one. The renderer orders the timeline by descending sequence rather than inferring same-day order from a title, filename, filesystem timestamp, or Git checkout. Each rendered record ends with adjacent-record navigation: **Previous change** uses `sequence - 1` and **Next change** uses `sequence + 1`. The first and last records show only the direction that exists. Discovered Sources The documentation model is assembled without a page registry or catalog JSON: Source Authority Markdown path and first heading Page identity, output route, title, navigation area, and reader-task branch. `labkit_launcher(\"list\")` plus `docs/apps///README.md` Public Apps, commands, owning folders, and App manuals. Complete public MATLAB help Library APIs and App-owned non-GUI APIs. History record metadata Change identity, order, components, compatibility, and history associations. Every non-history Markdown page under `docs/` is published. `README.md` maps to its directory index; App manuals retain their established sibling HTML route. Missing titles, duplicate ids, duplicate outputs, missing App manuals, or ambiguous App ownership stop the build. Navigation hierarchy The top navigation selects a documentation area. The left sidebar then shows the current area's own hierarchy instead of repeating one flat site-wide list. Where a page has descendants, the sidebar uses labeled branches: app pages are grouped by app family, with the family manual above its apps; function pages are grouped by library, and API pages list sibling functions under their MATLAB package; framework and development guides are grouped by their reader task; history uses its generated timeline and adjacent-record links rather than placing every historical record in the sidebar. The first path segment selects the documentation area. Under `framework/` and `development/`, the next directory names the sidebar branch, so moving a page also changes its ownership and navigation. App branches come from their family manual path, library branches come from facade directories, and history uses record sequence. A documentation area with no descendants remains a single page and does not render an empty artificial branch. Markdown keeps standard relative links so source browsing works on GitHub. After moving pages, run: maintainLabKitDocLinks(repoRoot, \"Update\", true) The maintainer rewrites only uniquely identifiable moved targets. Its default read-only mode fails on unresolved or ambiguous links. Authoring From Current Code Documentation changes begin from the current implementation, not from old prose. For an app manual, inspect at least its entrypoint, runtime definition, workbench layout, state/options owner, input loader, output writer, public app-owned APIs, and focused tests. For a library, inspect its complete public surface and tests. Existing documentation and history may identify questions to investigate, but they are not evidence that a behavior still exists. User-visible labels, defaults, supported file types, state fields, output columns, formulas, units, and error behavior must match their current code owners. If code and documentation disagree, resolve the product contract rather than documenting both as alternatives. App manuals are reader-oriented product pages. Keep App-specific requirements, the shortest successful workflow, scientific semantics, outputs, recovery, limitations, and supported GUI-free APIs. Do not append implementation inventories or repeat framework contracts such as callback ownership, runtime lifecycle, hover-help coverage, source serialization, or layout binding. Document those contracts once under `docs/framework/`; link there only when a reader needs the shared behavior to use the App. Public Function Help A public function help block is part of the tested API. It states public call syntax, behavior, inputs, outputs, option fields, defaults, legal values, and errors or side effects where they matter. The documentation guardrail compares the names in the function signature with the `Inputs` and `Outputs` sections and requires option-bearing APIs to describe named fields. Generated API pages use this same help block, so the command-line `help` view and HTML reference do not have separate prose owners. Code under an `Example:` heading is an executable contract. The test runner extracts that code from the function header and executes it in MATLAB. Use `Typical Call:` when a useful sketch depends on a user's data file, an existing app/session variable, or an interactive dialog. Such a sketch becomes an `Example:` only after it has self-contained synthetic setup and passes the example runner. A self-contained MATLAB block in an app manual may be preceded by ``. The documentation contract extracts and executes every marked block in a clean test setup. Mark only examples that use synthetic data, require no dialogs or user files, and avoid lasting file or UI side effects. Unmarked blocks may demonstrate a typical call with a user's own file, but every variable and placeholder must still be explained by the nearby text. Cross-References Use relative Markdown links between narrative sources. Link the first useful mention of an app, framework topic, library, or related guide. Function names use fully qualified MATLAB symbols so the renderer can link them to generated API pages. Every app manual links its family, related apps, relevant libraries, public app APIs, and component history when records exist. Every library landing links representative apps. Related links should help the reader continue a task; they are not a list of every page sharing a keyword. The renderer rejects unresolved local Markdown targets. External links remain ordinary HTTPS links and are not copied into the site. Rendering And Search `tools/docs/renderLabKitDocs.m` reads the structured model, renders all narrative and API pages, builds navigation and related links, and writes the static site to `site/`. The renderer uses MATLAB only and introduces no third-party runtime dependency. Search is a generated static index containing titles, headings, descriptions, keywords, components, MATLAB symbols, and help text. It works from GitHub Pages and when `site/index.html` is opened directly from disk; it must not depend on a local web server or a fetch request. Generated pages carry a generated-file marker. Regeneration builds a complete temporary tree, then synchronizes its files into `site/`: changed files are updated, obsolete files and empty directories are removed, and the canonical `site/` directory itself is preserved. Keeping that root stable avoids numbered conflict copies when the repository is stored in a synchronized folder. For direct MATLAB syntax, result fields, missing-output reconstruction, and consistency-check behavior, see [Documentation Build Tools](../tools/documentation.md). Build And Validate Use the documentation build and consistency tasks listed in [Testing](testing.md). The build regenerates the tracked site; the consistency check renders independently and compares that result with `site/`. Project documentation tests additionally check page/output uniqueness, source and output existence, API coverage, local links, direct-file search, generated markers, and prohibited retired paths. Before committing documentation work: review the Markdown diff for claims not backed by current code; regenerate `site/`; run the focused documentation guardrail; open representative app, module, API, history, and search pages; commit source and generated output together. Related Topics [Documentation Home](../README.md) [Architecture](../build-apps/architecture.md) [App Development](../build-apps/app-development.md) [Testing](testing.md) [Maintainer Tools](../tools/README.md) [Release Process](release.md)"},{"title":"DIC Postprocess","url":"apps/dic/dic-postprocess.html","kind":"app","section":"apps","keywords":"apps dic dic postprocess DIC Postprocess","text":"DIC Postprocess DIC Postprocess converts Ncorr strain fields into EXX and EYY overlays on an optical reference image and calculates descriptive strain statistics over a validated ROI. It is a rendering and summary tool; it does not rerun DIC. Requirements And Launch Input MAT files must contain the Ncorr result structure recognized by the app loader. labkit_DICPostprocess_app Inputs Three inputs define one analysis: an Ncorr MAT file containing readable EXX and EYY strain fields; the optical reference image used for the displayed specimen domain; a mask image whose nonzero pixels define the requested ROI. All three are required before **Generate overlays + summary** can produce a result. The loader validates the expected Ncorr structure and reports malformed or incomplete MAT files instead of guessing at unrelated arrays. Basic Workflow Choose the DIC MAT file, matching reference image, and mask image. Set the overlay range and opacity. Adjust oversampling, smoothing, and edge trimming when justified. Optionally tune optical-image brightness, contrast, gamma, saturation, and RGB gains. Generate the EXX/EYY overlays and review the summary table. Save clean overlay PNGs and export the summary CSV. Changing an option updates the reproducible project parameters. Generate again before export when inputs or processing parameters change. Overlay Parameters Parameter Default Meaning Alpha 0.60 opacity of strain color over the optical image Color min -0.15 lower strain value mapped into the color scale Color max 0.15 upper strain value mapped into the color scale Oversample 6 output-domain refinement factor used during preparation Smooth sigma 0.8 Gaussian smoothing scale applied to valid strain data Edge trim 1 boundary pixels removed from the valid strain support Color limits affect rendering, not the stored numeric strain values used for the summary. Alpha affects compositing only. Smoothing and edge trimming alter the processed field and therefore can affect the summary; record them with any reported measurements. Optical Image Parameters Brightness defaults to `0`; contrast, gamma, saturation, and red/green/blue gain default to `1`. These controls prepare the visual background. They do not modify EXX/EYY values. Extreme settings can hide features or imply contrast that is not present in the strain field, so exported figures should retain the processing manifest. Processing And Scientific Semantics The app constructs a valid-strain mask from finite Ncorr values, intersects it with the requested ROI, trims invalid edges, and maps the result to the optical image domain. Linear resizing is used for continuous image/strain data and nearest-neighbor resizing for masks. Invalid strain samples remain excluded rather than being converted to zero. Summary statistics are computed independently for EXX and EYY over the final valid ROI. The result table reports the metrics returned by `dic_postprocess.analysisRun.summarizeStrain`; open its API page for the exact table schema and non-finite-value behavior. Outputs a clean EXX overlay PNG; a clean EYY overlay PNG; a CSV table containing the ROI strain summary; a LabKit result manifest describing inputs, parameters, and output roles. The clean PNG exports contain only the composed image, not axes chrome or interactive toolbar controls. Input MAT, reference, and mask files are never modified. Use Without The GUI inputs = struct( ... \"referenceImage\", zeros(16, 16, 3), ... \"maskImage\", true(16), ... \"strain\", struct( ... \"exx\", zeros(8), ... \"eyy\", 0.01 .* ones(8), ... \"roiMask\", true(8))); parameters = struct( ... \"alpha\", 0.60, \"colorMin\", -0.15, \"colorMax\", 0.15, ... \"oversample\", 6, \"smoothSigma\", 0.8, \"edgeTrim\", 1, ... \"brightness\", 0, \"contrast\", 1, \"gamma\", 1, ... \"saturation\", 1, \"redGain\", 1, \"greenGain\", 1, \"blueGain\", 1); [summary, overlayExx, overlayEyy] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters); The source loader is app-owned; the generated API table below lists only operations intentionally supported as public GUI-free calculation APIs. Function Reference Use the generated pages for [`makeStrainOverlay`](../../../reference/api/dic_postprocess/analysisRun/makeStrainOverlay.html), [`prepareOutputs`](../../../reference/api/dic_postprocess/analysisRun/prepareOutputs.html), and [`summarizeStrain`](../../../reference/api/dic_postprocess/analysisRun/summarizeStrain.html) for exact required and optional fields, units, output shapes, empty selections, failure behavior, and related APIs. Errors And Limitations A MAT file without the recognized Ncorr strain structure is rejected. Reference image, mask, and strain arrays must describe the same specimen domain; resizing resolves array dimensions, not semantic misalignment. A mask with no valid finite strain pixels produces no meaningful summary. Smoothing can suppress local extrema. Choose it as an analysis parameter, not merely a visual preference. Related Topics [DIC Preprocess](../dic-preprocess/README.md) [DIC family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) dic_postprocess.analysisRun.makeStrainOverlay Blend a color-mapped strain field over a reference image. dic_postprocess.analysisRun.prepareOutputs Build DIC strain summaries and both display overlays. dic_postprocess.analysisRun.summarizeStrain Summarize finite EXX and EYY values inside a mask."},{"title":"Private Apps","url":"development/maintain-and-release/private-apps.html","kind":"guide","section":"development","keywords":"development maintain and release private apps Private Apps","text":"Private Apps [Development index](../README.md) | [App development](../build-apps/app-development.md) LabKit supports local private app workspaces for apps that should not be published in the public repository. This page documents only the public extension point, folder shape, and development contract. Private app names, workflow notes, data assumptions, validation details, and release notes belong in the private app repository itself. Repository And Mounting Model A private app workspace is its own Git repository. It owns its app source, tests, manuals, histories, and repository rules regardless of where it is stored. The public repository never owns or migrates those files. For convenient local development, the independent repository may be checked out at the public checkout's ignored `private_apps/` mount point: LabKit-MATLAB-Workbench/ apps/ public apps tracked by this repository +labkit/ shared LabKit facades private_apps/ independent private Git repository, ignored here README.md tests/ apps/ / / README.md HISTORY.md labkit__app.m +/ definition.m +workbench/buildLayout.m projectSpec.m optional createSession.m optional +workbench/present.m optional +sourceFiles/... as needed +analysisRun/... as needed +resultFiles/... as needed That physical nesting is only a launcher mount; it does not make the private repository a folder owned by the public repository. The private repository may instead live anywhere and be registered through `LABKIT_PRIVATE_APP_ROOTS`. Each entry points either to a private repository root containing `apps/` or directly to its `apps/` folder. Separate entries with the platform path separator. Launcher Discovery The launcher discovers: public app entry points under `apps/**/labkit_*_app.m` private app entry points under `private_apps/apps/**/labkit_*_app.m` private app entry points under `LABKIT_PRIVATE_APP_ROOTS` Private entries appear in the app catalog with `Visibility` set to `private`. They launch like ordinary LabKit apps after the launcher adds the app folder to the MATLAB path. The launcher can also package a selected private app for offline deployment as either source `.m` files or encoded `.p` files. The generated zip preserves the private app under `private_apps/apps/...` and includes the packaged launcher plus the deployment/profiling tool folders it uses. Private apps discovered through `LABKIT_PRIVATE_APP_ROOTS` are copied into that same `private_apps/apps/...` package shape so the zip does not depend on the source machine's environment variable or private workspace path. Git Ownership Create or clone the private repository independently, then choose either the ignored mount point or the environment-variable registration: cd LabKit-MATLAB-Workbench mkdir private_apps cd private_apps git init git remote add origin git@github.com:/.git The public LabKit repository ignores its `private_apps/` mount point, so a nested checkout remains a normal independent repository and is not added to public history, public release zips, or public CI. Do not use a submodule unless you intentionally want the public repository to record a private remote URL and commit pointer. Structure Rules Private apps should follow the same app-owned package shape as public apps: keep one public entrypoint named `labkit__app.m` keep app workflow code under the owning `+/` package use one `definition.m` for product identity, version, requirements, layout, and references to optional capabilities start with only the entrypoint, definition, and semantic workbench layout bind callbacks and plot renderers directly on their owning layout controls; add `+workbench/present.m` only for dynamic visible state add one `projectSpec.m` only for durable App-owned state; keep create, validate, and version-aware migrate functions local to that file add root `createSession.m` only for App-specific transient reconstruction group workflow code by concrete user capability, such as `+sourceFiles`, `+analysisRun`, `+resultFiles`, or another app-owned domain package use shared LabKit facades such as `labkit.app.*`, `labkit.image.*`, `labkit.thermal.*`, `labkit.dta.*`, `labkit.rhs.*`, and `labkit.biosignal.*` Do not add separate `requirements.m`, `version.m`, generic `+appLifecycle` or `+appState` packages, or per-version `migrateProjectVx.m` files. Supported legacy top-level MAT variables use explicit import functions declared by the project spec. See [Build a Complete App](../build-apps/complete-app.md) for a working file-by-file example and [Runtime and Lifecycle](../../framework/guides/runtime.md) for every definition, project, session, action, presenter, and renderer field. Do not put private app source under public `apps/`. Do not move private workflow formulas, private result schemas, private labels, or private data assumptions into `+labkit`. Private Documentation The public repository should contain only this generic private-app structure guide. Put private app catalogs, SOPs, validation commands, sample notes, deployment notes, and release history in the private app repository. Private app repositories own their own tests and validation runners. They do not need to add app-specific suites, fixtures, build tasks, or sample assets to the public LabKit repository. Private app internals may be looser than public apps when a local workflow needs it, but the launcher-facing surface should remain compatible with LabKit discovery and guardrails: keep launch commands, definition-backed `requirements` and `version` lightweight requests, path setup, and private sample hygiene valid when the private workspace is present next to a public checkout. The public launcher's Code Analyzer tool includes configured private app workspaces in local reports only after the private workspace opts in. Put an empty `.labkit-accept-main-guardrails` file at the private workspace root to accept the public repository's style and code-quality guardrails for that workspace. When `private_apps/apps/` exists or `LABKIT_PRIVATE_APP_ROOTS` points at an accepted private workspace, the generated `artifacts/code-check/matlab_code_issues_*.json` and `.html` files include those private app findings without adding private source to the public repo. For one-off local checks, `LABKIT_GUARD_PRIVATE_APPS=1` temporarily includes configured private roots even without the sentinel file. Because private workspaces are separate Git repositories, public changed-file tasks do not discover private diffs. For an accepted private workspace, run the private repository's own tests and the public `buildtool changedFast` guardrail before push when private source, tests, docs, component history, or version metadata changed; CI remains the complete validation gate."},{"title":"Release Policy","url":"development/maintain-and-release/release.html","kind":"guide","section":"development","keywords":"development maintain and release release Release Policy","text":"Release Policy [Development index](../README.md) | [Project history](../../history/README.md) | [Deployment tool](../tools/deployment.md) | [LabKit Launcher](../../apps/labkit-core/launcher/README.md) Use semantic versioning for public releases. Version Numbers Patch releases, such as `v2.1.1`, are for bug fixes that do not add user-visible capabilities. Minor releases, such as `v2.2.0`, are for user-visible features, workflow changes, or meaningful maintenance improvements. Major releases, such as `v3.0.0`, are for breaking changes or intentionally incompatible workflows. If a commit marked as breaking, such as `feat!`, changes user workflow or app compatibility, choose a major version. If the implementation changes a user workflow without intentionally breaking compatibility, describe it in the release notes so launcher users can choose or roll back versions deliberately. On a development branch, choose each component's final version directly from the merge base with `origin/main`, not from intermediate branch commits. A branch may edit version metadata while work is evolving, but its merge-ready state must be exactly one semantic-version step from the mainline baseline: the next patch, the next minor with patch zero, or the next major with minor and patch zero. The related component history record should describe that direct `main baseline -> branch final` transition. This prevents temporary branch versions from accumulating into artificial public version jumps. Maintain user documentation with the same squash-oriented view: it describes the final branch behavior and one net compatibility transition, not the sequence of intermediate commits used to develop it. Tags Use `vX.Y.Z` for new release tags, for example `v2.2.0`. Do not rename or delete historical published tags only to normalize naming. If an older release used a different tag style, keep it for compatibility with existing links and user checkouts. Future releases should use `vX.Y.Z`. Complete developer-led interactive App validation before starting a release. Then dispatch the `Release` workflow from `main`, provide the new version, and explicitly confirm that manual validation is complete. The exact main commit must already have a successful `Continuous Integration` push run, which proves the complete headless and hidden-GUI suites on Toolbox-free Linux, macOS, and Windows. The release workflow verifies that run instead of executing the same six jobs again, then creates the annotated tag. An invalid version, a non-`main` dispatch, a missing manual-validation confirmation, an existing tag or release, or the absence of a successful same-commit main CI run prevents tag creation. Ordinary push, pull-request, and documentation workflows never create release tags. gh workflow run release.yml --ref main \\ -f version=vX.Y.Z \\ -f manual_validation_confirmed=true gh run watch Use `buildtool coverage` locally when you need a coverage report without preparing a release. GitHub Releases Use the release title format: LabKit MATLAB Workbench vX.Y.Z Use this note structure: ## Highlights - User-facing summary bullets. ## Fixes - Bug fixes, if any. ## Upgrade Note - Compatibility or upgrade guidance, if any. Mention app entrypoint or app requirement changes, launcher or app version bumps, and any LabKit facade contract version or supported-range changes. ## Validation - Local and CI validation evidence. Omit an empty section when it does not apply. Keep validation factual: name the commands or CI workflow that passed and the commit used for the release. After it verifies the required CI run, the workflow exports `labkit_launcher.m` from the annotated tag blob, verifies its SHA-256 against that blob, pushes the tag, creates a draft GitHub Release, uploads the launcher, and verifies the remote asset. The tag points at the same `github.sha` recorded by that Base MATLAB CI run; a later advance of `main` does not move it. Automation deliberately stops at a draft. Before publishing, rewrite or complete the generated notes with the required sections above, confirm the version and tag target, inspect the launcher asset, and record the final manual and CI evidence. Publishing the draft is a developer release decision, not a side effect of ordinary CI. Attach `labkit_launcher.m` to each GitHub release. The root README download link points at the latest release asset so browsers download the launcher instead of opening the raw source text. The launcher version manager lists recent releases, tags, and main-branch commits. Keep release titles and upgrade notes clear enough for users to select an older release when the newest build is unsuitable for their workflow. Component History Release notes summarize one public release. Long-lived change explanations live with the framework, library, app, launcher, or project documentation they affect. Each record is one Markdown file under a relevant `history/` directory. The generated documentation site aggregates those files into the [Project History](../../history/README.md) page and automatically lists matching records on every related component page. When a change bumps `labkit_launcher.m`, a `+labkit/**/version.m` facade, or an App's `AppVersion` metadata in `apps/**/definition.m`: update the owning component documentation; add one history record using the authoritative [history record format](../../history/record-format.md), including the stable Change ID, next global history sequence, affected components, and direct version transition; complete every required narrative section in that format; rebuild the generated site and verify the record appears on each affected component page. Write a cross-component change once and list every affected component in its metadata. Do not copy its narrative into multiple app pages. Current versions come from launcher, facade, and app source metadata, so there is no separate lookup table to synchronize. Git branches, PRs, tags, and commits express delivery state; do not add a second pending/unreleased hierarchy. Before tagging a release that adds, renames, or removes release-blocking guardrail tests, verify that the buildfile CI tasks still discover the intended suite and tag coverage. The workflow should call those build tasks through `matlab-actions/run-build` rather than maintaining long-lived test class selectors. Launcher Asset Reproducibility Generate the launcher asset from the release tag, not from an editor buffer, copied file, or platform-dependent checkout. This keeps the release asset byte for byte identical to the tagged source file, including line endings. Use a tag-specific staging folder: mkdir -p artifacts/release/vX.Y.Z git show vX.Y.Z:labkit_launcher.m > artifacts/release/vX.Y.Z/labkit_launcher.m Before upload, compare the staged launcher against the tag blob: git show vX.Y.Z:labkit_launcher.m | shasum -a 256 shasum -a 256 artifacts/release/vX.Y.Z/labkit_launcher.m wc -c artifacts/release/vX.Y.Z/labkit_launcher.m After creating the GitHub release, verify that the uploaded asset reports the same byte count and SHA-256 digest: gh release view vX.Y.Z --json assets If the asset digest or size differs from the tag-exported file, delete only the incorrect asset and re-upload the tag-exported `labkit_launcher.m`. Do not move or recreate an already-published release tag to fix an asset upload mistake."},{"title":"Testing","url":"development/maintain-and-release/testing.html","kind":"guide","section":"development","keywords":"development maintain and release testing Testing","text":"Testing [Development index](../README.md) | [Architecture](../build-apps/architecture.md) | [Maintainer Tools](../tools/README.md) | [Documentation](documentation.md) LabKit tests specify observable contracts. They are not organized by an implementation-stage label, suite selector, tag query, or hand-maintained test catalog. Start from the production file whose behavior changed; the test catalog determines its required evidence and executes exact test identities. Everyday Workflow For a changed production source, ask the catalog where evidence belongs: addpath(\"tests\") labkittest.explain(\"apps/electrochem/cic/+cic/+analysisRun/computeCIC.m\") `explain` prints the exact specification folders, contracts, environments, selected identities, and the reason for every selection. Add a new behavioral spec without inventing a path, metadata, class shell, or runner command: labkittest.createSpec( ... \"apps/electrochem/cic/+cic/+analysisRun/computeCIC.m\", ... Contract=\"scientific\", Name=\"PulseWindow\", ... Reason=\"Regression: pulse-window bounds must remain stable.\") `Contract` is needed only when one source has more than one author-owned boundary. `Reason` starts with `Regression`, `Invariant`, or `Compatibility`; it records the durable behavior being protected, rather than a temporary implementation detail. The generated file contains the required `Contract:` and `Env:` tags plus an intentional failing placeholder. Replace that placeholder with a small behavioral proof. Remove or revise a specification when that stated behavior is intentionally retired. Never create a test by guessing a folder, a suite range, a test tag, or a wrapper class. During an edit, execute the narrow owner and contract that changed: labkittest.run(Owner=\"apps/electrochem/cic/analysisRun\", ... Contract=\"scientific\") Use `labkittest.run(File=...)` only when the source maps to one complete, known evidence closure. It refuses missing evidence instead of silently running a partial test. A changed analysis source normally requires its scientific behavior, result schema, and presentation consumer together. Test Model Runnable specifications are under `tests/specs/`. Their owner path mirrors a production capability, and every test declares exactly one contract and one execution environment. tests/specs/ ├── framework/ / ├── apps//// ├── apps/conformance/ └── system/ / `tests/+testfixtures/` contains only synthetic inputs reused by multiple specification owners. Keep an owner-specific fixture beside its specification; do not create a generic `shared`, `support`, or `helpers` test directory. Contracts describe evidence, not test cost: Contract Proves `scientific` calculations, numerical policies, and scientifically meaningful branches `source` source decoding, callbacks, and capability-local state transitions `state` durable/transient state transitions and reconstruction `persistence` project schema creation, validation, and migration `result` exports, schemas, manifests, and task identity `presentation` reader-facing snapshot data and declared layout semantics `definition` / `product` parameterized public App conformance `system` build, repository, CI, documentation, packaging, and release guardrails Environments are `headless`, `hidden-gui`, and `path-isolated`. Headless tests do not prove GUI behavior. Hidden-GUI conformance proves that an App can build its declared layout; it does not prove native dialogs, pointer feel, visual quality, real lab data, or scientific review. The path-isolated conformance probes every public App from a reset path boundary in the already running catalog executor. It retains the deployable path boundary and batches all App results without requiring a second concurrent MATLAB license. Build Tasks Use stable Build tasks for branch and CI gates: buildtool changedFast buildtool headless buildtool gui buildtool isolated buildtool coverage buildtool docs buildtool docsCheck Task Purpose `changedFast` Local final pre-commit/pre-push gate. Reads tracked and untracked working-tree paths; on a clean checkpoint it reads `HEAD^..HEAD`. `headless` Every headless catalog identity. `gui` Every hidden-GUI catalog identity. `isolated` Every path-isolated catalog identity. `coverage` Headless catalog with Cobertura XML and HTML coverage artifacts. `docs` / `docsCheck` Render or verify the generated documentation site. `changedFast` prints whether its plan is `focused-local` or `full-profile`, semantic reasons, exact identities, and any explicitly ignored paths. For ordinary App and facade source it runs only the required contract closure. Framework, Build, catalog, and repository-policy paths select explicit bounded system evidence. Documentation and generated-site paths are explicitly ignored because `docsCheck` owns their consistency. An unknown path is a planning error: declare its production role or an explicit no-test reason; do not hide missing ownership by widening the run. Use `labkittest.explainChanged` to inspect that decision without executing tests. It prints each changed path's classification, selected evidence, and any manual boundary. A focused-local result is rapid author feedback, not merge safety evidence; CI runs the full platform profiles. Run focused behavior during iteration. Run `changedFast` once when the branch is ready for review or direct-main integration. CI owns broad platform validation; do not repeatedly run broad tasks after each small edit. Artifacts and Failures Each run writes one folder beneath `artifacts/test-results//`: manifest.json run identity plan.json exact selected identities and reasons events.jsonl progress and heartbeat stream active-test.json latest machine-readable progress state junit.xml CI result exchange summary.json pass/fail summary coverage.xml coverage runs only coverage-html/ coverage runs only After a failure, copy its exact class/method identity from the output and run the smallest owner/contract or exact source that proves the repair. A zero selection or missing-contract error is a test-authoring defect, never passing evidence. When a mapped layout change leaves a non-automatable boundary, its plan can name a manual check. It is printed and recorded in `plan.json`, but it never makes an automated run pass. Manual checks are limited to native dialogs, pointer behavior, visual design, real-data suitability, and scientific interpretation; they cannot replace an automated calculation, state, export, migration, structural-GUI, or workflow proof. CI and Manual Evidence Continuous Integration runs `headless`, `gui`, and `isolated` on Linux, macOS, and Windows from a clean MATLAB runtime without optional Toolboxes. It also runs `docsCheck` once, then reports one aggregate `CI Gate` result that depends on every required profile. Configure repository branch protection to require `CI Gate`; the workflow does not silently replace repository protection policy. It uploads the catalog artifacts even after failure. Coverage is an explicit report, not a duplicate CI gate. CI classifies the exact pushed or pull-request diff before scheduling MATLAB. Source, test, build, workflow, and tool changes run the complete platform matrix. Human documentation-only changes run `docsCheck` without the platform matrix. Agent guidance and GitHub contribution-template-only changes run the lightweight change-policy check without starting MATLAB. Mixed changes run the union of their required profiles, and `CI Gate` verifies every profile selected by the classifier. Changing the classifier or workflow is itself a full-matrix change. Manual App validation remains required for native file dialogs, visual design, pointer interaction, real-data suitability, and scientific interpretation. Use synthetic, minimal fixtures in automated tests. Never add real lab files, local paths, subject/device identifiers, timestamps, or proprietary metadata. Maintainer Rules Keep specs beside the capability that owns their behavior; tests never own a parallel product API. Keep cross-owner synthetic inputs in `tests/+testfixtures/`; keep every other test helper beside the specification that owns its behavior. Prefer direct behavioral calls over full App workflows. Add a structural GUI proof only when layout or wiring itself is the contract. Do not add legacy suite folders, stage tags, selector registries, test wrappers, runner options, or Code Analyzer suppression pragmas. Add a new public framework test API only when it is a stable product boundary. Test infrastructure stays private under `tests/+labkittest/`. Follow `.agents/migration_guide.md` only while retiring compatibility debt; it is not an everyday authoring checklist."},{"title":"Maintainer Tools","url":"development/tools/index.html","kind":"overview","section":"development","keywords":"development tools Maintainer Tools","text":"Maintainer Tools LabKit keeps a small set of MATLAB tools for source-checkout maintenance. They can be launched from the LabKit Launcher or called directly after adding the owning tool directory to the MATLAB path. They are not dependencies of normal app calculations and write generated output under ignored `artifacts/` paths unless an explicit destination is supplied. Choose A Tool Goal Entry point Reference Scan MATLAB source and export reviewable issues `runCodecheckReport` [Code Analyzer Reports](codecheck.md) Package one or more apps for offline use `packageLabKitApp` [App Deployment Packages](deployment.md) Regenerate or verify the static documentation site `renderLabKitDocs`, `checkLabKitDocs` [Documentation Build Tools](documentation.md) Profile startup, callbacks, scripts, or functions `profileLabKitTarget` [Performance Profiling](profiling.md) Direct Invocation Pattern Add the narrow tool folder, not all of `tools/` recursively: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"profiling\")) profileLabKitTarget(\"labkit_launcher\") Each public tool file owns its private implementation folder. Adding the public folder makes those private helpers available only to that tool, without turning them into global commands. Launcher And Command-Line Use The launcher provides the common interactive defaults: it supplies the current checkout root, selected app commands, progress UI, and sensible report locations. Direct calls are preferable for automation, custom output paths, batch MATLAB, reusable profile targets, or packaging several named apps. Tool reports and ZIPs are generated artifacts, not source documentation or app project data. **Clean Artifacts** may remove them. Copy any evidence that must be retained outside `artifacts/` or attach it to the relevant release or issue. Related Documentation [LabKit Launcher](../../apps/labkit-core/launcher/README.md) [Testing](../maintain-and-release/testing.md) [Documentation System](../maintain-and-release/documentation.md) [Release Process](../maintain-and-release/release.md) [Architecture](../build-apps/architecture.md)"},{"title":"Code Analyzer Reports","url":"development/tools/codecheck.html","kind":"guide","section":"development","keywords":"development tools codecheck Code Analyzer Reports","text":"Code Analyzer Reports `runCodecheckReport` runs MATLAB `codeIssues` over the LabKit checkout and writes both the native JSON export and a browsable HTML report. Use it when you need the complete analyzer result; ordinary tests and CI own their own focused quality checks. Syntax report = runCodecheckReport(root) report = runCodecheckReport(root, \"OpenReport\", false) report = runCodecheckReport(root, \"ProgressFcn\", progressFcn) Add the tool folder first: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"codecheck\")) report = runCodecheckReport(repoRoot, \"OpenReport\", false); Inputs And Options Name Meaning `root` LabKit checkout root to scan. It is converted to a text scalar before file discovery. `OpenReport` Logical scalar. Default `true`; opens the generated HTML report in the system browser. `ProgressFcn` Empty or a function handle called as `fcn(message, value)`, where `value` progresses from 0 to 1. The scan includes MATLAB files beneath `root`. It excludes `.git`, `.github`, `.vscode`, `.codes`, `artifacts`, `node_modules`, and `photos`. Additional private app roots configured outside `root` are included when their workspace contains `.labkit-accept-main-guardrails`, or when `LABKIT_GUARD_PRIVATE_APPS` explicitly enables local inspection. Output The returned struct contains: Field Meaning `jsonFile` Native `codeIssues` JSON export path. `htmlFile` LabKit HTML report path. `fileCount` Number of unique MATLAB files scanned. `issueCount` Unsuppressed issue count. `suppressedIssueCount` Suppressed issue count reported by MATLAB. Default reports are timestamped beneath `artifacts/code-check/`. A second run within the same second receives a numeric suffix instead of overwriting the first report. Behavior And Limitations An analyzer finding is report data, not a thrown failure. File-access errors, invalid MATLAB analyzer inputs, or report-write failures still raise their underlying MATLAB error. LabKit source must not add Code Analyzer suppression pragmas merely to make this report empty; fix the source shape or document a real external limitation in project governance. Related Documentation [Maintainer Tools](README.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"App Deployment Packages","url":"development/tools/deployment.html","kind":"guide","section":"development","keywords":"development tools deployment App Deployment Packages","text":"App Deployment Packages `packageLabKitApp` creates one ZIP containing one or more selected LabKit apps, their assets, the shared `+labkit` runtime, standalone entry files, and a machine-readable package manifest. Source and P-code packages serve different deployment needs. Syntax result = packageLabKitApp(appSelector) result = packageLabKitApp(appSelector, zipFile) result = packageLabKitApp(appSelector, zipFile, Name, Value) Typical calls: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"deployment\")) one = packageLabKitApp(\"labkit_CIC_app\"); several = packageLabKitApp( ... [\"labkit_CIC_app\", \"labkit_EIS_app\"], [], ... \"OutputRoot\", fullfile(repoRoot, \"artifacts\", \"deployment\")); runtimeOnly = packageLabKitApp( ... \"labkit_CIC_app\", [], \"CodeFormat\", \"pcode\"); App Selector `appSelector` accepts: one app command such as `\"labkit_CIC_app\"`; a string array or cell collection of app commands; an app entry-file path; an app folder containing exactly one entry point; a launcher app metadata struct or struct array. Commands are resolved through the same public and configured private-app roots used by the launcher. Ambiguous commands, missing entry points, duplicate folder entries, or unavailable source files raise an error before packaging. Options Name Default Meaning `Root` Repository root Source/runtime root containing `+labkit`, apps, launcher, and tools. `OutputRoot` `artifacts/deployment/` Folder used when `zipFile` is empty. `ProgressFcn` Empty Function handle called as `fcn(message, value)` from 0 to 1. `CodeFormat` `\"source\"` `\"source\"`/`\"m\"` for M-files, or `\"pcode\"`/`\"p\"` for runtime-only P-code. Source And P-code Packages Property Source P-code App implementation `.m` files `.p` files Standalone entry `run_.m` `run_.p` `labkit_launcher.m` Included Omitted Deployment/profiling tools Included Omitted `+labkit` and selected app assets Included Included in encoded runtime form Both formats include `README.txt` and `packaged_app_manifest.json`. Neither format includes unrelated sibling apps. P-code is a deployment format, not a source backup; retain the corresponding private or public source repository. Result Structure The result identifies `zipFile`, `packageRootName`, `entryFiles`, `appCommands`, app-relative folders, visibility values, `codeFormat`, and `fileCount`. Legacy scalar fields `entryFile`, `appCommand`, `appRelativeFolder`, and `visibility` describe the first selected app. Packaging stages files in a temporary directory, replaces an existing target ZIP only after validation reaches archive creation, and removes the staging directory afterward. Related Documentation [Maintainer Tools](README.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md) [Private Apps](../maintain-and-release/private-apps.md) [Release Process](../maintain-and-release/release.md)"},{"title":"Documentation Build Tools","url":"development/tools/documentation.html","kind":"guide","section":"development","keywords":"development tools documentation Documentation Build Tools","text":"Documentation Build Tools LabKit uses three public MATLAB entry points for its documentation sources and generated site. `renderLabKitDocs` builds and synchronizes the site; `checkLabKitDocs` performs an independent build and verifies that the tracked site is current; `maintainLabKitDocLinks` checks or repairs standard relative Markdown links. Path-organized Markdown and MATLAB help blocks are the authored sources. Maintain Links result = maintainLabKitDocLinks result = maintainLabKitDocLinks(repoRoot) result = maintainLabKitDocLinks(repoRoot, \"Update\", true) The default is read-only. With `Update=true`, a missing Markdown target is rewritten only when filename and link label identify exactly one current page. Review the rewritten diff, then rerun the default check. Render The Site result = renderLabKitDocs result = renderLabKitDocs(sourceRoot) result = renderLabKitDocs(sourceRoot, outputRoot) repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"docs\")) result = renderLabKitDocs( ... fullfile(repoRoot, \"docs\"), fullfile(repoRoot, \"site\")); Empty or omitted roots default to `docs/` and `site/` beneath the current LabKit repository. The result contains `pageCount`, `apiCount`, `fileCount`, `sourceRoot`, and `outputRoot`. Rendering discovers every Markdown page, public App, history record, and complete public help contract. It first creates a complete temporary tree, then creates `outputRoot` when absent, copies new or changed files, deletes obsolete generated files and empty directories, and preserves the output root itself. Every eligible Markdown page is included automatically, and no duplicated navigation catalog exists in this model. Check The Tracked Site result = checkLabKitDocs result = checkLabKitDocs(sourceRoot, committedSiteRoot) The check renders to a temporary folder and compares generated file paths and bytes with the tracked site. A difference raises `LabKit:Docs:StaleGeneratedSite`; the result otherwise includes `comparedFileCount` in addition to the renderer result fields. Build Tasks The supported command-line wrappers are: buildtool docs buildtool docsCheck Use `docs` when sources changed and `docsCheck` when validation must prove the tracked HTML is already current. Never edit files under `site/` manually. Related Documentation [Maintainer Tools](README.md) [Documentation System](../maintain-and-release/documentation.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"Performance Profiling","url":"development/tools/profiling.html","kind":"guide","section":"development","keywords":"development tools profiling Performance Profiling","text":"Performance Profiling `profileLabKitTarget` runs a MATLAB target under the profiler and exports an interactive HTML report plus a complete JSON sidecar. It supports launcher and app startup, callbacks, scripts, functions, command strings, and already captured `profile(\"info\")` data. Syntax [htmlFile, artifacts] = profileLabKitTarget(target) [htmlFile, artifacts] = profileLabKitTarget(target, htmlFile) [htmlFile, artifacts] = profileLabKitTarget(target, htmlFile, Name, Value) Batch-friendly launcher profile: repoRoot = \"/path/to/LabKit-MATLAB-Workbench\"; addpath(fullfile(repoRoot, \"tools\", \"profiling\")) [htmlFile, artifacts] = profileLabKitTarget( ... \"labkit_launcher\", [], ... \"OpenReport\", false, ... \"WaitForGuiClose\", false, ... \"CloseFiguresAfterRun\", true, ... \"PrintSummary\", true); Calling the function with an empty target opens a MATLAB file picker. A target may also be a function handle, function name, command string, `.m` path, or a profile-info struct containing `FunctionTable`. Options Name Default Meaning `OpenReport` `false` Open the HTML report after writing it. `WaitForGuiClose` `true` Wait for newly opened figures before collecting the final profile. `CloseFiguresAfterRun` `false` Close figures opened by the target after capture. `ChangeFolder` `true` Temporarily use a resolved target file's folder as the working directory. `OutputRoot` `artifacts/profile/` Default report folder when `htmlFile` is empty. `InitialRows` `500` Initial number of function-table rows shown. Values below 20 become 20. `ChartTopN` `40` Number of top roots in the flame control. `SortBy` `\"SelfTime\"` Initial report-table sort field. `ProjectRoot` Repository root Root used to classify project-owned functions. `TargetFile` Empty Explicit target path when exporting supplied profile data. `JsonFile` Empty Explicit JSON sidecar path. `PrintSummary` `false` Print the structured summary to the MATLAB command window. `SummaryTopN` `20` Rows retained in each summary ranking. `RethrowError` `false` Rethrow the target error after preserving available profile data. Outputs `htmlFile` is the generated report path. `artifacts` contains `htmlFile`, `jsonFile`, and `functionCount`. The HTML embeds the complete profile and an `AGENT_SUMMARY` block; the JSON contains metadata, summary text and tables, MATLAB profiler metadata, and every captured function row. Reading A Report Start with `topProjectSelfTime` to find time spent directly in editable LabKit code. Use `topProjectTotalTime` to see inclusive project costs and `topCapturedTotalTime` to understand user actions, app launches, callbacks, network operations, or GUI close time captured below a parent. Inspect parent and child edges before optimizing an inclusive row. Rows carry source tags such as `project`, `matlab_internal`, `external`, and `profiler_tool`. Profiler-tool rows remain in the complete data but are omitted from the default captured rankings so report generation does not appear to be application cost. Profile one perceived operation at a time. Separate normal and debug startup, file registration and actual file reads, and startup and close latency. A profile identifies cost; it does not prove correctness or scientific parity. Related Documentation [Maintainer Tools](README.md) [Testing](../maintain-and-release/testing.md) [LabKit Launcher](../../apps/labkit-core/launcher/README.md)"},{"title":"LabKit App SDK","url":"framework/index.html","kind":"explanation","section":"framework","keywords":"framework LabKit App SDK","text":"LabKit App SDK `labkit.app` is the MATLAB App SDK above LabKit's GUI-free domain libraries. Apps own scientific workflow, calculations, units, wording, plots, and exports. The SDK owns semantic layout, transactions, project documents, portable sources, dialogs, resources, results, and native component lifecycle. Start Here Goal Documentation Build or refactor an App [Build a Complete App](../development/build-apps/complete-app.md) Understand ownership boundaries [Architecture](../development/build-apps/architecture.md) Declare compatible LabKit modules [Compatibility contracts](compatibility/contracts.md) Look up exact MATLAB syntax [Public API reference](../reference/README.md) Browse the App SDK API by capability [App SDK API](app-sdk-api.md) Validate framework or GUI changes [Testing](../development/maintain-and-release/testing.md) SDK Map The required path has three concepts: API Purpose `labkit.app.Definition` App identity, lifecycle callbacks, layout, and launch `labkit.app.layout.*` Semantic inputs, displays, containers, and workbench structure `labkit.app.view.Snapshot` Derived visible state and App-owned rendering Read an App in this order: `definition.m` shows its complete contract, `+workbench/buildLayout.m` shows the user workflow, and its capability packages show the controls, state transitions, presentation, and rendering owned by each feature. Open `projectSpec.m` or `createSession.m` only when the definition names those optional capabilities. Layout controls bind directly to named App callbacks, and plot areas bind directly to their renderer. Apps do not maintain handler, renderer, or capability registries. Runtime-injected values are `labkit.app.CallbackContext` and typed `labkit.app.event.*` payloads. Callbacks name those boundary values explicitly and delegate domain work through narrow inputs. Optional capabilities are grouped by purpose: Package Purpose `labkit.app.event.*` Typed callback payloads `labkit.app.interaction.*` Managed plot gestures with direct callbacks `labkit.app.plot.*` Domain-neutral axes redraw, message, fit, and annotation mechanics `labkit.app.project.*` Durable project schema and migration `labkit.app.result.*` Exported files and result packages `labkit.app.dialog.*` Explicit dialog outcomes `labkit.app.CallbackContext` Runtime operations available inside callbacks Smallest App function app = definition() workbench = labkit.app.layout.workbench({ ... labkit.app.layout.field(\"gain\", Label=\"Gain\", ... Kind=\"numeric\", Bind=\"project.parameters.gain\"), ... labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the result from the current gain.\")}); app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", AppId=\"example\", ... Title=\"Example\", Family=\"Examples\", ... AppVersion=\"1.0.0\", Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... Workbench=workbench); end The entrypoint calls `definition().launch(...)`. Definition compiles the immutable semantic graph before creating a figure, validates direct callback and renderer signatures, and builds one private native platform plan. Paved Road Bind ordinary state with `Bind=\"project...\"` or `Bind=\"session...\"`. Use `labkit.app.layout.fileList` for portable file records and selection. Set `AllowDuplicatePaths=true` only when separate workflow tasks may share one resolved path, and present row-level workflow state with `Snapshot.fileItemStatuses`. Source changes rebuild the transient session; Apps do not mirror choose, remove, clear, or selection UI events. Give every scientific or workflow action an App-owned `Tooltip`. The framework guarantees a nonempty label-based fallback, while repository guardrails require tracked Apps to explain the action instead of repeating its visible label. File-list choose/folder/remove/clear controls expose dedicated tooltip options and domain-neutral label defaults. Rebuild transient data with `session = createSession(project,context)` and resolve opaque source records with `context.resolveSourcePaths`. Return only derived view state from `labkit.app.view.Snapshot`; runtime supplies layout defaults, bindings, file state, log text, and status text. Give short `statusPanel` summaries an explicit `Lines` hint so the native layout reserves detail height only for genuinely multiline content. Use `labkit.app.layout.dataTable` with `labkit.app.event.TableCellEdit` and `labkit.app.event.TableCellSelection`; Apps never decode native events. Use `labkit.app.layout.plotArea` and a fixed `renderer(axesById,model)` callback. `axesById` is always a named struct, even when the plot area declares only one axis. Pass a transient `ViewRevision` to `Snapshot.renderPlot` when an App exposes an explicit reset-view action. The adapter preserves user zoom while the revision is unchanged and accepts renderer-fitted limits once when it changes. Pass one workspace node or a row cell array of vertically arranged nodes to `workspace.page`; growable tables and plots share the available page height without an App-authored wrapper section. Use `layout.group(..., Title=\"...\")` for a nested reader-facing control boundary inside a section; leave `Title` blank for arrangement-only groups. A control tab containing one growable file list, table, log, status, or plot surface fills the available tab height. Tabs with longer mixed content remain scrollable. Declare editable overlays with `labkit.app.interaction.*` on the plot area; supply their current values with same-named Snapshot methods. For a managed rectangle with `OnBackgroundPressed`, an un-dragged click anywhere on its plot—including inside the rectangle—uses that point callback; dragging the rectangle still uses its change callback. Use `labkit.app.plot.clearAxes`, `showMessage`, and `fitAxesToGraphics` for renderer mechanics; `EqualDataUnits=true` makes a one-time fitted equal-scale view from the settled native axes allocation without dispatching pending UI callbacks, without changing the allocation or locking later zoom. Apps still decide message wording and viewport policy. Use `labkit.app.project.Schema`, `labkit.app.result.File`, and `labkit.app.result.Package` only when those optional capabilities exist. Use `labkit.app.project.sourceRecord` only in pure project creation or migration code that must turn a legacy path into a portable source value; runtime callbacks still resolve paths through `CallbackContext`. Runtime validates candidate state and the complete view snapshot before publishing either. The private MATLAB adapter maps semantic IDs to native components, skips unchanged native property writes, reuses direct associations between controls and their labels, preserves plot viewports, normalizes native event differences, and never exposes component registries to Apps. Normal App launches show the completed native window. Official GUI validation uses the same launch path with a framework-owned visibility policy: `hidden` keeps the final window off screen and `minimized` minimizes it after startup. Tests therefore exercise real controls without individual Apps or test methods having to hide the window after launch. Built-in App Tools The native runtime installs one top-level **Tools** menu so framework-owned utilities do not compete with the App's workflow controls: **Plots** opens, copies, or saves the App's plot surfaces. **Screenshot** copies the complete App surface to the system clipboard or saves it to an image file. **Project State** saves or loads the current project document when the App declares a project schema. These actions are framework-owned native behavior. Apps do not declare menu items, implement clipboard integration, or duplicate project persistence callbacks. Framework concepts and source names are versionless. Compatibility belongs to `labkit.app.version`; saved-data versions belong to `labkit.app.project.Schema`. Related Topics [App catalog](../apps/README.md) [App development](../development/build-apps/app-development.md) [Project history](../history/README.md) labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.dataTable Add a tabular data display with optional editing and selection. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.plot.clearAxes Prepare an axes for an app-owned redraw. labkit.app.project.Schema Declare one durable App project contract. labkit.app.project.sourceRecord Create a portable source value during project migration. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.version Return the LabKit App SDK facade contract version. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"App SDK API","url":"framework/app-sdk-api.html","kind":"guide","section":"framework","keywords":"framework app sdk api App SDK API","text":"App SDK API Use this page to move from the App Framework concepts to exact MATLAB contracts. These links stay within the Framework reading path; each function page provides syntax, inputs, outputs, options, errors, and related APIs. Start With The Core Contract [Definition](../reference/api/labkit/app/Definition.html) declares an App. [layout](../reference/api/labkit/app/layout/workbench.html) assembles the semantic workbench. [Snapshot](../reference/api/labkit/app/view/Snapshot.html) presents derived visible state. [CallbackContext](../reference/api/labkit/app/CallbackContext.html) exposes runtime capabilities to callbacks. Browse By Capability Capability API entry point Layout and controls [App SDK layout API](../reference/api/labkit/app/layout/workbench.html) View snapshots [App SDK view API](../reference/api/labkit/app/view/Snapshot.html) Typed events [App SDK event API](../reference/api/labkit/app/event/ListSelection.html) Projects and sources [App SDK project API](../reference/api/labkit/app/project/Schema.html) Results [App SDK result API](../reference/api/labkit/app/result/File.html) Managed interactions [App SDK interaction API](../reference/api/labkit/app/interaction/anchorPath.html) Plot mechanics [App SDK plot API](../reference/api/labkit/app/plot/clearAxes.html) Runtime diagnostics [Runtime diagnostics and session logging](guides/runtime.md#diagnostics-and-session-logging) Dialog results [App SDK dialog API](../reference/api/labkit/app/dialog/Choice.html) Related Topics [App Framework](README.md) [Runtime and Lifecycle](guides/runtime.md) [Build a Complete App](../development/build-apps/complete-app.md) labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.dataTable Add a tabular data display with optional editing and selection. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.group Arrange related child elements without a titled boundary. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.rangeField Add an input for a numeric lower and upper bound. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.slider Add a bounded numeric slider. labkit.app.layout.statusPanel Add a text display for current App status. labkit.app.layout.tab Add a named tab containing related child elements. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.app.event.IntervalScroll Describe one normalized interval scroll gesture. labkit.app.event.ListSelection Describe selected file or list item identities. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.interaction.anchorPath Declare an editable open or closed path on one plot axis. labkit.app.interaction.interpolateAnchorPath Build a visible path through image anchor points. labkit.app.interaction.interval Declare an editable one-dimensional plot interval. labkit.app.interaction.pairedAnchors Declare matching editable points across plot axes. labkit.app.interaction.pointSlots Declare a fixed set of editable labeled point positions. labkit.app.interaction.rectangle Declare an editable rectangular plot region. labkit.app.interaction.regionSelection Declare a transient click-or-drag region gesture. labkit.app.interaction.scaleBarGeometry Compute serializable image scale-bar overlay geometry. labkit.app.interaction.scaleCalibration Convert a known image distance into pixels per unit. labkit.app.interaction.scaleReference Declare an editable two-point scale reference. labkit.app.plot.clampPointToAxes Keep data coordinates inside the visible axes box. labkit.app.plot.clearAxes Prepare an axes for an app-owned redraw. labkit.app.plot.enablePopout Add a standalone-figure action to an axes context menu. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data. labkit.app.plot.fitCanvasToSource Fit a preview axes into a fixed-aspect pixel frame. labkit.app.plot.offsetPointByAxesFraction Move data coordinates by normalized axes fractions. labkit.app.plot.showMessage Show a centered empty-state message in an axes. labkit.app.project.Schema Declare one durable App project contract. labkit.app.project.emptySourceRecords Create an empty portable source collection. labkit.app.project.sourceRecord Create a portable source value during project migration. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.dialog.Choice Represent a typed dialog choice or cancellation."},{"title":"DIC Preprocess","url":"apps/dic/dic-preprocess.html","kind":"app","section":"apps","keywords":"apps dic dic preprocess DIC Preprocess","text":"DIC Preprocess DIC Preprocess registers a moving optical image to a reference image, applies repeatable crop operations to the pair, and creates a binary analysis mask. Use it when camera motion or framing differences must be removed before an external DIC solver is run. Requirements And Launch Image IO and registration use MATLAB and repository-owned code; no external registration package is installed. labkit_DICPreprocess_app Inputs Choose one reference image and one moving image. Supported extensions are the formats returned by `labkit.image.supportedExtensions`, including common PNG, JPEG, TIFF, and BMP files. The reference and moving images may initially have different dimensions; alignment produces a moving image on the reference domain. Choosing a new reference or moving file clears operations derived from the old pair. Source files are read-only and are not overwritten. Basic Workflow Choose the reference and moving images. Select **Current pair** or **False-color overlay** to assess the initial displacement. Run manual point matching or **Auto align current pair**. Inspect the false-color result and undo or reset if necessary. Start the crop ROI, drag or resize the square, and apply it to both images. Start ROI editing, place a boundary, and add or subtract it from the mask. Save the prepared images and ROI mask. Alignment and crop actions form an ordered edit history. **Undo align/crop** removes the most recent applied operation. **Reset to originals** discards the derived working pair and replays no edits. Preview Modes Mode Display Current pair current reference above the current moving image Current moving image moving image in the main comparison view False-color overlay red/green registration comparison of the current pair Original pair source images before applied edits ROI mask current binary mask over the image domain Changing preview mode does not change project data. Point placement, point dragging, crop editing, mask editing, and applying an operation preserve the current axes zoom. Use the plot **Fit** action when a full-image view is wanted. Manual Point Matching Press **Start point matching**. Click a feature in the reference image, then click the same feature in the moving image. Repeat this reference/moving order for at least two complete pairs. Numbered markers show correspondence and the preview subtitle states which image expects the next point. Drag an existing marker to refine it. **Undo point pair** removes the newest complete pair. **Cancel point matching** discards the pending point set without changing the current images. **Apply point alignment** estimates and applies a rigid two-dimensional transform. Manual alignment uses all pairs in a least-squares rotation-and-translation fit. It does not estimate scale or shear and prevents a reflected solution. Choose points that are visually unambiguous and spread across the specimen; clustered points provide weak rotational leverage. Automatic Alignment **Auto align current pair** runs the app-owned base-MATLAB registration path. It returns the same aligned image and transform fields as manual alignment. Automatic alignment is a starting estimate, not a guarantee of DIC-quality correspondence. Always inspect the false-color overlay and prefer manual points when the image has repeated texture, large occlusion, or weak contrast. Crop ROI **Start/reset crop ROI** creates a square rectangle constrained to the current reference image. Drag the rectangle to move it and use its resize handles to change its size. **Apply ROI crop** uses exactly the same integer image-domain rectangle on the reference and aligned moving image. **Cancel ROI** exits the editor without adding a crop step. Applied crops change the coordinate domain for later operations. Undo the crop before reusing point coordinates defined on the larger image. Mask ROI Start ROI editing and choose **Curve** or **Straight lines**. Place boundary anchors as instructed by the canvas subtitle; anchors remain draggable while editing. **Undo point** removes the most recent boundary anchor and **Clear boundary** removes the pending boundary only. **Preview ROI mask** shows the rasterized boundary. **Add to mask** unions the boundary interior with the current mask; **Subtract from mask** removes it. **Undo mask edit** restores the previous mask snapshot. **Clear mask** removes all included pixels. The mask is stored as a logical image in the current reference domain. Outputs **Save current images** writes two images to the selected folder using stable reference and moving result names. **Save ROI mask** writes a binary image to the selected file. LabKit result manifests record source references, parameters, output roles, and generated filenames; the manifest JSON is provenance metadata, not an additional scientific result. The saved project stores portable source references, preview choices, edit steps, crop and mask annotations, and result references. Decoded source images and intermediate working images are loaded or recomputed when the project is opened; they are not duplicated inside the project file. Use Without The GUI reference = imread(\"reference.png\"); moving = imread(\"moving.png\"); referencePoints = [120 80; 360 95; 230 310]; movingPoints = [128 88; 369 101; 239 318]; [aligned, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... reference, moving, referencePoints, movingPoints); overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... reference, aligned); cropped = dic_preprocess.analysisRun.cropImage(aligned, [80 60 300 300]); Use the linked function pages below for exact coordinate, rectangle, return shape, interpolation, and error contracts. Function Reference The generated pages for [`alignMovingToReference`](../../../reference/api/dic_preprocess/analysisRun/alignMovingToReference.html), [`autoAlignMovingToReference`](../../../reference/api/dic_preprocess/analysisRun/autoAlignMovingToReference.html), [`applyRigidTransform`](../../../reference/api/dic_preprocess/analysisRun/applyRigidTransform.html), and the other cataloged DIC functions document coordinate conventions, assumptions, output shape, limitations, failure behavior, and related APIs. Errors And Troubleshooting Point alignment remains unavailable until at least two complete pairs exist. A strong colored double edge in the overlay indicates residual geometric mismatch, not a palette defect. Automatic alignment can fail on nearly uniform images; use separated manual points or improve the source capture. Crop and mask geometry is constrained to the active image domain; an old project source that changed size cannot safely reuse its prior annotations. Related Topics [DIC Postprocess](../dic-postprocess/README.md) [DIC family](../README.md) [Image Library](../../../libraries/image/README.md) [API Reference](../../../reference/README.md) dic_preprocess.analysisRun.alignMovingToReference Fit and apply a rigid transform from matched points. dic_preprocess.analysisRun.applyRigidTransform Resample a moving image onto a reference-sized canvas. dic_preprocess.analysisRun.autoAlignMovingToReference Estimate and apply an integer translation. dic_preprocess.analysisRun.cropImage Crop an image using base-MATLAB indexing. dic_preprocess.analysisRun.makeFalseColorOverlay Build a red/green registration preview."},{"title":"Framework Compatibility Contracts","url":"framework/compatibility/contracts.html","kind":"guide","section":"framework","keywords":"framework compatibility contracts Framework Compatibility Contracts","text":"Framework Compatibility Contracts [App Framework](../README.md) | [API reference](../../reference/README.md) | [App development](../../development/build-apps/app-development.md) `labkit.contract` lets an app state which LabKit API versions it supports and check those requirements before startup. It compares versions already present in this repository; it does not download, install, or update code. This is part of the documented App Framework surface. The MATLAB namespace remains `labkit.contract` because it checks UI and domain facades alike; it is not an App metadata registry or a UI-only helper. Typical Use An app normally defines its requirements in a small function: function req = requirements() req = labkit.contract.requirements( ... \"ui\", \">=7 <8\", ... \"image\", \">=4 <5\"); end The app entry point checks them before creating the interface: labkit.contract.assertRequirements( ... \"labkit_Example_app\", requirements()) If all requirements are compatible, `assertRequirements` returns without a value. Otherwise it throws `labkit_Example_app:IncompatibleLabKit` with one message for each incompatible module. Choose a Function Task Function Build a requirement structure [`labkit.contract.requirements`](../../reference/api/labkit/contract/requirements.html) Inspect compatibility without throwing [`labkit.contract.checkRequirements`](../../reference/api/labkit/contract/checkRequirements.html) Stop startup when requirements fail [`labkit.contract.assertRequirements`](../../reference/api/labkit/contract/assertRequirements.html) Create the structure returned by a module's `version` function [`labkit.contract.versionInfo`](../../reference/api/labkit/contract/versionInfo.html) Version Ranges A range contains one or more whitespace-separated constraints. Supported operators are `<`, `<=`, `>`, `>=`, `=`, and `==`: \">=7 <8\" % 7.x releases \">=2.1 <=2.4\" % from 2.1 through 2.4, inclusive \"=1.3.0\" % exactly 1.3.0 Versions may contain one, two, or three numeric components. Missing components are treated as zero during comparison, so `6`, `6.0`, and `6.0.0` compare as the same version. Facade names may be short, such as `\"thermal\"`, or include the prefix, such as `\"labkit.thermal\"`. Names are normalized to lowercase without the prefix. Listing the same facade twice in one requirement structure is an error. How Compatibility Is Decided Each module's `version` function returns a structure like this: info = labkit.thermal.version() Field Meaning `name` Full module name, such as `\"labkit.thermal\"` `facade` Short normalized name, such as `\"thermal\"` `current` Current API version `compatible` One or more requirement ranges implemented by this version `status` `\"stable\"`, `\"experimental\"`, or `\"deprecated\"` `notes` Short description of the module contract A requirement passes only when both conditions are true: The module's `current` version falls inside the app's requested range. The requested range overlaps at least one range in the module's `compatible` list. The second check prevents a current version number from appearing acceptable when the installed implementation does not advertise the requested contract. Inspecting a Compatibility Report Use `checkRequirements` when a caller should decide how to display or handle a failure: req = labkit.contract.requirements(\"thermal\", \">=1 <2\"); report = labkit.contract.checkRequirements(req); if ~report.ok disp(report.message) end The returned structure contains: Field Meaning `ok` True when every requirement passes `failures` One structure per failed requirement `message` Success text or newline-separated failure explanations Each failure records the normalized `facade`, the `required` range, the module's advertised `available` ranges, and a readable `message`. An unknown facade has an empty `available` array. Supplying Version Information Explicitly `checkRequirements` normally queries the installed UI, DTA, RHS, biosignal, image, and thermal modules. Diagnostic code can pass an explicit version structure instead: available = labkit.contract.versionInfo( ... \"image\", \"4.1.0\", \">=4 <5\", \"stable\", ... \"Image file IO and processing functions.\"); req = labkit.contract.requirements(\"image\", \">=4.0 <5\"); report = labkit.contract.checkRequirements(req, available); This form is useful for checking a proposed version declaration without changing the installed module. Related Topics [App development](../../development/build-apps/app-development.md) explains how one App definition owns facade requirements and product version metadata. [Architecture](../../development/build-apps/architecture.md) describes the public LabKit modules that can appear in a requirement list. [Project history](../../history/README.md) records API version and compatibility changes. labkit.contract.assertRequirements Throw an error when LabKit API requirements are not met. labkit.contract.checkRequirements Compare required API ranges with available LabKit versions. labkit.contract.requirements Describe the LabKit API versions required by a caller. labkit.contract.versionInfo Describe one LabKit API version and its compatibility range. labkit.thermal.version Return version information for the thermal API."},{"title":"Runtime And Lifecycle","url":"framework/guides/runtime.html","kind":"guide","section":"framework","keywords":"framework guides runtime Runtime And Lifecycle","text":"Runtime And Lifecycle `labkit.app` is the App-facing runtime contract. Apps declare product meaning; the private runtime owns native MATLAB components, event serialization, transactions, project documents, portable sources, resources, and cleanup. Definition And Launch Each App-owned `definition.m` is the single immutable product contract, and each entrypoint delegates to it: function varargout = labkit_Example_app(varargin) [varargout{1:nargout}] = example.definition().launch(varargin{:}); end function app = definition() app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", ... AppId=\"examples.example\", ... Title=\"Example\", ... Family=\"Examples\", ... AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", ... Requirements=labkit.contract.requirements(\"app\", \">=2 <3\"), ... Workbench=example.workbench.buildLayout(), ... ProjectSchema=example.projectSpec(), ... CreateSession=@example.createSession, ... PresentWorkbench=@example.workbench.present); end Required Definition arguments are product metadata, requirements, and one `labkit.app.layout.workbench` value. Optional callbacks are: Argument Signature Purpose `CreateSession` `session = callback(project,callbackContext)` Rebuild transient App data from durable project state. `PresentWorkbench` `view = callback(applicationState)` Return the App-owned fragment of the complete visible snapshot. `OnStart` `applicationState = callback(applicationState,callbackContext)` Perform a real post-first-commit request or resource initialization. `BuildSyntheticSample` `sample = callback(callbackContext)` Build clean-room debug input when the App supports it. Ordinary default state needs no startup callback. Exact syntax and errors are in the generated [public API reference](../../reference/README.md). `launch(\"requirements\")` and `launch(\"version\")` answer metadata without creating a figure. `launch()` constructs the private native adapter and shows the App. Static Workbench Contract `+workbench/buildLayout.m` returns a tree composed from `labkit.app.layout.*` values. Layout IDs are stable semantic identifiers and must be globally unique. function layout = buildLayout() controls = { ... labkit.app.layout.section(\"parameters\", \"Parameters\", { ... labkit.app.layout.field(\"gain\", ... Label=\"Gain\", Kind=\"numeric\", ... Bind=\"project.parameters.gain\"), ... labkit.app.layout.button(\"exportResult\", ... \"Export\", @example.resultFiles.exportResult, ... Tooltip=\"Export the current analyzed result.\")})}; workspace = labkit.app.layout.workspace( ... labkit.app.layout.plotArea( ... \"previewPlot\", @example.previewPlot.draw)); layout = labkit.app.layout.workbench( ... controls, Workspace=workspace); end Layout controls own direct callbacks. Plot areas own direct renderers. There is no handler, renderer, command, or capability registry for Apps to maintain. Definition compiles the immutable graph once and validates callback roles, renderer roles, IDs, bindings, and view capabilities before UI mutation. Complex Apps keep the top-level workbench readable by composing capability-owned `layoutSection`, `workspaceTable`, or `workspacePlot` functions in user order. State And Transactions Runtime state always has two App-owned buckets: applicationState.project applicationState.session `project` is durable, validated meaning. `session` is transient and reconstructible. A callback receives the previous complete state and returns a candidate complete state. The runtime: serializes the event; invokes the direct callback; validates project and session shape; builds and validates the complete view snapshot; reconciles native components; publishes state and view together. Failure rolls back both state and presentation and clears event-scoped resources. Apps do not implement busy flags, callback queues, readiness timers, or figure close guards. Use direct `Bind=\"project....\"` or `Bind=\"session....\"` paths for ordinary fields, ranges, sliders, file sources, and selection. Bound controls need no callback or presenter operation unless the App has additional derived meaning. Portable Sources And Session Reconstruction `labkit.app.layout.fileList` owns file/folder selection, portable source records, removal, clearing, and optional selection binding: labkit.app.layout.fileList(\"sources\", ... Filters=[\"*.csv\", \"CSV files\"], ... Bind=\"project.inputs.sources\", ... SelectionBind=\"session.selection.sources\", ... SourceRole=\"measurement\", ... SourceIdPrefix=\"source\") The App does not mirror those UI lifecycle actions with callbacks. Runtime updates the bound source records and invokes `CreateSession` after source changes: function session = createSession(project, callbackContext) paths = callbackContext.resolveSourcePaths(project.inputs.sources); session = struct(\"measurements\", example.sourceFiles.read(paths)); end Portable source records are opaque. Resolve their paths only at IO boundaries. Saved projects store portable references and use runtime relinking. Typed Events Callbacks are attached only where an App owns real behavior: button: `state = callback(state,callbackContext)` field, range, slider, workspace page, or interaction change: `state = callback(state,value,callbackContext)` table edit: `state = callback(state,labkit.app.event.TableCellEdit,callbackContext)` table selection: `state = callback(state,labkit.app.event.TableCellSelection,callbackContext)` Name the boundary values explicitly and delegate domain work through narrow inputs: function applicationState = replaceGroupValue( ... applicationState, cellEdit, callbackContext) arguments applicationState (1,1) struct cellEdit (1,1) labkit.app.event.TableCellEdit callbackContext (1,1) labkit.app.CallbackContext end groups = applicationState.project.groups; applicationState.project.groups = ... example.groupData.replaceValue(groups, ... cellEdit.Row, cellEdit.Column, cellEdit.NewValue); end Do not pass the complete state or callback context into calculation code that only needs groups and one edited value. Complete View Snapshots Runtime starts from layout defaults, bindings, file state, log text, and status text. `PresentWorkbench` returns only derived App-owned operations: function view = present(applicationState) view = labkit.app.view.Snapshot(); view = view.include(example.previewPlot.present( ... applicationState.session.measurements, ... applicationState.project.parameters)); view = view.enabled(\"exportResult\", ... ~isempty(applicationState.session.measurements)); end The combined snapshot must cover every semantic target exactly as its layout capabilities require. `Snapshot.include` composes feature-owned fragments without opening a generic property-patch schema. Plot presentation passes a prepared model to the renderer declared by its plot area: view = view.renderPlot(\"previewPlot\", model); function draw(axesById, model) ax = axesById.main; cla(ax); plot(ax, model.x, model.y); end Renderers own drawing and viewport policy, not workflow decisions or project mutation. Display-only graphics disable hit testing. Managed interaction specs own editable gestures and event-scoped resources. Declare managed gestures statically on their plot area and provide their current value in the snapshot: crop = labkit.app.interaction.rectangle( ... \"cropRegion\", @example.cropGeometry.moveCrop); plot = labkit.app.layout.plotArea( ... \"previewPlot\", @example.previewPlot.draw, Interactions={crop}); view = view.rectangle( ... \"cropRegion\", project.annotations.crop, ImageSize=size(image)); Named contracts also cover anchor paths, paired anchors, fixed point slots, transient region selection, intervals, and scale references. Apps never author `Kind`, `Targets`, `Event`, or `Options` transport structs. Renderer mechanics such as complete clears, empty-state messages, fitting, fixed-aspect canvases, and axes-relative annotation placement live under `labkit.app.plot.*`. Apps own when those operations occur, user wording, and whether a semantic change should preserve or fit the viewport. CallbackContext `labkit.app.CallbackContext` is sealed and exposes specifically named runtime operations for dialogs, status and diagnostics, portable sources, project documents, result packages, render surfaces, and managed resources. It does not expose figures, component registries, queues, lifecycle handles, or a nested service bag. Use context methods only at a callback or reconstruction boundary. Pure readers, calculations, result builders, and render-model builders accept ordinary explicit values. `callbackContext.chooseOption(prompt, choices, ...)` owns ordinary native confirmation choices. `Title` controls the dialog title, `DefaultChoice` selects the Enter-key action, and `CancelChoice` is returned when the user dismisses the dialog. All three named choices must be members of the declared nonempty unique choice row. File and folder methods remain separate because they return paths and use platform file choosers. An App-specific project button may choose a MAT file and return `callbackContext.restoreProjectDocument(filepath)`. The context prepares the same migrated, relinked project/session candidate used by the framework Load State menu; the active callback transaction still owns validation, native presentation, rollback, document metadata, and title publication. `callbackContext.newProjectDocument()` similarly returns the schema's fresh project/session state and publishes a new unsaved document identity only when that callback transaction commits. Diagnostics And Session Logging Every ordinary App launch starts one sanitized session event stream and durable journal. Launch arguments do not select a debug mode, change startup behavior, or generate sample data. Runtime automatically records lifecycle, callback, transaction, dialog, project, source, result, and failure boundaries with correlated operation IDs. App callbacks add domain events through `callbackContext.log(severity,eventName,message,Name=Value)`. Use `info` for useful progress and completed user actions, `warning` for recoverable conditions, and `error` or `critical` for failures. Stable event names and structured allowlisted attributes support diagnosis; messages remain concise and safe for display. Pass caught exceptions through the dedicated `Exception` option instead of copying stack, path, identifier, or scientific data into free text. The App's **Tools > Diagnostics** menu opens the live session viewer, enables more detailed trace capture for future activity, and exports a diagnostic bundle from the same session history. Enabling trace does not restart the App or reconstruct earlier detail. Journal degradation is itself exposed in the surviving in-memory stream; logging failures never alter callback transaction semantics or scientific results. The viewer's **View** filter describes intended readers, not access control. **Useful** shows user-workflow events plus developer warnings and failures; **User workflow**, **Developer details**, and **Everything** select the two event audiences explicitly. The **Action** filter groups a top-level user or lifecycle action with its nested callback, presentation, dialog, resource, and transaction records. Its readable label includes time and a semantic message while retaining the stable `op-*` correlation identifier for exported diagnostics. TRACE capture is independent of ordinary failure capture. With TRACE off, Runtime still retains DEBUG lifecycle/callback start and completion boundaries and all INFO, WARNING, ERROR, and CRITICAL events. The default viewer hides normal developer DEBUG/INFO records; selecting **DEBUG+** and **Developer details** reveals earlier retained callback boundaries. A callback exception is recorded as an ERROR with `failed` operation result, rollback disposition, safe exception identifier, and sanitized function stack. Runtime close is also an instrumented lifecycle operation. Resource and native adapter cleanup continue independently; a cleanup exception is retained and persisted before the journal closes, then returned to the caller. Diagnostics cannot manufacture evidence for a native event that never entered Runtime, an exception swallowed by App code without logging, or a MATLAB process that hangs or terminates before a terminal event. In those cases the last retained DEBUG boundary and durable journal state are the available evidence. Persistence, Results, And Cleanup Synthetic Inputs An App that declares `BuildSyntheticSample` exposes **Tools > Developer Tools > Generate Synthetic Inputs...**. The action writes an anonymous, validated `labkit.app.synthetic.Pack` and `synthetic-input-pack.json` into a new folder beneath the selected destination. Generation does not load the pack, mutate the open project, or suppress `OnStart`; every App launch follows the same clean startup path. Users deliberately import the generated files through the App's ordinary controls. `labkit.app.project.Schema` owns current project creation, validation, and ordered version migration. Runtime owns the project envelope, atomic save, restore, recovery, and relinking loop. Saved-project compatibility boundary Every save writes exactly one `labkitProject` variable using the current App payload version. A restore accepts an older payload only when the App's current schema declares one `Migrate(project, fromVersion)` callback; Runtime invokes it once for each missing version in order and then validates the current payload. A payload newer than the running App is rejected rather than guessed at. An App may declare an exact legacy MAT variable name in `LegacyImports` when real user files still require a one-way import. That callback converts the legacy value directly to the current project and optional resume state. It is read-only: current saves never write the legacy variable, and Runtime contains no App ID, field-shape, or filename heuristics. These readers are supported data contracts while their Apps declare and test them. They are not an excuse for duplicate live state fields or old runtime APIs. Removing a supported payload migration or importer is an explicit breaking saved-data decision; adding one requires App-owned persistence evidence plus a runtime restore test for the framework mechanism. `labkit.app.result.File` and `labkit.app.result.Package` describe App-owned outputs. `CallbackContext.writeResultPackage` writes through the runtime so source and project provenance remain consistent. Resources have event, interaction, document, or application scope. Replacing the same scope and ID is idempotent; the runtime cleans every surviving resource on scope end or close. Validation Use focused contract tests for Definition, layout, callbacks, snapshots, project schema, and runtime transactions. Add downstream App tests for changed behavior and a bounded hidden-GUI test for native wiring. Automated hidden GUI tests do not prove dialog quality, pointer feel, scientific validity, or a complete interactive workflow. See [Testing](../../development/maintain-and-release/testing.md) and [Build A Complete App](../../development/build-apps/complete-app.md). labkit.app.CallbackContext Provide declared App-neutral runtime capabilities. labkit.app.Definition Compile and launch one immutable App SDK contract. labkit.app.event.TableCellEdit Describe one validated table-cell edit signal. labkit.app.event.TableCellSelection Describe selected cells in a semantic data table. labkit.app.interaction.rectangle Declare an editable rectangular plot region. labkit.app.layout.button Add a push button with one explicit pressed callback. labkit.app.layout.field Add a text, numeric, choice, or logical input field. labkit.app.layout.fileList Add file selection and portable-source controls. labkit.app.layout.plotArea Add one or more axes rendered by an App-owned renderer. labkit.app.layout.section Arrange related child elements under a visible title. labkit.app.layout.workbench Assemble controls and a central workspace into the root layout. labkit.app.layout.workspace Define the App's central working area and optional pages. labkit.app.project.Schema Declare one durable App project contract. labkit.app.result.File Declare one validated result-package output. labkit.app.result.Package Declare one App-owned result manifest request. labkit.app.synthetic.Pack Describe one typed anonymous App reproduction scenario. labkit.app.view.Snapshot Build one immutable complete visible-state snapshot. labkit.contract.requirements Describe the LabKit API versions required by a caller."},{"title":"Getting Started","url":"getting-started/index.html","kind":"tutorial","section":"getting-started","keywords":"getting started Getting Started","text":"Getting Started This guide covers the shortest path from an empty folder to a running LabKit app. For source development, continue with the [development guide](../development/README.md). Requirements A supported MATLAB installation. Internet access for the first launcher download or update. Installed source and P-code packages run offline unless an app's own input lives on a network drive. Any MathWorks products declared by the selected app. The launcher shows app requirements before launch. Production apps do not create Python or Conda environments, download model weights, or install third-party packages on first use. Install And Launch Download the latest [`labkit_launcher.m`](https://github.com/Pluze/LabKit-MATLAB-Workbench/releases/latest/download/labkit_launcher.m). Put it in a standalone folder such as `LabKit/`. Open MATLAB in that folder. Run: labkit_launcher The launcher can install or update the runtime, discover available apps, and start the selected app. Keep experimental data and exported results outside the LabKit runtime folder. See the [LabKit Launcher manual](../apps/labkit-core/launcher/README.md) for every button, programmatic mode, discovery rule, and maintenance action. Choose A Version **Release** installs the latest stable GitHub release. **Latest** installs the current `main` branch. **Versions** selects a recent release, tag, or commit for a deliberate upgrade or rollback. Before replacing an installed runtime, the launcher moves its current contents to a dated `LabKit-previous-*` folder. This provides a local recovery point but is not a substitute for backing up lab data. Open An App Select one row in the launcher and choose **Open**. The launcher prepares the MATLAB path, checks the app's LabKit version requirements, and displays startup progress until the app is ready. Use the [app catalog](../apps/README.md) to choose a workflow and confirm its input and output formats. Common App Commands Every current LabKit app exposes one top-level **Tools** menu: **Tools > Plots** contains plot-specific actions such as opening larger editable views, copying plots, and saving plots. **Tools > Screenshot > Copy to Clipboard** copies the complete active App surface as an image. **Tools > Screenshot > Save to File...** saves the complete App surface. **Tools > Project State > Save State...** writes the current project document. **Tools > Project State > Load State...** opens a compatible project document. State files preserve app projects. They are different from exported result files and from ignored diagnostic manifests under `artifacts/diagnostics/`. Source Checkout Clone the repository only when you need source development, tests, profiling, or review: git clone https://github.com/Pluze/LabKit-MATLAB-Workbench.git cd LabKit-MATLAB-Workbench buildtool headless See [Testing](../development/maintain-and-release/testing.md) before choosing a broader build task. Next Steps [App guide](../apps/README.md) [LabKit Launcher](../apps/labkit-core/launcher/README.md) [Public API reference](../reference/README.md) [Development guide](../development/README.md)"},{"title":"Project History","url":"history/index.html","kind":"history index","section":"history","keywords":"history Project History","text":"Project History This page lists the recorded changes to LabKit in reverse chronological order. Each change has its own page, so a reader can open the reason, effect, compatibility notes, and validation without searching through one very long changelog. Reading The Timeline Every recorded change, including work before v1.0, appears once in the same reverse-chronological timeline below. Early records describe how imported laboratory scripts became an app workbench; later records use the same page format to explain framework, library, App, validation, and release decisions. Version boundaries are milestones in this one timeline, not separate kinds of history. History source files have one predictable home: docs/history/records/// The folder is chronological, while the metadata inside each record names the apps, framework, or libraries affected by the change. Every record also has a global positive-integer `sequence`. Sequence values are unique and contiguous: the first record is `1`, and each later change takes the next value even when several changes share a date. The generated timeline sorts by descending sequence, so titles and filenames cannot reorder same-day versions. Dates must remain nondecreasing as sequence increases. The authoritative [history record format](record-format.md) defines every metadata field and body section. The documentation site uses that metadata to show a relevant **Change history** section on each component page. A change that affects several components is stored once and linked from all of them. Find A Change Use the site search for an app command, public function, Change ID, or feature name. You can also browse the complete timeline below by date. Current version numbers come from the launcher, facade `version.m` files, and App `definition.m` metadata rather than from a separate table in this history."},{"title":"History record format","url":"history/record-format.html","kind":"guide","section":"history","keywords":"history record format History record format","text":"History record format Each file under `docs/history/records///` records one durable project decision or user-visible evolution. This page is the sole authority for its authored structure. The documentation renderer validates every record, builds the timeline from its sequence, adds component links, and indexes its scopes for search. Canonical metadata The title is the first line. It is followed by one blank line and exactly one `labkit-change` block. Metadata fields use the shown order; no other metadata fields are legal. [Level-one Markdown title: concise change title] id: LK-YYYYMMDD-lowercase-change-slug date: YYYY-MM-DD sequence: N type: feat compatibility: compatible component: `component-id` | `old-version -> new-version` scope: Search phrase Field Required Value Used for `id` Yes, once `LK-` + ISO date without hyphens + lowercase hyphenated slug Stable change identity and search `date` Yes, once ISO date: `YYYY-MM-DD` Timeline display and ordering check `sequence` Yes, once Next positive integer; all records are contiguous from `1` Definitive timeline order `type` Yes, once `feat`, `fix`, `perf`, `refactor`, `test`, `docs`, `ci`, or `chore` Timeline classification and search `compatibility` Yes, once `compatible` or `breaking` Upgrade expectation and search `component` Optional, repeated `` `component-id` `` or `` `component-id` \\ `old-version -> new-version` ``; use ``new -> X.Y.Z`` for an introduced component Component-page history aggregation `scope` Yes, one or more A trimmed, human-readable search phrase of at most 96 characters Search keywords Use a component ID only when the record should appear on that component's documentation page. A record may have multiple components and scopes; list all components before all scopes. Do not add a `schema` field or encode release state in a record. Canonical narrative After the metadata block, every record contains these headings exactly once and in this order. Each heading has substantive content. ## Context ## Decision and rationale ## Changes ## User and data impact ## Compatibility and migration ## Validation ## Evidence ## Known limitations and follow-up State what changed and why in the first three sections; state affected users, data, compatibility, and validation in the next four; reserve the last section for remaining limits or explicitly state that none are known. Write one cross-component record rather than duplicating a narrative across components. When to write a record Write a record for a versioned App, launcher, or facade change, or for a meaningful project evolution decision. Do not create one solely for generated site output, a typo-only edit, or mechanical formatting churn."},{"title":"Biosignal API","url":"libraries/biosignal/index.html","kind":"reference","section":"reference","keywords":"libraries biosignal Biosignal API","text":"Biosignal API [Public API index](../../reference/README.md) | [App guide](../../apps/README.md) `labkit.biosignal.*` provides GUI-free import and processing functions for physiological and wearable time-series data. Use it to build a MATLAB script from the same recording, channel, event, segment, template, and measurement structures used by LabKit apps. `labkit.biosignal.version()` returns biosignal-library version and compatibility information used by `labkit.contract` requirement checks. ECG peak detection is the only modality-specific detector in the current module. Import, filtering, segmentation, template construction, measurement, and group comparison use generic biosignal structures. Public Surface Current app-facing functions: [recording, status] = labkit.biosignal.readRecording(filepath); names = labkit.biosignal.listChannels(recording); signal = labkit.biosignal.getChannel(recording, channel); cropped = labkit.biosignal.cropSignal(signal, [startSec endSec]); filtered = labkit.biosignal.filterSignal(signal, filterSpec); ecgPeakOptions = labkit.biosignal.defaultEcgPeakOptions(\"qrs-streaming\"); events = labkit.biosignal.detectEcgPeaks(signal, ecgPeakOptions); segments = labkit.biosignal.segmentByEvents(signal, events, windowSec); template = labkit.biosignal.buildTemplate(segments, templateOptions); measurements = labkit.biosignal.measureSegments(segments, template, measureOptions); comparison = labkit.biosignal.compareGroups(values, groups); `readRecording` accepts MAT files containing timetable variables and delimited text tables such as CSV and TSV. For delimited text tables, time handling is deliberately conservative. The reader does not treat an arbitrary monotonic numeric column as seconds. It uses a table column as time only when the column is `datetime`/`duration`, when the column name is time-like such as `time_s` or `time_ms`, or when the caller explicitly passes `timeColumn` and optionally `timeUnit`. Otherwise the recording uses a synthetic sample-index time axis and keeps numeric columns as signal channels. CSV and TXT files from wearable workflows can contain preamble rows, headerless numeric data, `I0/I1`-style Arduino columns, `sec`/channel tables with per-column count rows, epoch timestamps, footer metadata rows, gaps, duplicated rows, or time axes that jump backward. The table reader tries to handle the common cases automatically and records import choices in `recording.metadata`. Backward or duplicate time steps are stitched forward with a nominal sample interval; large positive gaps are preserved and counted in `metadata.timeRepair`. If auto-detection is ambiguous, the app or caller should pass explicit import options rather than relying on inference. Example explicit text-table time options: opts = struct('timeColumn', 'timestamp', 'timeUnit', 'milliseconds'); [recording, status] = labkit.biosignal.readRecording(filepath, opts); Useful delimited-table options include `headerLine`, `hasHeader`, `timeColumn`, `timeUnit`, `signalColumns`, `fallbackFs`, and `timeRepair`. `readRecording` Options Option Type Default Valid values / meaning `headerLine` positive integer auto Header line for text tables, or first data line when `hasHeader=false`. `hasHeader` logical auto Whether the detected/explicit line contains column names. `timeColumn` name or 1-based index auto Explicit time column. Use this when auto-detection is ambiguous. `timeUnit` string infer `seconds`, `milliseconds`, `microseconds`, `nanoseconds`, or sample/index aliases. `signalColumns` names or 1-based indices all numeric non-time columns Restricts imported signal channels. `fallbackFs` positive scalar Hz none Used for synthetic sample-index time or timestamp repair fallback. `timeRepair` string `auto` `auto` stitches duplicate/backward time steps; `none`/`off` disables repair. `gapFactor` positive scalar `20` Positive gaps larger than this multiple of nominal `dt` are counted as large gaps. `useFirstNumericColumnAsTime` logical `false` Opt-in fallback for ambiguous text tables. Data Structures Functions exchange ordinary MATLAB structures with these principal fields: recording.sourcePath, recording.name, recording.signals, recording.metadata signal.time, signal.values, signal.fs, signal.name, signal.displayName, signal.metadata events.index, events.time, events.amplitude, events.score, events.label segments.values, segments.timeOffset, segments.eventIndex, segments.eventTime, segments.fs template.values, template.timeOffset, template.keptSegmentIndex, template.score measurements.perSegment, measurements.summary Open an individual function page for required fields, row/column orientation, units, empty-result behavior, and errors. Processing Provided By This Module The public functions cover: file loading and timetable/table normalization channel listing and extraction time ROI cropping generic filtering ECG/QRS peak detection event-centered segmentation template building and template-residual SNR-style segment measurements generic group summaries and pairwise Welch-style comparisons `detectEcgPeaks` supports `qrs-streaming`, `pan-tompkins`, and `local` methods. Select the method through its options; no detector-specific helper call is required. `detectEcgPeaks` Options Start from `labkit.biosignal.defaultEcgPeakOptions(method)` and override only the fields the app exposes: opts = labkit.biosignal.defaultEcgPeakOptions(\"pan-tompkins\"); opts.polarity = \"positive\"; opts.minDistanceSec = 0.35; events = labkit.biosignal.detectEcgPeaks(signal, opts); Option Type Default Valid values / meaning `method` string `qrs-streaming` `qrs-streaming`, `pan-tompkins`, or `local`. `polarity` string `auto` `auto`, `positive`, `negative`, or `absolute`. `minDistanceSec` positive scalar `0.25` for ECG methods, `0.05` for local Minimum accepted peak spacing. `thresholdStd` scalar `3` Local method robust-threshold multiplier. `smoothSec` positive scalar `0.01` Local method score smoothing window. `integrationWindowSec` positive scalar `0.150` Pan-Tompkins moving-integration window. `refineSearchSec` positive scalar `0.120` Pan-Tompkins, `0.090` streaming Detector-trace peak snap search half-window. `rawRefineSearchSec` positive scalar `0.020` Final raw-signal peak snap half-window for Pan-Tompkins and streaming. `baselineWindowSec` positive scalar `0.600` Streaming causal baseline window. `envelopeWindowSec` positive scalar `0.080` Streaming slope-envelope window. `lookaheadSec` positive scalar `0.080` Streaming local-maximum lookahead. `minTemplateScore` scalar `0.45` Streaming rolling-template QC threshold. `medianPolarityCorrection` logical `true` Streaming post-pass for `auto`/`positive` polarity: reviews recent anchors against the signal median and re-snaps inverted anchors. `medianReviewPeakCount` positive integer `3` Number of latest streaming peaks considered by the median polarity review. Other Processing Options Function Options / parameters `filterSignal(signal, spec)` `spec.type`: `bandpass`, `lowpass`, `highpass`, `none`, `off`; `spec.cutoffHz`: scalar or `[low high]`; `spec.edgeMode`: `reflect` default or `none`; `spec.edgePadSec`: padding seconds, auto by default; `spec.edgeTaperSec`: padded-edge taper seconds, default `1`. `segmentByEvents(signal, events, windowSec)` `windowSec`: `[start end]` seconds relative to event, default `[-0.35 0.35]`. `buildTemplate(segments, opts)` `opts.topN`: positive integer, default `min(30, segmentCount)`. `measureSegments(segments, template, opts)` `opts.signalWindowSec`: `[start end]`, default `[-0.06 0.06]`; `opts.noiseWindowsSec`: N-by-2 matrix, default `[-0.30 -0.20; 0.40 0.50]`. Applications decide: ECG-specific labels and workflow wording which channel is treated as ECG GUI controls, previews, plot arrangement, and printing or export layout which measurements are displayed or exported class labels and interpretation of group comparisons These application choices are intentionally not parameters of the generic biosignal functions. Reference Contract The generated page for every `labkit.biosignal` function documents its exact input structure, implemented options, defaults, legal values, empty-result and error behavior, and related functions. The repository documentation contract discovers the whole public package rather than relying on a hand-maintained function list, and executes every block labeled `Example:`. labkit.biosignal.buildTemplate Build a representative segment template. labkit.biosignal.compareGroups Summarize groups and compute pairwise Welch comparisons. labkit.biosignal.cropSignal Return a signal clipped to a time range in seconds. labkit.biosignal.defaultEcgPeakOptions Return documented defaults for ECG/QRS peak detection. labkit.biosignal.detectEcgPeaks Detect ECG/QRS peaks as event anchors. labkit.biosignal.filterSignal Apply a zero-phase FFT-domain filter to a biosignal. labkit.biosignal.getChannel Return one signal from a recording by display name or index. labkit.biosignal.listChannels Return display names for all channels in a recording. labkit.biosignal.measureSegments Measure generic template-residual segment quality. labkit.biosignal.readRecording Read a biosignal file into a standard recording struct. labkit.biosignal.segmentByEvents Extract fixed windows around event anchors. labkit.biosignal.version Return the LabKit biosignal facade contract version."},{"title":"Gamry DTA Files","url":"libraries/dta/index.html","kind":"reference","section":"reference","keywords":"libraries dta Gamry DTA Files","text":"Gamry DTA Files [API reference](../../reference/README.md) | [Electrochemistry apps](../../apps/electrochemistry/README.md) The `labkit.dta` functions find and read Gamry DTA files without opening an app. They recognize chrono, electrochemical impedance spectroscopy (EIS), and cyclic-voltammetry/charge-time (CV/CT) data, and return ordinary MATLAB structures and numeric arrays for further analysis. Start Here Use `loadFile` when you know the path to one file: [item, status] = labkit.dta.loadFile(\"measurement.DTA\", \"auto\"); if ~status.ok error(\"Could not read DTA file: %s\", status.message) end switch item.type case \"chrono\" plot(item.t_s, item.Vf_V) xlabel(\"Time (s)\") ylabel(\"Voltage (V)\") case \"eis\" loglog(item.freq_Hz, item.Zmod_ohm) xlabel(\"Frequency (Hz)\") ylabel(\"|Z| (ohm)\") case \"cvct\" curve = item.curves(1); [x, y] = labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); plot(x, y) end The second argument restricts the accepted data family. Use `\"auto\"` to detect it, or use `\"chrono\"`, `\"eis\"`, or `\"cvct\"` when loading the wrong family should be reported as a failure. Values are case-insensitive and may contain surrounding whitespace. A blank value also means `\"auto\"`. Choose a Function Task Function Identify the data family in one file [`labkit.dta.detectType`](../../reference/api/labkit/dta/detectType.html) Read one file [`labkit.dta.loadFile`](../../reference/api/labkit/dta/loadFile.html) Read a known list of files [`labkit.dta.loadFiles`](../../reference/api/labkit/dta/loadFiles.html) Find and read every DTA file below a folder [`labkit.dta.loadFolder`](../../reference/api/labkit/dta/loadFolder.html) Find paths without reading their contents [`labkit.dta.findFiles`](../../reference/api/labkit/dta/findFiles.html) Select a chrono or EIS table [`getMainCurve`](../../reference/api/labkit/dta/getMainCurve.html), [`getZCurve`](../../reference/api/labkit/dta/getZCurve.html) Extract numeric columns from parsed tables [`getColumn`](../../reference/api/labkit/dta/getColumn.html), [`getCurveXY`](../../reference/api/labkit/dta/getCurveXY.html) Locate chrono pulse windows [`labkit.dta.detectPulses`](../../reference/api/labkit/dta/detectPulses.html) Check library version and compatibility [`labkit.dta.version`](../../reference/api/labkit/dta/version.html) Loading Several Files `loadFiles` keeps every successful item and records failures instead of stopping at the first unreadable file: files = [\"run-01.DTA\", \"run-02.DTA\", \"run-03.DTA\"]; [items, report] = labkit.dta.loadFiles(files, \"chrono\"); fprintf(\"Loaded %d of %d files.\\n\", ... report.nLoaded, report.nRequested) for k = 1:numel(report.failed) warning(\"%s: %s\", ... report.failed(k).filepath, report.failed(k).message) end `report.statuses` contains one status in the same order as the input paths. `items` contains successful items only, so use the status array when you need to match a result to every original position. Use `loadFolder` for recursive folder import. It adds `folder`, `filepaths`, and `nDiscovered` to the same report: [items, report] = labkit.dta.loadFolder(\"experiment\", \"eis\"); Chrono Data A chrono item contains the parsed metadata and tables, the selected transient curve, pulse information, and these unit-explicit vectors: Field Meaning `t_s` Time in seconds `Vf_V` Measured voltage in volts `Im_A` Measured current in amperes `pt` Point number from the file, or a generated zero-based sequence `controlMode` `\"current\"`, `\"voltage\"`, or `\"unknown\"`, inferred from step metadata `pulse` Cathodic, gap, and anodic windows returned by `detectPulses` `meta`, `tables`, `curve` Parsed metadata, all parsed tables, and the selected T/Vf/Im table Rows with nonfinite T, Vf, or Im values are removed. Duplicate time values keep their first occurrence. Loading fails when fewer than two valid samples remain. Pulse Detection The default mode, `\"Metadata first, then auto\"`, first looks for negative and positive ISTEP/TSTEP metadata. If current-step metadata is absent, it can use VSTEP/TSTEP timing. If metadata detection fails, it locates dominant negative and positive segments in the measured current. [pulse, message] = labkit.dta.detectPulses( ... item.t_s, item.Im_A, item.meta, \"Metadata first, then auto\"); Other modes are `\"Metadata only\"` and `\"Auto from Im only\"`. Programmatic aliases `\"metadata_first\"`, `\"metadata_only\"`, and `\"current_only\"` are also accepted. Current-based detection uses 25 percent of `max(abs(Im))` as its threshold, with a `1e-12 A` floor, then selects the longest cathodic segment and the longest later anodic segment. An unknown mode returns `pulse.ok=false` with an explanatory message; it is never silently treated as the default. On success, `pulse.pre`, `pulse.cath`, `pulse.gap`, `pulse.anod`, and `pulse.post` provide unit-explicit `start_s` and `end_s` windows. `pulse.cath.current_A` and `pulse.anod.current_A` contain nominal or measured phase currents, and `pulse.gap.center_s` contains the alignment center. `pulse.ok` is false and numeric fields are `NaN` when a valid cathodic/anodic pair cannot be found. Always check `pulse.ok` before using the windows in a calculation. Version 3 returns only this unit-explicit item and pulse model. Chrono items use `t_s`, `Vf_V`, and `Im_A`; EIS items use the fields listed below. Earlier unit-ambiguous aliases are not emitted. EIS Data An EIS item selects the `ZCURVE` table, or the first compatible table with Freq, Zreal, and Zimag columns. It exposes these unit-explicit vectors: Field Meaning `freq_Hz` Frequency in hertz `time_s` Elapsed time in seconds `point` Point number `Zreal_ohm` Real impedance in ohms `Zimag_ohm` Imaginary impedance in ohms `negZimag_ohm` Negative imaginary impedance in ohms `Zmod_ohm` Impedance magnitude in ohms `Zphz_deg` Phase angle in degrees `Idc_A` DC current in amperes `Vdc_V` DC voltage in volts Rows are retained when at least one primary impedance value is finite. Missing optional columns are returned as same-length `NaN` vectors. Loading fails when fewer than two usable ZCURVE rows remain. CV/CT Data A CV/CT item contains `curves` in file order and exposes the parsed scan rate as `scanRate_V_per_s`. The parser converts a Gamry `SCANRATE` value from mV/s to V/s. Each curve retains its headers, units, numeric data, and information about which values were parsed successfully. Use exact header names with `getCurveXY`: curve = item.curves(1); [voltageV, currentA, xname, yname] = ... labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); Rows where either selected column is `NaN` are removed as a pair. `getCurveXY` returns empty arrays and empty names when either header is absent. Parsed Table Structure Chrono and EIS parsers return `tables`, a structure array containing the table name, headers, units when available, and numeric data. Use `getMainCurve` or `getZCurve` to select a compatible table, then use `getColumn` for case-insensitive column lookup: [curve, ok, message] = labkit.dta.getMainCurve(item.tables); if ok timeSec = labkit.dta.getColumn(curve, \"T\"); currentA = labkit.dta.getColumn(curve, \"Im\"); end `getColumn` returns `[]` when the requested header is absent. It does not change units or remove `NaN` values. File Format and Limitations The reader treats DTA files as tab-delimited text. It scans metadata and named TABLE or CURVE sections, preserves headers and units when available, and converts numeric rows conservatively. Gamry software versions and experiment methods can produce additional sections or columns. Successful parsing means that a supported core curve was found; it does not imply that every vendor-specific field was interpreted. `detectType` and `loadFile` return readable status messages for missing files, unsupported content, parse failures, and expected-kind mismatches. Invalid argument types and invalid expected-kind strings throw documented LabKit errors. Reference Contract Open an individual DTA function page for exact accepted kinds, path forms, status fields, thrown errors, and related loaders or accessors. Public help is checked against implemented options and its executable examples are run in a clean MATLAB test process. Related Topics [CIC](../../apps/electrochemistry/cic/README.md), [CSC](../../apps/electrochemistry/csc/README.md), and [EIS](../../apps/electrochemistry/eis/README.md) use DTA records in interactive workflows. [Contract functions](../../framework/compatibility/contracts.md) explain how apps declare a compatible `labkit.dta` API version. [Project history](../../history/README.md) lists parser, schema, and compatibility changes. labkit.dta.detectPulses Locate cathodic and anodic pulse windows in chrono data. labkit.dta.detectType Identify the supported Gamry DTA data family in a file. labkit.dta.findFiles Find DTA files in a folder and its subfolders. labkit.dta.getColumn Extract a named column from a parsed DTA table. labkit.dta.getCurveXY Extract paired X and Y data from a parsed CV/CT curve. labkit.dta.getMainCurve Select the transient table containing T, Vf, and Im data. labkit.dta.loadFile Read one supported Gamry DTA file. labkit.dta.loadFiles Read a list of Gamry DTA files. labkit.dta.loadFolder Read every DTA file below a folder. labkit.dta.version Return version information for the DTA API."},{"title":"Image API","url":"libraries/image/index.html","kind":"reference","section":"reference","keywords":"libraries image Image API","text":"Image API [Public API index](../../reference/README.md) | [App guide](../../apps/README.md) `labkit.image.*` provides GUI-free image file IO, conversion, preview sizing, and common enhancement operations. App-specific registration, measurement, ROI, matching, and export workflows build on these functions. `labkit.image.version()` returns image-library version and compatibility information used by App `definition.m` requirement declarations. Common Calls filter = labkit.image.fileDialogFilter(\"IncludeAll\", true); paths = labkit.image.normalizePaths(rawPaths); records = labkit.image.readFiles(paths); source = labkit.image.im2double(records(1).image); preview = labkit.image.ensureRgb(source); preview = min(max(preview, 0), 1); luma = labkit.image.rgb2gray(preview); [preview, scale] = labkit.image.resizeToFit(preview, \"MaxHeight\", 1500); [preview, budget] = labkit.image.previewBudget(preview, \"MaxPixels\", appPreviewBudget); blurred = labkit.image.meanFilter2(preview(:, :, 1), 7); enhanced = labkit.image.adjustBrightnessContrast(preview, 10, 20); enhanced = labkit.image.localContrast(enhanced, 50, 3); enhanced = labkit.image.sharpen(enhanced, 30, 1.5); enhanced = labkit.image.grayWorldWhiteBalance(enhanced, 80, 0); labkit.image.writeFile(enhanced, outputPath); Normalization Helpers `labkit.image.im2double` follows the MATLAB `im2double` call contract for supported numeric image classes, including the optional `\"indexed\"` mode. `labkit.image.rgb2gray` follows the MATLAB `rgb2gray` call contract for RGB images and colormaps while using the documented Rec.601 luma transform. `labkit.image.ensureRgb` changes channel shape only. It expands grayscale data to three channels or drops channels after RGB without changing class or sample values. Callers that need display-ready RGB data explicitly compose `im2double`, `ensureRgb`, and `[0, 1]` clamping as shown above. `labkit.image.previewBudget` preserves native pixels by default. An App passes an explicit finite `\"MaxPixels\"` value only when its own workflow permits sampling; the resulting integer coordinate scale is returned with the preview. Provided Operations The module provides: supported source-image extension lists and file-dialog filters path normalization and display names `imread`/`imwrite` wrappers that normalize app-facing edge behavior MATLAB-compatible image conversion, explicit RGB shaping, preview-size fitting, and edge-normalized mean filtering caller-owned display-pixel budget helpers for responsive previews while preserving a documented integer coordinate scale generic image enhancement primitives such as brightness/contrast, HSV hue/saturation, gray-world white balance, local contrast, and sharpening Applications add: which filters or processing steps appear in the app UI parameter labels, defaults, validation ranges, and step history semantics reference matching, protected background/ROI behavior, scientific formulas, crop geometry, focus stacking, curvature, and DIC processing export manifests, result columns, filenames, failed-row policy, alerts, and log text Use `labkit.image.readFiles` when an app needs generic source-image records. Apps may copy the returned `path`, `name`, and `image` fields into their own item structures. Specialized formats and result structures remain documented by the app that uses them. Reference Contract The generated Image API pages linked from the [public API index](../../reference/README.md) document exact syntax, inputs, outputs, implemented options and defaults, legal values, failure behavior, examples, and related functions. In particular, [`resizeToFit`](../../reference/api/labkit/image/resizeToFit.html) accepts only the documented `\"bilinear\"` and `\"nearest\"` methods and rejects any other method instead of silently selecting an interpolation policy. labkit.image.adjustBrightnessContrast Apply simple brightness and contrast adjustment. labkit.image.ensureRgb Return image data with exactly three color channels. labkit.image.fileDialogFilter Return a file-chooser-compatible image filter. labkit.image.grayWorldWhiteBalance Apply generic gray-world white balance. labkit.image.im2double Convert image data to double using MATLAB's im2double contract. labkit.image.localContrast Enhance local value-channel contrast with a mean-filter mask. labkit.image.meanFilter2 Apply normalized 2-D mean filtering with edge correction. labkit.image.normalizePaths Normalize image file path inputs to a string column. labkit.image.previewBudget Downsample image data to fit a display pixel budget. labkit.image.readFiles Read image files into path/name/image records. labkit.image.resizeToFit Resize an image to fit within maximum row/column limits. labkit.image.rgb2gray Convert RGB data using MATLAB's rgb2gray call contract. labkit.image.sharpen Apply generic unsharp-mask sharpening. labkit.image.version Return the LabKit image facade contract version. labkit.image.writeFile Write one image file, creating the parent folder when needed."},{"title":"Intan RHS Recordings","url":"libraries/rhs/index.html","kind":"reference","section":"reference","keywords":"libraries rhs Intan RHS Recordings","text":"Intan RHS Recordings [API reference](../../reference/README.md) | [Neurophysiology apps](../../apps/neurophysiology/README.md) The `labkit.rhs` functions inspect Intan RHS recordings and read selected waveform windows without loading an entire file into memory. They expose file metadata, channel names, data-block layout, timestamps, and samples in physical units. The functions can be called directly from MATLAB without opening a LabKit app. Start Here Inspect a file first when you need to see its channels and duration: [info, status] = labkit.rhs.inspectFile(\"recording.rhs\"); if ~status.ok error(\"Could not inspect RHS file: %s\", status.message) end fprintf(\"%.2f s at %.0f Hz\\n\", info.durationSec, info.sampleRateHz) disp(info.channelTable(:, [\"family\", \"nativeName\", \"customName\"])) Then read only the channels and times required by the analysis: opts = struct( ... \"family\", \"amplifier\", ... \"channels\", [\"C-001\", \"C-002\"], ... \"timeRangeSec\", [0.100 0.150]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", opts); if status.ok plot(window.timeSec, window.values) xlabel(\"Time (s)\") ylabel(\"Amplitude (microvolts)\") legend(window.channels) end `window.values` is always samples-by-channels. `window.timeSec` contains the timestamps stored in the recording, converted to seconds. Choose a Function Task Function Find RHS paths recursively [`labkit.rhs.findFiles`](../../reference/api/labkit/rhs/findFiles.html) Read header, channel, and duration information [`labkit.rhs.inspectFile`](../../reference/api/labkit/rhs/inspectFile.html) Build the byte/sample layout for later reads [`labkit.rhs.indexFile`](../../reference/api/labkit/rhs/indexFile.html) Read one channel family and time interval [`labkit.rhs.readWindow`](../../reference/api/labkit/rhs/readWindow.html) Check library version and compatibility [`labkit.rhs.version`](../../reference/api/labkit/rhs/version.html) Header Information `inspectFile` reads the header and calculates how much complete waveform data is available. Important fields include: Field Meaning `fileVersion` Intan file-format major and minor version `sampleRateHz` Amplifier sample rate in hertz `durationSec` Duration represented by complete data blocks `channelFamilies` Channel structures grouped as amplifier, board ADC, board DAC, digital input, and digital output `channelTable` One table combining enabled stored channels and their native/custom names `frequencyParameters` Requested and actual filter and bandwidth settings `stimParameters` Stimulation step size and charge-recovery settings `dcAmplifierSaved` Whether DC-amplifier samples are stored `blockCount`, `sampleCount` Number of complete blocks and samples `exactBlocks` Whether the data section ends on a complete block boundary Header-only files can be inspected successfully and have a sample count of zero. A file with trailing partial bytes remains readable through its last complete block; `exactBlocks` is false and the status message reports the condition. Channel Families and Units `readWindow` reads one family at a time: Family Accepted examples Returned unit `\"amplifier\"` `\"amplifier\"`, `\"amp\"` microvolts `\"stim\"` `\"stim\"`, `\"stimulus\"`, `\"stimcurrent\"` microamps `\"dcAmplifier\"` `\"dc\"`, `\"dcamplifier\"`, `\"dcamp\"` volts `\"boardAdc\"` `\"boardadc\"`, `\"adc\"` volts `\"boardDac\"` `\"boarddac\"`, `\"dac\"` volts `\"boardDigIn\"` `\"boarddigin\"`, `\"digitalin\"`, `\"digin\"` logical 0 or 1 `\"boardDigOut\"` `\"boarddigout\"`, `\"digitalout\"`, `\"digout\"` logical 0 or 1 Family matching ignores case and punctuation. Selecting a family that has no stored channels returns `status.ok=false` with an empty window. Selecting Channels Set `channels=[]` to read every channel in the selected family. Otherwise use one-based numeric positions, native names, or custom names: opts.channels = [1 3]; opts.channels = [\"C-001\", \"C-003\"]; opts.channels = [\"Reference\", \"Recording\"]; Names are matched exactly first. If no exact match is found, comparison ignores case and punctuation, so `\"C001\"` can match the native name `\"C-001\"`. Returned `window.channels` always contains native channel names. Selecting a Time Window `timeRangeSec=[start end]` selects an inclusive sample interval. The start is clamped to zero, `Inf` reads through the last complete sample, and an end past the recording duration is clipped. For example: opts.timeRangeSec = [0 0.050]; % first 50 ms opts.timeRangeSec = [1.5 Inf]; % from 1.5 s to the end A valid request that begins after the available data returns `status.ok=true` with empty `timeSec` and `values`, allowing a caller to distinguish an empty interval from a file-read failure. Block Indexing and Lazy Reads RHS waveform data is stored in 128-sample blocks. `indexFile` calculates the header offset, bytes per block, complete block count, sample count, and duration without reading the waveform arrays. `readWindow` uses this index to seek directly to the blocks that overlap the requested interval. [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); if status.ok fprintf(\"%d samples in %d blocks\\n\", ... index.sampleCount, index.blockCount) end This makes short previews and event-centered analyses practical for recordings that are too large to load all at once. Physical-Unit Conversion The reader follows the Intan RHS block layout and applies the documented conversion gains and code offsets for amplifier, DC-amplifier, stimulation, board ADC, and board DAC data. Digital inputs and outputs are decoded from their stored bit positions. No filtering, baseline correction, event detection, channel-role interpretation, or response measurement is performed by these file-reading functions. Errors and Unsupported Files Missing files, malformed headers, unsupported magic numbers, absent channel families, and waveform read failures are returned through `status.ok=false` and `status.message`. Invalid function arguments, such as an unknown family, channel, or reversed time range, throw the documented `labkit:rhs:*` error. Successful parsing confirms that the supported RHS header and complete block layout were readable. It does not validate an experiment protocol, decide which channels form a differential signal, or assess recording quality. Reference Contract The generated pages for `findFiles`, `inspectFile`, `indexFile`, `readWindow`, and `version` state complete inputs, outputs, options, defaults, legal values, status/error behavior, and related APIs. Contract tests discover the complete public package and execute each documented `Example:` block. Related Topics [RHS Preview](../../apps/neurophysiology/rhs-preview/README.md) provides interactive waveform review and channel-role drafting. [Nerve Response Analysis](../../apps/neurophysiology/nerve-response-analysis/README.md) reads selected RHS channels for event and response calculations. [Biosignal functions](../biosignal/README.md) operate on imported recordings and generic waveforms after file IO. [Project history](../../history/README.md) lists RHS parser and app changes. labkit.rhs.findFiles Find Intan RHS files in a folder and its subfolders. labkit.rhs.indexFile Build a block index for reading selected RHS time windows. labkit.rhs.inspectFile Read metadata and data-layout information from an RHS file. labkit.rhs.readWindow Read selected channels and times from an Intan RHS file. labkit.rhs.version Return version information for the RHS API."},{"title":"Thermal Images","url":"libraries/thermal/index.html","kind":"reference","section":"reference","keywords":"libraries thermal Thermal Images","text":"Thermal Images [API reference](../../reference/README.md) | [FLIR Thermal app](../../apps/image-measurement/flir-thermal/README.md) The `labkit.thermal` functions read FLIR radiometric JPEG files, expose their raw sensor data and calibration metadata, convert sensor values to degrees Celsius, and create display-ready RGB images. They can be called from MATLAB without opening a LabKit app. Start Here Use this sequence when you want to analyze thermal files in a script: files = [\"capture-01.rjpg\", \"capture-02.rjpg\"]; [records, report] = labkit.thermal.readFiles(files, ... struct(\"SkipInvalid\", true)); for k = 1:numel(records) temperatureC = records(k).temperatureC; fprintf(\"%s: %.1f to %.1f C\\n\", records(k).name, ... min(temperatureC, [], \"all\"), max(temperatureC, [], \"all\")); end fprintf(\"Loaded %d files; skipped %d.\\n\", ... report.loaded, report.skipped) `readFiles` preserves the input order. With `SkipInvalid` set to `true`, it returns every readable record and describes unreadable files in `report.failures`. Without that option, the first read error stops the call. Choose a Function Task Function Read one radiometric image [`labkit.thermal.readFile`](../../reference/api/labkit/thermal/readFile.html) Read a list of images [`labkit.thermal.readFiles`](../../reference/api/labkit/thermal/readFiles.html) Check a file without throwing a read error [`labkit.thermal.inspectFile`](../../reference/api/labkit/thermal/inspectFile.html) Convert raw counts with supplied calibration [`labkit.thermal.rawToTemperatureC`](../../reference/api/labkit/thermal/rawToTemperatureC.html) Convert a numeric matrix to RGB colours [`labkit.thermal.renderImage`](../../reference/api/labkit/thermal/renderImage.html) Build a file-dialog filter [`labkit.thermal.fileDialogFilter`](../../reference/api/labkit/thermal/fileDialogFilter.html) Test or list recognized extensions [`isSupportedPath`](../../reference/api/labkit/thermal/isSupportedPath.html), [`supportedExtensions`](../../reference/api/labkit/thermal/supportedExtensions.html) Check library version and compatibility [`labkit.thermal.version`](../../reference/api/labkit/thermal/version.html) Thermal Records `readFile` returns one structure with these fields: Field Meaning `path`, `name` Source location and filename `format` Detected radiometric container format `raw` Decoded sensor-count matrix `temperatureC` Same-size double matrix in degrees Celsius; invalid conversion pixels are `NaN` `units` `\"C\"` when temperature conversion succeeded, otherwise `\"raw\"` `metadata` Parsed FLIR tags, calibration values, record information, and conversion diagnostics `message` Short description of the conversion result An ordinary JPEG preview is not a temperature matrix. The reader looks for an embedded FLIR FFF `RawThermalImage` record and uses that sensor data for the conversion. A `.jpg` or `.rjpg` extension therefore indicates only a candidate file. Call `inspectFile` when you need to know whether the contents are actually readable as thermal data. Temperature Correction The default `\"environment\"` correction uses the camera's Planck constants and, when available, emissivity, object distance, reflected apparent temperature, atmospheric temperature, humidity, infrared-window temperature, and infrared-window transmission. Missing environmental values receive documented defaults so that conversion can continue. The five Planck constants never receive fallback values. Use two outputs from `rawToTemperatureC` to see exactly what happened: [temperatureC, diagnostics] = labkit.thermal.rawToTemperatureC( ... raw, calibration, struct(\"Correction\", \"environment\")); if diagnostics.usedDefaults fprintf(\"Defaults used for: %s\\n\", ... strjoin(diagnostics.defaultedFields, \", \")) end `diagnostics.parameterSources` records `\"calibration\"` or `\"default\"` for each environmental input. A conversion that used defaults may be useful for previewing data, but the result is not evidence that the assumed emissivity, distance, humidity, or temperatures match the experiment. Check those values before using the temperatures in quantitative analysis. Choose `\"planck-basic\"` only when you intentionally want the conversion based on the five Planck constants alone: temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration, ... struct(\"Correction\", \"planck-basic\")); Rendering a Temperature Matrix `renderImage` maps a numeric matrix onto a colour palette. It does not change the temperatures and does not perform calibration. rgb = labkit.thermal.renderImage(temperatureC, ... struct(\"Limits\", [20 40], \"Palette\", \"iron\", \"Levels\", 256)); image(rgb) axis image off Values outside `Limits` are clipped to the end colours. Supported palettes are `\"turbo\"`, `\"parula\"`, `\"hot\"`, `\"gray\"`, and `\"iron\"`. The FLIR Thermal app also offers display-only log and gamma controls; those controls are app features and do not alter exported temperature values. Supported Files and Limitations The current reader supports FLIR radiometric JPEG and RJPEG files with an embedded FFF raw-thermal record. Camera models and firmware revisions can encode metadata differently. Use `inspectFile` or `readFiles` with `SkipInvalid=true` for mixed or uncertain inputs, and retain the returned failure messages when reporting an unsupported file. Do not infer measurement accuracy from successful parsing alone. Quantitative temperature accuracy still depends on the camera calibration and on correct environmental inputs for the photographed material and scene. Reference Contract Each generated Thermal API page distinguishes returned status from thrown errors and documents every implemented option, default, legal value, output field, and related function. Executable examples and package-wide public-help discovery prevent the manual and callable surface from silently diverging. Related Topics [Image functions](../image/README.md) cover ordinary image IO and image processing that does not use radiometric calibration. [FLIR Thermal app](../../apps/image-measurement/flir-thermal/README.md) provides interactive file review, measurements, display controls, and exports. [Project history](../../history/README.md) lists changes to thermal file support and calibration behavior. labkit.thermal.fileDialogFilter Create a file-selection filter for thermal images. labkit.thermal.inspectFile Check whether a file contains supported radiometric data. labkit.thermal.rawToTemperatureC Convert FLIR raw sensor values to degrees Celsius. labkit.thermal.readFile Read radiometric image data and convert it to degrees Celsius. labkit.thermal.readFiles Read several radiometric image files. labkit.thermal.renderImage Map a thermal matrix to an RGB image. labkit.thermal.version Return version information for the thermal API."},{"title":"Electrochemistry Apps","url":"apps/electrochemistry/index.html","kind":"app family","section":"apps","keywords":"apps electrochemistry Electrochemistry Apps","text":"Electrochemistry Apps The Electrochemistry family reads Gamry DTA files through `labkit.dta` and keeps experiment-specific analysis, plotting, defaults, and export schemas in the owning app. Source DTA files are never modified. Choose An App Measurement or task App Required DTA content Main result Compare voltage and current transients across files [Chrono Overlay](chrono-overlay/README.md) chrono curve with `T`, `Vf`, `Im` aligned overlay and wide CSV Charge-injection capacity and polarization voltage [CIC](cic/README.md) biphasic chrono transient per-file CIC, charge, and voltage metrics Charge-storage capacity by time and CV integration [CSC](csc/README.md) CV/CT cycles with `T`, `Vf`, `Im` per-cycle CT/CV CSC comparison Impedance inspection and export [EIS](eis/README.md) `ZCURVE` configurable Nyquist/Bode-style overlay Steady pulse resistance [VT Resistance](vt-resistance/README.md) biphasic chrono transient cathodic, anodic, and mean resistance Shared File Behavior File controls accept one or more `.DTA` files from one folder in a single selection. A canceled chooser leaves the current project unchanged. CIC, CSC, and VT Resistance use the selected row as the active preview while retaining the loaded source list for batch export. Invalid items are reported per file; one failed item does not silently replace another result. The DTA library returns structured items, curve tables, headers, units, metadata, parser messages, and status. Apps use exact required columns for scientific calculations and do not infer missing physical quantities from a plot label. The Apps require the version 3 unit-explicit DTA contract rather than branching between duplicate legacy and canonical item fields. Units And Traceability Time is seconds, voltage is volts, current is amperes, impedance is ohms, charge is coulombs, electrode area is square centimetres, and normalized CIC or CSC is reported in the unit shown by the app. UI display conversions do not change stored base-unit calculations. Export tables include the source identity and analysis settings needed to interpret the result. Runtime-generated `.labkit.json` files are provenance manifests describing inputs, parameters, and output roles; they are not a second numerical result and can be kept with the corresponding CSV. Use Without The GUI Reusable parsing and curve access live in `labkit.dta`. App-specific numeric operations such as `cic.analysisRun.computeCIC`, `csc.analysisRun.computeCSC`, and `vt_resistance.analysisRun.computeResistance` are documented on the owning app page and in the generated API reference. Related Modules [DTA Library](../../libraries/dta/README.md) [API Reference](../../reference/README.md) [All Apps](../README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics. csc.analysisRun.computeCSC Compare time-integrated and scan-rate-derived electrode charge. vt_resistance.analysisRun.computeResistance Estimate cathodic and anodic resistance from a VT pulse."},{"title":"API Reference","url":"reference/index.html","kind":"reference","section":"reference","keywords":"reference API Reference","text":"API Reference This index covers domain libraries and GUI-free App APIs. The App SDK itself is documented under [Framework: App SDK API](../framework/app-sdk-api.md), so its contracts stay with the lifecycle and architecture guidance they support. Use this reference to look up supported MATLAB functions. Every function name in the generated module tables is a direct link to an individual page with syntax, arguments, outputs, behavior, source, and related APIs. Find The Right Layer Need Module guide App lifecycle, layout, plotting, interaction, state, debugging, and compatibility requirements [App Framework](../framework/README.md) Image IO and generic image operations [Image](../libraries/image/README.md) Radiometric image decoding and temperature conversion [Thermal](../libraries/thermal/README.md) Gamry DTA discovery, parsing, curves, and pulses [DTA](../libraries/dta/README.md) Intan RHS inspection and waveform windows [RHS](../libraries/rhs/README.md) Generic biosignal import and processing [Biosignal](../libraries/biosignal/README.md) Scientific operations owned by one app [Apps](../apps/README.md) Module guides explain scope, data models, normal call sequences, algorithms, and representative examples. Function pages are the source for exact callable contracts. Files in `private/` directories and App helpers without a complete public help contract are not supported entry points. Compatibility Each reusable library publishes a `version()` result. Apps declare compatible library ranges in the `Requirements` field of their single `definition.m` contract. A function documented here is public within that library version; compatibility across a breaking version is not implied. The tables below are generated from current MATLAB source. Each entry links to the current function help. labkit.biosignal Recording import, channel access, filtering, events, templates, and measurements. Framework Compatibility Version and MathWorks-product requirement contracts. labkit.dta Gamry DTA discovery, parsing, curve access, and pulse detection. labkit.image Generic image file IO, normalization, resizing, filtering, and enhancement primitives. labkit.rhs Intan RHS discovery, indexing, inspection, and bounded waveform reads. labkit.thermal Radiometric image inspection, conversion, reading, and rendering. Batch Image Crop Supported GUI-free operations owned by the Batch Crop app in the Image Measurement family. Charge-Injection Capacity Supported GUI-free operations owned by the Cic app in the Electrochemistry family. Charge-Storage Capacity Supported GUI-free operations owned by the Csc app in the Electrochemistry family. Curvature Measurement Supported GUI-free operations owned by the Curvature app in the Image Measurement family. DIC Postprocess Supported GUI-free operations owned by the Dic Postprocess app in the Digital Image Correlation family. DIC Preprocess Supported GUI-free operations owned by the Dic Preprocess app in the Digital Image Correlation family. ECG Print Supported GUI-free operations owned by the Ecg Print app in the Wearable family. FLIR Thermal Supported GUI-free operations owned by the Flir Thermal app in the Image Measurement family. Focus Stack Supported GUI-free operations owned by the Focus Stack app in the Image Measurement family. Gait Analysis Supported GUI-free operations owned by the Gait Analysis app in the Gait family. Image Enhance Supported GUI-free operations owned by the Image Enhance app in the Image Measurement family. Image Match Supported GUI-free operations owned by the Image Match app in the Image Measurement family. Nerve Response Analysis Supported GUI-free operations owned by the Nerve Response Analysis app in the Neurophysiology family. Response Review And Stats Supported GUI-free operations owned by the Response Review Stats app in the Neurophysiology family. RHS Preview Supported GUI-free operations owned by the Rhs Preview app in the Neurophysiology family. T-Test Wizard Supported GUI-free operations owned by the Ttest Wizard app in the Statistics family. Video Marker Supported GUI-free operations owned by the Video Marker app in the Image Measurement family. VT Resistance Supported GUI-free operations owned by the Vt Resistance app in the Electrochemistry family."},{"title":"Chrono Overlay","url":"apps/electrochemistry/chrono-overlay.html","kind":"app","section":"apps","keywords":"apps electrochemistry chrono overlay Chrono Overlay","text":"Chrono Overlay Chrono Overlay compares voltage and current transients from multiple Gamry DTA files on a common pulse-centered time axis and exports the aligned curves. Requirements And Launch Each source must contain a readable chrono curve with time, voltage, and current data. labkit_ChronoOverlay_app Inputs Use **Add DTA files** to select one or more `.DTA` files from one directory. The app parses each file as chrono data and reports unreadable items. The file list controls curve order, legend labels, and removal; selection does not discard other loaded curves. Saved projects preserve the ordered file list and reopen it through portable source references. Basic Workflow Add the DTA files to compare. Inspect the overlaid voltage and current plots. Choose seconds, milliseconds, or sample number for the X axis. Set line width, legend visibility, and grid visibility. Export the aligned curves CSV. Plot styling does not alter exported values. Removing a file removes its curves from both plots and the next export. Alignment Algorithm For each valid transient, the app uses the detected cathodic/anodic blank interval and defines time zero at its center. If pulse detection or the gap center is unavailable, it explicitly falls back to the first recorded sample and reports that fallback in the app log. If files use different sample times, export constructs a merged aligned-time axis and interpolates each voltage/current series onto that axis. Missing coverage remains missing; the app does not extrapolate a transient beyond its recorded time range. Parameters Parameter Default Effect X axis Time (s) plot coordinate only; alternatives are Time (ms) and Sample # Line width 1.3 rendered curve width Show file-name legend on legend visibility Show grid on axes grid visibility Output **Export curves CSV** writes `TimeGapCenterAligned_s` followed by paired voltage and current columns for each source. Column names retain a sanitized source identity. The app also writes a result manifest containing source references, current parameters, and the CSV output role. Use Without The GUI [items, status] = labkit.dta.loadFiles([\"run01.DTA\", \"run02.DTA\"], ... \"chrono\"); assert(all([status.ok]), \"One or more DTA files could not be read.\"); aligned = arrayfun(@(item) ... chrono_overlay.sourceFiles.alignByPulseGap(item), items); tableOut = chrono_overlay.resultFiles.buildOverlayExportTable(aligned); These app-owned helpers expose the workflow to scripts but are not currently listed as stable public APIs; reusable DTA reading is supported through `labkit.dta`. Errors And Limitations A source without a detectable biphasic gap is aligned to its first sample; review the log before comparing it with gap-centered files. Interpolation enables a common export table but does not make differently sampled instruments scientifically identical. Compare only experiments whose control mode, scale, and units are compatible. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [CIC](../cic/README.md) [VT Resistance](../vt-resistance/README.md)"},{"title":"Charge-Injection Capacity","url":"apps/electrochemistry/cic.html","kind":"app","section":"apps","keywords":"apps electrochemistry cic Charge-Injection Capacity","text":"Charge-Injection Capacity The CIC app measures charge delivered by a biphasic current pulse, normalizes charge by electrode area, and reports voltage-transient polarization metrics at a controlled delay after each pulse phase. Requirements And Launch The app requires a chrono DTA curve with valid `T`, `Vf`, and `Im` columns. labkit_CIC_app Inputs And Batch Behavior Add one or more chrono `.DTA` files. The selected row is decoded for immediate preview; batch calculation is performed with the same analysis settings when results are exported. This avoids repeatedly decoding every large file while the user is only switching previews. Saved projects preserve file order and portable source identity through removal, later additions, and reopen. Electrode area comes from a positive UI override when supplied, otherwise from the parsed DTA metadata. Without a valid positive area, charge in coulombs can still be calculated but area-normalized CIC fields are `NaN`. Basic Workflow Add chrono DTA files and select a representative file. Choose a water-window preset or set custom cathodic/anodic limits. Choose pulse detection, post-pulse delay, electrode area, phase mode, and output unit. Inspect detected phases, sampling markers, and voltage/current plots. Review the current-file summary. Export the batch result CSV. Analysis Parameters Parameter Default Legal choices or unit Preset Pt (-0.6 to 0.8 V) Pt, PEDOT:PSS, or Custom Cathodic / anodic limits -0.6 / 0.8 volts Delay 10 microseconds after each phase end Pulse detection Metadata first, then auto metadata fallback, metadata only, or current-only auto CIC phase Total biphasic Cathodic phase, Anodic phase, Total biphasic CIC unit mC/cm^2 mC/cm^2 or uC/cm^2 Use measured current on integrate measured `Im`; otherwise use detected pulse estimates Plot markers, limit lines, shading, axes, and grid choices affect presentation only. Analysis controls apply to the entire exported batch. Calculation Semantics The app removes samples where time, voltage, or current is not finite and requires at least five valid points. `labkit.dta.detectPulses` identifies the cathodic phase, interpulse gap, anodic phase, and surrounding baseline windows. Measured-current mode integrates each detected current phase over time. The phase charges `Qc_C` and `Qa_C` are positive magnitudes; total charge is their sum. With area `A` in cm^2: CIC_mC_cm2 = 1000 * Q_C / A_cm2 Maximum cathodic and anodic polarization potentials are interpolated at `phase end + delay`. The app does not extrapolate: if either requested time is outside the recorded range, that file fails with an explicit delay message. Baseline candidates are selected from pre-pulse, interpulse, and post-pulse windows, with documented fallbacks recorded in the result struct. Water-window status compares the calculated polarization potentials with the selected cathodic and anodic limits. The limits are an app policy and do not change the integrated charge. Output Schema The batch CSV contains source identity, area, delay, pulse-detection method, phase timing, measured/estimated current information, charge fields, area-normalized CIC fields, polarization and baseline values, water-window status, and a per-file result message. `Area_cm2` and `Delay_us` are exported so normalization and sample timing remain auditable. A `.labkit.json` result manifest accompanies the CSV and records source references, parameters, and the output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message); options = struct( ... \"delay_s\", 10e-6, ... \"cathLimit\", -0.6, ... \"anodLimit\", 0.8, ... \"area_cm2\", 0.05, ... \"pulseMode\", \"Metadata first, then auto\", ... \"usedMeasuredCurrent\", true); result = cic.analysisRun.computeCIC(item, options); assert(result.ok, result.message); Errors And Limitations Missing `T`, `Vf`, or `Im`, fewer than five valid samples, or failed pulse detection returns `ok=false` with a message. A delay outside the recorded time range fails instead of extrapolating. Area-normalized values require a positive area; invalid area is not replaced by an arbitrary default. CIC results depend on pulse detection, baseline selection, sampling rate, electrode area, and whether measured or estimated current is used. Related Topics [Electrochemistry family](../README.md) [DTA pulse detection](../../../libraries/dta/README.md) [VT Resistance](../vt-resistance/README.md) [API Reference](../../../reference/README.md) cic.analysisRun.computeCIC Calculate charge-injection and voltage-transient metrics."},{"title":"Charge-Storage Capacity","url":"apps/electrochemistry/csc.html","kind":"app","section":"apps","keywords":"apps electrochemistry csc Charge-Storage Capacity","text":"Charge-Storage Capacity The CSC app compares charge obtained from time-domain current integration with charge obtained from cyclic-voltammetry integration for every readable CV/CT cycle in one or more Gamry DTA files. Requirements And Launch Each analyzed cycle must expose exact `T`, `Vf`, and `Im` columns and a positive scan rate. labkit_CSC_app Inputs And Selection Add one or more CV/CT `.DTA` files. The selected file determines the current curve list, readout, and plots. Selecting another file resets the curve selection and default plot quantities to that file; it does not silently keep a cycle from the previous source. Saved projects preserve the successfully decoded file order through removal, later additions, and reopen. The default curve selection is **All cycles**. Individual cycle selection updates the comparison readout for that cycle. Basic Workflow Add CV/CT files and select the active source. Inspect all cycles for incomplete or anomalous edge cycles. Choose Full, Cathodic, or Anodic comparison mode. Enter electrode area when area-normalized CSC is required. Optionally ignore the first and last cycle. Configure the two plot panes and review CT/CV agreement. Export all-cycle results and, when needed, column-oriented CV data. Plot Behavior All-cycle plots use a distinct color for each cycle; cathodic and anodic current branches use related dark/light variants. Time is shifted so each cycle begins at zero. Selecting new X/Y quantities refits the axes. Toggling trim overlays preserves the user's current view because it changes an overlay, not the underlying coordinate selection. **Ignore first/last cycle** applies consistently to the all-cycle plot, result table, CSC export, and CV-data export. It is intended for known incomplete edge cycles and must not be used to hide arbitrary outliers. Calculation Semantics Time-domain charge integrates current against time. CV-domain charge integrates current against potential and divides by positive scan rate. Each line segment is split at a current zero crossing so cathodic and anodic contributions are assigned without smearing a sign change across one trapezoid. Q_CT,cath = integral |I(t)| dt over cathodic current Q_CT,anod = integral I(t) dt over anodic current Q_CV = integral I(V) dV / scanRate, split by current sign CSC = 1000 * Q_C / area_cm2 Full mode sums positive cathodic and anodic charge magnitudes. Relative difference is `100 * abs(Q_CT - Q_CV) / max(abs(Q_CT), abs(Q_CV))`, with zero reported when both charges are zero. `dtErr` reports the maximum discrepancy between measured time increments and `abs(dV)/scanRate` over valid segments. Without a valid positive area, charge comparison remains available but CSC fields in mC/cm^2 are `NaN`. Outputs **Export all cycles CSV** writes one row per included file cycle with source, cycle, mode, scan rate, area, CT and CV cathodic/anodic/full charges, selected charge and CSC values, absolute/relative difference, time-step consistency, and status fields. **Export CV data CSV** writes potential plus paired current and scan-rate columns for replotting and independent calculation. When all exported cycles share the same potential vector, they occupy one file. When vectors differ, the app writes one CSV per source item using the selected filename as a stem. Each export action has its own `.labkit.json` provenance manifest. Use Without The GUI [item, status] = labkit.dta.loadFile(\"cv.DTA\", \"cvct\"); assert(status.ok, status.message); curve = item.curves(1); options = struct(\"scanRate\", 0.05, ... \"area_cm2\", 0.05, \"mode\", \"Full\"); result = csc.analysisRun.computeCSC(curve, options); assert(result.ok, result.message); Function Reference The generated pages for [`computeCSC`](../../../reference/api/csc/analysisRun/computeCSC.html) and [`chargeDensity`](../../../reference/api/csc/analysisRun/chargeDensity.html) state the exact numeric inputs, units, sign conventions, outputs, invalid-area behavior, executable examples, and related APIs used outside the GUI. Errors And Limitations Scan rate must be finite and positive. Required column names are exact; a visually similar arbitrary table is not accepted as a CV/CT cycle. Area normalization is omitted rather than guessed when area is invalid. Agreement between CT and CV integration is a consistency measurement, not proof that the electrochemical experiment is otherwise valid. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md) csc.analysisRun.chargeDensity Convert charge in coulombs to density in mC/cm^2. csc.analysisRun.computeCSC Compare time-integrated and scan-rate-derived electrode charge."},{"title":"batch_crop.cropGeometry.cropImage","url":"reference/api/batch_crop/cropGeometry/cropImage.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.cropImage","text":"CROPIMAGE Rotate and crop an image at a fixed pixel size. CROPIMAGE Rotate and crop an image at a fixed pixel size. Usage: result = batch_crop.cropGeometry.cropImage(imageData, opts) Description: Adds optional padding, rotates the resulting canvas, and extracts a crop centered at a point expressed in the original image coordinate system. The crop always has the requested pixel dimensions. Areas outside the transformed canvas use the selected fill value. This function does not resize the crop, display graphics, or write files. Inputs: imageData - Nonempty numeric or logical image. Accepted sizes are M-by-N and M-by-N-by-C. The output preserves its class and channel count. opts - Scalar structure containing the crop options below. Options: cropWidth - Required positive pixel count. Noninteger values are rounded. cropHeight - Required positive pixel count. Noninteger values are rounded. angleDeg - Counterclockwise canvas rotation in degrees. Default: 0. centerXY - Crop center [x y] in original-image pixel coordinates, where x is the column and y is the row. Invalid or empty input selects the geometric image center. The center is limited so the requested crop remains representable on the prepared canvas. Default: []. paddingPercent - Padding added around each source edge as a percentage of the corresponding source dimension. Negative values are treated as zero by the canvas preparation code. Default: 0. fillValue - Scalar or channel-compatible value used outside the rotated image. Empty input selects the app's white value for imageData. Default: []. Outputs: result - Scalar crop result structure. result.image is exactly cropHeight-by-cropWidth-by-C. result.centerX and centerY retain the effective center in original-image coordinates. rotationDeg, paddingPercent, sourceWidth, sourceHeight, cropWidth, cropHeight, nativeCropWidth, nativeCropHeight, outputWidth, and outputHeight describe the operation. scaleMode is \"Pixels\", ok is true, status is \"cropped\", and message is \"OK\". Errors: labkit_BatchImageCrop_app:InvalidImage - imageData is empty, nonnumeric, nonlogical, or has more than three dimensions. labkit_BatchImageCrop_app:InvalidCropSize - cropWidth or cropHeight is absent, nonfinite, nonscalar, or smaller than one. Example: imageData = reshape(uint8(1:100), 10, 10); opts = struct(\"cropWidth\", 4, \"cropHeight\", 3, \"centerXY\", [5 6]); result = batch_crop.cropGeometry.cropImage(imageData, opts); assert(result.ok && isequal(size(result.image), [3 4])) See also batch_crop.cropGeometry.cropScaledImage, batch_crop.cropGeometry.scalePlan"},{"title":"batch_crop.cropGeometry.cropScaledImage","url":"reference/api/batch_crop/cropGeometry/cropScaledImage.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.cropScaledImage","text":"CROPSCALEDIMAGE Crop one calibrated image to a common physical output scale. CROPSCALEDIMAGE Crop one calibrated image to a common physical output scale. Usage: result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan) result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan, itemIndex) Description: Uses the selected item's native crop dimensions from scalePlan, delegates rotation and cropping to batch_crop.cropGeometry.cropImage, then resamples the crop to the plan's common output dimensions. Resampling uses bilinear interpolation independently for every channel and preserves image class. The function has no graphics or file side effects. Inputs: imageData - Nonempty numeric or logical M-by-N or M-by-N-by-C image. opts - Crop option structure accepted by cropImage. cropWidth and cropHeight are replaced by the native dimensions selected by plan. plan - Scalar structure returned by batch_crop.cropGeometry.scalePlan. itemIndex - Positive index selecting nativeCropWidth, nativeCropHeight, sourcePixelsPerUnit, resampleFactor, and warnings for imageData. Default: 1. Options: angleDeg - Counterclockwise canvas rotation in degrees. Default: 0. centerXY - Crop center [x y] in original-image pixel coordinates. Empty or invalid input selects the image center. Default: []. paddingPercent - Padding around the source before rotation, as a percentage of each source dimension. Default: 0. fillValue - Value used outside the rotated image. Empty selects the app's white value for imageData. Default: []. Outputs: result - cropImage result updated for physical mode. result.image has plan.outputHeight rows and plan.outputWidth columns. cropWidth and cropHeight describe that output; nativeCropWidth and nativeCropHeight describe the pre-resampling crop. scaleUnit, sourcePixelsPerUnit, targetPixelsPerUnit, resampleFactor, and scaleWarning come from plan. Failure Behavior: itemIndex must select an element from every per-image plan field, and plan output dimensions must be positive. Missing fields, out-of-range indices, invalid image/crop options, or unsupported interpolation classes propagate the originating cropGeometry or MATLAB error. Example: imageData = reshape(uint8(1:100), 10, 10); opts = struct(\"centerXY\", [5 5]); plan = struct(\"nativeCropWidth\", 4, \"nativeCropHeight\", 4, ... \"outputWidth\", 8, \"outputHeight\", 6, \"unit\", \"um\", ... \"sourcePixelsPerUnit\", 2, \"targetPixelsPerUnit\", 4, ... \"resampleFactor\", 2, \"warnings\", \"\"); result = batch_crop.cropGeometry.cropScaledImage(imageData, opts, plan); assert(isequal(size(result.image), [6 8])) See also batch_crop.cropGeometry.cropImage, batch_crop.cropGeometry.scalePlan"},{"title":"batch_crop.cropGeometry.scalePlan","url":"reference/api/batch_crop/cropGeometry/scalePlan.html","kind":"reference","section":"reference","keywords":"batch_crop.cropGeometry.scalePlan","text":"SCALEPLAN Plan equal-size physical crops across calibrated images. SCALEPLAN Plan equal-size physical crops across calibrated images. Usage: plan = batch_crop.cropGeometry.scalePlan(items, opts) Description: Converts every image calibration to a requested physical unit, determines a common output pixel density, and calculates native crop and common output dimensions. Automatic density is the median converted source density. Large scale differences produce warnings but do not reject the plan. Inputs: items - Nonempty structure array. Each element must have a valid scaleCalibration accepted by batch_crop.scaleCalibration.isSet, including a positive pixelsPerUnit value and its source unit. opts - Scalar structure containing the physical crop options below. Options: physicalWidth - Required positive crop width in scaleUnit. physicalHeight - Required positive crop height in scaleUnit. scaleUnit - Unit used for physical dimensions and pixel densities. Legal values are \"m\", \"cm\", \"mm\", \"um\", and \"nm\". Default: \"um\". targetPixelsPerUnit - Positive common output density in pixels per scaleUnit. Zero, negative, or nonfinite input selects the median source density automatically. Default: 0. maxUpsamplePercent - Largest accepted upsampling above native density before a warning is recorded. Negative input is treated as zero. Default: 15. Outputs: plan - Scalar structure with these fields: Plan Fields: mode - \"Physical\". unit - Effective scaleUnit. targetSource - \"Manual\" for a positive targetPixelsPerUnit, otherwise \"Auto\". physicalWidth, physicalHeight - Requested dimensions in unit. sourcePixelsPerUnit - Converted density for each input item. targetPixelsPerUnit - Common output density. resampleFactor - targetPixelsPerUnit ./ sourcePixelsPerUnit. nativeCropWidth, nativeCropHeight - Per-item native crop sizes in pixels, rounded to integers with a minimum of one. outputWidth, outputHeight - Common output size in pixels, rounded to integers with a minimum of one. warnings - Per-item text. Upsampling beyond maxUpsamplePercent reports \"upsample ...x\"; resample factors below 0.5 report \"downsample ...x\". Errors: labkit_BatchImageCrop_app:ScaleCalibrationMissing - Any item lacks a valid calibration convertible to scaleUnit. labkit_BatchImageCrop_app:InvalidPhysicalSize - physicalWidth or physicalHeight is missing, nonscalar, nonfinite, or nonpositive. labkit_BatchImageCrop_app:InvalidScaleUnit - scaleUnit is unsupported. Example: cal = labkit.app.interaction.scaleCalibration(20, 10, \"um\"); items = struct(\"scaleCalibration\", cal); opts = struct(\"physicalWidth\", 5, \"physicalHeight\", 3, ... \"scaleUnit\", \"um\", \"targetPixelsPerUnit\", 4); plan = batch_crop.cropGeometry.scalePlan(items, opts); assert(plan.outputWidth == 20 && plan.outputHeight == 12) See also batch_crop.cropGeometry.cropScaledImage, labkit.app.interaction.scaleCalibration"},{"title":"cic.analysisRun.computeCIC","url":"reference/api/cic/analysisRun/computeCIC.html","kind":"reference","section":"reference","keywords":"cic.analysisRun.computeCIC","text":"COMPUTECIC Calculate charge-injection and voltage-transient metrics. COMPUTECIC Calculate charge-injection and voltage-transient metrics. Usage: A = cic.analysisRun.computeCIC(item) A = cic.analysisRun.computeCIC(item, opts) Description: Analyzes one biphasic chronopotentiometry transient. The function detects the cathodic and anodic pulse windows, samples the polarization voltage after each phase, integrates injected charge, normalizes charge by electrode area, and checks the measured voltages against the selected cathodic and anodic limits. It performs no plotting or file export. Inputs: item - Scalar DTA item structure. item.curve, or the main curve selected from item.tables, must provide T in seconds, Vf in volts, and Im in amperes. Pt is optional; sample numbers are generated when it is absent. item.meta may contain pulse metadata and area_cm2. opts - Optional scalar structure. See Options. Options: delay_s - Time in seconds from the end of each pulse phase to the Emc or Ema sample. The voltage is linearly interpolated at cath_end+delay_s and anod_end+delay_s. Default: 10e-6 seconds. cathLimit - Cathodic water-window limit in volts. Emc passes when Emc >= cathLimit. Default: -0.6 V. anodLimit - Anodic water-window limit in volts. Ema passes when Ema <= anodLimit. Default: 0.8 V. areaOverride - Positive electrode area in square centimetres, supplied as a numeric scalar or numeric text. A valid value takes precedence over area_cm2 and item.meta.area_cm2. Default: \"\". area_cm2 - Positive numeric electrode area in square centimetres used when areaOverride is empty or invalid. If neither option supplies an area, item.meta.area_cm2 is used. Without a valid area, charge is still calculated but CIC density fields are NaN. Default: NaN. pulseMode - Pulse-detection policy. Allowed display values are \"Metadata first, then auto\", \"Metadata only\", and \"Auto from Im only\". The equivalent values \"metadata_first\", \"metadata_only\", and \"current_only\" are also accepted by labkit.dta.detectPulses. Default: \"Metadata first, then auto\". usedMeasuredCurrent - Logical scalar. When true, Qc and Qa are the absolute trapezoidal integrals of measured Im over the detected phase windows. When false, charge is the absolute nominal phase current multiplied by phase duration. Default: true. Calculations: Samples with NaN T, Vf, or Im are removed together. At least five remaining samples are required. Emc is Vf at cath_end+delay_s and Ema is Vf at anod_end+delay_s. The cathodic baseline Eipp uses the first finite value in this order: pre-pulse median, interpulse median, post-pulse median, zero. The anodic baseline Eipp_gap prefers the interpulse median, followed by the pre-pulse median, post-pulse median, and Eipp. Qc_C and Qa_C are stored as positive charge magnitudes in coulombs; Qt_C = Qc_C + Qa_C. For a valid area, CIC density is 1000*Q/area_cm2 in mC/cm^2. CICc_mCcm2, CICa_mCcm2, and CICt_mCcm2 refer to the cathodic, anodic, and summed biphasic charge respectively. Outputs: A - Scalar result structure. A.ok is true only when curve selection, pulse detection, delayed voltage sampling, and both charge integrations succeed. A.message is \"OK\" on success and explains the first failed stage otherwise. Output Fields: ok - Logical success flag. message - \"OK\" or a description of the first failed stage. delay_s - Effective delayed-voltage sampling interval in seconds. cathLimit - Effective cathodic voltage limit in volts. anodLimit - Effective anodic voltage limit in volts. area_cm2 - Selected positive electrode area in cm^2, or NaN when missing. usedMeasuredCurrent - Effective charge-integration policy. logOnFailure - True for failures that the app should copy into its visible diagnostic log; false for ordinary incomplete-input results. Emc - Delayed cathodic polarization voltage in volts. Ema - Delayed anodic polarization voltage in volts. t_emc - Requested Emc sample time in seconds. t_ema - Requested Ema sample time in seconds. emc_idx - Index of the recorded sample nearest t_emc. ema_idx - Index of the recorded sample nearest t_ema. Epre - Median pre-pulse voltage in volts. Ebetween - Median interpulse voltage in volts. Epost - Median post-pulse voltage in volts. Eipp - Baseline selected for the cathodic phase, in volts. Eipp_gap - Baseline selected for the anodic phase, in volts. baselineCathSource - Text naming the cathodic baseline source. baselineAnodSource - Text naming the anodic baseline source. baselineCathWindow - Two-element time window used for Eipp, in seconds. baselineAnodWindow - Two-element time window used for Eipp_gap, in seconds. Vc_on - Voltage sampled delay_s after cathodic phase onset, in volts. Va_on - Voltage sampled delay_s after anodic phase onset, in volts. t_conset - Requested cathodic onset sample time in seconds. t_aonset - Requested anodic onset sample time in seconds. Va_cath_mag - Absolute Eipp-to-Vc_on voltage change, in volts. Va_anod_mag - Absolute Eipp_gap-to-Va_on voltage change, in volts. tc_s - Cathodic phase duration in seconds. ta_s - Anodic phase duration in seconds. tip_s - Interpulse gap duration in seconds. Ic_est_A - Median measured cathodic current in amperes. Ia_est_A - Median measured anodic current in amperes. cathMask - Logical vector selecting cathodic phase samples. anodMask - Logical vector selecting anodic phase samples. Qc_C - Cathodic charge magnitude in coulombs. Qa_C - Anodic charge magnitude in coulombs. Qt_C - Qc_C + Qa_C in coulombs. CICc_mCcm2 - Cathodic charge density in mC/cm^2. CICa_mCcm2 - Anodic charge density in mC/cm^2. CICt_mCcm2 - Total biphasic charge density in mC/cm^2. cathOK - True when Emc is not below cathLimit. anodOK - True when Ema is not above anodLimit. safe - True when both cathOK and anodOK are true. limitSide - \"safe\", \"cathodic exceeded\", \"anodic exceeded\", or \"both exceeded\". pulse - Pulse geometry returned by labkit.dta.detectPulses. detectMode - Pulse-detection method that produced pulse. detectMsg - Pulse-detection status message. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. pt - Filtered source point numbers or generated zero-based sample numbers. sample_dt - Median difference between consecutive filtered times, in seconds. sample_dt_report contains the same value for exports. ampEstimate_A - Largest absolute filtered current in amperes. Failure Behavior: Missing curves, too few valid samples, failed pulse detection, an out-of-range delay, or a pulse window with fewer than two samples returns A.ok=false. These data failures normally return a message instead of throwing. Invalid MATLAB types or malformed structures can still raise an error from the DTA access or numeric functions. Typical Call: [item, status] = labkit.dta.loadFile(\"pulse.DTA\", \"chrono\"); assert(status.ok, status.message) opts = struct( ... \"delay_s\", 10e-6, ... \"cathLimit\", -0.6, ... \"anodLimit\", 0.8, ... \"area_cm2\", 0.03, ... \"pulseMode\", \"Metadata first, then auto\", ... \"usedMeasuredCurrent\", true); A = cic.analysisRun.computeCIC(item, opts); assert(A.ok, A.message) fprintf(\"Cathodic CIC: %.4g mC/cm^2\\n\", A.CICc_mCcm2) See also labkit.dta.detectPulses, vt_resistance.analysisRun.computeResistance"},{"title":"csc.analysisRun.chargeDensity","url":"reference/api/csc/analysisRun/chargeDensity.html","kind":"reference","section":"reference","keywords":"csc.analysisRun.chargeDensity","text":"CHARGEDENSITY Convert charge in coulombs to density in mC/cm^2. CHARGEDENSITY Convert charge in coulombs to density in mC/cm^2. Usage: value = csc.analysisRun.chargeDensity(chargeC, areaCm2) Inputs: chargeC - Numeric scalar or array of charge values in coulombs. Signed values remain signed. areaCm2 - Positive finite scalar electrode area in square centimetres. Outputs: value - Charge density with the same size as chargeC, in mC/cm^2. When areaCm2 is nonfinite or nonpositive, value is scalar NaN. Description: The conversion is 1000*chargeC/areaCm2 because one coulomb equals 1000 millicoulombs. This unit helper performs no integration and does not take the absolute value of cathodic charge. Failure Behavior: A nonfinite or nonpositive scalar area returns scalar NaN. areaCm2 must be scalar and chargeC must support numeric division; incompatible MATLAB values or nonscalar area conditions raise the originating logical or arithmetic error. Example: density = csc.analysisRun.chargeDensity([0.002 -0.001], 0.5); assert(isequal(density, [4 -2])) See also csc.analysisRun.computeCSC"},{"title":"csc.analysisRun.computeCSC","url":"reference/api/csc/analysisRun/computeCSC.html","kind":"reference","section":"reference","keywords":"csc.analysisRun.computeCSC","text":"COMPUTECSC Compare time-integrated and scan-rate-derived electrode charge. COMPUTECSC Compare time-integrated and scan-rate-derived electrode charge. Usage: A = csc.analysisRun.computeCSC(curve) A = csc.analysisRun.computeCSC(curve, opts) Inputs: curve - Scalar DTA curve struct. curve.headers must contain the exact names T, Vf, and Im, and curve.data must hold the corresponding numeric columns. T is in seconds, Vf in volts, and Im in amperes. opts - Optional scalar struct described below. Default: struct(). Options: scanRate - Positive finite voltage scan rate in V/s. This value is required for the CV-derived charge. Default: NaN, which returns ok=false. mode - Charge branch reported in Qct and Qcv: \"Full\", \"Cathodic\", or \"Anodic\". Unrecognized values use \"Full\". Default: \"Full\". area_cm2 - Positive electrode area in cm^2 as a numeric scalar or numeric text. Invalid or missing values leave density fields NaN while charge calculations still succeed. Default: NaN. Outputs: A - Scalar result struct. On success, it contains every field described below. Validation failures return ok=false, a stable message and logMessage, plus the option fields established before the failure. Result Fields: ok - Logical success flag. message - \"OK\", \"scan rate missing\", \"Need T, Vf, Im\", or \"Not enough points\". logMessage - User-facing explanation for a failed comparison; blank on success. mode - Effective branch used for Qct and Qcv. scanRate - Effective scan rate in V/s. area_cm2 - Parsed positive electrode area, or NaN. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. IcathDisp - Im with zero and anodic samples replaced by NaN. IanodDisp - Im with zero and cathodic samples replaced by NaN. QctCath - Absolute cathodic integral of I dt in coulombs. QctAnod - Anodic integral of I dt in coulombs. QctFull - QctCath + QctAnod in coulombs. QcvCath - Absolute cathodic scan-rate-derived charge in coulombs. QcvAnod - Anodic scan-rate-derived charge in coulombs. QcvFull - QcvCath + QcvAnod in coulombs. Qct - QctCath, QctAnod, or QctFull selected by mode. Qcv - QcvCath, QcvAnod, or QcvFull selected by mode. diff_C - Signed difference Qct-Qcv in coulombs. rel_pct - 100*abs(diff_C)/max(abs(Qct),abs(Qcv)); zero when both selected charges are zero. dtErr - Largest absolute difference, in seconds, between each measured time interval and abs(dV)/scanRate after zero-crossing subdivision. Qct_mC_cm2 - Selected CT charge density in mC/cm^2, or NaN without area. Qcv_mC_cm2 - Selected CV charge density in mC/cm^2, or NaN without area. diff_mC_cm2 - Signed density difference in mC/cm^2, or NaN without area. Description: computeCSC evaluates one current-voltage cycle two ways. The CT result uses recorded time, while the CV result reconstructs interval duration from the commanded scan rate. Reporting both values makes timing inconsistencies and charge-density differences visible without requiring the CSC GUI. Calculations: Rows containing NaN in T, Vf, or Im are removed together. Each adjacent sample interval is split where linearly interpolated current crosses zero. CT charge integrates current over measured time. CV charge uses dt=abs(dV)/scanRate for the same current endpoints. Cathodic contributions are stored as positive magnitudes, so each full charge is the sum of two nonnegative phase charges. Failure Behavior: A missing scan rate, missing exact column name, or fewer than two remaining samples returns ok=false instead of throwing. Malformed MATLAB types or inconsistent curve dimensions may still raise an indexing or numeric error. Example: curve = struct(\"headers\", {{'T','Vf','Im'}}, ... \"data\", [0 0 -1; 1 1 1; 2 2 1]); A = csc.analysisRun.computeCSC(curve, ... struct(\"scanRate\", 2, \"mode\", \"Full\")); assert(A.ok && abs(A.QctFull - 1.5) < 1e-12) See also csc.analysisRun.chargeDensity"},{"title":"curvature.analysisRun.computeCurvatureFit","url":"reference/api/curvature/analysisRun/computeCurvatureFit.html","kind":"reference","section":"reference","keywords":"curvature.analysisRun.computeCurvatureFit","text":"COMPUTECURVATUREFIT Fit a circle and curvature to traced image points. COMPUTECURVATUREFIT Fit a circle and curvature to traced image points. Usage: fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix) fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix, calibration) fit = curvature.analysisRun.computeCurvatureFit(xPix, yPix, ... calibration, doDensify, denseN, fitPathX, fitPathY) Description: Removes consecutive duplicate points, optionally resamples a traced path at equal arc-length intervals, obtains an algebraic Kasa circle estimate, and minimizes radial squared error with base-MATLAB fminsearch. The result includes pixel-space values and, when calibration is valid, physical radius, curvature, residuals, RMSE, and traced length. No GUI or file is required. Inputs: xPix - Numeric x-coordinate vector in image pixels (image columns). At least three unique consecutive point pairs must remain. yPix - Numeric y-coordinate vector in image pixels (image rows), with the same number of elements as xPix. calibration - Scale structure accepted by curvature.analysisRun.normalizeScaleCalibration. Important fields are referencePixels, referenceLength, unit, pixelsPerUnit, isCalibrated, and referenceLine. Empty or omitted input selects uncalibrated pixels. doDensify - Logical-like scalar controlling equal-arc-length resampling. Default: true. denseN - Number of resampled fitting points, rounded to an integer with a minimum of three. Default: 300. fitPathX - Optional displayed-path x-coordinate vector. fitPathY - Optional displayed-path y-coordinate vector. When fitPathX and fitPathY have equal length and at least three unique points, this path supplies curve length and, when doDensify is true, the circle fit. Otherwise the anchor vectors supply both calculations. Calculations: R_px is the fitted radius and kappa_per_px = 1/R_px. rmse_px is the root mean square radial residual of the points used by the optimizer. When calibration.isCalibrated is true, displayed lengths are divided by pixelsPerUnit and displayed curvature is the reciprocal physical radius. residuals_show always corresponds to the original unique anchor points. Outputs: fit - Scalar structure describing the successful fit. Fit Fields: ok, message - true and empty text after a successful calculation. xc_px, yc_px, R_px - Circle center and radius in pixels. kappa_per_px, rmse_px - Pixel curvature and radial RMSE. R_show, kappa_show, rmse_show - Values in unitLen/unitK when calibrated, otherwise the corresponding pixel values. unitLen, unitK - Length and curvature units, for example \"um\" and \"1/um\", or \"px\" and \"1/px\" without calibration. residuals_show - Anchor-point radial residual column vector in unitLen. xPix, yPix - Unique anchor coordinates used for reported residuals. xFit, yFit - Points passed to the circle optimizer. curveLength_px, curveLength_show, curveLengthUnit, curvePointCount - Polyline length result for the accepted fit path. referencePx, referenceLength, scaleUnit, px_per_unit, usePhysicalScale - Effective scale metadata. Errors: labkit_CurvatureMeasurement_app:NotEnoughPoints - Fewer than three unique anchor points remain. labkit_CurvatureMeasurement_app:InvalidFit - Optimization returns a nonfinite or nonpositive radius. Example: theta = (0:11).' * (2*pi/12); x = 30 + 10*cos(theta); y = 40 + 10*sin(theta); fit = curvature.analysisRun.computeCurvatureFit(x, y, [], false); assert(fit.ok && abs(fit.R_px - 10) < 1e-3) See also curvature.analysisRun.computeCurveLength, curvature.analysisRun.normalizeScaleCalibration"},{"title":"curvature.analysisRun.computeCurveLength","url":"reference/api/curvature/analysisRun/computeCurveLength.html","kind":"reference","section":"reference","keywords":"curvature.analysisRun.computeCurveLength","text":"COMPUTECURVELENGTH Measure the polyline length of traced image points. COMPUTECURVELENGTH Measure the polyline length of traced image points. Usage: lengthResult = curvature.analysisRun.computeCurveLength(xPix, yPix) lengthResult = curvature.analysisRun.computeCurveLength( ... xPix, yPix, calibration) Description: Removes consecutive points closer than the app's duplicate tolerance and sums the Euclidean distance between the remaining adjacent points. A valid calibration converts the pixel length to the requested physical unit. The function does not smooth, interpolate, close the path, plot, or write files. Inputs: xPix - Numeric x-coordinate vector in image pixels (image columns). At least two unique consecutive point pairs are required. yPix - Numeric y-coordinate vector in image pixels (image rows), with the same number of elements as xPix. calibration - Scale structure accepted by curvature.analysisRun.normalizeScaleCalibration. Empty or omitted input reports length in pixels. Outputs: lengthResult - Scalar structure with the following fields. Result Fields: ok, message - true and empty text after a successful calculation. length_px - Sum of adjacent segment lengths in pixels. length_show - length_px divided by pixelsPerUnit when calibrated; otherwise length_px. unitLen - Calibration unit when calibrated, otherwise \"px\". referencePx, referenceLength, scaleUnit, px_per_unit, usePhysicalScale - Effective scale metadata. pointCount - Number of points remaining after duplicate removal. Errors: labkit_CurvatureMeasurement_app:NotEnoughLengthPoints - Fewer than two unique points remain. Example: x = [0 3 3]; y = [0 0 4]; result = curvature.analysisRun.computeCurveLength(x, y); assert(result.length_px == 7 && result.unitLen == \"px\") See also curvature.analysisRun.computeCurvatureFit, curvature.analysisRun.normalizeScaleCalibration"},{"title":"dic_postprocess.analysisRun.makeStrainOverlay","url":"reference/api/dic_postprocess/analysisRun/makeStrainOverlay.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.makeStrainOverlay","text":"MAKESTRAINOVERLAY Blend a color-mapped strain field over a reference image. MAKESTRAINOVERLAY Blend a color-mapped strain field over a reference image. Usage: overlay = dic_postprocess.analysisRun.makeStrainOverlay( ... referenceImage, strainMap, mask, roiMask, opts) Inputs: referenceImage - Numeric grayscale or RGB image used as the background. strainMap - Two-dimensional numeric strain field on the DIC grid. mask - Logical display mask. It is resized to referenceImage by nearest neighbor before blending. roiMask - Optional logical mask on the strain grid. When nonempty, it determines valid strain samples together with finite strainMap values; otherwise a resized form of mask is used. opts - Scalar overlay option struct described below. Required Options: alpha - Strain-color blend fraction; 0 shows the reference and 1 shows the color map inside the overlay mask. colorRange - Two-element [minimum maximum] strain range mapped to the first and last colormap rows. Values outside the range are clipped. colormap - N-by-3 RGB colormap. oversample - Positive strain-grid enlargement factor, rounded to an integer before linear resize. Values below 1 use 1. sigmaSmooth - Gaussian smoothing sigma on the extended strain grid. Values at or below 0 disable smoothing. brightness - Additive reference-image brightness adjustment. contrast - Multiplicative contrast around normalized intensity 0.5. gamma - Exponent applied after brightness and contrast adjustment. saturation - Multiplier for the HSV saturation channel. rgbGain - Three-element red, green, and blue gain vector. Optional Options: edgeTrim - Nonnegative number of valid-mask edge pixels removed before interpolation. Default: 1 when the field is absent. Outputs: overlay - Double RGB image with the reference height and width. Reference pixels remain unchanged outside the intersection of mask and valid resized strain data. Description: The reference is normalized and enhanced first. Valid strain values are extended into their ROI, optionally smoothed, linearly resized, clipped to colorRange, and blended only where both masks permit. The function creates image data only and performs no plotting or file output. Failure Behavior: referenceImage, strainMap, masks, and every required option must have compatible numeric shapes. Missing fields, an invalid color range or colormap, and incompatible mask/image sizes propagate the originating App helper or MATLAB indexing/conversion error. Typical Call: opts = struct(\"alpha\", 0.55, \"colorRange\", [-0.02 0.02], ... \"colormap\", jet(256), \"oversample\", 2, \"sigmaSmooth\", 1, ... \"edgeTrim\", 1, \"brightness\", 0, \"contrast\", 1, ... \"gamma\", 1, \"saturation\", 1, \"rgbGain\", [1 1 1]); overlay = dic_postprocess.analysisRun.makeStrainOverlay( ... referenceImage, strainMap, displayMask, roiMask, opts); See also dic_postprocess.analysisRun.prepareOutputs, dic_postprocess.analysisRun.summarizeStrain"},{"title":"dic_postprocess.analysisRun.prepareOutputs","url":"reference/api/dic_postprocess/analysisRun/prepareOutputs.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.prepareOutputs","text":"PREPAREOUTPUTS Build DIC strain summaries and both display overlays. PREPAREOUTPUTS Build DIC strain summaries and both display overlays. Usage: [summaryTable, overlayExx, overlayEyy] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters) Inputs: inputs - Scalar source struct with referenceImage, maskImage, and strain. strain must contain exx, eyy, and roiMask arrays on a common DIC grid. parameters - Scalar display struct described below. Parameters: alpha - Overlay blend fraction. colorMin - Lower shared EXX/EYY strain color limit. colorMax - Upper shared EXX/EYY strain color limit. oversample - Strain-grid interpolation enlargement factor. smoothSigma - Gaussian smoothing sigma. edgeTrim - Valid strain edge pixels removed before interpolation. brightness - Additive normalized reference brightness adjustment. contrast - Reference contrast multiplier around 0.5. gamma - Reference-image intensity exponent. saturation - Reference HSV saturation multiplier. redGain - Reference red-channel multiplier. greenGain - Reference green-channel multiplier. blueGain - Reference blue-channel multiplier. Outputs: summaryTable - Five-row table of Mean, Std, Median, Min, and Max with EXX and EYY columns. overlayExx - Double RGB EXX overlay at referenceImage size. overlayEyy - Double RGB EYY overlay at referenceImage size. Description: prepareOutputs is the GUI-free composition used by the app. It converts the mask image to a logical display mask, uses a fixed 256-color jet map for both components, renders EXX and EYY with identical display parameters, and summarizes finite strain samples inside roiMask when supplied. Failure Behavior: Missing input/parameter fields, incompatible EXX/EYY/mask geometry, or invalid display values propagate the originating field-access, overlay, or summary error. The function does not catch partial-output failures. Typical Call: [summary, exxImage, eyyImage] = ... dic_postprocess.analysisRun.prepareOutputs(inputs, parameters); See also dic_postprocess.analysisRun.makeStrainOverlay, dic_postprocess.analysisRun.summarizeStrain"},{"title":"dic_postprocess.analysisRun.summarizeStrain","url":"reference/api/dic_postprocess/analysisRun/summarizeStrain.html","kind":"reference","section":"reference","keywords":"dic_postprocess.analysisRun.summarizeStrain","text":"SUMMARIZESTRAIN Summarize finite EXX and EYY values inside a mask. SUMMARIZESTRAIN Summarize finite EXX and EYY values inside a mask. Usage: T = dic_postprocess.analysisRun.summarizeStrain(strain, mask) Inputs: strain - Scalar struct with numeric exx and eyy arrays of the same size. mask - Logical array matching exx and eyy. true entries select samples for both components. Outputs: T - Five-row table. Metric contains \"Mean\", \"Std\", \"Median\", \"Min\", and \"Max\"; EXX and EYY contain the corresponding statistics. Description: Nonfinite values are removed independently from each selected component. Std uses MATLAB's default sample standard deviation. When a component has no finite selected values, all five values for that component are NaN. Failure Behavior: An empty finite selection returns NaN statistics as documented. Missing exx or eyy fields, incompatible mask geometry, or values unsupported by MATLAB numeric statistics propagate the originating field/indexing error. Example: strain = struct(\"exx\", [1 2; NaN 4], \"eyy\", [2 4; 6 Inf]); T = dic_postprocess.analysisRun.summarizeStrain( ... strain, true(2)); assert(T.EXX(T.Metric == \"Mean\") == 7/3) assert(T.EYY(T.Metric == \"Max\") == 6) See also dic_postprocess.analysisRun.prepareOutputs"},{"title":"dic_preprocess.analysisRun.alignMovingToReference","url":"reference/api/dic_preprocess/analysisRun/alignMovingToReference.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.alignMovingToReference","text":"ALIGNMOVINGTOREFERENCE Fit and apply a rigid transform from matched points. ALIGNMOVINGTOREFERENCE Fit and apply a rigid transform from matched points. Usage: [alignedImage, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... referenceImage, movingImage, fixedPoints, movingPoints) Inputs: referenceImage - Reference image whose height and width define the output canvas. movingImage - Numeric grayscale or multichannel image to transform. fixedPoints - N-by-2 [x y] coordinates in referenceImage. movingPoints - N-by-2 corresponding [x y] coordinates in movingImage. Use the same number and order of points as fixedPoints. At least two distinct pairs are needed to constrain a 2-D rigid transform. Outputs: alignedImage - movingImage resampled onto the reference canvas. It has the moving image class and channel count; pixels outside the source are 0. tformRigid - Three-by-three row-vector homogeneous transform, shown as transform in the usage syntax. A moving coordinate maps to reference coordinates as [x y 1]*tformRigid. Description: The function uses the least-squares orthogonal Procrustes solution for one rotation and translation; reflection is explicitly rejected. It does not estimate scale or shear. Resampling is delegated to applyRigidTransform. Failure Behavior: The caller is responsible for supplying corresponding finite N-by-2 point arrays with enough nondegenerate geometry to constrain the fit. The function does not validate pair count or fit quality; insufficient or degenerate data can therefore return an arbitrary transform. Shapes that cannot participate in the matrix operations, unsupported image classes, and SVD/interpolation failures propagate the originating MATLAB error. Example: reference = zeros(12, 12); moving = zeros(12, 12); moving(4:6, 3:5) = 1; movingPoints = [3 4; 5 4; 3 6]; fixedPoints = movingPoints + [2 1]; [aligned, transform] = ... dic_preprocess.analysisRun.alignMovingToReference( ... reference, moving, fixedPoints, movingPoints); assert(isequal(size(aligned), size(reference))) assert(norm([movingPoints ones(3,1)]*transform - ... [fixedPoints ones(3,1)], \"fro\") < 1e-10) See also dic_preprocess.analysisRun.applyRigidTransform, dic_preprocess.analysisRun.autoAlignMovingToReference"},{"title":"dic_preprocess.analysisRun.applyRigidTransform","url":"reference/api/dic_preprocess/analysisRun/applyRigidTransform.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.applyRigidTransform","text":"APPLYRIGIDTRANSFORM Resample a moving image onto a reference-sized canvas. APPLYRIGIDTRANSFORM Resample a moving image onto a reference-sized canvas. Usage: alignedImage = dic_preprocess.analysisRun.applyRigidTransform( ... referenceImage, movingImage, transform) Inputs: referenceImage - Image whose height and width define the output canvas. Its pixel values and channel count are not used. movingImage - Numeric two-dimensional or multichannel source image. transform - Three-by-three row-vector homogeneous rigid transform. Source points map forward as [x y 1]*transform. The upper-left two-by-two block is rotation and row 3, columns 1:2 are [x y] translation. Outputs: alignedImage - Linearly interpolated moving image on the reference height and width, preserving movingImage class and channels. Samples outside movingImage are 0; integer outputs are rounded and clipped to range. Description: The function performs inverse mapping for every output pixel using base MATLAB interp2. It assumes the supplied two-by-two block is an orthogonal rotation; scale or shear matrices are not validated. Failure Behavior: transform is expected to be a finite 3-by-3 numeric matrix and images must be compatible with interp2 and output casting. The function does not validate finiteness, rigidity, or invertibility; nonfinite or nonrigid values can therefore produce an unusable image without a dedicated error. Invalid dimensions and unsupported classes propagate the originating indexing, interpolation, or cast error. Example: moving = uint8(reshape(1:25, 5, 5)); transform = [1 0 0; 0 1 0; 1 0 1]; aligned = dic_preprocess.analysisRun.applyRigidTransform( ... zeros(5), moving, transform); assert(isa(aligned, \"uint8\") && aligned(1,1) == 0) assert(aligned(1,2) == moving(1,1)) See also dic_preprocess.analysisRun.alignMovingToReference"},{"title":"dic_preprocess.analysisRun.autoAlignMovingToReference","url":"reference/api/dic_preprocess/analysisRun/autoAlignMovingToReference.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.autoAlignMovingToReference","text":"AUTOALIGNMOVINGTOREFERENCE Estimate and apply an integer translation. AUTOALIGNMOVINGTOREFERENCE Estimate and apply an integer translation. Usage: [alignedImage, transform, method] = ... dic_preprocess.analysisRun.autoAlignMovingToReference( ... referenceImage, movingImage) Inputs: referenceImage - Numeric grayscale or RGB reference image. Its first two dimensions define the output canvas and correlation size. movingImage - Numeric grayscale or RGB image to translate. Outputs: alignedImage - Original movingImage translated onto the reference canvas, with linear interpolation and zero fill. tformRigid - Three-by-three row-vector homogeneous translation transform, shown as transform in the usage syntax. method - Character vector identifying the fixed phase-correlation method. Description: Each image is converted to normalized grayscale independently. For shift estimation only, moving grayscale data is resized to the reference size by nearest-neighbor sampling. Phase correlation returns a whole-pixel circular shift. Rotation, scale, deformation, repeated texture, and large nonoverlap can produce a poor fit. Failure Behavior: The function does not assign a confidence score or reject an ambiguous phase-correlation peak; low-texture or repeated-pattern inputs can return a numerically valid but poor translation. Empty arrays, unsupported image classes, or invalid channel shapes propagate image conversion/interpolation errors. Example: reference = zeros(16); reference(5:8, 6:9) = 1; moving = circshift(reference, [2 -3]); [aligned, transform, method] = ... dic_preprocess.analysisRun.autoAlignMovingToReference( ... reference, moving); assert(isequal(size(aligned), size(reference))) assert(isequal(size(transform), [3 3]) && contains(method, \"phase-correlation\")) See also dic_preprocess.analysisRun.alignMovingToReference, dic_preprocess.analysisRun.applyRigidTransform"},{"title":"dic_preprocess.analysisRun.cropImage","url":"reference/api/dic_preprocess/analysisRun/cropImage.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.cropImage","text":"CROPIMAGE Crop an image using base-MATLAB indexing. CROPIMAGE Crop an image using base-MATLAB indexing. Usage: cropped = dic_preprocess.analysisRun.cropImage(imageData, rect) Inputs: imageData - Numeric two-dimensional or multichannel image. rect - Four finite values [x y width height] in image-pixel coordinates. Values are rounded to pixel indices and limited to image bounds. Outputs: cropped - Inclusive pixel crop preserving input class and channels. Description: Width and height follow MATLAB imcrop's inclusive-endpoint convention: [x y 10 10] selects 11-by-11 pixels when fully inside the image. A partially overlapping rectangle is clipped; a rectangle with no overlap errors. Errors: labkit_DICPreprocess_app:InvalidCropRectangle - rect does not contain four finite numeric values. labkit_DICPreprocess_app:EmptyCropRectangle - The rounded rectangle does not overlap imageData. Example: imageData = reshape(1:100, 10, 10); cropped = dic_preprocess.analysisRun.cropImage(imageData, [2 3 4 2]); assert(isequal(size(cropped), [3 5])) See also dic_preprocess.analysisRun.applyRigidTransform"},{"title":"dic_preprocess.analysisRun.makeFalseColorOverlay","url":"reference/api/dic_preprocess/analysisRun/makeFalseColorOverlay.html","kind":"reference","section":"reference","keywords":"dic_preprocess.analysisRun.makeFalseColorOverlay","text":"MAKEFALSECOLOROVERLAY Build a red/green registration preview. MAKEFALSECOLOROVERLAY Build a red/green registration preview. Usage: overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... referenceImage, alignedImage) Inputs: referenceImage - Numeric grayscale or RGB reference image. alignedImage - Numeric grayscale or RGB moving image. A different height or width is resized to the reference dimensions by nearest neighbor. Outputs: overlay - Double RGB image the size of referenceImage. Red contains the independently normalized moving image, green contains the independently normalized reference, and blue is zero. Description: Coincident structure appears yellow, moving-only structure red, and reference-only structure green. Independent min/max grayscale normalization emphasizes geometry rather than preserving absolute intensity calibration. Failure Behavior: A constant image retains its normalized intensity instead of being stretched. An all-NaN image remains NaN. Empty arrays, unsupported image classes, or invalid channel shapes propagate errors from LabKit image conversion or nearest-neighbor resizing. Example: reference = eye(5); moving = circshift(reference, [0 1]); overlay = dic_preprocess.analysisRun.makeFalseColorOverlay( ... reference, moving); assert(isequal(size(overlay), [5 5 3])) assert(all(overlay(:,:,3) == 0, \"all\")) See also dic_preprocess.analysisRun.autoAlignMovingToReference"},{"title":"ecg_print.analysisRun.analyzeSignal","url":"reference/api/ecg_print/analysisRun/analyzeSignal.html","kind":"reference","section":"reference","keywords":"ecg_print.analysisRun.analyzeSignal","text":"ANALYZESIGNAL Rebuild ECG analysis products from one decoded signal cache. ANALYZESIGNAL Rebuild ECG analysis products from one decoded signal cache. Usage: cache = ecg_print.analysisRun.analyzeSignal(cache, parameters) Description: Runs the numerical pipeline used by ECG Print without opening the app. It filters the complete decoded channel, optionally crops the raw and filtered signals to a time interval, detects ECG peaks, extracts event-centered segments, builds a representative template, and measures each segment against that template. Filtering before cropping reduces FFT boundary artifacts at the requested interval edges. The input structure is returned with six rebuildable analysis fields replaced. Other cache fields are preserved, so callers can keep source and import metadata beside the derived results. The function creates no graphics and reads or writes no files. Inputs: cache - Scalar structure containing cache.signal. The signal must have corresponding time and values vectors, a positive sample rate fs in hertz, a displayName, and a metadata structure. These are the fields used by the biosignal functions in this pipeline. parameters - Scalar structure containing every field listed under Parameter Fields. Unlike the ECG Print controls, this direct API does not fill missing fields or sanitize invalid values. Parameter Fields: lowCut - Lower band-pass cutoff in hertz. Supply a finite nonnegative scalar below highCut and below 45% of the sample rate. highCut - Requested upper band-pass cutoff in hertz. For a conventional lowCut, the effective value is min(highCut, 0.45*fs). If lowCut is at or above that limit, the cap becomes lowCut+eps so the two cutoffs can remain ordered. Callers should avoid that case because it leaves no useful safety margin below Nyquist. roiStart - Start time in seconds on cache.signal.time. roiEnd - End time in seconds on cache.signal.time. When roiEnd is greater than roiStart, samples at both endpoints are retained and the cropped time vectors restart at zero. Otherwise the full signal is analyzed. peakMethod - ECG Print method label: \"QRS streaming\", \"Pan-Tompkins\", or \"Local peaks\". Any other value currently falls back to QRS streaming. peakDistance - Minimum accepted peak spacing in seconds. Use a positive finite scalar. segmentWindow - Positive half-width in seconds for the symmetric interval [-segmentWindow, segmentWindow] around every peak. Peaks too close to either signal boundary are omitted. Values below 0.5 seconds may not cover all default noise-measurement windows and can yield NaN metrics. templateTopN - Number of highest-correlation segments requested for the template. buildTemplate rounds the value and limits it to the available segment count, with a minimum of one for nonempty input. Fixed Analysis Settings: The detector uses automatic polarity and a 2.8 robust-standard-deviation threshold setting. Segment measurements use signal window [-0.06 0.06] seconds and noise windows [-0.30 -0.20] and [0.40 0.50] seconds. Call the individual labkit.biosignal functions when those settings must be changed. Outputs: cache - Updated analysis cache with the fields below. Cache Fields: signal - Original decoded signal, unchanged. workingSignal - Raw full signal or selected time crop. filteredSignal - Corresponding band-pass filtered signal. events - Peak anchors from labkit.biosignal.detectEcgPeaks. segments - Retained event-centered columns from segmentByEvents. template - Representative waveform and segment ranking from buildTemplate. measurements - Per-segment and summary signal-quality tables from measureSegments. Empty detections lead to empty downstream results instead of invented measurements. Errors: Missing cache or parameter fields raise normal MATLAB field-reference errors. Invalid signal shapes, time ranges, filter settings, or detector choices raise the documented errors from the corresponding labkit.biosignal function; this function does not catch them. Example: fs = 100; time = (0:1/fs:6)'; values = 0.02*sin(2*pi*1.5*time); values(101:100:501) = values(101:100:501) + 1; signal = struct('time', time, 'values', values, 'fs', fs, ... 'displayName', \"Synthetic ECG\", 'metadata', struct()); cache = struct('signal', signal); parameters = struct('lowCut', 0.5, 'highCut', 40, ... 'roiStart', 0, 'roiEnd', 0, 'peakMethod', \"Local peaks\", ... 'peakDistance', 0.5, 'segmentWindow', 0.7, 'templateTopN', 5); cache = ecg_print.analysisRun.analyzeSignal(cache, parameters); assert(isfield(cache, 'measurements')) See also labkit.biosignal.filterSignal, labkit.biosignal.cropSignal, labkit.biosignal.detectEcgPeaks, labkit.biosignal.segmentByEvents, labkit.biosignal.buildTemplate, labkit.biosignal.measureSegments"},{"title":"flir_thermal.analysisRun.extremeTemperatureReadings","url":"reference/api/flir_thermal/analysisRun/extremeTemperatureReadings.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.extremeTemperatureReadings","text":"EXTREMETEMPERATUREREADINGS Locate finite global thermal extrema. EXTREMETEMPERATUREREADINGS Locate finite global thermal extrema. Usage: [hotSpot, coldSpot] = ... flir_thermal.analysisRun.extremeTemperatureReadings(temperatureC) Description: Finds the highest and lowest finite values in a calibrated temperature image. NaN, Inf, and -Inf pixels are ignored. Coordinates are one-based image coordinates: x is the column and y is the row. If an extreme occurs more than once, MATLAB column-major order selects the first occurrence. Display palettes and color limits do not affect this calculation. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before calculation. Outputs: hotSpot - Scalar structure containing the finite maximum's x, y, and temperatureC fields. coldSpot - Scalar structure containing the finite minimum's x, y, and temperatureC fields. Empty, nonmatrix, or all-nonfinite input returns NaN in every field of both outputs. Failure Behavior: Empty, nonmatrix, or all-nonfinite input returns NaN readings. Values must otherwise be convertible to double; unsupported MATLAB types propagate the originating conversion error. Example: T = [NaN 24; 18 31]; [hot, cold] = flir_thermal.analysisRun.extremeTemperatureReadings(T); assert(hot.x == 2 && hot.y == 2 && hot.temperatureC == 31) assert(cold.x == 1 && cold.y == 2 && cold.temperatureC == 18) See also labkit.thermal.readFile, flir_thermal.analysisRun.roiTemperatureMeanReading"},{"title":"flir_thermal.analysisRun.pointTemperatureReading","url":"reference/api/flir_thermal/analysisRun/pointTemperatureReading.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.pointTemperatureReading","text":"POINTTEMPERATUREREADING Read the nearest calibrated thermal pixel. POINTTEMPERATUREREADING Read the nearest calibrated thermal pixel. Usage: reading = flir_thermal.analysisRun.pointTemperatureReading( ... temperatureC, pointXY) Description: Samples the nearest pixel in a calibrated temperature image. Coordinates are rounded to integers and limited to the image bounds, so a point outside the image samples the nearest edge pixel. The function does not change the image, draw a marker, or apply display color limits. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before sampling. pointXY - Numeric image coordinate [x y], where x is the column and y is the row. Additional values are ignored. Both selected values must be finite. Outputs: reading - Scalar structure with x, y, and temperatureC fields. x and y are the effective one-based integer pixel coordinates. An empty or nonmatrix image, or a coordinate without two finite values, returns NaN in all fields. A nonfinite sampled pixel remains nonfinite in the result. Failure Behavior: Invalid image geometry or fewer than two finite coordinates returns a NaN reading. Inputs must otherwise be convertible to double; unsupported MATLAB types propagate the originating conversion error. Example: T = [10 20 30; 40 50 60]; reading = flir_thermal.analysisRun.pointTemperatureReading(T, [2.2 1.7]); assert(reading.x == 2 && reading.y == 2 && reading.temperatureC == 50) See also flir_thermal.analysisRun.roiTemperatureMeanReading, flir_thermal.analysisRun.extremeTemperatureReadings"},{"title":"flir_thermal.analysisRun.roiTemperatureMeanReading","url":"reference/api/flir_thermal/analysisRun/roiTemperatureMeanReading.html","kind":"reference","section":"reference","keywords":"flir_thermal.analysisRun.roiTemperatureMeanReading","text":"ROITEMPERATUREMEANREADING Measure an inclusive rectangular thermal ROI. ROITEMPERATUREMEANREADING Measure an inclusive rectangular thermal ROI. Usage: [hotSpot, coldSpot, meanReading] = ... flir_thermal.analysisRun.roiTemperatureMeanReading( ... temperatureC, startXY, endXY) Description: Interprets two points as opposite corners of an inclusive rectangular ROI. Bounds are rounded, ordered, and limited to the image. NaN, Inf, and -Inf pixels are excluded from the extrema and arithmetic mean. Coordinates are one-based: x is the column and y is the row. No graphics are created. Inputs: temperatureC - Two-dimensional numeric temperature matrix in degrees Celsius. The values are converted to double before calculation. startXY - Numeric [x y] image coordinate at one ROI corner. endXY - Numeric [x y] image coordinate at the opposite corner. Corner order is irrelevant. Additional values are ignored; the first two values of each corner must be finite. Outputs: hotSpot - Scalar structure with x, y, and temperatureC for the finite ROI maximum. coldSpot - Scalar structure with x, y, and temperatureC for the finite ROI minimum. Ties select the first value in column-major order. meanReading - Scalar structure with x and y at the clamped upper-left ROI corner, inclusive width and height, mean temperatureC, and pixelCount. pixelCount counts only finite pixels, whereas width and height describe the entire clamped rectangle. Failure Behavior: Empty or nonmatrix temperature data, invalid corners, or an ROI with no finite pixels returns NaN point fields. meanReading contains NaN geometry and temperature and a pixelCount of zero. Example: T = [10 NaN 30; 40 50 60; 70 80 90]; [hot, cold, avg] = ... flir_thermal.analysisRun.roiTemperatureMeanReading(T, [1 1], [2 2]); assert(hot.temperatureC == 50 && cold.temperatureC == 10) assert(avg.temperatureC == 100/3 && avg.pixelCount == 3) See also flir_thermal.analysisRun.pointTemperatureReading, flir_thermal.analysisRun.extremeTemperatureReadings"},{"title":"focus_stack.analysisRun.computeFocusStack","url":"reference/api/focus_stack/analysisRun/computeFocusStack.html","kind":"reference","section":"reference","keywords":"focus_stack.analysisRun.computeFocusStack","text":"COMPUTEFOCUSSTACK Fuse a stack of images with different focus planes. COMPUTEFOCUSSTACK Fuse a stack of images with different focus planes. Usage: result = focus_stack.analysisRun.computeFocusStack(images) result = focus_stack.analysisRun.computeFocusStack(images, opts) Description: Builds a Laplacian pyramid for each input image, estimates local detail energy at every pyramid level, and blends pixels using the resulting focus weights. Low-confidence regions use normalized soft weights instead of a hard source selection. The first image defines the output height and width; other images are resized to that geometry. Grayscale and RGB inputs may be mixed, in which case the working stack and output are RGB. This function fuses focus only. It does not align images, compensate for parallax, or correct moving subjects. Use the app's registration step before calling it when the camera or specimen moved between frames. Inputs: images - At least two numeric images supplied as a cell array or numeric stack. A four-dimensional array uses its fourth dimension as the image index. A three-dimensional numeric array is interpreted as a stack of two-dimensional grayscale images, not as one RGB image; use a cell array for multiple RGB images. Values are converted with labkit.image.im2double. opts - Optional scalar structure containing the fusion options below. Options: focusWindow - Local detail-energy window in pixels. The value must be a positive finite scalar; it is rounded up to an odd integer of at least three. Default: 31. smoothRadius - Radius in pixels used to smooth source weights at the finest pyramid level. It is rounded to a nonnegative integer and reduced at coarser levels. Default: 4. minConfidence - Threshold in [0,1] below which fusion uses relative detail scores rather than only the best source image. Default: 0.05. pyramidLevels - Requested number of Laplacian levels. It is rounded and limited to the range supported by the image geometry, from one through five. Default: 4. Outputs: result - Scalar structure containing the fused image and diagnostics. Result Fields: ok, message - true and empty text after successful fusion. fused - Grayscale or RGB double image in [0,1]. focusIndex - M-by-N uint16 map naming the source with the largest finest- level detail score at each pixel. Values run from 1 to inputCount. confidence - M-by-N double map in [0,1] measuring separation between the best and second-best finest-level detail scores. focusCoverage - One fraction per source image, calculated from focusIndex; the fractions sum to one. inputCount, imageHeight, imageWidth, channelCount - Working stack geometry. focusWindow, smoothRadius, minConfidence, pyramidLevels - Effective options. meanConfidence - Mean of the confidence map. method - \"Laplacian pyramid focus fusion\". resizedCount - Number of input images resized to the first image's geometry. Errors: labkit_FocusStack_app:NotEnoughImages - Fewer than two images are supplied. labkit_FocusStack_app:InvalidImages - The input container or one of its elements is not a supported numeric image. MATLAB validation errors - focusWindow or minConfidence is outside its documented numeric domain. Example: [x, y] = meshgrid(1:24, 1:20); first = double(mod(x, 4) < 2); second = double(mod(y, 4) < 2); result = focus_stack.analysisRun.computeFocusStack( ... {first, second}, struct(\"focusWindow\", 5, \"smoothRadius\", 1)); assert(result.ok && isequal(size(result.fused), [20 24])) assert(abs(sum(result.focusCoverage) - 1) < 1e-12) See also labkit.image.im2double, labkit.image.meanFilter2"},{"title":"gait_analysis.analysisRun.computeGait","url":"reference/api/gait_analysis/analysisRun/computeGait.html","kind":"reference","section":"reference","keywords":"gait_analysis.analysisRun.computeGait","text":"COMPUTEGAIT Calculate joint, segment, swing, and step-quality measurements. COMPUTEGAIT Calculate joint, segment, swing, and step-quality measurements. Usage: result = gait_analysis.analysisRun.computeGait(pose, opts) Description: Analyzes named two-dimensional pose coordinates without opening the app. The function maps five anatomical roles to source points, smooths their coordinates, calculates joint angles and segment lengths, detects repeated active swings, evaluates each swing against quality thresholds, and returns the same four tables used by app preview and CSV export. Inputs: pose - Scalar normalized pose structure, normally returned by gait_analysis.sourceFiles.readPoseFile. Required fields are coords, an F-by-P-by-2 numeric array of [x y] image coordinates, and pointNames, a P-element text vector. Optional frameIndex and time vectors must have F elements. unitName labels coordinates that are already physical. sourceFormat is copied into the summary table. opts - Scalar option structure. Start with gait_analysis.analysisRun.defaultOptions; missing fields use those defaults. Options: iliacPoint - Case-insensitive point name assigned to the iliac role. Default: \"iliac\". hipPoint - Point name assigned to the hip role. Default: \"hip\". kneePoint - Point name assigned to the knee role. Default: \"knee\". anklePoint - Point name assigned to the ankle role. Default: \"ankle\". footPoint - Point name used for event detection and step length. Default: \"foot\". frameRate - Frames per second used only when pose.time has no finite values. Current Video Marker projects supply this value from embedded video metadata. Zero leaves time_s as NaN. Default: 0. pixelsPerUnit - Positive pixel density for physical output. Distances and scaled coordinates are divided by this value. When invalid, pose.unitName is retained at scale 1 when present; otherwise output uses pixels. Default: 1. unitName - Label used with a valid pixelsPerUnit. Default: \"px\". originAtFirstFrameFirstPoint - When true, coordinateTable subtracts the first point's first-frame coordinate from every scaled point. Raw pixel columns are unchanged. Default: false. smoothWindow - Requested centered smoothing span in frames, rounded to an integer of at least one. The effective odd span is 2*floor((smoothWindow-1)/2)+1. Finite values are averaged separately for each point and axis. Default: 5. detectionProminence - Minimum foot-X peak prominence in source coordinate units. Default: 20, preserving the legacy treadmill workflow threshold. detectionMinHeightSigma - A lift-off peak must be at least mean(foot X)-value*std(foot X). Default: 2, preserving the legacy treadmill peak-height floor. minLiftOffIntervalSeconds - Minimum separation between lift-off peaks. It is converted to frames from source time/frame rate. Default: 0.2 seconds. minSwingFrames - Minimum accepted lift-off-to-landing span. It is also the event separation fallback when source timing is unavailable. Default: 3. maxSwingFrames - Maximum accepted inclusive lift-off-to-landing span. Default: 300. minStepLength - Minimum accepted two-dimensional foot endpoint displacement. Default: 1. maxHipTranslation - Maximum accepted hip endpoint displacement in output units. Default: 1000000, which normally leaves this check inactive. Event Detection: The detection trace is smoothed foot X. A lift-off candidate is a local maximum meeting detectionProminence; nearby candidates retain the higher peak. Each lift-off is independently paired with the following foot-X minimum before the next lift-off, or before the recording ends. Therefore a completed final swing is retained even without a later lift-off. These are kinematic treadmill events, not force-plate contact measurements. Measurements: Hip, knee, and ankle angles are the unsigned angles from 0 to 180 degrees between the two adjacent segment vectors. A zero-length or nonfinite segment produces NaN. Segment lengths are Euclidean distances. For each cycle, Step length and each point translation are Euclidean endpoint displacements from lift-off to landing. Range of motion is the finite maximum-minus-minimum joint angle within that swing. When a next lift-off exists, cycle time, stance time, cadence, and duty factor are also reported; the final complete swing may legitimately lack them. Outputs: result - Scalar structure containing status, normalized options, events, and four tables. Result Fields: ok, message - true and \"Analysis complete\" after successful calculation. options - Effective options after defaults, scalar cleanup, and rounding. events - Structure with paired liftOffFrames and landingFrames, peak prominence, the foot-X detectionSignal, and footRelativeX diagnostic. frameTable - One row per frame. It contains frame/time/step membership, landing and lift-off flags, coordinate unit, three joint angles, four segment lengths, and scaled _x/_y columns. coordinateTable - One row per frame with frame/time and origin metadata, raw __x_px/__y_px columns, and scaled, optionally shifted __x/__y columns. The unshifted origin is [0 0] in pixel coordinates rather than the center of pixel [1 1]. stepTable - One row per paired lift-off-to-landing swing. It contains validity and reason, event frames, swing/cycle/stance timing, cadence, duty factor, two-dimensional step length and point translations, three joint extrema/ranges of motion, and recording-wide joint extrema. invalid_reason is \"ok\", \"duration_out_of_range\", \"step_length_too_small\", or \"hip_translation_too_large\". summaryTable - Metric/value text table reporting source geometry, detected and valid step counts, mean valid-swing time and step length, and global finite joint-angle extrema. Errors: labkit_GaitAnalysis_app:NoPoseData - pose has no nonempty coords field. labkit_GaitAnalysis_app:MissingRolePoint - A configured role name is absent from pose.pointNames. Example: frame = (1:12).'; footX = [-2; -3; -1; 2; 4; -3; -1; 2; 4; -3; -1; 1]; pose = struct(\"coords\", NaN(12, 5, 2), ... \"pointNames\", [\"iliac\"; \"hip\"; \"knee\"; \"ankle\"; \"foot\"], ... \"frameIndex\", frame, \"time\", (frame-1)/30, ... \"unitName\", \"px\", \"sourceFormat\", \"synthetic\"); pose.coords(:, :, 1) = [-2*ones(12,1), zeros(12,1), ... ones(12,1), 2*ones(12,1), footX]; pose.coords(:, :, 2) = repmat([8 6 4 2 0], 12, 1); opts = gait_analysis.analysisRun.defaultOptions(); opts.smoothWindow = 1; opts.detectionProminence = 2; opts.minLiftOffIntervalSeconds = 0.1; result = gait_analysis.analysisRun.computeGait(pose, opts); assert(result.ok && height(result.stepTable) == 2) See also gait_analysis.sourceFiles.readPoseFile, gait_analysis.analysisRun.defaultOptions, video_marker.coordinateExport.buildTable"},{"title":"gait_analysis.sourceFiles.readPoseFile","url":"reference/api/gait_analysis/sourceFiles/readPoseFile.html","kind":"reference","section":"reference","keywords":"gait_analysis.sourceFiles.readPoseFile","text":"READPOSEFILE Read one current Video Marker MAT project for gait analysis. READPOSEFILE Read one current Video Marker MAT project for gait analysis. Usage: pose = gait_analysis.sourceFiles.readPoseFile(filepath) Inputs: filepath - Scalar path to a MAT file containing one current `labkitProject` envelope produced by Video Marker payload version 2. The reader loads only that named variable and never reopens the video. Outputs: pose - Normalized scalar structure. coords is F-by-P-by-2 [x y] image coordinates; pointNames and skeleton preserve the Video Marker order; frameIndex is 1:F; time is derived from embedded frameRate; annotation status/source, calibration, video metadata, and units are preserved. Errors: labkit_GaitAnalysis_app:PoseFileNotFound - filepath does not exist. labkit_GaitAnalysis_app:UnsupportedPoseFile - filepath is not MAT. labkit_GaitAnalysis_app:InvalidMarkerProject - The file is not a current Video Marker project or its coordinate/skeleton shapes disagree. labkit_GaitAnalysis_app:MissingVideoMetadata - Embedded timing metadata is absent, invalid, or has a nonpositive frame rate. Description: Video Marker MAT is the sole file-input contract for Gait Analysis. CSV, arbitrary workspace MAT variables, and legacy marker payloads are rejected because they do not jointly own timing, skeleton, calibration, and frame provenance. `computeGait` remains callable with an in-memory normalized pose for deterministic tests and GUI-free calculations. Typical Call: pose = gait_analysis.sourceFiles.readPoseFile(\"walk.video_marker.autosave.mat\"); assert(pose.ok && pose.frameRate > 0) See also gait_analysis.analysisRun.computeGait, video_marker.videoSource.metadataFromInfo"},{"title":"image_enhance.analysisRun.applyPipeline","url":"reference/api/image_enhance/analysisRun/applyPipeline.html","kind":"reference","section":"reference","keywords":"image_enhance.analysisRun.applyPipeline","text":"APPLYPIPELINE Replay an ordered non-destructive image enhancement history. APPLYPIPELINE Replay an ordered non-destructive image enhancement history. Usage: processed = image_enhance.analysisRun.applyPipeline(images, steps) processed = image_enhance.analysisRun.applyPipeline(images, steps, contexts) Description: Converts every input image to RGB double data, then applies each history step in order. The same step sequence is applied independently to every image. The input arrays and step records are not modified, so the function can be used outside the GUI for reproducible preview or export processing. Inputs: images - One numeric image or a cell array of numeric images. Grayscale and multichannel inputs are converted to M-by-N-by-3 RGB double images and limited to [0,1]. The returned cell order matches the input order. steps - Structure array created by image_enhance.analysisRun.makeStep. Steps are reshaped to a column and executed in array order. An empty array performs normalization only. contexts - Optional cell array with one entry per image. Context is used by \"White ROI calibration\" to find whiteRoi, either directly as context.whiteRoi or as context.item.whiteRoi. Other step kinds ignore it. Default: one empty context per image. Step Fields: kind - One label returned by image_enhance.imagePreview.presentationData.toolKinds: \"Brightness/contrast\", \"Local contrast\", \"Sharpen\", \"Hue/saturation\", \"White balance\", \"White ROI calibration\", or \"Subject-preserving enhance\". amount - Primary numeric control. It is brightness percentage, local- contrast strength, sharpening strength, hue rotation in degrees, white-balance strength, or protected-enhancement blend strength, depending on kind. secondary - Secondary numeric control. It is contrast percentage, radius in pixels, saturation percentage, warm/cool percentage, or target background white level in percent, depending on kind. referenceIndex - Preserved history metadata; applyPipeline does not use it. label - Human-readable history text; applyPipeline does not branch on it. Outputs: processed - Column cell array of M-by-N-by-3 double images in [0,1]. Each output preserves its corresponding source height and width. Errors: labkit_ImageEnhance_app:UnknownEnhancementStep - A step kind is not one of the supported labels. labkit_ImageEnhance_app:MissingWhiteRoi - \"White ROI calibration\" is used without a valid [x y width height] ROI in that image's context. MATLAB indexing errors - A nonempty contexts array has fewer entries than images. Example: imageData = repmat(linspace(0.1, 0.8, 20), 12, 1); steps = [ ... image_enhance.analysisRun.makeStep(\"Brightness/contrast\", 10, 20, 0); ... image_enhance.analysisRun.makeStep(\"Sharpen\", 30, 1, 0)]; processed = image_enhance.analysisRun.applyPipeline(imageData, steps); assert(iscell(processed) && isequal(size(processed{1}), [12 20 3])) See also image_enhance.analysisRun.applyStep, image_enhance.analysisRun.makeStep, labkit.image.adjustBrightnessContrast"},{"title":"image_match.analysisRun.applyMatch","url":"reference/api/image_match/analysisRun/applyMatch.html","kind":"reference","section":"reference","keywords":"image_match.analysisRun.applyMatch","text":"APPLYMATCH Transfer reference appearance statistics to one image. APPLYMATCH Transfer reference appearance statistics to one image. Usage: outputImage = image_match.analysisRun.applyMatch( ... inputImage, referenceImage, step) Description: Converts the source and reference to RGB double images, transfers the selected tone and color statistics from the reference, and blends the matched result with the source. Matching never registers, crops, or resizes either image; only appearance statistics are transferred. The source geometry therefore determines the output geometry. Inputs: inputImage - Numeric or logical grayscale, RGB, or multichannel source image. It is converted to M-by-N-by-3 double data in [0,1]. referenceImage - Numeric or logical reference image. Its dimensions may differ from inputImage because matching uses image-wide statistics. Empty input returns the normalized source unchanged. step - Scalar structure created by image_match.analysisRun.makeStep. Step Fields: matchMethod - One label returned by image_match.imagePreview.presentationData.matchMethods. Punctuation and spaces are ignored when matching labels. An unknown or empty value uses \"Balanced\". amount - Overall blend percentage. Zero returns the source; 100 returns the selected matched result. Values are limited to [0,100]. secondary - Tone-match percentage, limited to [0,100]. colorStrength - Color-match percentage, limited to [0,100]. Match Methods: Balanced - Applies robust white-balance gains, then Lab tone quantile and a/b covariance matching. White balance - Matches robust bright neutral RGB channel ratios only. Tone only - Quantile-matches Lab lightness; colorStrength is unused. Protected tone - Moves background lightness and color toward the reference while limiting changes in saturated subject regions. Lab style - Quantile-matches Lab lightness and covariance-matches the Lab a/b color channels. Histogram - Quantile-matches all three Lab channels independently. Outputs: outputImage - M-by-N-by-3 double image in [0,1], with the same height and width as inputImage. Failure Behavior: Empty referenceImage returns normalized inputImage unchanged. Missing step fields use the App's match defaults; unsupported image classes/channel shapes or malformed numeric step values propagate the originating LabKit image or MATLAB calculation error. Example: source = cat(3, 0.3*ones(8), 0.5*ones(8), 0.8*ones(8)); reference = cat(3, 0.8*ones(6), 0.5*ones(6), 0.3*ones(6)); step = image_match.analysisRun.makeStep(\"White balance\", 100, 100, 100); outputImage = image_match.analysisRun.applyMatch(source, reference, step); assert(isequal(size(outputImage), [8 8 3])) See also image_match.analysisRun.applyPipeline, image_match.analysisRun.makeStep, image_match.imagePreview.presentationData.matchMethods"},{"title":"image_match.analysisRun.applyPipeline","url":"reference/api/image_match/analysisRun/applyPipeline.html","kind":"reference","section":"reference","keywords":"image_match.analysisRun.applyPipeline","text":"APPLYPIPELINE Replay ordered reference-match steps over source images. APPLYPIPELINE Replay ordered reference-match steps over source images. Usage: processed = image_match.analysisRun.applyPipeline(images, steps) processed = image_match.analysisRun.applyPipeline( ... images, steps, referenceImage) Description: Normalizes each source image and applies the ordered reference-match history independently to every source. A step sees the output of the preceding step. The reference image is normalized once, remains separate from the source batch, and is never included in the returned cell array. Inputs: images - One numeric image or a cell array of source images. Each source is converted to RGB double data in [0,1]. Cell order is preserved. steps - Structure array created by image_match.analysisRun.makeStep. Steps are reshaped to a column and executed in array order. An empty array performs source normalization only. referenceImage - Optional numeric reference image. Its dimensions may differ from the sources. Empty or omitted input makes every match step a no-op after source normalization. Default: []. Step Fields: kind - History category; makeStep sets it to \"Reference match\". matchMethod - \"Balanced\", \"White balance\", \"Tone only\", \"Protected tone\", \"Lab style\", or \"Histogram\". amount - Overall match blend percentage. secondary - Tone-match percentage. colorStrength - Color-match percentage. label - Display text retained for history and export; it does not control the calculation. Outputs: processed - Column cell array containing only processed source images. Each entry is an M-by-N-by-3 double image in [0,1] and keeps its own source height and width. Failure Behavior: Empty images returns an empty cell column; empty steps or referenceImage performs normalization only. An invalid image container, unsupported image value, or malformed step propagates the originating normalization or applyMatch error and no partial output is returned. Example: sourceA = cat(3, 0.2*ones(6), 0.4*ones(6), 0.7*ones(6)); sourceB = 0.6*ones(4, 5); reference = cat(3, 0.7*ones(8), 0.5*ones(8), 0.3*ones(8)); step = image_match.analysisRun.makeStep(\"Balanced\", 75, 100, 80); processed = image_match.analysisRun.applyPipeline( ... {sourceA, sourceB}, step, reference); assert(numel(processed) == 2 && isequal(size(processed{2}), [4 5 3])) See also image_match.analysisRun.applyMatch, image_match.analysisRun.makeStep, image_match.imagePreview.presentationData.matchMethods"},{"title":"labkit.app.CallbackContext","url":"reference/api/labkit/app/CallbackContext.html","kind":"reference","section":"reference","keywords":"labkit.app.CallbackContext","text":"CALLBACKCONTEXT Provide declared App-neutral runtime capabilities. CALLBACKCONTEXT Provide declared App-neutral runtime capabilities. Usage: context.log(severity, eventName, message, Name=Value) context.alert(message, title) result = context.chooseOption(prompt, choices, Name=Value) result = context.chooseInputFile(filters, startPath) result = context.chooseInputFolder(startPath) result = context.chooseOutputFile(filters, startPath) result = context.chooseOutputFolder(startPath) result = context.saveProjectDocument(state, filepath) state = context.restoreProjectDocument(filepath) state = context.newProjectDocument() context.saveRecoveryDocument(state, filepath) paths = context.resolveSourcePaths(sources) paths = context.resolveSourcePaths(sources, ids) context.setResource(scope, id, value, cleanup) value = context.getResource(scope, id) context.removeResource(scope, id) context.clearResourceScope(scope) result = context.writeResultPackage(folder, result) Description: CallbackContext is the sealed callback capability boundary. Each specifically named method invokes one private runtime operation. Apps receive this value only as a callback argument and never construct it. It exposes no figure, registry, component, launch request, debug object, or nested service bag. Inputs: state - Complete App-owned state value. message - Scalar reader-facing text. severity - Log severity from \"trace\" through \"critical\". eventName - Stable semantic event identifier. Category - Semantic App capability category. Default: \"workflow\". Audience - \"user\" or \"developer\"; default: \"user\". Attributes - Scalar privacy-safe structured details. Default: struct(). Exception - Scalar MException associated with the event. Default: []. id - Stable semantic diagnostic or resource identifier. count - Nonnegative integer diagnostic count. title - Scalar reader-facing dialog title. prompt - Scalar reader-facing choice prompt. choices - Row string or cellstr array. Title - Reader-facing choice-dialog title. Default: \"Choose an option\". DefaultChoice - Choice selected by pressing Enter. Default: the first choice. CancelChoice - Choice returned when the dialog is dismissed. Default: the first choice. filters - Runtime-supported file-dialog filter value. startPath - Scalar starting file or folder path. filepath - Scalar project or recovery source or destination path. sources - Runtime-owned portable source collection. ids - Optional source identifiers to resolve. scope - \"event\", \"interaction\", \"document\", or \"application\". value - App-neutral resource value. cleanup - Empty or fixed callback cleanup(value). folder - Scalar result-package folder. result - labkit.app.result.Package value for writeResultPackage. Outputs: state - Complete restored App state prepared for the current runtime transaction. result - labkit.app.dialog.Choice for dialogs, project saves, and result writing. paths - Column string array of resolved source paths. value - Stored resource value. Errors: labkit:app:contract:InvalidValue - A public argument is malformed. labkit:app:runtime:InvariantFailure - A private backend operation is unavailable. Typical Call: function state = runAnalysis(state,event,callbackContext) arguments state (1,1) struct event callbackContext (1,1) labkit.app.CallbackContext end callbackContext.log(\"info\", \"analysis.started\", ... \"Analysis started.\"); end See also labkit.app.Definition, labkit.app.dialog.Choice, labkit.app.result.Package"},{"title":"labkit.app.Definition","url":"reference/api/labkit/app/Definition.html","kind":"reference","section":"reference","keywords":"labkit.app.Definition","text":"DEFINITION Compile and launch one immutable App SDK contract. DEFINITION Compile and launch one immutable App SDK contract. Usage: app = labkit.app.Definition(Entrypoint=entrypoint, AppId=appId, ... Title=title, ... Family=family, AppVersion=version, Updated=date, ... Requirements=requirements, Workbench=workbench, Name=Value) fig = app.launch() fig = app.launch(InitialProject=project) requirements = app.launch(\"requirements\") version = app.launch(\"version\") Description: Definition validates product metadata, layout-owned callbacks and renderers, global IDs, callback roles, and references in one atomic constructor. The static target graph is cached once. validateViewSnapshot checks a complete view snapshot against that graph without rebuilding the layout. Required Name-Value Arguments: Entrypoint - Public MATLAB launch function name as a scalar identifier. AppId - Stable App identifier beginning with an ASCII letter and containing letters, digits, underscore, hyphen, or period. Title - Nonempty reader-facing scalar text. Family - Nonempty reader-facing scalar text. AppVersion - Semantic version in X.Y.Z form. Updated - Product date in YYYY-MM-DD form. Requirements - Empty value or labkit.contract.requirements result. Workbench - Root value returned by labkit.app.layout.workbench. Optional Name-Value Arguments: DisplayName - Nonempty scalar text. Default: Title. ProjectSchema - labkit.app.project.Schema or empty for a static App. Default: empty. CreateSession - Fixed callback session = callback(project,context). Portable project sources remain opaque; use context.resolveSourcePaths while rebuilding transient session data. Default: empty. PresentWorkbench - Fixed callback view = callback(state). Default: empty. OnStart - Fixed callback state = callback(state,context), invoked after the first view commit. Default: empty. BuildSyntheticSample - Fixed callback pack = callback(context). Default: empty. Outputs: app - Immutable compiled labkit.app.Definition value. Definition Methods: launch() - Build and show the native MATLAB App figure. launch(\"requirements\") - Return declared facade requirements without creating a figure. launch(\"version\") - Return product version metadata without creating a figure. validateViewSnapshot(view) - Validate target references, target capabilities and complete target coverage. Returns true or throws before any runtime UI mutation. Errors: labkit:app:contract:UnknownArgument - A required argument is missing or an argument is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Metadata, requirements, workbench, callbacks, or renderers are malformed. labkit:app:contract:DuplicateId - A layout ID is duplicated. labkit:app:contract:UnknownReference - A view target is undeclared. labkit:app:contract:UnsupportedOperation - A view operation is not legal for its target. labkit:app:contract:InvalidValue - A launch request or output count is unsupported. Typical Call: workbench = labkit.app.layout.workbench({ ... labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the current analysis.\")}); app = labkit.app.Definition( ... Entrypoint=\"labkit_Example_app\", AppId=\"example.app\", ... Title=\"Example\", Family=\"Examples\", AppVersion=\"1.0.0\", ... Updated=\"2026-07-19\", Requirements=[], Workbench=workbench); See also labkit.app.layout.workbench, labkit.app.view.Snapshot, labkit.contract.requirements"},{"title":"labkit.app.dialog.Choice","url":"reference/api/labkit/app/dialog/Choice.html","kind":"reference","section":"reference","keywords":"labkit.app.dialog.Choice","text":"DIALOGRESULT Represent a typed dialog choice or cancellation. DIALOGRESULT Represent a typed dialog choice or cancellation. Usage: result = labkit.app.dialog.Choice(value) result = labkit.app.dialog.Choice(value, Cancelled=cancelled) Description: Choice separates cancellation from the chosen value so an empty string, zero, false, or empty App value is not interpreted as cancel. Inputs: value - App-facing value returned by the dialog. Name-Value Arguments: Cancelled - Logical scalar. Default: false. Outputs: result - Immutable labkit.app.dialog.Choice value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Cancelled is not logical scalar. Example: result = labkit.app.dialog.Choice(\"\", Cancelled=false); assert(~result.Cancelled) See also labkit.app.CallbackContext"},{"title":"labkit.app.event.IntervalScroll","url":"reference/api/labkit/app/event/IntervalScroll.html","kind":"reference","section":"reference","keywords":"labkit.app.event.IntervalScroll","text":"INTERVALSCROLL Describe one normalized interval scroll gesture. INTERVALSCROLL Describe one normalized interval scroll gesture. Usage: event = labkit.app.event.IntervalScroll(Anchor=anchor, Count=count) Description: IntervalScroll replaces native MATLAB scroll events and untyped structs with the horizontal data coordinate under the pointer and the signed vertical scroll count used by interval interactions. Required Name-Value Arguments: Anchor - Finite scalar horizontal data coordinate. Count - Finite nonzero scalar vertical scroll count. Outputs: event - Immutable labkit.app.event.IntervalScroll value. Errors: labkit:app:contract:UnknownArgument - An argument is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Anchor or Count is malformed. Example: event = labkit.app.event.IntervalScroll(Anchor=0.25, Count=-1); assert(event.Anchor == 0.25) See also labkit.app.interaction.interval"},{"title":"labkit.app.event.ListSelection","url":"reference/api/labkit/app/event/ListSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.event.ListSelection","text":"LISTSELECTION Describe selected file or list item identities. LISTSELECTION Describe selected file or list item identities. Usage: selection = labkit.app.event.ListSelection(Name=Value) Description: ListSelection carries stable item IDs, positive display indices, or both for file panels and list controls. When both are supplied they have equal lengths and matching order. Optional Name-Value Arguments: Ids - Unique row string or cellstr array. Default: empty. Indices - Unique positive integer numeric row. Default: empty. Outputs: selection - Immutable labkit.app.event.ListSelection value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - IDs or indices are malformed or have inconsistent lengths. Example: selection = labkit.app.event.ListSelection( ... Ids=[\"sample-a\", \"sample-b\"], Indices=[1 3]); assert(isequal(selection.Indices, [1 3])) See also labkit.app.event.TableCellSelection, labkit.app.layout.fileList"},{"title":"labkit.app.event.TableCellEdit","url":"reference/api/labkit/app/event/TableCellEdit.html","kind":"reference","section":"reference","keywords":"labkit.app.event.TableCellEdit","text":"TABLEEDIT Describe one validated table-cell edit signal. TABLEEDIT Describe one validated table-cell edit signal. Usage: edit = labkit.app.event.TableCellEdit(Name=Value) Description: TableEdit replaces raw MATLAB CellEditData and event metadata with stable row/column identity, the previous and proposed values, and the complete proposed table data when the callback must interpret pasted or related rows atomically. Required Name-Value Arguments: RowIndex - Positive integer row index. ColumnIndex - Positive integer column index. PreviousValue - App-owned value before the edit. NewValue - App-owned proposed value. Optional Name-Value Arguments: RowId - Empty or nonempty scalar text stable across sorting. Default: empty. ColumnId - Empty or nonempty scalar text stable across display-name changes. Default: empty. Data - Complete proposed table data after the edit. Default: empty. Outputs: edit - Immutable labkit.app.event.TableCellEdit value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - An index or ID is malformed. Example: edit = labkit.app.event.TableCellEdit(RowIndex=2, ColumnIndex=3, ... ColumnId=\"group\", PreviousValue=\"A\", NewValue=\"B\"); assert(edit.ColumnId == \"group\") See also labkit.app.layout.dataTable, labkit.app.event.ListSelection"},{"title":"labkit.app.event.TableCellSelection","url":"reference/api/labkit/app/event/TableCellSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.event.TableCellSelection","text":"TABLECELLSELECTION Describe selected cells in a semantic data table. TABLECELLSELECTION Describe selected cells in a semantic data table. Usage: selection = labkit.app.event.TableCellSelection(cellIndices) Description: TableCellSelection carries unique N-by-2 row/column index pairs. It replaces native MATLAB table-selection event shapes at the App callback boundary. Inputs: cellIndices - Unique positive integer N-by-2 matrix. An empty selection is zeros(0,2). Outputs: selection - Immutable TableCellSelection value. Errors: labkit:app:contract:InvalidValue - cellIndices is not a unique positive integer N-by-2 matrix. Example: selection = labkit.app.event.TableCellSelection([1 2; 3 1]); assert(isequal(selection.CellIndices, [1 2; 3 1])) See also labkit.app.event.TableCellEdit, labkit.app.layout.dataTable"},{"title":"labkit.app.interaction.anchorPath","url":"reference/api/labkit/app/interaction/anchorPath.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.anchorPath","text":"ANCHORPATH Declare an editable open or closed path on one plot axis. ANCHORPATH Declare an editable open or closed path on one plot axis. Usage: spec = labkit.app.interaction.anchorPath(id, onChanged, Name=Value) Description: Creates the semantic declaration for a managed multi-anchor path editor; the runtime owns native graphics, viewport preservation, and dispatch. Inputs: id - Unique MATLAB identifier for this interaction. onChanged - Callback state = callback(state,points,context). Options: Axis - Axis ID within the owning plotArea. Default: \"main\". Style - Scalar struct of anchor editor visual options. Default: struct(). Instruction - Scalar user guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration accepted by layout.plotArea. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: spec = labkit.app.interaction.anchorPath( ... \"curve\", @changeCurve, Style=struct(\"closed\", false)); See also labkit.app.layout.plotArea, labkit.app.view.Snapshot"},{"title":"labkit.app.interaction.interpolateAnchorPath","url":"reference/api/labkit/app/interaction/interpolateAnchorPath.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.interpolateAnchorPath","text":"INTERPOLATEANCHORPATH Build a visible path through image anchor points. INTERPOLATEANCHORPATH Build a visible path through image anchor points. Usage: curve = labkit.app.interaction.interpolateAnchorPath(points, imageSize) curve = labkit.app.interaction.interpolateAnchorPath(points, imageSize, Name=Value) [curve, owners] = labkit.app.interaction.interpolateAnchorPath(...) Inputs: points - N-by-2 numeric matrix of [x y] anchor coordinates in image-pixel coordinates. imageSize - Numeric vector whose first two elements are the positive finite image height and width. options - Name-value argument container for Style and Closed. Supply these values by name; do not pass an options struct. Name-Value Arguments: Style - \"Curve\" for a Catmull-Rom path or \"Straight lines\" for line segments joining the anchors. Default: \"Curve\". Closed - Logical scalar. true joins the last anchor to the first and requires at least three anchors. false requires at least two. Default: false. Outputs: curve - M-by-2 path samples. Coordinates are limited to pixel-edge bounds [0.5, width+0.5] and [0.5, height+0.5]. The result is empty until the selected open or closed path has enough anchors. owners - (M-1)-by-1 anchor-segment indices used to associate each visible curve segment with its starting anchor. It is empty with curve. Description: anchorPath contains the deterministic geometry shared by managed anchor editors and app previews. Curved paths pass through the supplied anchors; two-point open curves reduce to a straight segment. The function creates no graphics and can be used independently of a LabKit app. Errors: MATLAB argument-validation or assertion errors are raised when points is not N-by-2, imageSize lacks positive finite height and width, Style is unsupported, or a named argument has an incompatible type or shape. Example: points = [10 30; 30 10; 50 30]; curve = labkit.app.interaction.interpolateAnchorPath( ... points, [40 60], \"Style\", \"Straight lines\"); assert(isequal(curve, points)) See also labkit.app.interaction.anchorPath"},{"title":"labkit.app.interaction.interval","url":"reference/api/labkit/app/interaction/interval.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.interval","text":"INTERVAL Declare an editable one-dimensional plot interval. INTERVAL Declare an editable one-dimensional plot interval. Usage: spec = labkit.app.interaction.interval(id,onChanged,Name=Value) Description: Creates the semantic declaration for a managed interval editor and its optional typed scroll callback on one plot axis. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,range,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnScrolled - Optional callback state = callback(state,event,context), where event is labkit.app.event.IntervalScroll. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.interval(\"window\",@changeWindow); See also labkit.app.layout.plotArea, labkit.app.event.IntervalScroll"},{"title":"labkit.app.interaction.pairedAnchors","url":"reference/api/labkit/app/interaction/pairedAnchors.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.pairedAnchors","text":"PAIREDANCHORS Declare matching editable points across plot axes. PAIREDANCHORS Declare matching editable points across plot axes. Usage: spec = labkit.app.interaction.pairedAnchors(id,onChanged,Name=Value) Description: Creates the semantic declaration for corresponding editable points across two or more axes while the runtime owns their native editors. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,pointSets,context). Options: Axes - Two or more axis IDs within the owning plotArea. Required. Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.pairedAnchors(\"matches\",@changeMatches); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.pointSlots","url":"reference/api/labkit/app/interaction/pointSlots.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.pointSlots","text":"POINTSLOTS Declare a fixed set of editable labeled point positions. POINTSLOTS Declare a fixed set of editable labeled point positions. Usage: spec = labkit.app.interaction.pointSlots(id,onChanged,Name=Value) Description: Creates the semantic declaration for a fixed, labeled set of editable points whose structured value is committed by the runtime. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,value,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.pointSlots(\"markers\",@changeMarkers); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.rectangle","url":"reference/api/labkit/app/interaction/rectangle.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.rectangle","text":"RECTANGLE Declare an editable rectangular plot region. RECTANGLE Declare an editable rectangular plot region. Usage: spec = labkit.app.interaction.rectangle(id,onChanged,Name=Value) Description: Creates the semantic declaration for a persistent editable rectangle, including an optional background-point callback on the same axis. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,position,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnBackgroundPressed - Optional point callback state = callback(state,point,context). It receives clicks on blank plot space and clicks on the rectangle that do not move it. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.rectangle(\"crop\",@moveCrop); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.regionSelection","url":"reference/api/labkit/app/interaction/regionSelection.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.regionSelection","text":"REGIONSELECTION Declare a transient click-or-drag region gesture. REGIONSELECTION Declare a transient click-or-drag region gesture. Usage: spec = labkit.app.interaction.regionSelection(id,onSelected,Name=Value) Description: Creates the semantic declaration for a transient click-or-drag selection whose result is delivered without exposing native interaction objects. Inputs: id - Unique MATLAB identifier. onSelected - Callback state = callback(state,position,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". OnBackgroundPressed - Optional point callback. Default: []. Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.regionSelection( ... \"temperatureRegion\",@measureRegion, ... OnBackgroundPressed=@measurePoint); See also labkit.app.layout.plotArea"},{"title":"labkit.app.interaction.scaleBarGeometry","url":"reference/api/labkit/app/interaction/scaleBarGeometry.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleBarGeometry","text":"SCALEBARGEOMETRY Compute serializable image scale-bar overlay geometry. SCALEBARGEOMETRY Compute serializable image scale-bar overlay geometry. Usage: geometry = labkit.app.interaction.scaleBarGeometry(imageSize, ... calibration, barLength, position, colorName) Inputs: imageSize - Numeric vector whose first two elements are positive finite image height and width in pixels. calibration - Struct with a positive finite pixelsPerUnit field and a unit field, normally returned by scaleBarCalibration. barLength - Positive finite physical length expressed in calibration.unit. position - Text containing \"top\" or \"bottom\" and optionally \"left\", \"center\", or \"right\". Unrecognized vertical text uses bottom; unrecognized horizontal text uses center. colorName - \"White\" selects [1 1 1]. Every other value selects black. Outputs: geometry - Scalar data struct with the fields described below. Geometry Fields: line - Two-by-two [x y] endpoints in image-pixel coordinates. label - Display text containing barLength and calibration.unit. color - RGB triplet selected by colorName. labelPosition - One-by-two centered label position in image pixels. verticalAlignment - \"top\" for a top bar or \"bottom\" for a bottom bar. pixelsPerUnit - Calibration value copied into the geometry. unit - Unit label copied into the geometry as a string scalar. barLength - Requested physical length. position - Supplied position text as a string scalar. colorName - Supplied color text as a string scalar. Description: The helper places the line with an eight-percent image-edge margin and a five-pixel minimum inset. It errors when the requested bar cannot fit in the available horizontal span. Apps and renderers own drawing; this function creates no graphics and is suitable for saved project state. Errors: labkit:app:interaction:InvalidImageSize - imageSize lacks positive finite height and width. labkit:app:interaction:InvalidScaleBar - calibration or barLength is not a positive finite scalar. labkit:app:interaction:ScaleBarTooLong - The requested bar does not fit inside the horizontal margins. Example: cal = labkit.app.interaction.scaleCalibration(80, 20, \"mm\"); geometry = labkit.app.interaction.scaleBarGeometry( ... [600 800], cal, 10, \"Bottom right\", \"White\"); assert(abs(diff(geometry.line(:,1))) == 40) See also labkit.app.interaction.scaleCalibration"},{"title":"labkit.app.interaction.scaleCalibration","url":"reference/api/labkit/app/interaction/scaleCalibration.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleCalibration","text":"SCALECALIBRATION Convert a known image distance into pixels per unit. SCALECALIBRATION Convert a known image distance into pixels per unit. Usage: cal = labkit.app.interaction.scaleCalibration(referencePixels, ... referenceLength, unitName) cal = labkit.app.interaction.scaleCalibration(..., opts) Inputs: referencePixels - Measured reference distance in image pixels. Empty, nonnumeric, nonfinite, or nonpositive values are treated as missing. referenceLength - Physical reference distance expressed in unitName. Missing, nonfinite, or negative values become 0. unitName - Unit label. With default options, legal values are \"m\", \"cm\", \"mm\", \"um\", and \"nm\". An unsupported value uses defaultUnit. opts - Optional scalar struct described below. Default: struct(). Options: units - Allowed unit labels. Default: {'m','cm','mm','um','nm'}. defaultUnit - Fallback unit. Default: the first entry in units. referenceLine - N-by-2 numeric reference points stored with the result. When it contains exactly two rows and referencePixels is missing, their Euclidean distance supplies referencePixels. Default: zeros(0,2). Outputs: cal - Scalar struct with the fields described below. Calibration Fields: referencePixels - Positive measured pixel distance, or NaN when missing. referenceLength - Nonnegative physical reference distance. unit - Normalized unit label as a character vector. pixelsPerUnit - referencePixels/referenceLength, or 0 when calibration is incomplete. isCalibrated - true when pixelsPerUnit is positive. referenceLine - Normalized N-by-2 numeric reference coordinates. Description: This function builds a serializable calibration value; it does not read an image or draw a scale bar. Repeating the call with identical inputs returns identical numeric fields. Divide a pixel distance by pixelsPerUnit to obtain a distance in cal.unit. Failure Behavior: Missing or invalid measurement values are normalized into an uncalibrated result instead of throwing. opts must be a scalar structure whose units can be converted to text and whose referenceLine can be converted to an N-by-2 numeric array; incompatible MATLAB values propagate conversion errors. Example: cal = labkit.app.interaction.scaleCalibration(80, 20, \"mm\"); physicalLength = 40 / cal.pixelsPerUnit; assert(cal.isCalibrated && physicalLength == 10) See also labkit.app.interaction.scaleBarGeometry"},{"title":"labkit.app.interaction.scaleReference","url":"reference/api/labkit/app/interaction/scaleReference.html","kind":"reference","section":"reference","keywords":"labkit.app.interaction.scaleReference","text":"SCALEREFERENCE Declare an editable two-point scale reference. SCALEREFERENCE Declare an editable two-point scale reference. Usage: spec = labkit.app.interaction.scaleReference(id,onChanged,Name=Value) Description: Creates the semantic declaration for an editable two-endpoint physical scale reference while the runtime owns its native editor lifecycle. Inputs: id - Unique MATLAB identifier. onChanged - Callback state = callback(state,endpoints,context). Options: Axis - Axis ID. Default: \"main\". Style - Scalar visual-option struct. Default: struct(). Instruction - Scalar guidance text. Default: \"\". ViewportPolicy - \"preserve\" or \"fit\". Default: \"preserve\". Outputs: spec - Immutable interaction declaration. Errors: Throws labkit:app:contract:* for invalid values. Typical Call: spec = labkit.app.interaction.scaleReference(\"scale\",@changeScale); See also labkit.app.layout.plotArea"},{"title":"labkit.app.layout.button","url":"reference/api/labkit/app/layout/button.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.button","text":"BUTTON Add a push button with one explicit pressed callback. BUTTON Add a push button with one explicit pressed callback. Usage: node = labkit.app.layout.button(id, label, onPressed, Name=Value) Description: Declares a semantic push button without creating a native component. Inputs: id - Unique MATLAB identifier for the layout target. label - Nonempty text displayed on the button. onPressed - Scalar function handle with the fixed callback state = onPressed(state,context). Options: BusyMessage - Status text while the action runs. Default: \"\". Enabled - Initial logical enabled state. Default: true. Tooltip - Nonempty hover text explaining the action's scientific or workflow effect. Default: label. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.button(\"run\", \"Run\", @runAnalysis, ... Tooltip=\"Compute the current analysis from the selected inputs.\"); See also labkit.app.layout.workbench, labkit.app.CallbackContext"},{"title":"labkit.app.layout.dataTable","url":"reference/api/labkit/app/layout/dataTable.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.dataTable","text":"DATATABLE Add a tabular data display with optional editing and selection. DATATABLE Add a tabular data display with optional editing and selection. Usage: node = labkit.app.layout.dataTable(id, Name=Value) Description: Declares a semantic data table and typed cell callbacks. Inputs: id - Unique MATLAB identifier for the layout target. Options: Title - Reader-facing panel title or blank. A single-table section supplies its title when this is blank. Default: blank. Columns - Column-label text row. Default: strings(1,0). RowNames - Row-label text row. Default: strings(1,0). ColumnEditable - Logical scalar or row matching Columns. Default: false. OnCellEdited - Scalar callback state = callback(state,edit,context), where edit is a labkit.app.event.TableCellEdit. Default: []. OnCellSelectionChanged - Scalar callback state = callback(state,selection,context), where selection is a labkit.app.event.TableCellSelection. Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid options or callback signatures. Typical Call: node = labkit.app.layout.dataTable(\"results\", Columns=[\"Name\" \"Value\"]); See also labkit.app.event.TableCellEdit, labkit.app.event.TableCellSelection"},{"title":"labkit.app.layout.field","url":"reference/api/labkit/app/layout/field.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.field","text":"FIELD Add a text, numeric, choice, or logical input field. FIELD Add a text, numeric, choice, or logical input field. Usage: node = labkit.app.layout.field(id, Name=Value) Description: Declares one bound or event-driven scalar input field. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Kind - \"text\", \"numeric\", \"choice\", \"logical\", or \"readonly\". Readonly fields display one labeled result value. Default: \"text\". Value - Initial value. Default: []. Choices - Text row for choice fields. Default: strings(1,0). Limits - Increasing finite numeric 1-by-2 row. Default: []. Step - Positive numeric scalar. Default: []. Bind - Project or session field path. Default: \"\". ValueDisplayFormat - MATLAB numeric display format. Default: \"\". ShowTicks - Logical slider-tick preference. Default: false. Enabled - Initial logical enabled state. Default: true. OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.field(\"gain\", Kind=\"numeric\", ... Bind=\"project.parameters.gain\"); See also labkit.app.layout.rangeField, labkit.app.layout.slider"},{"title":"labkit.app.layout.fileList","url":"reference/api/labkit/app/layout/fileList.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.fileList","text":"FILELIST Add file selection and portable-source controls. FILELIST Add file selection and portable-source controls. Usage: node = labkit.app.layout.fileList(id, Name=Value) Description: Declares framework-owned file choosing, removal, selection, and portable source binding. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Reader-facing collection label. Default: id. Mode - \"files\" or \"folder\". Default: \"files\". Filters - File-dialog filter text row. Default: strings(1,0). SelectionMode - \"single\" or \"multiple\". Default: \"multiple\". MaxFiles - Positive scalar or Inf. Default: Inf. FolderWarningThreshold - Positive scalar or Inf. Default: 500. ShowStatus - Logical status visibility. Default: true. StartPath - Initial folder text. Default: \"\". ChooseLabel - File button text. Default: \"Choose\". FolderLabel - Folder button text. Default: \"Choose Folder\". RecursiveFolderLabel - Recursive button text. Default: \"Choose Folder Recursively\". RemoveLabel - Remove button text. Default: \"Remove\". ClearLabel - Clear button text. Default: \"Clear\". ChooseTooltip - File button hover text. Default: ChooseLabel. FolderTooltip - Folder button hover text. Default: FolderLabel. RecursiveFolderTooltip - Recursive-folder button hover text. Default: RecursiveFolderLabel. RemoveTooltip - Remove button hover text. Default: RemoveLabel. ClearTooltip - Clear button hover text. Default: ClearLabel. EmptyText - Empty-list text. Default: \"No files selected\". AllowDuplicatePaths - Preserve separate portable source records that resolve to the same path. Use this when each list row is a distinct workflow task. Default: false. Bind - Project source-record field path. Default: \"\". SelectionBind - ListSelection field path. Default: \"\". OnSelectionChanged - Optional callback applicationState = callback(applicationState,selection,callbackContext) for business effects such as lazily decoding the selected source. selection is labkit.app.event.ListSelection. Ordinary selection state needs only SelectionBind. Default: empty. SourceRole - Portable source role. Default: id. SourceIdPrefix - Portable source ID prefix. Default: id. Required - Logical relinking requirement. Default: true. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid options, paths, or callbacks. Typical Call: node = labkit.app.layout.fileList(\"files\", ... Bind=\"project.inputs.sources\", ... ChooseTooltip=\"Choose calibrated source images for this analysis.\"); See also labkit.app.event.ListSelection, labkit.app.CallbackContext, labkit.app.view.Snapshot"},{"title":"labkit.app.layout.group","url":"reference/api/labkit/app/layout/group.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.group","text":"GROUP Arrange related child elements without a titled boundary. GROUP Arrange related child elements without a titled boundary. Usage: node = labkit.app.layout.group(id, children, Name=Value) Description: Groups compatible control nodes into one semantic arrangement. A titled group draws a nested reader-facing boundary inside its owning section. Inputs: id - Unique MATLAB identifier for the layout target. children - Row cell array of layout nodes. Options: Layout - \"auto\", \"vertical\", or \"horizontal\". Default: \"auto\". Title - Reader-facing nested-group title or blank. Default: blank. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:* for invalid IDs, children, or options. Typical Call: node = labkit.app.layout.group(\"inputs\", {gainField}); See also labkit.app.layout.section, labkit.app.layout.workbench"},{"title":"labkit.app.layout.plotArea","url":"reference/api/labkit/app/layout/plotArea.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.plotArea","text":"PLOTAREA Add one or more axes rendered by an App-owned renderer. PLOTAREA Add one or more axes rendered by an App-owned renderer. Usage: node = labkit.app.layout.plotArea(id, renderer, Name=Value) Description: Declares axes and their one App-owned renderer. The renderer is declared here once; a view snapshot supplies only its current model. Inputs: id - Unique MATLAB identifier for the layout target. renderer - Scalar function handle renderer(axesById,model) with no output. axesById is always a scalar struct with one graphics axes field per declared AxisIds value, including single-axis plots. Options: Title - Reader-facing panel title or blank. Default: blank. Layout - Axes arrangement: \"single\", \"pair\", or \"stack\". Default: \"single\" for one axis and \"stack\" for multiple axes. AxisIds - Unique MATLAB identifier row. Default: \"main\". AxisTitles - One title per axis. Default: axis IDs. XLabels - One x-axis label per axis. Default: blank. YLabels - One y-axis label per axis. Default: blank. ColumnWidths - One positive pixel, \"fit\", or \"1x\" value per axis for pair layout. Default: equal flexible widths. RowHeights - One positive pixel, \"fit\", or \"1x\" value per axis for stack layout. Default: equal flexible heights. ScrollZoomAxes - One \"xy\", \"x\", or \"y\" value per axis. Default: \"xy\". ViewModes - App-owned mode labels. Default: strings(1,0). OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Interactions - Row cell array returned by named labkit.app.interaction.* declarations. Default: {}. Outputs: node - Immutable internal layout node accepted by workspace. Errors: Throws labkit:app:contract:* for invalid IDs, options, or handlers. Typical Call: node = labkit.app.layout.plotArea(\"preview\", @drawTrace, ... AxisIds=\"trace\"); See also labkit.app.view.Snapshot, labkit.app.layout.workspace, labkit.app.interaction.anchorPath"},{"title":"labkit.app.layout.rangeField","url":"reference/api/labkit/app/layout/rangeField.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.rangeField","text":"RANGEFIELD Add an input for a numeric lower and upper bound. RANGEFIELD Add an input for a numeric lower and upper bound. Usage: node = labkit.app.layout.rangeField(id, Name=Value) Description: Declares one two-value numeric range input. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Value - Finite numeric 1-by-2 row. Default: []. Limits - Increasing finite numeric 1-by-2 row. Default: []. Enabled - Initial logical enabled state. Default: true. Bind - Project or session field path. Default: \"\". OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: node = labkit.app.layout.rangeField(\"window\", Limits=[0 10]); See also labkit.app.layout.field, labkit.app.layout.slider"},{"title":"labkit.app.layout.section","url":"reference/api/labkit/app/layout/section.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.section","text":"SECTION Arrange related child elements under a visible title. SECTION Arrange related child elements under a visible title. Usage: node = labkit.app.layout.section(id, title, children, Name=Value) Description: Groups compatible controls under a reader-facing title. Inputs: id - Unique MATLAB identifier for the layout target. title - Nonempty section title. children - Row cell array of layout nodes. Options: Collapsible - Logical collapse support. Default: false. Expanded - Initial logical expansion state. Default: true. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:* for invalid IDs, children, or options. Typical Call: node = labkit.app.layout.section(\"inputs\", \"Inputs\", {gainField}); See also labkit.app.layout.group, labkit.app.layout.tab"},{"title":"labkit.app.layout.slider","url":"reference/api/labkit/app/layout/slider.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.slider","text":"SLIDER Add a bounded numeric slider. SLIDER Add a bounded numeric slider. Usage: node = labkit.app.layout.slider(id, Name=Value) Description: Declares one bounded scalar slider. Inputs: id - Unique MATLAB identifier for the layout target. Options: Label - Display text. Default: id. Value - Finite numeric scalar. Default: lower Limits value. Limits - Increasing finite numeric 1-by-2 row. Default: [0 1]. Step - Positive numeric scalar. Default: []. ValueDisplayFormat - Optional spinner numeric format such as \"%.6g\". Default: \"\". ShowTicks - Logical tick visibility. Default: false. Bind - Project or session field path. Default: \"\". Enabled - Initial logical enabled state. Default: true. OnValueChanged - Scalar callback state = callback(state,value,context). Default: []. Outputs: node - Immutable internal layout node accepted by layout containers. Errors: Throws labkit:app:contract:* for invalid IDs, options, or callbacks. Typical Call: node = labkit.app.layout.slider(\"frame\", Limits=[1 100], Step=1); See also labkit.app.layout.field, labkit.app.layout.rangeField"},{"title":"labkit.app.layout.statusPanel","url":"reference/api/labkit/app/layout/statusPanel.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.statusPanel","text":"STATUSPANEL Add a text display for current App status. STATUSPANEL Add a text display for current App status. Usage: node = labkit.app.layout.statusPanel(id, Name=Value) Description: Declares a current-status or static instruction display. Inputs: id - Unique MATLAB identifier for the layout target. Options: Title - Visible panel title. Default: \"Status\". Text - Static text lines. When omitted, the runtime's latest status is displayed. Default: strings(1,0). Lines - Approximate visible text lines. The native layout uses this hint to size short summaries without consuming detail-panel height. Integer from 1 through 12. Default: 5. Outputs: node - Immutable internal layout node accepted by containers. Errors: Throws labkit:app:contract:InvalidValue for an invalid ID. Typical Call: node = labkit.app.layout.statusPanel(\"status\"); See also labkit.app.CallbackContext"},{"title":"labkit.app.layout.tab","url":"reference/api/labkit/app/layout/tab.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.tab","text":"TAB Add a named tab containing related child elements. TAB Add a named tab containing related child elements. Usage: node = labkit.app.layout.tab(id, title, children) Description: Declares one named control-side tab. Inputs: id - Unique MATLAB identifier for the layout target. title - Nonempty tab title. children - Row cell array of compatible layout nodes. Outputs: node - Immutable internal layout node accepted by workbench. Errors: Throws labkit:app:contract:* for invalid IDs, titles, or children. Typical Call: node = labkit.app.layout.tab(\"settings\", \"Settings\", {gainField}); See also labkit.app.layout.section, labkit.app.layout.workbench"},{"title":"labkit.app.layout.workbench","url":"reference/api/labkit/app/layout/workbench.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.workbench","text":"WORKBENCH Assemble controls and a central workspace into the root layout. WORKBENCH Assemble controls and a central workspace into the root layout. Usage: node = labkit.app.layout.workbench(children, Name=Value) Description: Produces the root semantic layout required by labkit.app.Definition. Inputs: children - Row cell array of controls, groups, sections, or tabs. Options: Workspace - Value returned by layout.workspace. Default: []. Usage - Static workflow instruction lines appended consistently to the first control tab. Default: strings(1,0). UsageTitle - Title for generated workflow instructions. Default: \"Usage\". Outputs: node - Immutable root layout node. Errors: Throws labkit:app:contract:* for invalid children or workspace values. Typical Call: node = labkit.app.layout.workbench({runButton}); See also labkit.app.Definition, labkit.app.layout.workspace"},{"title":"labkit.app.layout.workspace","url":"reference/api/labkit/app/layout/workspace.html","kind":"reference","section":"reference","keywords":"labkit.app.layout.workspace","text":"WORKSPACE Define the App's central working area and optional pages. WORKSPACE Define the App's central working area and optional pages. Usage: node = labkit.app.layout.workspace() node = labkit.app.layout.workspace(content, Name=Value) Description: Declares a single-content workspace or a workspace extended with node.page(id,title,content) and node.initialPage(id). A named page accepts one workspace node or a nonempty row cell array of nodes arranged vertically; growable tables and plots share the available page height. Inputs: content - Optional plotArea, dataTable, group, or section node. For node.page, content may also be a nonempty row cell array of workspace nodes. Options: Title - Reader-facing workspace title. Default: \"Workspace\". OnPageChanged - Callback state = callback(state,pageId,context). Default: []. Outputs: node - Immutable workspace node accepted by layout.workbench. Errors: Throws labkit:app:contract:* for invalid content, pages, or callbacks. Typical Call: node = labkit.app.layout.workspace(plotArea); See also labkit.app.layout.workbench, labkit.app.layout.plotArea"},{"title":"labkit.app.plot.clampPointToAxes","url":"reference/api/labkit/app/plot/clampPointToAxes.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.clampPointToAxes","text":"CLAMPPOINTTOAXES Keep data coordinates inside the visible axes box. CLAMPPOINTTOAXES Keep data coordinates inside the visible axes box. Usage: xyOut = labkit.app.plot.clampPointToAxes(ax, xy) xyOut = labkit.app.plot.clampPointToAxes(ax, xy, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. Its XLim, YLim, XScale, YScale, XDir, and YDir properties define the visible box. xy - N-by-2 numeric matrix of [x y] data coordinates. Name-Value Arguments: Padding - Minimum distance from each axes edge, expressed as a fraction of the visible width or height. Values are limited to [0, 0.49]. Default: 0.04. Outputs: xy - N-by-2 data coordinates, shown as xyOut in the usage syntax. Each point is moved only as far as needed to satisfy Padding. Description: clampData is useful for labels and annotations that must remain readable near an axes boundary. Conversion through normalized axes coordinates keeps the result visually consistent on logarithmic or reversed axes. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidPointPairs - xy is not an N-by-2 numeric array. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig, \"XLim\", [0 10], \"YLim\", [0 20]); xy = labkit.app.plot.clampPointToAxes(ax, [-2 25], \"Padding\", 0.1); assert(isequal(xy, [1 18])) See also labkit.app.plot.offsetPointByAxesFraction"},{"title":"labkit.app.plot.clearAxes","url":"reference/api/labkit/app/plot/clearAxes.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.clearAxes","text":"CLEARAXES Prepare an axes for an app-owned redraw. CLEARAXES Prepare an axes for an app-owned redraw. Usage: labkit.app.plot.clearAxes(ax) labkit.app.plot.clearAxes(ax, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle to clear. Name-Value Arguments: ResetScale - Logical value. true restores linear X/Y scales and automatic X/Y tick modes. false preserves those four properties. Default: false. ClearLegend - Logical value. true turns the legend off. Default: true. Outputs: None. Description: clear deletes plotted children, clears LabKit's cached image home view, releases hold, and returns XLim, YLim, ZLim, and CLim to automatic mode. Labels and other axes decorations follow MATLAB cla behavior. Call it once at the start of a complete redraw, not when adding an overlay that should preserve the current zoom. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. MATLAB graphics errors raised while clearing a valid axes propagate to the caller. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); plot(ax, 1:3, [2 1 3]); labkit.app.plot.clearAxes(ax, \"ResetScale\", true); assert(isempty(ax.Children)) See also labkit.app.plot.fitAxesToGraphics, labkit.app.plot.showMessage"},{"title":"labkit.app.plot.enablePopout","url":"reference/api/labkit/app/plot/enablePopout.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.enablePopout","text":"ENABLEPOPOUT Add a standalone-figure action to an axes context menu. ENABLEPOPOUT Add a standalone-figure action to an axes context menu. Usage: labkit.app.plot.enablePopout(ax) Inputs: ax - MATLAB axes or uiaxes handle that receives an \"Open axes in new figure\" context-menu item. Empty or invalid handles are ignored. Outputs: None. Description: The menu copies the current axes content, limits, labels, colors, and visible legend entries into a normal MATLAB figure with publication and export tools. Existing axes context-menu items are preserved. Children without their own context menu inherit the axes menu, including children added after this call. Repeated calls do not add duplicate menu items. Failure Behavior: Empty or invalid handles are ignored. Errors raised while MATLAB creates the context menu, copies graphics, or opens the standalone figure are not caught and propagate from the originating graphics operation. Typical Call: plot(ax, time, signal); labkit.app.plot.enablePopout(ax); See also labkit.app.layout.plotArea"},{"title":"labkit.app.plot.fitAxesToGraphics","url":"reference/api/labkit/app/plot/fitAxesToGraphics.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.fitAxesToGraphics","text":"FITAXESTOGRAPHICS Fit axes limits to finite plotted X/Y data. FITAXESTOGRAPHICS Fit axes limits to finite plotted X/Y data. Usage: limits = labkit.app.plot.fitAxesToGraphics(ax) limits = labkit.app.plot.fitAxesToGraphics(ax, graphicsHandles) limits = labkit.app.plot.fitAxesToGraphics(..., Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle whose limits are changed. graphicsHandles - Optional graphics handle array. Objects with numeric XData and YData contribute to the fitted range. When omitted, all current axes children are examined. Name-Value Arguments: Padding - Nonnegative fractional padding added on each side of the data range. Default: 0.02. For logarithmic axes, padding is computed in base-10 logarithmic space. EqualDataUnits - Logical value. true expands one fitted dimension so one data unit occupies the same screen distance on X and Y without changing the axes position. For logarithmic dimensions, equality is evaluated in base-10 logarithmic space. Default: false. Outputs: limits - Scalar struct with x and y fields. Each field contains the applied two-element limit, or [] when that dimension had no usable data and was returned to automatic limit mode. Description: fit ignores nonfinite XData and YData. Nonpositive values do not contribute to a logarithmic dimension. Supplying graphicsHandles lets an app exclude annotations such as reference lines from the fitted range. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Invalid supplied graphics handles are ignored when they do not expose numeric XData/YData. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); h = plot(ax, [1 2 3], [10 20 15]); xline(ax, 100); limits = labkit.app.plot.fitAxesToGraphics(ax, h, \"Padding\", 0); assert(isequal(limits.x, [1 3])) See also labkit.app.plot.clearAxes"},{"title":"labkit.app.plot.fitCanvasToSource","url":"reference/api/labkit/app/plot/fitCanvasToSource.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.fitCanvasToSource","text":"FITCANVASTOSOURCE Fit a preview axes into a fixed-aspect pixel frame. FITCANVASTOSOURCE Fit a preview axes into a fixed-aspect pixel frame. Usage: [applied, frame] = labkit.app.plot.fitCanvasToSource(ax, width, height) [applied, frame] = labkit.app.plot.fitCanvasToSource(..., Name=Value) Inputs: ax - UI axes whose parent is a uigridlayout created for a LabKit preview. width - Positive finite source-canvas width in pixels. height - Positive finite source-canvas height in pixels. Name-Value Arguments: margin - Preferred empty margin around the frame in pixels. Default: 24. maxScale - Largest allowed ratio between displayed and source dimensions. Default: 1, so the helper does not enlarge the source canvas. Outputs: applied - true when the grid and axes positions were updated; false when the axes, parent grid, dimensions, or available space were unsuitable. frame - Scalar struct with width, height, ratio, position, scale, and pixelPosition fields. It is an empty struct when applied is false. Description: fitCanvas centers the axes in the middle row and column of a three-by-three flexible grid and preserves the requested aspect ratio. The helper keeps one managed parent-position listener with the axes and reapplies the most recent canvas request after host resizing. It returns false instead of throwing when the host has not been laid out yet or is smaller than the minimum usable preview area. Failure Behavior: Invalid axes, host grids, source dimensions, or insufficient available space return applied=false and frame=struct(). Malformed or unsupported name-value arguments throw labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption. Typical Call: [ok, frame] = labkit.app.plot.fitCanvasToSource(ax, 720, 540); See also labkit.app.layout.plotArea"},{"title":"labkit.app.plot.offsetPointByAxesFraction","url":"reference/api/labkit/app/plot/offsetPointByAxesFraction.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.offsetPointByAxesFraction","text":"OFFSETPOINTBYAXESFRACTION Move data coordinates by normalized axes fractions. OFFSETPOINTBYAXESFRACTION Move data coordinates by normalized axes fractions. Usage: xyOut = labkit.app.plot.offsetPointByAxesFraction(ax, xy, offsetFraction) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. Its current limits, scales, and directions define the coordinate conversion. xy - N-by-2 numeric matrix of [x y] data coordinates. offsetFraction - 1-by-2 or N-by-2 axes-fraction offsets. One row is reused for every point; otherwise the row count must match xy. For example, [0.03 -0.04] moves a label slightly right and down in visual axes space, including on log or reversed axes. Outputs: xy - N-by-2 offset coordinates in data units, shown as xyOut in the usage syntax. Values are not clamped to the visible axes box. Description: offsetData expresses annotation spacing in visual axes fractions instead of data units. This keeps the apparent offset stable as data ranges change and correctly handles logarithmic and reversed axes. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidPointPairs - xy is not an N-by-2 numeric array. labkit:app:plot:InvalidPointOffsets - offsetFraction is neither one row nor one row per point. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig, \"XLim\", [0 10], \"YLim\", [0 20]); xy = labkit.app.plot.offsetPointByAxesFraction(ax, [5 10], [0.1 -0.1]); assert(isequal(xy, [6 8])) See also labkit.app.plot.clampPointToAxes"},{"title":"labkit.app.plot.showMessage","url":"reference/api/labkit/app/plot/showMessage.html","kind":"reference","section":"reference","keywords":"labkit.app.plot.showMessage","text":"SHOWMESSAGE Show a centered empty-state message in an axes. SHOWMESSAGE Show a centered empty-state message in an axes. Usage: hText = labkit.app.plot.showMessage(ax, message) hText = labkit.app.plot.showMessage(ax, message, Name=Value) Inputs: ax - Valid scalar MATLAB axes or uiaxes handle. message - User-visible text scalar displayed at the axes center. Name-Value Arguments: Title - Axes title text. Default: \"\". Color - MATLAB color value for the message text. Default: [0.30 0.30 0.30]. Outputs: hText - MATLAB text object containing the message. Description: message clears the axes, resets it to linear unit limits [0 1], removes ticks, and draws non-pickable text. Use it for an empty preview, loading prompt, or unavailable result. Because it performs a complete clear, it is not an overlay operation and does not preserve the previous zoom. Errors: labkit:app:plot:InvalidAxes - ax is not a valid scalar axes handle. labkit:app:plot:InvalidOptions or labkit:app:plot:InvalidOption - Name-value arguments are malformed or unsupported. Invalid MATLAB text or color values propagate their originating graphics error. Example: fig = figure(\"Visible\", \"off\"); cleanup = onCleanup(@() close(fig)); ax = axes(fig); h = labkit.app.plot.showMessage(ax, \"No data\", \"Title\", \"Preview\"); assert(string(h.String) == \"No data\") See also labkit.app.plot.clearAxes"},{"title":"labkit.app.project.Schema","url":"reference/api/labkit/app/project/Schema.html","kind":"reference","section":"reference","keywords":"labkit.app.project.Schema","text":"PROJECTCONTRACT Declare one durable App project contract. PROJECTCONTRACT Declare one durable App project contract. Usage: contract = labkit.app.project.Schema() contract = labkit.app.project.Schema(Name=Value) Description: Project Schema owns payload creation, validation, sequential migration, optional legacy import, resume, and source-relink callback signatures. App-specific fields and scientific meaning remain inside the payload and are not interpreted by this value. Default Contract: With no arguments, Version is 1, Create returns struct(), and Validate accepts any scalar struct. This is the standard path for a simple App with no migration or specialized project invariants. Required Name-Value Arguments (custom contract): Version - Positive integer payload version. Create - Fixed callback project = create(). Validate - Fixed callback accepted = validate(project). Optional Name-Value Arguments: Migrate - Fixed callback project = migrate(project,fromVersion). Required when Version is greater than 1. Default: empty. LegacyImports - Scalar struct mapping legacy MAT variable names to fixed callbacks accepting one value and returning project or [project,resume]. Default: struct(). CreateResume - Fixed callback resume = createResume(session,project). Default: empty. ApplyResume - Fixed callback session = applyResume(session,resume,project). Default: empty. RelinkSources - Fixed callback project = relink(project,unresolved,projectFile). Returning empty cancels the load. Default: empty. Outputs: contract - Immutable labkit.app.project.Schema value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Version or LegacyImports is invalid. labkit:app:contract:CallbackRoleMismatch - A callback does not have its documented fixed input and output count. Typical Call: contract = labkit.app.project.Schema(Version=1, ... Create=@createProject, Validate=@validateProject); See also labkit.app.Definition, labkit.app.result.Package"},{"title":"labkit.app.project.emptySourceRecords","url":"reference/api/labkit/app/project/emptySourceRecords.html","kind":"reference","section":"reference","keywords":"labkit.app.project.emptySourceRecords","text":"EMPTYSOURCERECORDS Create an empty portable source collection. EMPTYSOURCERECORDS Create an empty portable source collection. Usage: records = labkit.app.project.emptySourceRecords() Description: Returns a zero-row struct collection with the same durable field shape as labkit.app.project.sourceRecord. App project factories use this value so the first file selection can assign a portable source without exposing or duplicating its field schema. Outputs: records - Empty column collection of portable source records. Failure Behavior: None - The function accepts no inputs and returns the canonical empty collection deterministically. Example: project.inputs.sources = labkit.app.project.emptySourceRecords(); assert(isempty(project.inputs.sources)) See also labkit.app.project.sourceRecord, labkit.app.project.Schema"},{"title":"labkit.app.project.sourceRecord","url":"reference/api/labkit/app/project/sourceRecord.html","kind":"reference","section":"reference","keywords":"labkit.app.project.sourceRecord","text":"SOURCERECORD Create a portable source value during project migration. SOURCERECORD Create a portable source value during project migration. Usage: record = labkit.app.project.sourceRecord(id,role,filepath) record = labkit.app.project.sourceRecord(id,role,filepath,required) Description: Creates the opaque durable source value accepted by App project schemas. Use this pure constructor only while creating or migrating payloads. Runtime callbacks resolve paths through CallbackContext. Inputs: id - Nonempty scalar text stable within the project. role - Nonempty scalar semantic source role. filepath - Nonempty scalar source path, or an existing scalar opaque reference struct during legacy migration. required - Optional logical scalar relinking requirement. Default: true. Outputs: record - Scalar portable source struct owned by the App framework. Errors: labkit:app:contract:InvalidValue - Text or required is malformed. Example: source = labkit.app.project.sourceRecord( ... \"image1\",\"cropSource\",\"image.png\"); assert(source.id == \"image1\") See also labkit.app.CallbackContext, labkit.app.project.Schema"},{"title":"labkit.app.result.File","url":"reference/api/labkit/app/result/File.html","kind":"reference","section":"reference","keywords":"labkit.app.result.File","text":"RESULTOUTPUT Declare one validated result-package output. RESULTOUTPUT Declare one validated result-package output. Usage: output = labkit.app.result.File(id, role, relativePath, Name=Value) Description: File records App-owned output identity and media semantics. Runtime result writing later resolves the relative path, file size, checksum, and aggregate manifest status. Inputs: id - Nonempty scalar text unique within one Result. role - Nonempty scalar text describing output purpose. relativePath - Nonempty package-relative path without traversal. Optional Name-Value Arguments: MediaType - Nonempty media-type scalar text. Default: \"application/octet-stream\". Status - \"success\", \"failed\", or \"skipped\". Default: \"success\". Message - Scalar reader-facing status text. Default: empty. Warnings - Row string or cellstr array. Default: empty. Outputs: output - Immutable labkit.app.result.File value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Text, path, status, or warnings are malformed. Example: output = labkit.app.result.File( ... \"summary\", \"primary\", \"summary.csv\", MediaType=\"text/csv\"); assert(output.Status == \"success\") See also labkit.app.result.Package, labkit.app.CallbackContext"},{"title":"labkit.app.result.Package","url":"reference/api/labkit/app/result/Package.html","kind":"reference","section":"reference","keywords":"labkit.app.result.Package","text":"RESULT Declare one App-owned result manifest request. RESULT Declare one App-owned result manifest request. Usage: result = labkit.app.result.Package(Name=Value) Description: Result contains validated output declarations and App-owned input, parameter, and summary data. Runtime result writing owns provenance, App and facade versions, document identity, file bytes, checksums, creation identity/time, aggregate status, and atomic replacement. Required Name-Value Arguments: Outputs - Nonempty row cell array of labkit.app.result.File values with unique IDs and relative paths and at least one success. Inputs - Scalar App-owned struct describing result inputs. Parameters - Scalar App-owned struct describing result parameters. Summary - Scalar App-owned struct describing result summary. Optional Name-Value Arguments: Warnings - Row string or cellstr array. Default: empty. ManifestName - Nonempty filename without folders. Default: \"labkit_result.json\". Outputs: result - Immutable labkit.app.result.Package value. Errors: labkit:app:contract:UnknownArgument - An option is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - A value or manifest name is invalid. labkit:app:contract:DuplicateId - Output IDs or paths repeat. Example: output = labkit.app.result.File( ... \"summary\", \"primary\", \"summary.csv\", MediaType=\"text/csv\"); result = labkit.app.result.Package(Outputs={output}, Inputs=struct(), ... Parameters=struct(), Summary=struct(\"rows\", 1)); assert(result.ManifestName == \"labkit_result.json\") See also labkit.app.result.File, labkit.app.CallbackContext"},{"title":"labkit.app.synthetic.Artifact","url":"reference/api/labkit/app/synthetic/Artifact.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Artifact","text":"ARTIFACT Describe one anonymous synthetic-input artifact. ARTIFACT Describe one anonymous synthetic-input artifact. Usage: artifact = labkit.app.synthetic.Artifact( ... id,role,relativePath,Name=Value) Description: Identifies one synthetic input, expected export, or support file beneath a synthetic-input root without exposing a user file path. Inputs: id - Nonempty semantic identifier unique within a Pack. role - Nonempty App-owned artifact purpose. relativePath - Nonempty synthetic-root-relative path without traversal. Optional Name-Value Arguments: Expectation - \"loads\", \"rejects\", \"exports\", or \"support\". Default: \"loads\". Outputs: artifact - Immutable synthetic-input artifact value. Errors: labkit:app:contract:UnknownArgument - An option is unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Text, path, or Expectation is malformed. Example: artifact = labkit.app.synthetic.Artifact( ... \"input\",\"source\",\"samples/input.csv\"); assert(artifact.Expectation == \"loads\") See also labkit.app.synthetic.Context, labkit.app.synthetic.Pack"},{"title":"labkit.app.synthetic.Context","url":"reference/api/labkit/app/synthetic/Context.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Context","text":"CONTEXT Provide bounded folders for anonymous synthetic inputs. CONTEXT Provide bounded folders for anonymous synthetic inputs. Usage: context = labkit.app.synthetic.Context(rootFolder) filepath = context.samplePath(relativePath) filepath = context.outputPath(relativePath) record = context.sourceRecord(id,role,filepath,required) artifact = context.artifact(id,role,filepath,Name=Value) Description: Context creates one synthetic-input root with samples and outputs children. App-owned BuildSyntheticSample callbacks may write only anonymous synthetic files beneath these folders. The value does not expose a runtime, recorder, project store, or UI object. Inputs: rootFolder - Nonempty scalar folder dedicated to one generated pack. relativePath - Nonempty child path without an absolute root, empty segment, current segment, or parent traversal. id - Stable portable-source identifier. role - Stable portable-source role. filepath - Path returned by samplePath. required - Logical scalar source requirement. Outputs: context - Immutable synthetic-input context. filepath - Absolute path bounded by SampleFolder or OutputFolder. record - Portable source value from labkit.app.project.sourceRecord. artifact - Typed synthetic artifact whose relative path is derived from a filepath beneath RootFolder. Errors: labkit:app:contract:InvalidValue - A folder, relative path, or source argument is malformed. MATLAB filesystem errors propagate when the synthetic folders cannot be created. Typical Call: context = labkit.app.synthetic.Context(tempname); filepath = context.samplePath(\"input.csv\"); See also labkit.app.synthetic.Artifact, labkit.app.synthetic.Pack, labkit.app.project.sourceRecord"},{"title":"labkit.app.synthetic.Pack","url":"reference/api/labkit/app/synthetic/Pack.html","kind":"reference","section":"reference","keywords":"labkit.app.synthetic.Pack","text":"PACK Describe one typed anonymous App reproduction scenario. PACK Describe one typed anonymous App reproduction scenario. Usage: pack = labkit.app.synthetic.Pack( ... Scenario=scenario,InitialProject=project,Artifacts=artifacts) Description: Couples one App-authored synthetic project with its anonymous artifact declarations so users and tests can reproduce a named scenario through the App's ordinary import workflow. Required Name-Value Arguments: Scenario - Nonempty stable scenario identifier. InitialProject - Scalar current App project struct. Artifacts - Row cell array of labkit.app.synthetic.Artifact values. Empty is legal for an App whose scenario needs no files. Outputs: pack - Immutable synthetic-input pack. Errors: labkit:app:contract:UnknownArgument - An argument is missing, unknown, duplicated, or unpaired. labkit:app:contract:InvalidValue - Scenario, project, or Artifacts is malformed. labkit:app:contract:DuplicateId - Artifact IDs or relative paths are duplicated. Example: artifact = labkit.app.synthetic.Artifact( ... \"input\",\"source\",\"samples/input.csv\"); pack = labkit.app.synthetic.Pack( ... Scenario=\"representative\",InitialProject=struct(), ... Artifacts={artifact}); assert(pack.Scenario == \"representative\") See also labkit.app.synthetic.Context, labkit.app.synthetic.Artifact, labkit.app.Definition"},{"title":"labkit.app.version","url":"reference/api/labkit/app/version.html","kind":"reference","section":"reference","keywords":"labkit.app.version","text":"VERSION Return the LabKit App SDK facade contract version. VERSION Return the LabKit App SDK facade contract version. Usage: info = labkit.app.version() Description: Reports the semantic version and compatibility range of the public LabKit App SDK. The SDK owns App definitions, semantic layout, transactions, projects, portable sources, resources, results, dialogs, and native MATLAB presentation. It is independent of App product and saved-project schema versions. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo with name, current, compatible, status, and notes fields. Errors: labkit:contract:InvalidVersionInfo - Embedded facade metadata is invalid. Example: info = labkit.app.version(); assert(startsWith(info.current, \"2.\")) See also labkit.contract.versionInfo, labkit.contract.requirements, labkit.app.Definition"},{"title":"labkit.app.view.Snapshot","url":"reference/api/labkit/app/view/Snapshot.html","kind":"reference","section":"reference","keywords":"labkit.app.view.Snapshot","text":"SNAPSHOT Build one immutable complete visible-state snapshot. SNAPSHOT Build one immutable complete visible-state snapshot. Usage: view = labkit.app.view.Snapshot() view = view.value(target, value) view = view.choices(target, choices) view = view.limits(target, limits) view = view.enabled(target, enabled) view = view.visible(target, visible) view = view.text(target, text) view = view.filePaths(target, paths) view = view.fileItemStatuses(target, statuses) view = view.listSelection(target, selection) view = view.tableCellSelection(target, selection) view = view.tableData(target, data, Name=Value) view = view.renderPlot(target, model, Name=Value) view = view.workspacePage(target, Name=Value) view = view.anchorPath(interaction, points, Name=Value) view = view.pairedAnchors(interaction, pointSets, Name=Value) view = view.pointSlots(interaction, value, Name=Value) view = view.rectangle(interaction, position, Name=Value) view = view.regionSelection(interaction, Name=Value) view = view.interval(interaction, range, Name=Value) view = view.scaleReference(interaction, endpoints, Name=Value) view = view.include(fragment) Description: View snapshot is a closed immutable operation vocabulary. One value describes the complete current visible state; Definition validates targets, capabilities, renderer references, and completeness before a runtime may reconcile it. Apps do not author patches or generic target/property setters. Inputs: target - Nonempty Layout target ID. value - Target value owned by the App state. choices - Text array or cellstr of legal choices. limits - Increasing finite two-element numeric row. enabled - Logical scalar availability. visible - Logical scalar visibility. text - Scalar text. paths - String or cell array of file paths. statuses - Empty or one reader-facing status per file-list row. selection - Selection value accepted by the target. data - App-owned table, numeric array, or cell array. model - App-owned model passed to the renderer declared by plotArea. Options: ViewRevision - Nonnegative integer identifying the requested initial viewport. The adapter preserves user zoom while the value is unchanged and accepts renderer limits once when it changes. Default: 0. Outputs: view - New immutable labkit.app.view.Snapshot snapshot. fragment - Another Snapshot whose non-duplicate operations should be included in this snapshot. Errors: labkit:app:contract:InvalidValue - An operation argument is malformed. labkit:app:contract:DuplicateId - The same operation is supplied twice for one target. Example: view = labkit.app.view.Snapshot(); view = view.choices(\"group\", [\"A\", \"B\"]); view = view.value(\"group\", \"A\"); assert(isa(view, \"labkit.app.view.Snapshot\")) See also labkit.app.Definition, labkit.app.layout.workbench"},{"title":"labkit.biosignal.buildTemplate","url":"reference/api/labkit/biosignal/buildTemplate.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.buildTemplate","text":"BUILDTEMPLATE Build a representative segment template. BUILDTEMPLATE Build a representative segment template. Usage: template = labkit.biosignal.buildTemplate(segments) template = labkit.biosignal.buildTemplate(segments, opts) Description: Forms a first-pass template by averaging every segment, then ranks each segment by its correlation with that average. The returned waveform is the sample-by-sample mean of the highest-ranked segments. This two-pass procedure reduces the influence of beats or trials whose shape differs from the majority. Correlations are computed after subtracting each waveform's mean. Missing samples are omitted from means and correlations. A constant waveform has a NaN score and sorts behind finite scores. If segments contains no waveforms, the function returns an empty template with the same timeOffset vector. Inputs: segments - Segment struct returned by labkit.biosignal.segmentByEvents. segments.values is an M-by-N matrix, with one segment per column; segments.timeOffset contains the M relative times. opts - Optional scalar struct containing the fields listed below. Options: topN - Number of ranked segments to average. The default is the smaller of 30 and the number of available segments. The value is rounded to an integer and limited to the range 1:N. Outputs: template - Structure containing the representative waveform and the ranking information used to construct it. Output Fields: type - String scalar \"biosignalTemplate\". values - M-by-1 template waveform, or an M-by-0 array when no segments are available. timeOffset - M-by-1 relative time vector copied from segments. keptSegmentIndex - Indices of the segment columns included in values, ordered from highest to lowest correlation. score - N-by-1 correlation score for every input segment. metadata.topN - Number of segments included in a nonempty template. metadata.sourceName - Source signal name copied from segments for a nonempty template. metadata is empty when no segment waveforms are available. Errors: labkit:biosignal:InvalidSegments - segments is not a structure with values and timeOffset fields. Invalid numeric values or an unusable topN value may also raise the originating MATLAB conversion error. Example: segments = struct('values', [0 0; 1 0.9; 0 0], ... 'timeOffset', [-1; 0; 1], 'sourceName', \"ECG\"); template = labkit.biosignal.buildTemplate(segments, struct('topN', 1)); See also labkit.biosignal.segmentByEvents, labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.compareGroups","url":"reference/api/labkit/biosignal/compareGroups.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.compareGroups","text":"COMPAREGROUPS Summarize groups and compute pairwise Welch comparisons. COMPAREGROUPS Summarize groups and compute pairwise Welch comparisons. Usage: result = labkit.biosignal.compareGroups(values, groups) Description: Calculates descriptive statistics for each group and a two-sided Welch t-test for every pair of groups. Welch's test does not assume equal variances. Group order follows the first occurrence of each label in groups. Rows with a nonfinite measurement or a blank group label are discarded before calculation. A pairwise row is still returned when either group has fewer than two observations or the estimated standard error is zero; its test statistic, degrees of freedom, and p-value are NaN. Inputs: values - Numeric vector of measurements. groups - Vector of labels accepted by string, such as a string array, cell array of character vectors, or categorical array. It must contain one label for each element of values. Outputs: result - Structure with summary and pairwise tables. Output Fields: summary - One row per group. Columns are Group, N, Mean, Std, Median, Min, and Max. pairwise - One row per unique group pair. Columns are GroupA, GroupB, MeanDifference, T, DF, and P. MeanDifference is mean(GroupA) minus mean(GroupB); P is the two-sided Welch-test p-value. Failure Behavior: Groups with fewer than two finite observations, or with zero estimated standard error, retain a pairwise row with NaN test statistics. values and groups must contain the same number of elements; incompatible input shapes or values that cannot be converted to numeric/text raise the originating MATLAB error. Example: values = [4.8 5.1 5.0 6.2 6.0 6.4]; groups = [\"control\" \"control\" \"control\" \"treated\" \"treated\" \"treated\"]; result = labkit.biosignal.compareGroups(values, groups); See also labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.cropSignal","url":"reference/api/labkit/biosignal/cropSignal.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.cropSignal","text":"CROPSIGNAL Return a signal clipped to a time range in seconds. CROPSIGNAL Return a signal clipped to a time range in seconds. Usage: cropped = labkit.biosignal.cropSignal(signal, timeRangeSec) cropped = labkit.biosignal.cropSignal(signal) Description: Keeps samples whose timestamps lie between the requested start and end times, including both endpoints. For a nonempty result, time is shifted so that the first retained sample occurs at zero. The sample rate is recalculated from the median positive time step when at least two usable timestamps remain; otherwise the input sample rate is preserved. Omitting timeRangeSec or passing [] returns signal unchanged. A valid range that contains no samples returns empty time and values vectors. Inputs: signal - Biosignal structure with time, values, fs, and metadata fields. time and values must describe corresponding samples. timeRangeSec - Two-element numeric vector [startSec endSec], expressed on signal.time, with startSec less than endSec. The value may be omitted or empty to skip cropping. Outputs: cropped - Copy of signal containing only the selected samples. The original range is recorded in metadata.cropTimeRangeSec. Errors: labkit:biosignal:InvalidSignal - signal does not contain time, values, and fs fields. labkit:biosignal:InvalidTimeRange - timeRangeSec is not an increasing two-element vector. Example: signal = struct('time', (0:0.5:3)', 'values', (10:16)', ... 'fs', 2, 'metadata', struct()); cropped = labkit.biosignal.cropSignal(signal, [0.75 2.25]); See also labkit.biosignal.filterSignal, labkit.biosignal.getChannel"},{"title":"labkit.biosignal.defaultEcgPeakOptions","url":"reference/api/labkit/biosignal/defaultEcgPeakOptions.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.defaultEcgPeakOptions","text":"DEFAULTECGPEAKOPTIONS Return documented defaults for ECG/QRS peak detection. DEFAULTECGPEAKOPTIONS Return documented defaults for ECG/QRS peak detection. Usage: opts = labkit.biosignal.defaultEcgPeakOptions() opts = labkit.biosignal.defaultEcgPeakOptions(method) Description: Returns a complete starting options structure for one ECG peak detector. Modify only the fields needed for a recording, then pass the structure to labkit.biosignal.detectEcgPeaks. Fields that do not apply to the selected method are omitted, which makes saved settings easier to interpret. Inputs: method - Optional character vector or scalar string. Canonical values are \"qrs-streaming\" (default), \"pan-tompkins\", and \"local\". Common spellings such as \"streaming\", \"pantompkin\", and \"simple\" are accepted and normalized to a canonical value. Outputs: opts - Scalar structure containing the selected method and its defaults. Common Options: method - Canonical detector name. polarity - Peak direction: \"auto\", \"positive\", \"negative\", or \"absolute\". The default is \"auto\". minDistanceSec - Minimum interval between accepted peaks. The default is 0.25 seconds for qrs-streaming and pan-tompkins, and 0.05 seconds for local. Local Options: thresholdStd - Number of robust standard deviations above the median score used as the automatic threshold. Default 3. smoothSec - Moving-average duration applied to the peak score. Default 0.01 seconds. Pan-Tompkins Options: integrationWindowSec - Moving integration duration for squared QRS-band slope energy. Default 0.150 seconds. refineSearchSec - Half-width used to move an energy candidate to the nearest peak in the detector trace. Default 0.120 seconds. rawRefineSearchSec - Half-width for the final move to a peak in the raw signal. Default 0.020 seconds. Streaming QRS Options: baselineWindowSec - Causal moving-baseline duration. Default 0.600 seconds. envelopeWindowSec - Causal slope-envelope duration. Default 0.080 seconds. lookaheadSec - Forward interval used to confirm a local envelope maximum. Default 0.080 seconds. refineSearchSec - Half-width used to move a candidate from the envelope to the baseline-corrected signal. Default 0.090 seconds. rawRefineSearchSec - Half-width for the final move to a raw-signal peak. Default 0.020 seconds. minTemplateScore - Minimum correlation with the rolling QRS template once at least four templates are available. Default 0.45. medianPolarityCorrection - Logical value that enables a final correction of isolated opposite-polarity anchors. Default true. medianReviewPeakCount - Number of accepted peaks used by the polarity review. Default 3. Errors: labkit:biosignal:UnsupportedPeakMethod - method cannot be normalized to a supported detector. Example: opts = labkit.biosignal.defaultEcgPeakOptions(\"pan-tompkins\"); opts.minDistanceSec = 0.35; See also labkit.biosignal.detectEcgPeaks"},{"title":"labkit.biosignal.detectEcgPeaks","url":"reference/api/labkit/biosignal/detectEcgPeaks.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.detectEcgPeaks","text":"DETECTECGPEAKS Detect ECG/QRS peaks as event anchors. DETECTECGPEAKS Detect ECG/QRS peaks as event anchors. Usage: events = labkit.biosignal.detectEcgPeaks(signal) events = labkit.biosignal.detectEcgPeaks(signal, opts) Description: Detects beat anchors from one ECG-like signal. The function first obtains the documented defaults for opts.method, then replaces them with fields supplied in opts. Input time, sample indices, and raw amplitudes are preserved in the returned events so the anchors can be used directly by labkit.biosignal.segmentByEvents. qrs-streaming uses a causal moving baseline and slope envelope, adaptive signal and noise levels, a rolling morphology template, and optional median-polarity review. pan-tompkins uses a QRS-band signal, squared derivative energy, moving integration, adaptive thresholding, and search back. local applies a robust amplitude threshold followed by nonmaximum suppression. Pan-Tompkins and streaming candidates are finally moved to nearby peaks in the raw signal. Empty signals, invalid sample rates, and signals too short for the chosen detector return an empty events structure rather than an error. Inputs: signal - Biosignal structure with time, values, and fs fields. time and values must have corresponding samples; fs is in hertz. opts - Optional scalar struct. Start with labkit.biosignal.defaultEcgPeakOptions and change the fields that need tuning for the recording. Options: method - \"qrs-streaming\" (default), \"pan-tompkins\", or \"local\". polarity - \"auto\" (default), \"positive\", \"negative\", or \"absolute\". auto chooses the stronger direction; absolute scores either direction by magnitude. minDistanceSec - Minimum accepted peak spacing in seconds. Defaults are method-specific; see defaultEcgPeakOptions. threshold - Local method only. Explicit score threshold. The default [] derives a threshold from thresholdStd. thresholdStd - Local method only. Robust standard-deviation multiplier; default 3. smoothSec - Local method only. Score smoothing duration; default 0.01 seconds. integrationWindowSec - Pan-Tompkins integration duration; default 0.150 seconds. refineSearchSec - Detector-trace search half-width. Defaults are 0.120 seconds for pan-tompkins and 0.090 seconds for qrs-streaming. rawRefineSearchSec - Final raw-signal search half-width for pan-tompkins and qrs-streaming. Default 0.020 seconds. baselineWindowSec - qrs-streaming baseline duration; default 0.600 seconds. envelopeWindowSec - qrs-streaming slope-envelope duration; default 0.080 seconds. lookaheadSec - qrs-streaming local-maximum lookahead; default 0.080 seconds. minTemplateScore - qrs-streaming rolling-template correlation threshold; default 0.45. medianPolarityCorrection - qrs-streaming logical switch for final anchor polarity correction; default true. medianReviewPeakCount - qrs-streaming review length; default 3 peaks. Outputs: events - Structure containing one row-aligned entry per detected peak. Output Fields: type - String scalar \"biosignalEvents\". index - 1-based sample indices in signal.values. time - Event times copied from signal.time at index. amplitude - Raw signal amplitudes at the accepted indices. score - Method-specific detector score at each event. label - \"qrs\" for pan-tompkins and qrs-streaming, or \"peak\" for local. threshold - Final score threshold used by the detector. metadata - Selected method, polarity, minimum spacing, and other method-specific settings used during detection. Errors: labkit:biosignal:InvalidSignal - signal lacks time, values, or fs. labkit:biosignal:UnsupportedPeakMethod - opts.method is unsupported. labkit:biosignal:UnsupportedPolarity - opts.polarity is unsupported. Example: fs = 100; time = (0:1/fs:4)'; values = zeros(size(time)); values(101:100:401) = 1; signal = struct('time', time, 'values', values, 'fs', fs); opts = struct('method', 'local', 'polarity', 'positive', ... 'minDistanceSec', 0.5, 'thresholdStd', 1); events = labkit.biosignal.detectEcgPeaks(signal, opts); See also labkit.biosignal.defaultEcgPeakOptions, labkit.biosignal.segmentByEvents"},{"title":"labkit.biosignal.filterSignal","url":"reference/api/labkit/biosignal/filterSignal.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.filterSignal","text":"FILTERSIGNAL Apply a zero-phase FFT-domain filter to a biosignal. FILTERSIGNAL Apply a zero-phase FFT-domain filter to a biosignal. Usage: filtered = labkit.biosignal.filterSignal(signal) filtered = labkit.biosignal.filterSignal(signal, spec) Description: Applies an ideal frequency mask in the discrete Fourier domain and uses an inverse transform to return a zero-phase filtered waveform. Low-pass and band-pass filtering restore the input mean after filtering; high-pass filtering does not. Missing samples are filled before the transform, so filtered.values is finite when the input contains enough finite data for interpolation. Reflect edge mode extrapolates both ends of the waveform before applying the FFT and removes the padding afterward. A cosine taper is applied only to the outer padded samples. This reduces wraparound artifacts without changing the length or time axis of the signal. Use edgeMode \"none\" when the unpadded periodic FFT behavior is required. Inputs: signal - Biosignal structure with values, fs, and metadata fields. fs is the sample rate in hertz. spec - Optional scalar struct containing the fields listed below. Options: type - \"bandpass\" (default), \"lowpass\", \"highpass\", \"none\", or \"off\". The latter two skip frequency masking but still fill missing data. cutoffHz - Scalar cutoff for low-pass or high-pass filtering, or a two-element [low high] band for band-pass filtering. The default is [0.5 40] Hz. Empty or nonfinite values skip filtering and return the filled waveform. edgeMode - \"reflect\" (default) or \"none\". The aliases \"reflection\", \"symmetric\", \"pad\", and \"padded\" select reflection; \"off\" and \"raw\" select no padding. edgePadSec - Nonnegative padding duration in seconds. By default, band-pass and high-pass filters use three periods of the lowest cutoff, limited to 0.5 through 5 seconds. Other filters use 1 second. Padding cannot exceed half the available signal minus one sample. edgeTaperSec - Nonnegative duration of the outer padding taper. The default is 1 second. Outputs: filtered - Copy of signal with filtered values. All other signal fields are preserved, and metadata.filter records spec exactly as supplied by the caller. Errors: labkit:biosignal:InvalidSignal - signal lacks values or fs. labkit:biosignal:UnsupportedFilter - type is not supported. labkit:biosignal:UnsupportedFilterEdgeMode - edgeMode is not supported. Example: fs = 100; time = (0:1/fs:2)'; values = sin(2*pi*2*time) + 0.2*sin(2*pi*30*time); signal = struct('time', time, 'values', values, 'fs', fs, ... 'metadata', struct()); filtered = labkit.biosignal.filterSignal(signal, ... struct('type', 'lowpass', 'cutoffHz', 10)); See also labkit.biosignal.cropSignal, labkit.biosignal.detectEcgPeaks"},{"title":"labkit.biosignal.getChannel","url":"reference/api/labkit/biosignal/getChannel.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.getChannel","text":"GETCHANNEL Return one signal from a recording by display name or index. GETCHANNEL Return one signal from a recording by display name or index. Usage: signal = labkit.biosignal.getChannel(recording, channel) Description: Selects one channel from a recording created by labkit.biosignal.readRecording. A numeric channel is interpreted as a 1-based index. Text is matched exactly against displayName first and then against the source name. Matching is case-sensitive. Use labkit.biosignal.listChannels to obtain the display names presented by the recording. Display names are unique even when source channel names are repeated. Inputs: recording - Recording structure returned by labkit.biosignal.readRecording. channel - Positive integer index, character vector, or scalar string containing a channel name or display name. Outputs: signal - Selected signal structure. Its principal fields are time in seconds, values, fs in hertz, name, displayName, unit, and metadata. Errors: labkit:biosignal:InvalidRecording - recording has no signals field. labkit:biosignal:NoChannels - recording contains no signals. labkit:biosignal:InvalidChannelIndex - numeric index is not an integer within the available range. labkit:biosignal:InvalidChannel - channel has an unsupported type. labkit:biosignal:UnknownChannel - no exact name matches channel. Example: channel = struct('name', \"ECG\", 'displayName', \"ECG\", ... 'time', [0; 0.01], 'values', [0; 1], 'fs', 100, ... 'unit', \"mV\", 'metadata', struct()); recording = struct('signals', channel); signal = labkit.biosignal.getChannel(recording, \"ECG\"); See also labkit.biosignal.listChannels, labkit.biosignal.readRecording"},{"title":"labkit.biosignal.listChannels","url":"reference/api/labkit/biosignal/listChannels.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.listChannels","text":"LISTCHANNELS Return display names for all channels in a recording. LISTCHANNELS Return display names for all channels in a recording. Usage: names = labkit.biosignal.listChannels(recording) Description: Returns channel display names in recording order. These are the same names accepted by labkit.biosignal.getChannel and are suitable for a list box or drop-down menu. A recording with no signals returns a 1-by-0 cell array. Inputs: recording - Recording structure returned by labkit.biosignal.readRecording. Outputs: names - 1-by-N cell array of display-name character vectors. Errors: labkit:biosignal:InvalidRecording - recording is not a structure with a signals field. Example: signals(1).displayName = \"ECG\"; signals(2).displayName = \"Respiration\"; recording = struct('signals', signals); names = labkit.biosignal.listChannels(recording); See also labkit.biosignal.getChannel, labkit.biosignal.readRecording"},{"title":"labkit.biosignal.measureSegments","url":"reference/api/labkit/biosignal/measureSegments.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.measureSegments","text":"MEASURESEGMENTS Measure generic template-residual segment quality. MEASURESEGMENTS Measure generic template-residual segment quality. Usage: result = labkit.biosignal.measureSegments(segments, template) result = labkit.biosignal.measureSegments(segments, template, opts) Description: Measures each segment against a representative template. SignalP2P is the peak-to-peak amplitude inside the signal window. NoiseRMS is the root mean square of segment minus template inside the selected noise windows. SNRdB is 20*log10(SignalP2P/NoiseRMS), and TemplateCorrelation is the mean-centered correlation between the complete segment and template. The function also summarizes the per-segment measurements with means and sample standard deviations. If either the segment matrix or template is empty, both returned tables and metadata are empty. Inputs: segments - Segment structure returned by labkit.biosignal.segmentByEvents. Its values matrix must have the same number of rows as template.values. template - Template structure returned by labkit.biosignal.buildTemplate. opts - Optional scalar struct containing the fields listed below. Options: signalWindowSec - Two-element interval used for SignalP2P. The default is [-0.06 0.06] seconds relative to the event. The interval must include at least one timeOffset sample. noiseWindowsSec - N-by-2 matrix of intervals used for NoiseRMS. The default is [-0.30 -0.20; 0.40 0.50] seconds. Their union must include at least one timeOffset sample. Outputs: result - Structure containing per-segment measurements, aggregate statistics, and the windows used in the calculation. Output Fields: type - String scalar \"biosignalSegmentMeasurements\". perSegment - Table with Segment, EventIndex, EventTime, SignalP2P, NoiseRMS, SNRdB, and TemplateCorrelation columns. summary - One-row table with SegmentCount and the mean and standard deviation of SignalP2P, NoiseRMS, and SNRdB, plus the mean TemplateCorrelation. metadata.signalWindowSec - Signal interval used for the calculation. metadata.noiseWindowsSec - Noise intervals used for the calculation. Errors: labkit:biosignal:InvalidSegments - segments lacks values or timeOffset. labkit:biosignal:InvalidTemplate - template lacks values. Example: segments = struct('values', [0 0; 2 1.8; 0 0], ... 'timeOffset', [-0.1; 0; 0.1], 'eventIndex', [10; 20], ... 'eventTime', [1; 2]); template = struct('values', [0; 1.9; 0]); opts = struct('signalWindowSec', [-0.05 0.05], ... 'noiseWindowsSec', [-0.1 -0.05; 0.05 0.1]); result = labkit.biosignal.measureSegments(segments, template, opts); See also labkit.biosignal.buildTemplate, labkit.biosignal.segmentByEvents"},{"title":"labkit.biosignal.readRecording","url":"reference/api/labkit/biosignal/readRecording.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.readRecording","text":"READRECORDING Read a biosignal file into a standard recording struct. READRECORDING Read a biosignal file into a standard recording struct. Usage: [recording, status] = labkit.biosignal.readRecording(filepath) [recording, status] = labkit.biosignal.readRecording(filepath, opts) Description: Imports supported biosignal files into one recording format used by the rest of labkit.biosignal. MAT files contribute each numeric or logical vector stored in a timetable. CSV, TSV, and TXT files contribute each selected numeric column other than the time column. For delimited text, the importer detects a header after an optional preamble and recognizes numeric, datetime, or duration time columns. An explicit timeColumn takes precedence. Otherwise, names that look like time are preferred; a headerless first numeric column and the common I0 device column are also recognized. If no time column can be identified, the importer creates a sample-index time axis. All returned signal times are measured in seconds from zero. Import and format errors are reported in status instead of being thrown. The returned recording is then an empty recording carrying the requested source path. An invalid filepath value is a programming error and is thrown before import begins. Inputs: filepath - Character vector or scalar string naming a MAT, CSV, TSV, or TXT file. opts - Optional scalar struct containing the fields listed below. Options: headerLine - Positive integer giving the header line, or the first data line when hasHeader is false. By default the importer scans the first 120 lines. hasHeader - Logical scalar indicating whether headerLine contains column names. The default [] selects automatic detection. timeColumn - Column name or 1-based column index to use as time. The default [] enables the inference described above. timeUnit - Unit for a numeric time column: \"seconds\", \"milliseconds\", \"microseconds\", \"nanoseconds\", or the abbreviations s, sec, ms, us, and ns. \"sample\", \"samples\", \"index\", and \"sample_index\" leave the numeric scale unchanged. The default infers SI-prefixed units from the column name and otherwise uses seconds. signalColumns - Column names or 1-based indices to import from a delimited file. The default [] imports every numeric non-time vector. fallbackFs - Positive sample rate in hertz. It sets the spacing of a synthetic time axis and supplies a nominal interval when timestamps cannot provide one. Without it, synthetic sample-index time advances by one second per row. timeRepair - \"auto\" (default) replaces duplicate or backward timestamp steps with the nominal positive step. \"none\" and \"off\" preserve the converted intervals after shifting to zero. gapFactor - Positive scalar used only to report unusually large positive gaps. A gap larger than gapFactor times the median positive interval is counted. The default is 20. useFirstNumericColumnAsTime - Logical scalar. true permits the first numeric column to be used when no other time rule matches. The default is false. Outputs: recording - Scalar recording structure. signals is a structure array with one element per imported channel. status - Scalar structure describing whether import succeeded. Recording Fields: type - String scalar \"biosignalRecording\". version - Recording schema version, currently 1. sourcePath - Source path supplied by filepath. name - Source file name including its extension. signals - Channel structures with time, values, fs, name, displayName, sourceName, unit, and metadata fields. metadata - Import information. Text imports report sourceKind, timeColumn, timeUnit, timeSource, timeRepair, importHeaderLine, and importHasHeader. Status Fields: ok - Logical true when import completed. message - Empty string on success; otherwise the import error message. kind - \"mat\" or \"table\" on success. Failed requests report \"unknown\" or the attempted file extension. filepath - Normalized source path. Failure Behavior: A missing, unsupported, unreadable, or malformed source returns status.ok=false, preserves the normalized source path, and returns an empty recording. Only an invalid filepath MATLAB value is thrown as labkit:biosignal:InvalidFilepath before import begins. Example: filepath = [tempname '.csv']; sampleTime = (0:0.01:0.03)'; ECG = [0; 1; 0; -1]; writetable(table(sampleTime, ECG), filepath); cleanup = onCleanup(@() delete(filepath)); [recording, status] = labkit.biosignal.readRecording(filepath, ... struct('timeColumn', 'sampleTime', 'timeUnit', 'seconds')); signal = labkit.biosignal.getChannel(recording, 'ECG'); See also labkit.biosignal.listChannels, labkit.biosignal.getChannel"},{"title":"labkit.biosignal.segmentByEvents","url":"reference/api/labkit/biosignal/segmentByEvents.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.segmentByEvents","text":"SEGMENTBYEVENTS Extract fixed windows around event anchors. SEGMENTBYEVENTS Extract fixed windows around event anchors. Usage: segments = labkit.biosignal.segmentByEvents(signal, events) segments = labkit.biosignal.segmentByEvents(signal, events, windowSec) Description: Extracts one fixed-length signal segment around each event index. Events whose requested window would extend beyond the signal are omitted rather than padded. The returned columns therefore correspond only to interior events, and eventIndex and eventTime identify which events were retained. Window boundaries are converted to sample counts by rounding after multiplication by signal.fs. The default window is 0.35 seconds before through 0.35 seconds after each event. Inputs: signal - Biosignal structure with values, fs, and displayName fields. events - Event structure returned by labkit.biosignal.detectEcgPeaks. index contains 1-based sample positions and time contains the corresponding event times. windowSec - Optional two-element vector [startSec endSec] relative to an event. Use a negative start and nonnegative end to span the event. The default is [-0.35 0.35] seconds. Outputs: segments - Structure containing the retained waveform segments. Output Fields: type - String scalar \"biosignalSegments\". values - M-by-N matrix with one retained event segment per column. timeOffset - M-by-1 vector of sample times relative to the event. eventIndex - N-by-1 source sample indices of retained events. eventTime - N-by-1 source times of retained events. fs - Sample rate in hertz copied from signal. sourceName - signal.displayName. metadata.windowSec - Requested window in seconds. Errors: labkit:biosignal:InvalidSignal - signal lacks values or fs. labkit:biosignal:InvalidEvents - events lacks index or time. labkit:biosignal:InvalidWindow - windowSec is not an increasing two-element vector. Example: signal = struct('values', (1:21)', 'fs', 10, 'displayName', \"ECG\"); events = struct('index', [6; 16], 'time', [0.5; 1.5]); segments = labkit.biosignal.segmentByEvents(signal, events, [-0.2 0.2]); See also labkit.biosignal.detectEcgPeaks, labkit.biosignal.buildTemplate, labkit.biosignal.measureSegments"},{"title":"labkit.biosignal.version","url":"reference/api/labkit/biosignal/version.html","kind":"reference","section":"reference","keywords":"labkit.biosignal.version","text":"VERSION Return the LabKit biosignal facade contract version. VERSION Return the LabKit biosignal facade contract version. Usage: info = labkit.biosignal.version() Description: Reports the semantic version and compatibility range of the public labkit.biosignal API. Use this information when an app or saved project needs to check whether the installed biosignal facade satisfies a known contract range. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. It identifies the biosignal component, current version, compatible contract range, stability status, and a short description. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.biosignal.version(); currentVersion = info.current; See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.contract.assertRequirements","url":"reference/api/labkit/contract/assertRequirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.assertRequirements","text":"ASSERTREQUIREMENTS Throw an error when LabKit API requirements are not met. ASSERTREQUIREMENTS Throw an error when LabKit API requirements are not met. Usage: labkit.contract.assertRequirements(appName, req) labkit.contract.assertRequirements(appName, req, versions) Description: Calls checkRequirements and returns normally when every requirement is compatible. On failure, combines the individual report messages into one error. Use checkRequirements instead when incompatibility should be shown without throwing. Inputs: appName - Text scalar used as the error-identifier prefix. It should be a valid MATLAB identifier, for example \"labkit_Example_app\". req - Scalar structure returned by labkit.contract.requirements. versions - Optional version structure array accepted by checkRequirements. Outputs: None - Returns no value. Errors: Throws :IncompatibleLabKit when compatibility checks fail. Input and range validation errors from checkRequirements are passed through. Example: req = labkit.contract.requirements(\"app\", \">=1 <2\"); labkit.contract.assertRequirements(\"labkit_Example_app\", req) See also labkit.contract.checkRequirements, labkit.contract.requirements"},{"title":"labkit.contract.checkRequirements","url":"reference/api/labkit/contract/checkRequirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.checkRequirements","text":"CHECKREQUIREMENTS Compare required API ranges with available LabKit versions. CHECKREQUIREMENTS Compare required API ranges with available LabKit versions. Usage: report = labkit.contract.checkRequirements(req) report = labkit.contract.checkRequirements(req, versions) Description: Checks each requested facade in two ways: its current version must satisfy the caller's range, and that range must overlap at least one compatibility range advertised by the facade. When versions is omitted, the current app, dta, rhs, biosignal, image, and thermal version functions are queried. Inputs: req - Scalar structure returned by labkit.contract.requirements. versions - Optional structure array returned by facade version functions or versionInfo. Every element must contain name, current, compatible, status, and notes. An optional facade field overrides the name used for matching. This argument is mainly useful for diagnostics and tests. Outputs: report - Scalar structure with ok, failures, and message fields. ok is true when every requirement passes. message is a success sentence or newline-separated failure text. Output Fields: failures - Structure array with one element per failed requirement. failures.facade - Normalized facade name. failures.required - Range requested by the caller. failures.available - Compatibility ranges advertised by the facade, or an empty string array when the facade is unknown. failures.message - Readable incompatibility explanation. Errors: Throws labkit:contract:InvalidRequirements for a malformed req, labkit:contract:InvalidVersionInfo for a malformed versions array, labkit:contract:InvalidFacadeName for invalid names, and labkit:contract:InvalidVersionRange when a version or constraint cannot be parsed. Versions contain one to three numeric components, such as 6, 6.1, or 6.1.2. Example: req = labkit.contract.requirements(\"thermal\", \">=1.0 <2\"); report = labkit.contract.checkRequirements(req); if ~report.ok warning(\"%s\", report.message) end See also labkit.contract.requirements, labkit.contract.assertRequirements, labkit.contract.versionInfo"},{"title":"labkit.contract.requirements","url":"reference/api/labkit/contract/requirements.html","kind":"reference","section":"reference","keywords":"labkit.contract.requirements","text":"REQUIREMENTS Describe the LabKit API versions required by a caller. REQUIREMENTS Describe the LabKit API versions required by a caller. Usage: req = labkit.contract.requirements(\"app\", \">=1.0 <2\", ...) Description: Builds a normalized requirement structure from facade/range pairs. A facade name may be written as \"app\" or \"labkit.app\". Names are converted to lowercase and the optional \"labkit.\" prefix is removed. No compatibility check is performed until checkRequirements or assertRequirements is called. Inputs: varargin - Alternating facade names and version ranges. Facade names are text scalars containing letters, digits, or underscores. A range is a nonempty text scalar containing whitespace-separated constraints such as \">=2.0 <3\". Supported operators are >, >=, <, <=, =, and ==. Outputs: req - Scalar structure with type=\"labkit.requirements\" and a facades structure array. Each facades element has normalized facade and range fields. Calling requirements() with no pairs returns an empty list. Errors: Throws labkit:contract:InvalidRequirements for an odd number of arguments, labkit:contract:InvalidFacadeName for an invalid name, labkit:contract:InvalidVersionRange for an empty or nontext range, and labkit:contract:DuplicateRequirement when a facade appears more than once. Example: req = labkit.contract.requirements( ... \"app\", \">=1 <2\", ... \"image\", \">=4.0 <5\"); report = labkit.contract.checkRequirements(req); See also labkit.contract.checkRequirements, labkit.contract.assertRequirements, labkit.contract.versionInfo"},{"title":"labkit.contract.versionInfo","url":"reference/api/labkit/contract/versionInfo.html","kind":"reference","section":"reference","keywords":"labkit.contract.versionInfo","text":"VERSIONINFO Describe one LabKit API version and its compatibility range. VERSIONINFO Describe one LabKit API version and its compatibility range. Usage: info = labkit.contract.versionInfo(facade, current, compatible, status, notes) Description: Creates the standard structure returned by module version functions. The function normalizes text and validates the status value, but it does not parse current or compatible as semantic versions. checkRequirements parses those values when it performs a comparison. Inputs: facade - Character vector or string scalar, such as \"ui\", \"dta\", or \"labkit.image\". The optional \"labkit.\" prefix is removed. current - Character vector or string scalar containing the current API version, normally a semantic version such as \"2.0.0\". compatible - Character vector, string scalar, or string array containing one or more supported requirement ranges, such as \">=2.0 <3\". status - \"stable\", \"deprecated\", or \"experimental\". Matching is case-insensitive. notes - Character vector or string scalar summarizing the module contract. Outputs: info - Scalar structure with name, facade, current, compatible, status, and notes. name includes the \"labkit.\" prefix; compatible is a string column vector; other text values are string scalars. Errors: Throws labkit:contract:InvalidVersionInfo for nontext scalar inputs, an empty facade, no nonempty compatible ranges, or an unsupported status. Example: info = labkit.contract.versionInfo( ... \"image\", \"4.1.0\", \">=4 <5\", \"stable\", ... \"Image file IO and processing functions.\"); See also labkit.contract.requirements, labkit.contract.checkRequirements"},{"title":"labkit.dta.detectPulses","url":"reference/api/labkit/dta/detectPulses.html","kind":"reference","section":"reference","keywords":"labkit.dta.detectPulses","text":"DETECTPULSES Locate cathodic and anodic pulse windows in chrono data. DETECTPULSES Locate cathodic and anodic pulse windows in chrono data. Usage: [pulse, message] = labkit.dta.detectPulses(t, Im, meta) [pulse, message] = labkit.dta.detectPulses(t, Im, meta, mode) Description: Finds a negative pulse followed by a positive pulse. Metadata modes use ISTEP/TSTEP values when available, or VSTEP/TSTEP values when current-step metadata is absent. Current-based detection identifies dominant negative and positive segments directly from Im. Inputs: t - Numeric time vector in seconds. Values are used in their supplied order and must correspond element-for-element with Im. Im - Numeric current vector in amperes with the same number of elements as t. meta - Chrono metadata structure, usually item.meta from loadFile. A steps field may contain I, V, and T values parsed from ISTEP, VSTEP, and TSTEP metadata. Use struct() when metadata is unavailable. mode - Character vector, string scalar, or structure with a mode field. See Mode Values. Default: \"Metadata first, then auto\". Mode Values: metadata_first - Accepts \"metadata_first\" or \"Metadata first, then auto\". Uses metadata first and falls back to Im when metadata detection fails. This is the default. metadata_only - Accepts \"metadata_only\" or \"Metadata only\". Does not use measured-current fallback. current_only - Accepts \"current_only\" or \"Auto from Im only\". Ignores metadata and detects segments from Im. The threshold is 25 percent of max(abs(Im)), with a 1e-12 A numerical floor. Outputs: pulse - Scalar structure describing the detected windows. pulse.ok is false and numeric window fields are NaN when detection fails. message - Character vector explaining which method succeeded or why detection failed. The same text is stored in pulse.message. Output Fields: ok - Logical success flag. method - \"metadata-current\", \"metadata-voltage\", \"auto-from-Im\", or \"-\". pre - Structure with start_s and end_s. cath - Structure with start_s, end_s, and current_A. gap - Structure with start_s, end_s, and center_s. anod - Structure with start_s, end_s, and current_A. post - Structure with start_s and end_s. Failure Behavior: Missing usable metadata, absent opposite-polarity current phases, or unsupported mode text returns pulse.ok=false with NaN window fields and an explanatory message. t and Im must be corresponding numeric vectors; incompatible MATLAB values or lengths may raise the originating indexing or conversion error. Example: t = (0:0.001:0.020).'; Im = zeros(size(t)); Im(4:7) = -1e-3; Im(13:16) = 1e-3; [pulse, message] = labkit.dta.detectPulses( ... t, Im, struct(), \"Auto from Im only\"); See also labkit.dta.loadFile, labkit.dta.getMainCurve"},{"title":"labkit.dta.detectType","url":"reference/api/labkit/dta/detectType.html","kind":"reference","section":"reference","keywords":"labkit.dta.detectType","text":"DETECTTYPE Identify the supported Gamry DTA data family in a file. DETECTTYPE Identify the supported Gamry DTA data family in a file. Usage: [kind, status] = labkit.dta.detectType(filepath) Description: Reads enough of one DTA file to determine whether it contains EIS, cyclic-voltammetry/charge-time, or chrono data. Detection verifies usable curve columns rather than relying on the filename. A missing or unsupported file is reported in status instead of throwing a read error. Inputs: filepath - Character vector or string scalar naming one DTA file. Outputs: kind - String scalar: \"chrono\", \"eis\", \"cvct\", or \"unknown\". status - Scalar structure describing detection. ok is true only for a recognized file; message is empty on success and explains failure otherwise; kind repeats the detected value; and filepath contains the normalized character-vector path. Errors: Throws labkit:dta:InvalidFilepath when filepath is not a character vector or string scalar. Example: [kind, status] = labkit.dta.detectType(\"experiment.DTA\"); if status.ok fprintf(\"Detected %s data.\\n\", kind) else warning(\"%s\", status.message) end See also labkit.dta.loadFile, labkit.dta.findFiles"},{"title":"labkit.dta.findFiles","url":"reference/api/labkit/dta/findFiles.html","kind":"reference","section":"reference","keywords":"labkit.dta.findFiles","text":"FINDFILES Find DTA files in a folder and its subfolders. FINDFILES Find DTA files in a folder and its subfolders. Usage: filepaths = labkit.dta.findFiles(rootDir) Description: Recursively walks rootDir and collects files whose extension is .dta, using a case-insensitive comparison. The function does not inspect file contents; use detectType or loadFile to validate each result. Inputs: rootDir - Character vector or string scalar naming an existing folder. Outputs: filepaths - Cell row vector of full file paths in directory traversal order. The value is an empty cell array when no DTA files are found. Errors: Throws labkit:dta:InvalidFolder when rootDir is not a text scalar or does not name an existing folder. Typical Call: files = labkit.dta.findFiles(\"data\"); [items, report] = labkit.dta.loadFiles(files, \"auto\"); See also labkit.dta.detectType, labkit.dta.loadFiles, labkit.dta.loadFolder"},{"title":"labkit.dta.getColumn","url":"reference/api/labkit/dta/getColumn.html","kind":"reference","section":"reference","keywords":"labkit.dta.getColumn","text":"GETCOLUMN Extract a named column from a parsed DTA table. GETCOLUMN Extract a named column from a parsed DTA table. Usage: col = labkit.dta.getColumn(tbl, name) Description: Finds the first header that matches name without regard to letter case and returns the corresponding numeric data column. The function does not convert units or remove NaN values. Inputs: tbl - Scalar parsed table or curve structure with headers and data fields. data has one column for each element of headers. name - Character vector or string scalar naming the requested header. Outputs: col - Numeric column vector from tbl.data, or [] when no header matches. Failure Behavior: A missing header returns []. tbl must expose corresponding headers and data fields; malformed structures or an out-of-range data layout raise the originating MATLAB field or indexing error. Typical Call: timeSec = labkit.dta.getColumn(item.curve, \"T\"); currentA = labkit.dta.getColumn(item.curve, \"Im\"); See also labkit.dta.getCurveXY, labkit.dta.getMainCurve, labkit.dta.getZCurve"},{"title":"labkit.dta.getCurveXY","url":"reference/api/labkit/dta/getCurveXY.html","kind":"reference","section":"reference","keywords":"labkit.dta.getCurveXY","text":"GETCURVEXY Extract paired X and Y data from a parsed CV/CT curve. GETCURVEXY Extract paired X and Y data from a parsed CV/CT curve. Usage: [x, y, xname, yname] = labkit.dta.getCurveXY(curve, xsel, ysel) Description: Looks up two headers using case-sensitive, exact-name matching. Rows where either selected column is NaN are removed from both outputs so x and y remain paired. Use getColumn when case-insensitive lookup or unfiltered data is required. Inputs: curve - Scalar parsed curve structure with headers and data fields. xsel - Character vector or string scalar matching one header exactly. ysel - Character vector or string scalar matching one header exactly. Outputs: x - Numeric column vector for xsel, with paired NaN rows removed. y - Numeric column vector for ysel, with paired NaN rows removed. xname - Matched header character vector, or '' when lookup fails. yname - Matched header character vector, or '' when lookup fails. Failure Behavior: If either exact header is absent, all numeric outputs remain empty and unmatched names are ''. curve must expose corresponding headers and data fields; malformed structures raise the originating MATLAB error. Typical Call: [voltageV, currentA, xname, yname] = ... labkit.dta.getCurveXY(curve, \"Vf\", \"Im\"); See also labkit.dta.getColumn, labkit.dta.loadFile"},{"title":"labkit.dta.getMainCurve","url":"reference/api/labkit/dta/getMainCurve.html","kind":"reference","section":"reference","keywords":"labkit.dta.getMainCurve","text":"GETMAINCURVE Select the transient table containing T, Vf, and Im data. GETMAINCURVE Select the transient table containing T, Vf, and Im data. Usage: [curve, ok, msg] = labkit.dta.getMainCurve(tables) Description: First selects a table named CURVE or CURVE1. If neither name is present, selects the first table whose headers include T, Vf, and Im, using case-insensitive comparisons. No table is selected when the input is empty or no compatible columns are found. Inputs: tables - Structure array of parsed DTA tables. Each element is expected to contain name, headers, and data fields. Outputs: curve - Selected table structure, or an empty structure on failure. ok - Logical scalar indicating whether a table was selected. msg - Character vector naming the selected table or stating that the main transient table was not found. Failure Behavior: Empty input or no table with the required headers returns curve=struct(), ok=false, and a diagnostic message. Nonempty elements must expose name and headers fields; malformed table structures raise the originating MATLAB field-access error. Typical Call: [curve, ok, msg] = labkit.dta.getMainCurve(item.tables); if ok timeSec = labkit.dta.getColumn(curve, \"T\"); end See also labkit.dta.getColumn, labkit.dta.detectPulses, labkit.dta.loadFile"},{"title":"labkit.dta.getZCurve","url":"reference/api/labkit/dta/getZCurve.html","kind":"reference","section":"reference","keywords":"labkit.dta.getZCurve","text":"GETZCURVE Select the impedance table from parsed EIS data. GETZCURVE Select the impedance table from parsed EIS data. Usage: [curve, ok, msg] = labkit.dta.getZCurve(tables) Description: First selects a table named ZCURVE. If that name is absent, selects the first table whose headers include Freq, Zreal, and Zimag, using case-insensitive comparisons. Inputs: tables - Structure array of parsed DTA tables. Each element is expected to contain name, headers, and data fields. Outputs: curve - Selected impedance table structure, or an empty structure on failure. ok - Logical scalar indicating whether a table was selected. msg - Character vector naming the selected table or stating that a ZCURVE table was not found. Failure Behavior: Empty input or no impedance table returns curve=struct(), ok=false, and a diagnostic message. Nonempty elements must expose name and headers fields; malformed table structures raise the originating MATLAB field-access error. Typical Call: [curve, ok, msg] = labkit.dta.getZCurve(item.tables); if ok frequencyHz = labkit.dta.getColumn(curve, \"Freq\"); end See also labkit.dta.getColumn, labkit.dta.loadFile"},{"title":"labkit.dta.loadFile","url":"reference/api/labkit/dta/loadFile.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFile","text":"LOADFILE Read one supported Gamry DTA file. LOADFILE Read one supported Gamry DTA file. Usage: [item, status] = labkit.dta.loadFile(filepath) [item, status] = labkit.dta.loadFile(filepath, expectedKind) [item, status] = labkit.dta.loadFile(filepath, expectedKind, opts) Description: Detects and parses one chrono, EIS, or CV/CT DTA file. Set expectedKind to a specific family when a workflow must reject other DTA types. Parse and format failures are returned in status; invalid arguments throw errors. Inputs: filepath - Character vector or string scalar naming one DTA file. expectedKind - Character vector or string scalar. Allowed values are \"auto\", \"chrono\", \"eis\", and \"cvct\". Matching is case-insensitive, surrounding whitespace is ignored, and a blank value means \"auto\". Default: \"auto\". opts - Optional scalar structure. Options apply to chrono loading only. Options: pulseMode - Pulse-detection mode accepted by detectPulses, such as \"Metadata first, then auto\", \"Metadata only\", or \"Auto from Im only\". pulseOptions - Structure with a mode field accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: item - Scalar parsed item structure when status.ok is true, or an empty structure array when loading fails. See Output Fields. status - Scalar structure with ok, message, kind, expectedKind, and filepath fields. A kind mismatch reports both the expected and detected family in message. Output Fields: type - \"chrono\", \"eis\", or \"cvct\". filepath - Source path as a character vector. name - Source filename including extension. meta - Parsed metadata for chrono and EIS items. tables - Parsed table structures for chrono and EIS items. t_s - Chrono time in seconds. Vf_V - Chrono measured voltage in volts. Im_A - Chrono measured current in amperes. pulse - Chrono pulse structure returned by detectPulses. freq_Hz - EIS frequency in hertz. Zreal_ohm - EIS real impedance in ohms. Zimag_ohm - EIS imaginary impedance in ohms. Zmod_ohm - EIS impedance magnitude in ohms. Zphz_deg - EIS phase angle in degrees. scanRate_V_per_s - CV/CT scan rate in volts per second. curves - Parsed CV/CT curve structures in file order. analysis - Empty structure reserved for caller-owned results. Errors: Throws labkit:dta:InvalidFilepath for a nonscalar path and labkit:dta:InvalidKind for an unsupported expectedKind. Missing files, unsupported content, parse failures, and type mismatches are returned as status.ok=false rather than thrown. Example: [item, status] = labkit.dta.loadFile(\"measurement.DTA\", \"eis\"); if status.ok semilogx(item.freq_Hz, item.Zmod_ohm) else warning(\"%s\", status.message) end See also labkit.dta.detectType, labkit.dta.loadFiles, labkit.dta.detectPulses"},{"title":"labkit.dta.loadFiles","url":"reference/api/labkit/dta/loadFiles.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFiles","text":"LOADFILES Read a list of Gamry DTA files. LOADFILES Read a list of Gamry DTA files. Usage: [items, report] = labkit.dta.loadFiles(filepaths) [items, report] = labkit.dta.loadFiles(filepaths, expectedKind) [items, report] = labkit.dta.loadFiles(filepaths, expectedKind, opts) Description: Calls loadFile for each path in input order. Successful items are returned in items; failures do not stop the batch and are recorded in report. The positions in items therefore need not match the positions in filepaths; use report.statuses when per-input correspondence matters. Inputs: filepaths - One character-vector or string-scalar path, a string array, or a cell array of character vectors and string scalars. An empty input is allowed and returns an empty report. expectedKind - \"auto\", \"chrono\", \"eis\", or \"cvct\", with the same normalization as loadFile. Default: \"auto\". opts - Optional scalar structure forwarded unchanged to loadFile. Options: pulseMode - Pulse-detection mode used for chrono files. See detectPulses for the legal labels and normalization behavior. pulseOptions - Scalar structure accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: items - Cell row vector containing successfully loaded item structures in input order, with failed inputs omitted. report - Scalar structure summarizing every requested path. Output Fields: loaded - Cell array of paths that loaded successfully. failed - Structure array with filepath, kind, and message for failures. statuses - One loadFile status structure per requested input, in input order. nRequested - Number of input paths. nLoaded - Number of successful items. nFailed - Number of failed inputs. Errors: Throws labkit:dta:InvalidFilepaths or labkit:dta:InvalidFilepath when the path collection contains unsupported values, and labkit:dta:InvalidKind for an unsupported expectedKind. Individual file read failures are stored in report instead of thrown. Example: files = [\"run-01.DTA\", \"run-02.DTA\"]; [items, report] = labkit.dta.loadFiles(files, \"chrono\"); fprintf(\"Loaded %d of %d files.\\n\", report.nLoaded, report.nRequested) See also labkit.dta.loadFile, labkit.dta.findFiles, labkit.dta.loadFolder"},{"title":"labkit.dta.loadFolder","url":"reference/api/labkit/dta/loadFolder.html","kind":"reference","section":"reference","keywords":"labkit.dta.loadFolder","text":"LOADFOLDER Read every DTA file below a folder. LOADFOLDER Read every DTA file below a folder. Usage: [items, report] = labkit.dta.loadFolder(rootDir) [items, report] = labkit.dta.loadFolder(rootDir, expectedKind) [items, report] = labkit.dta.loadFolder(rootDir, expectedKind, opts) Description: Recursively discovers .dta files with findFiles and passes the resulting list to loadFiles. Unsupported or unreadable files are recorded in report while readable items are retained. Inputs: rootDir - Character vector or string scalar naming an existing folder. expectedKind - \"auto\", \"chrono\", \"eis\", or \"cvct\", with the same normalization as loadFile. Default: \"auto\". opts - Optional scalar structure forwarded to loadFiles and loadFile. Options: pulseMode - Pulse-detection mode used for discovered chrono files. See detectPulses for the legal labels and normalization behavior. pulseOptions - Scalar structure accepted by detectPulses. When both pulseOptions and pulseMode are present, pulseOptions takes precedence. Outputs: items - Cell row vector of successfully loaded item structures. report - loadFiles report with three additional discovery fields. Output Fields: folder - Requested root folder as a character vector. filepaths - Cell array of discovered DTA paths in traversal order. nDiscovered - Number of discovered paths. This equals nRequested. loaded - Cell array of paths that loaded successfully. failed - Structure array describing failed inputs. statuses - One loadFile status per discovered input. nRequested - Number of discovered paths sent to loadFiles. nLoaded - Number of successful items. nFailed - Number of failed inputs. Errors: Throws labkit:dta:InvalidFolder for an invalid rootDir and labkit:dta:InvalidKind for an unsupported expectedKind. Typical Call: [items, report] = labkit.dta.loadFolder(\"experiment\", \"auto\"); fprintf(\"Found %d DTA files; loaded %d.\\n\", ... report.nDiscovered, report.nLoaded) See also labkit.dta.findFiles, labkit.dta.loadFiles, labkit.dta.loadFile"},{"title":"labkit.dta.version","url":"reference/api/labkit/dta/version.html","kind":"reference","section":"reference","keywords":"labkit.dta.version","text":"VERSION Return version information for the DTA API. VERSION Return version information for the DTA API. Usage: info = labkit.dta.version() Description: Reports the API version and compatibility range used when an app declares a dependency on labkit.dta. This is not the Gamry software version or a version identifier read from a DTA file. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.dta\"; facade is \"dta\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.dta.version(); fprintf(\"DTA API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.image.adjustBrightnessContrast","url":"reference/api/labkit/image/adjustBrightnessContrast.html","kind":"reference","section":"reference","keywords":"labkit.image.adjustBrightnessContrast","text":"ADJUSTBRIGHTNESSCONTRAST Apply simple brightness and contrast adjustment. ADJUSTBRIGHTNESSCONTRAST Apply simple brightness and contrast adjustment. Usage: imageOut = labkit.image.adjustBrightnessContrast(imageIn, brightnessPct, contrastPct) Description: Converts imageIn to clamped RGB double, then applies a linear adjustment around mid-gray. For normalized input x, the calculation is (x - 0.5)*(1 + contrastPct/100) + 0.5 + brightnessPct/100. A contrast scale below zero is limited to zero, and final values are clamped to [0,1]. Positive brightness shifts every channel upward. Positive contrast moves values away from 0.5; negative contrast moves them toward 0.5. A contrastPct of -100 produces a flat mid-gray image before the brightness offset is applied. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. brightnessPct - Numeric scalar offset as percent of normalized full scale. contrastPct - Numeric scalar percent change in contrast around 0.5. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. brightnessPct and contrastPct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = reshape(linspace(0, 1, 12), 3, 4); imageOut = labkit.image.adjustBrightnessContrast(imageIn, 10, 20); See also labkit.image.adjustHueSaturation, labkit.image.grayWorldWhiteBalance, labkit.image.im2double"},{"title":"labkit.image.adjustHueSaturation","url":"reference/api/labkit/image/adjustHueSaturation.html","kind":"reference","section":"reference","keywords":"labkit.image.adjustHueSaturation","text":"ADJUSTHUESATURATION Apply HSV hue and saturation adjustment. ADJUSTHUESATURATION Apply HSV hue and saturation adjustment. Usage: imageOut = labkit.image.adjustHueSaturation(imageIn, hueDeg, saturationPct) Description: Converts imageIn to clamped RGB double and then to HSV. hueDeg rotates the hue circle and wraps at 360 degrees. Saturation is multiplied by 1 + saturationPct/100 and limited to [0,1]. The HSV value channel is not changed directly. A saturationPct of -100 or less produces grayscale. Positive saturation increases colorfulness until individual saturation values reach 1. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. hueDeg - Numeric scalar hue rotation in degrees. Values outside one turn are wrapped. saturationPct - Numeric scalar percent change in HSV saturation. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. hueDeg and saturationPct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = cat(3, ones(2), 0.4*ones(2), zeros(2)); imageOut = labkit.image.adjustHueSaturation(imageIn, 30, 25); See also labkit.image.adjustBrightnessContrast, labkit.image.grayWorldWhiteBalance"},{"title":"labkit.image.assertSupportedPaths","url":"reference/api/labkit/image/assertSupportedPaths.html","kind":"reference","section":"reference","keywords":"labkit.image.assertSupportedPaths","text":"ASSERTSUPPORTEDPATHS Throw when any path has an unsupported image extension. ASSERTSUPPORTEDPATHS Throw when any path has an unsupported image extension. Usage: labkit.image.assertSupportedPaths(paths) Description: Validates a collection of source-image paths by filename extension. The function accepts the same path forms as labkit.image.normalizePaths and stops at the first unsupported extension. It does not test whether files exist or contain valid image data. Empty input succeeds. Inputs: paths - Character vector, string array, cell array, or empty value accepted by labkit.image.normalizePaths. Outputs: None. Errors: labkit:image:UnsupportedImageFile - A normalized path has an extension outside supportedExtensions. labkit:image:InvalidPaths - paths has an unsupported container type. Example: labkit.image.assertSupportedPaths([\"frame01.png\"; \"frame02.TIF\"]); See also labkit.image.normalizePaths, labkit.image.isSupportedPath, labkit.image.supportedExtensions"},{"title":"labkit.image.displayName","url":"reference/api/labkit/image/displayName.html","kind":"reference","section":"reference","keywords":"labkit.image.displayName","text":"DISPLAYNAME Return a short image-file display name. DISPLAYNAME Return a short image-file display name. Usage: name = labkit.image.displayName(pathValue) Description: Removes the folder portion of one path and returns the final filename with its extension. The function performs text processing only; the path does not need to exist. If fileparts cannot derive a filename, the trimmed input text is returned. Inputs: pathValue - Character vector, string scalar, or another value convertible to one string scalar. Outputs: name - String scalar containing the filename and extension. Errors: labkit:image:InvalidPath - pathValue converts to more than one string. Example: name = labkit.image.displayName(fullfile(\"images\", \"frame01.tif\")); See also labkit.image.normalizePaths, labkit.image.readFiles"},{"title":"labkit.image.ensureRgb","url":"reference/api/labkit/image/ensureRgb.html","kind":"reference","section":"reference","keywords":"labkit.image.ensureRgb","text":"ENSURERGB Return image data with exactly three color channels. ENSURERGB Return image data with exactly three color channels. Usage: rgb = labkit.image.ensureRgb(imageData) Description: Converts grayscale image arrays to RGB by copying the same values into the red, green, and blue channels. RGB input passes through unchanged, and input with more than three channels keeps only the first three. This is useful for removing an alpha or auxiliary channel after image import. The function does not change numeric values, normalize intensity, or convert the input class. Empty input is returned with its original empty shape. Inputs: imageData - Nonsparse numeric or logical M-by-N grayscale, M-by-N-by-1 grayscale, M-by-N-by-3 RGB, or M-by-N-by-C array with C greater than three. Outputs: rgb - Array with the same class and first two dimensions as imageData. Nonempty output has exactly three channels. Errors: labkit:image:ensureRgb:InvalidChannelCount - Nonempty 3-D input has two color channels. Example: gray = uint8([0 64; 128 255]); rgb = labkit.image.ensureRgb(gray); See also labkit.image.im2double, labkit.image.rgb2gray"},{"title":"labkit.image.fileDialogFilter","url":"reference/api/labkit/image/fileDialogFilter.html","kind":"reference","section":"reference","keywords":"labkit.image.fileDialogFilter","text":"FILEDIALOGFILTER Return a file-chooser-compatible image filter. FILEDIALOGFILTER Return a file-chooser-compatible image filter. Usage: filterSpec = labkit.image.fileDialogFilter() filterSpec = labkit.image.fileDialogFilter(\"IncludeAll\", true) Description: Builds the two-column filter specification used by MATLAB file-selection dialogs and LabKit file panels. The first row contains every extension returned by labkit.image.supportedExtensions. By default, files outside that set are hidden from the chooser. Inputs: None. Name-Value Arguments: IncludeAll - Logical scalar. true appends an \"All files (*.*)\" row after the image filter. The default is false. Numeric scalars are converted to logical values. Outputs: filterSpec - One- or two-row cell array. Column 1 contains wildcard patterns and column 2 contains user-visible descriptions. Errors: MATLAB inputParser errors are raised for an unknown name, a missing name-value partner, or an IncludeAll value that is not a logical or numeric scalar. Example: filterSpec = labkit.image.fileDialogFilter(\"IncludeAll\", true); See also labkit.image.supportedExtensions, labkit.image.isSupportedPath"},{"title":"labkit.image.grayWorldWhiteBalance","url":"reference/api/labkit/image/grayWorldWhiteBalance.html","kind":"reference","section":"reference","keywords":"labkit.image.grayWorldWhiteBalance","text":"GRAYWORLDWHITEBALANCE Apply generic gray-world white balance. GRAYWORLDWHITEBALANCE Apply generic gray-world white balance. Usage: imageOut = labkit.image.grayWorldWhiteBalance(imageIn, strengthPct, temperaturePct) Description: Estimates one gain per RGB channel so that the three channel means move toward their common mean. strengthPct blends between the original image and the fully balanced result. This is the gray-world assumption: over a representative scene, the average reflected color is expected to be neutral. temperaturePct adds a small red offset and subtracts the same blue offset after gain correction. Positive values produce a warmer result; negative values produce a cooler result. The temperature offset is 0.08 times the normalized percentage. Final values are clamped to [0,1]. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. strengthPct - Numeric scalar blend percentage. Values below 0 become 0; values above 100 become 100. temperaturePct - Numeric scalar warm/cool adjustment in percent. Use 0 for neutral gray-world balancing. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. strengthPct and temperaturePct must participate in scalar numeric arithmetic; incompatible MATLAB values raise the originating conversion or array-size error. Example: imageIn = cat(3, 0.3*ones(4), 0.5*ones(4), 0.7*ones(4)); imageOut = labkit.image.grayWorldWhiteBalance(imageIn, 100, 0); See also labkit.image.adjustBrightnessContrast, labkit.image.adjustHueSaturation"},{"title":"labkit.image.im2double","url":"reference/api/labkit/image/im2double.html","kind":"reference","section":"reference","keywords":"labkit.image.im2double","text":"IM2DOUBLE Convert image data to double using MATLAB's im2double contract. IM2DOUBLE Convert image data to double using MATLAB's im2double contract. Usage: imageOut = labkit.image.im2double(imageIn) imageOut = labkit.image.im2double(indexedImage, \"indexed\") Description: Converts image data to double without requiring Image Processing Toolbox. Logical values become 0 or 1. uint8 and uint16 values are divided by the maximum value of their class. int16 values are mapped linearly from the complete signed range to [0,1]. single and double values are converted to double without rescaling or clipping. For uint8 or uint16 indexed images, the \"indexed\" form converts the zero-based integer storage to one-based double colormap indices. For example, uint8 values [0 1] become [1 2]. Empty input returns an empty double array. Inputs: imageIn - Logical, uint8, uint16, int16, single, or double image array. imageType - Optional character vector or scalar string \"indexed\". No other image-type value is accepted. Outputs: imageOut - Double array with the same size as imageIn. Errors: labkit:image:im2double:InvalidImageType - imageType is not \"indexed\". labkit:image:im2double:UnsupportedClass - imageIn has an unsupported numeric class. Example: intensity = labkit.image.im2double(uint8([0 128 255])); indices = labkit.image.im2double(uint8([0 1 2]), \"indexed\"); See also labkit.image.ensureRgb, labkit.image.rgb2gray"},{"title":"labkit.image.isSupportedPath","url":"reference/api/labkit/image/isSupportedPath.html","kind":"reference","section":"reference","keywords":"labkit.image.isSupportedPath","text":"ISSUPPORTEDPATH Return true when a path has a supported image extension. ISSUPPORTEDPATH Return true when a path has a supported image extension. Usage: tf = labkit.image.isSupportedPath(pathValue) Description: Compares the final filename extension with labkit.image.supportedExtensions. The comparison is case-insensitive and does not open the file, inspect its contents, or require it to exist. A path with no extension returns false. Inputs: pathValue - One value convertible to text and accepted by fileparts. Outputs: tf - Logical scalar indicating whether the extension is supported. Failure Behavior: Missing or unknown extensions return false without accessing the file system. pathValue must be convertible to scalar text; unsupported MATLAB values or shapes may raise the originating string or fileparts error. Example: tf = labkit.image.isSupportedPath(\"experiment.TIFF\"); See also labkit.image.supportedExtensions, labkit.image.assertSupportedPaths, labkit.image.normalizePaths"},{"title":"labkit.image.localContrast","url":"reference/api/labkit/image/localContrast.html","kind":"reference","section":"reference","keywords":"labkit.image.localContrast","text":"LOCALCONTRAST Enhance local value-channel contrast with a mean-filter mask. LOCALCONTRAST Enhance local value-channel contrast with a mean-filter mask. Usage: imageOut = labkit.image.localContrast(imageIn, amountPct, radiusPx) Description: Converts imageIn to clamped RGB double and operates on the HSV value channel. A square local mean with width 2*radius+1 forms the low-frequency reference. The difference between the value channel and that mean is multiplied by 1.5*amountPct/100 and added back before clipping. Hue and saturation are preserved by the adjustment. At image boundaries, the local mean uses only available pixels. This is a lightweight local contrast operation, not adaptive histogram equalization. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. amountPct - Numeric scalar effect strength in percent. Negative values are treated as 0. radiusPx - Numeric scalar neighborhood radius in pixels. The value is rounded and limited to at least 1. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. amountPct and radiusPx must be numeric scalars; incompatible values raise the originating conversion or indexing error. Example: imageIn = repmat(linspace(0.2, 0.8, 20), 10, 1); imageOut = labkit.image.localContrast(imageIn, 40, 3); See also labkit.image.meanFilter2, labkit.image.sharpen"},{"title":"labkit.image.meanFilter2","url":"reference/api/labkit/image/meanFilter2.html","kind":"reference","section":"reference","keywords":"labkit.image.meanFilter2","text":"MEANFILTER2 Apply normalized 2-D mean filtering with edge correction. MEANFILTER2 Apply normalized 2-D mean filtering with edge correction. Usage: imageOut = labkit.image.meanFilter2(imageIn, windowSize) Description: Replaces each sample with the arithmetic mean of the rectangular neighborhood that overlaps the image. At an edge or corner, the divisor is reduced to the number of available samples instead of assuming zero padding. Each channel of a 3-D image is filtered independently. windowSize is rounded to an integer and limited to at least 1. Both odd and even widths are accepted; conv2 determines the alignment of an even window. Input is converted to double for calculation. NaN values are not omitted and therefore propagate to neighborhoods that include them. Inputs: imageIn - 2-D image matrix or 3-D image with channels in dimension 3. windowSize - Numeric scalar neighborhood width in pixels. Outputs: imageOut - Double array with the same size as imageIn. Failure Behavior: imageIn must be compatible with double and conv2, and windowSize must be convertible to one numeric scalar. Unsupported types, sparse arrays, or nonscalar window values raise the originating MATLAB error. Example: impulse = [0 0 0; 0 1 0; 0 0 0]; smoothed = labkit.image.meanFilter2(impulse, 3); See also labkit.image.localContrast, labkit.image.sharpen"},{"title":"labkit.image.normalizePaths","url":"reference/api/labkit/image/normalizePaths.html","kind":"reference","section":"reference","keywords":"labkit.image.normalizePaths","text":"NORMALIZEPATHS Normalize image file path inputs to a string column. NORMALIZEPATHS Normalize image file path inputs to a string column. Usage: paths = labkit.image.normalizePaths(paths) paths = labkit.image.normalizePaths(paths, \"AllowEmpty\", false) Description: Converts the path forms commonly returned by MATLAB file selection into one string column. Leading and trailing whitespace is removed and blank entries are discarded. Order and duplicate paths are preserved. A single character vector is treated as one path rather than one path per row. This function normalizes text only. It does not expand relative paths, validate extensions, remove duplicates, or test file existence. Inputs: paths - Character vector, string array, cell array, or empty value. Outputs: paths - String column containing the nonblank normalized paths. Name-Value Arguments: AllowEmpty - Logical scalar controlling whether zero paths are accepted. The default is true. false throws labkit:image:NoPaths when normalization produces an empty result. Errors: labkit:image:InvalidPaths - paths is not char, string, cell, or empty. labkit:image:NoPaths - No path remains and AllowEmpty is false. Example: paths = labkit.image.normalizePaths([\" frame01.png \"; \"\"; \"frame02.JPG\"]); See also labkit.image.assertSupportedPaths, labkit.image.isSupportedPath, labkit.image.readFiles"},{"title":"labkit.image.previewBudget","url":"reference/api/labkit/image/previewBudget.html","kind":"reference","section":"reference","keywords":"labkit.image.previewBudget","text":"PREVIEWBUDGET Downsample image data to fit a display pixel budget. PREVIEWBUDGET Downsample image data to fit a display pixel budget. Usage: [preview, info] = labkit.image.previewBudget(imageData) [preview, info] = labkit.image.previewBudget(imageData, Name, Value) Description: When MaxPixels is finite, produces a lightweight preview by taking every Nth row and column. N is the smallest integer stride whose estimated processing area does not exceed MaxPixels. The estimate is source rows times source columns times Expansion, so callers can reserve memory for workflows that pad, tile, or otherwise enlarge an image before display. The default is Inf and preserves native pixels; each caller owns whether a display budget is appropriate for its workflow. This is sampling rather than interpolating resize. The first source pixel is always retained, and image class and channel count are preserved. A preview may use fewer pixels than the budget because the stride is an integer. Inputs: imageData - Nonempty numeric or logical 2-D or 3-D image array. Name-Value Arguments: MaxPixels - Positive display-area budget or Inf. Default: Inf, retaining native pixels. Invalid, empty, or nonpositive values fall back to the default rather than throwing an error. Expansion - Positive multiplier applied only to estimatedPixels. The default is 1. Invalid values fall back to 1. Outputs: preview - imageData sampled with the selected integer stride. info - Scalar structure describing the preview calculation. Info Fields: scaleFactor - Integer row and column stride N. coordinateScale - Reciprocal 1/N for mapping source distances to the sampled preview scale. maxPixels - Validated display-area budget. estimatedPixels - Source row-column area multiplied by Expansion. sourceSize - Original size vector. previewSize - Returned preview size vector. Errors: labkit:image:InvalidImageData - imageData is empty, nonnumeric/nonlogical, or has more than three dimensions. labkit:image:InvalidPreviewBudgetOptions - Name-value arguments are not paired. labkit:image:InvalidPreviewBudgetOption - An option name is unsupported. Example: rgb = zeros(100, 200, 3, 'uint8'); [preview, info] = labkit.image.previewBudget(rgb, ... \"MaxPixels\", 5000, \"Expansion\", 2); See also labkit.image.resizeToFit"},{"title":"labkit.image.readFiles","url":"reference/api/labkit/image/readFiles.html","kind":"reference","section":"reference","keywords":"labkit.image.readFiles","text":"READFILES Read image files into path/name/image records. READFILES Read image files into path/name/image records. Usage: records = labkit.image.readFiles(paths) records = labkit.image.readFiles(paths, opts) Description: Reads one or more image files in the supplied order and returns a uniform record for each file. The default path validates source extensions and file existence, converts data with labkit.image.im2double, expands grayscale to RGB, discards channels after the first three, and clamps values to [0,1]. Set Normalize to false when the exact class and channel layout returned by imread must be preserved. progressFcn is called immediately before and after every imread. If a read, conversion, or callback fails, the error propagates and no partial records array is returned. Inputs: paths - Character vector, string array, cell array, or empty value accepted by labkit.image.normalizePaths. opts - Optional scalar struct containing the fields listed below. Options: Normalize - Logical scalar controlling conversion to clamped RGB double. The default is true. AllowEmpty - Logical scalar controlling whether an empty path collection returns an empty record column. The default is true. ValidateExtensions - Logical scalar controlling validation with assertSupportedPaths. The default is true. RequireExisting - Logical scalar controlling the explicit file-existence check before imread. The default is true. imread can still fail when this option is false. progressFcn - Function handle or []. The callback receives a scalar structure before and after each read. The default is []. Outputs: records - N-by-1 structure array with path, name, and image fields. path and name are strings; image contains the normalized or raw image matrix. Progress Fields: stage - \"beforeRead\" or \"afterRead\". index - 1-based index of the current path. count - Total number of paths. path - Current path as a string scalar. name - Filename and extension from labkit.image.displayName. Errors: labkit:image:InvalidOptions - opts or one of its fields is invalid. labkit:image:NoPaths - paths is empty while AllowEmpty is false. labkit:image:UnsupportedImageFile - An extension is unsupported while ValidateExtensions is true. labkit:image:ImageFileNotFound - A file is missing while RequireExisting is true. Typical Call: opts = struct('Normalize', true, 'progressFcn', @updateProgress); records = labkit.image.readFiles(selectedPaths, opts); See also labkit.image.normalizePaths, labkit.image.assertSupportedPaths, labkit.image.writeFile"},{"title":"labkit.image.resizeToFit","url":"reference/api/labkit/image/resizeToFit.html","kind":"reference","section":"reference","keywords":"labkit.image.resizeToFit","text":"RESIZETOFIT Resize an image to fit within maximum row/column limits. RESIZETOFIT Resize an image to fit within maximum row/column limits. Usage: imageOut = labkit.image.resizeToFit(imageIn) imageOut = labkit.image.resizeToFit(imageIn, Name, Value) [imageOut, scale] = labkit.image.resizeToFit(___) Description: Computes one uniform scale that satisfies both the maximum row and column limits, preserving aspect ratio. With the default AllowUpscale value, images that already fit are returned unchanged. Target dimensions are rounded to the nearest integer and limited to at least one pixel. \"nearest\" samples endpoint-aligned nearest input pixels. \"bilinear\" performs endpoint-aligned linear interpolation independently in each channel. Output keeps the input class; integer linear results are rounded and limited to the class range. Inputs: imageIn - Numeric or logical 2-D image or 3-D multichannel image. Empty input is returned unchanged with scale 1. Name-Value Arguments: MaxHeight - Positive maximum output row count. The default is Inf. MaxWidth - Positive maximum output column count. The default is Inf. AllowUpscale - Logical scalar controlling enlargement when both limits permit it. The default is false. Method - \"bilinear\" (default) or \"nearest\". Outputs: imageOut - Resized image, or imageIn unchanged when scale is 1. scale - Uniform requested scale before target dimensions are rounded. Errors: MATLAB inputParser errors are raised for invalid limits, AllowUpscale, malformed name-value arguments, or unsupported option names. labkit:image:InvalidResizeMethod - Method is not \"bilinear\" or \"nearest\". Unsupported image classes or dimensions may raise the originating MATLAB interpolation or cast error. Example: imageIn = reshape(uint8(1:200), 10, 20); [imageOut, scale] = labkit.image.resizeToFit(imageIn, ... \"MaxHeight\", 5, \"Method\", \"nearest\"); See also labkit.image.previewBudget"},{"title":"labkit.image.rgb2gray","url":"reference/api/labkit/image/rgb2gray.html","kind":"reference","section":"reference","keywords":"labkit.image.rgb2gray","text":"RGB2GRAY Convert RGB data using MATLAB's rgb2gray call contract. RGB2GRAY Convert RGB data using MATLAB's rgb2gray call contract. Usage: gray = labkit.image.rgb2gray(rgb) Description: Converts an RGB image with the ITU-R BT.601 luma coefficients 0.2989, 0.5870, and 0.1140 for red, green, and blue. The output retains the input numeric class. Integer results therefore use MATLAB's normal cast and rounding behavior. For an M-by-3 colormap, each row is converted to one luma value and that value is repeated across three columns. This preserves the colormap shape while removing color. The function does not clamp floating-point input. Inputs: rgb - Real, nonsparse M-by-N-by-3 RGB image or M-by-3 colormap of class uint8, uint16, int16, single, or double. Outputs: gray - M-by-N grayscale image or M-by-3 grayscale colormap with the same numeric class as rgb. Errors: labkit:image:rgb2gray:InvalidShape - rgb is neither an RGB image nor an M-by-3 colormap. Example: rgb = cat(3, ones(2), zeros(2), zeros(2)); gray = labkit.image.rgb2gray(rgb); See also labkit.image.ensureRgb, labkit.image.im2double"},{"title":"labkit.image.sharpen","url":"reference/api/labkit/image/sharpen.html","kind":"reference","section":"reference","keywords":"labkit.image.sharpen","text":"SHARPEN Apply generic unsharp-mask sharpening. SHARPEN Apply generic unsharp-mask sharpening. Usage: imageOut = labkit.image.sharpen(imageIn, amountPct, radiusPx) Description: Converts imageIn to clamped RGB double and constructs an unsharp mask by subtracting a square local mean from the image. The detail mask is scaled by 2*amountPct/100 and added to the original. Each RGB channel is treated independently, and the result is clamped to [0,1]. radiusPx selects an odd mean-filter width of 2*round(radius)+1, with a minimum width of 3 pixels. This function uses a box blur rather than a Gaussian blur, so radius describes neighborhood size rather than a Gaussian standard deviation. Inputs: imageIn - Numeric or logical grayscale, RGB, or multichannel image. amountPct - Numeric scalar effect strength in percent. Negative values are treated as 0. radiusPx - Numeric scalar blur radius in pixels. Values below 0.5 are treated as 0.5 before rounding. Outputs: imageOut - M-by-N-by-3 double image with values in [0,1]. Failure Behavior: Unsupported image classes or channel shapes propagate errors from im2double or ensureRgb. amountPct and radiusPx must be numeric scalars; incompatible values raise the originating conversion or indexing error. Example: imageIn = zeros(9); imageIn(3:7, 3:7) = 0.6; imageOut = labkit.image.sharpen(imageIn, 50, 1); See also labkit.image.meanFilter2, labkit.image.localContrast"},{"title":"labkit.image.supportedExtensions","url":"reference/api/labkit/image/supportedExtensions.html","kind":"reference","section":"reference","keywords":"labkit.image.supportedExtensions","text":"SUPPORTEDEXTENSIONS Return extensions supported by LabKit image file inputs. SUPPORTEDEXTENSIONS Return extensions supported by LabKit image file inputs. Usage: extensions = labkit.image.supportedExtensions() Description: Lists the filename extensions accepted by the generic LabKit image input functions. Matching in labkit.image.isSupportedPath is case-insensitive. This list describes source-image support; an individual app may offer a smaller set of export formats. Inputs: None. Outputs: extensions - String column of lowercase extensions, including the leading dot. The current values are .png, .jpg, .jpeg, .tif, .tiff, and .bmp. Failure Behavior: The function accepts no caller input and performs no file-system access. It returns the fixed facade extension list without a recoverable failure state. Example: extensions = labkit.image.supportedExtensions(); acceptsTiff = any(extensions == \".tiff\"); See also labkit.image.isSupportedPath, labkit.image.fileDialogFilter"},{"title":"labkit.image.version","url":"reference/api/labkit/image/version.html","kind":"reference","section":"reference","keywords":"labkit.image.version","text":"VERSION Return the LabKit image facade contract version. VERSION Return the LabKit image facade contract version. Usage: info = labkit.image.version() Description: Reports the semantic version and compatibility range of the public labkit.image API. Apps can use this structure to check whether the installed image facade satisfies a saved or declared requirement. Inputs: None. Outputs: info - Scalar structure returned by labkit.contract.versionInfo with name, facade, current, compatible, status, and notes fields. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.image.version(); currentVersion = info.current; See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.image.writeFile","url":"reference/api/labkit/image/writeFile.html","kind":"reference","section":"reference","keywords":"labkit.image.writeFile","text":"WRITEFILE Write one image file, creating the parent folder when needed. WRITEFILE Write one image file, creating the parent folder when needed. Usage: labkit.image.writeFile(imageData, filepath) Description: Writes imageData with MATLAB imwrite. The extension in filepath selects the output format. If the parent folder does not exist, the function creates it, including intermediate folders supported by mkdir. Existing files are replaced according to imwrite behavior. The function does not rescale, clamp, or otherwise normalize imageData. Errors from folder creation and imwrite propagate to the caller. Inputs: imageData - Image matrix accepted by MATLAB imwrite. filepath - Nonempty character vector or scalar string. The filename extension controls the output format. Outputs: None. Errors: labkit:image:InvalidOutputPath - filepath is empty or nonscalar. Other folder and image-format errors are raised by mkdir or imwrite. Typical Call: labkit.image.writeFile(processedImage, fullfile(outputFolder, \"result.png\")); See also labkit.image.readFiles, labkit.image.displayName"},{"title":"labkit.rhs.findFiles","url":"reference/api/labkit/rhs/findFiles.html","kind":"reference","section":"reference","keywords":"labkit.rhs.findFiles","text":"FINDFILES Find Intan RHS files in a folder and its subfolders. FINDFILES Find Intan RHS files in a folder and its subfolders. Usage: filepaths = labkit.rhs.findFiles(rootDir) Description: Recursively collects files whose extension is .rhs and sorts their full paths for reproducible batch order. The function does not inspect file headers; use inspectFile when the contents must be validated. Inputs: rootDir - Character vector or string scalar naming an existing folder. Outputs: filepaths - Cell column vector of sorted full paths. The value is an empty cell array when no RHS files are found. Errors: Throws labkit:rhs:InvalidFolder when rootDir is not a text scalar or does not name an existing folder. Typical Call: files = labkit.rhs.findFiles(\"recordings\"); [info, status] = labkit.rhs.inspectFile(files{1}); See also labkit.rhs.inspectFile, labkit.rhs.indexFile, labkit.rhs.readWindow"},{"title":"labkit.rhs.indexFile","url":"reference/api/labkit/rhs/indexFile.html","kind":"reference","section":"reference","keywords":"labkit.rhs.indexFile","text":"INDEXFILE Build a block index for reading selected RHS time windows. INDEXFILE Build a block index for reading selected RHS time windows. Usage: [index, status] = labkit.rhs.indexFile(filepath) Description: Inspects the file header and records the byte and sample layout used by readWindow. No waveform arrays are loaded. Header-only files produce a valid index with hasData=false. When trailing partial bytes are present, complete blocks remain usable and status.message describes the condition. Inputs: filepath - Character vector or string scalar naming one RHS file. Outputs: index - Scalar structure containing header information and block layout. On inspection failure, hasData is false and numeric layout fields use empty defaults. status - Scalar structure with ok, message, and filepath. The index can be usable when ok is true and message reports trailing partial bytes. Output Fields: info - Full structure returned by inspectFile. sampleRateHz - Sample rate in hertz. samplesPerBlock - Number of samples in each complete block; currently 128. dataOffsetBytes - Byte offset of the first waveform block. bytesPerBlock - Calculated bytes in one complete block. dataBytes - Number of bytes following the header. blockCount - Number of complete waveform blocks. sampleCount - Number of samples in complete blocks. durationSec - Complete-block duration in seconds. exactBlocks - True when no trailing partial bytes remain. hasData - True when at least one complete sample is available. Errors: Throws labkit:rhs:InvalidFilepath for an invalid filepath argument. File and header failures are returned in status. Example: [index, status] = labkit.rhs.indexFile(\"recording.rhs\"); if status.ok && index.hasData fprintf(\"%d samples in %d blocks\\n\", ... index.sampleCount, index.blockCount) end See also labkit.rhs.inspectFile, labkit.rhs.readWindow"},{"title":"labkit.rhs.inspectFile","url":"reference/api/labkit/rhs/inspectFile.html","kind":"reference","section":"reference","keywords":"labkit.rhs.inspectFile","text":"INSPECTFILE Read metadata and data-layout information from an RHS file. INSPECTFILE Read metadata and data-layout information from an RHS file. Usage: [info, status] = labkit.rhs.inspectFile(filepath) Description: Reads the Intan RHS header and calculates the complete-block count, available sample count, and recording duration without loading waveform arrays. A missing, malformed, or unsupported file is returned as status.ok=false after filepath validation. Inputs: filepath - Character vector or string scalar naming one RHS file. Outputs: info - Scalar structure containing the parsed header and calculated file layout. On failure, identity fields remain available and other fields contain empty or NaN values. status - Scalar structure with ok, message, and filepath. message is empty on success and explains the read failure otherwise. Output Fields: fileVersion - Two-element numeric vector [major minor]. sampleRateHz - Amplifier sample rate in hertz. durationSec - Duration represented by complete data blocks, in seconds. channelFamilies - Structures for amplifier, boardAdc, boardDac, boardDigIn, and boardDigOut channels. channelTable - Table containing family and channel-name metadata for all enabled stored channels. frequencyParameters - Requested and actual filter/sample frequencies. stimParameters - Stimulation step size and charge-recovery settings. dcAmplifierSaved - Logical flag indicating whether DC amplifier samples are present. dataOffsetBytes - Byte offset of the first waveform block. bytesPerBlock - Calculated bytes in one complete 128-sample block. blockCount - Number of complete waveform blocks. sampleCount - Number of samples in complete blocks. exactBlocks - True when the data section has no trailing partial bytes. Errors: Throws labkit:rhs:InvalidFilepath when filepath is not a character vector or string scalar. File-open and header-format errors are returned in status instead of thrown. Example: [info, status] = labkit.rhs.inspectFile(\"recording.rhs\"); if status.ok fprintf(\"%.1f s at %.0f Hz\\n\", info.durationSec, info.sampleRateHz) disp(info.channelTable(:, [\"family\", \"nativeName\", \"customName\"])) end See also labkit.rhs.indexFile, labkit.rhs.readWindow, labkit.rhs.findFiles"},{"title":"labkit.rhs.readWindow","url":"reference/api/labkit/rhs/readWindow.html","kind":"reference","section":"reference","keywords":"labkit.rhs.readWindow","text":"READWINDOW Read selected channels and times from an Intan RHS file. READWINDOW Read selected channels and times from an Intan RHS file. Usage: [window, status] = labkit.rhs.readWindow(filepath) [window, status] = labkit.rhs.readWindow(filepath, opts) Description: Reads only the complete data blocks needed for one time interval and one channel family. Returned values are converted to physical units using the Intan RHS scaling constants. File/header/read failures are returned in status; invalid option values throw errors. Inputs: filepath - Character vector or string scalar naming one RHS file. opts - Optional scalar structure. See Options. Options: family - String scalar selecting a channel family. Canonical values are \"amplifier\", \"stim\", \"dcAmplifier\", \"boardAdc\", \"boardDac\", \"boardDigIn\", and \"boardDigOut\". Matching ignores case and punctuation. Short aliases include \"amp\", \"dc\", \"adc\", \"dac\", \"digin\", and \"digout\". Default: \"amplifier\". channels - Channel selection within the chosen family. Use [] for all channels; positive one-based numeric indices for positions in that family; or a character vector, string array, or cell array of native or custom channel names. Names are matched exactly first, then with case and punctuation removed. Default: []. timeRangeSec - Two-element numeric vector [start end] in seconds. A negative start is clamped to zero; Inf as the end reads through the last complete sample. Both calculated endpoint samples are included. Default: [0 Inf]. Outputs: window - Scalar structure containing the selected waveform data. On a read failure it retains source identity and contains empty arrays. status - Scalar structure with ok, message, and filepath. A valid request beyond the recording duration returns ok=true, empty values, and a message stating that no samples were in the requested window. Output Fields: sourcePath - Source RHS path as a character vector. name - Source filename including extension. family - Canonical selected family name. unit - \"microvolts\" for amplifier, \"microamps\" for stim, \"volts\" for DC amplifier and board analog channels, or \"logical\" for digital channels. sampleRateHz - Sample rate in hertz. sampleRange - One-based inclusive [start end] sample indices. channels - String row vector of returned native channel names. timeSec - Sample-by-one time vector in seconds, derived from stored Intan timestamps. values - Samples-by-channels numeric matrix in the unit named by unit. Errors: Throws labkit:rhs:InvalidFilepath for an invalid path, labkit:rhs:InvalidFamily for an unsupported family, labkit:rhs:InvalidChannel for an unknown or out-of-range channel, and labkit:rhs:InvalidTimeRange for a malformed or reversed time range. Missing files, malformed headers, absent channel families, files without complete data blocks, and waveform read failures return status.ok=false. Example: opts = struct(\"family\", \"stim\", ... \"channels\", [\"C-001\", \"C-002\"], ... \"timeRangeSec\", [0.1 0.2]); [window, status] = labkit.rhs.readWindow(\"recording.rhs\", opts); if status.ok plot(window.timeSec, window.values) xlabel(\"Time (s)\") ylabel(\"Stimulation current (microamps)\") legend(window.channels) end See also labkit.rhs.indexFile, labkit.rhs.inspectFile"},{"title":"labkit.rhs.version","url":"reference/api/labkit/rhs/version.html","kind":"reference","section":"reference","keywords":"labkit.rhs.version","text":"VERSION Return version information for the RHS API. VERSION Return version information for the RHS API. Usage: info = labkit.rhs.version() Description: Reports the API version and compatibility range used when an app declares a dependency on labkit.rhs. This is not the file-format version stored in an Intan recording. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.rhs\"; facade is \"rhs\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.rhs.version(); fprintf(\"RHS API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"labkit.thermal.fileDialogFilter","url":"reference/api/labkit/thermal/fileDialogFilter.html","kind":"reference","section":"reference","keywords":"labkit.thermal.fileDialogFilter","text":"FILEDIALOGFILTER Create a file-selection filter for thermal images. FILEDIALOGFILTER Create a file-selection filter for thermal images. Usage: filterSpec = labkit.thermal.fileDialogFilter() filterSpec = labkit.thermal.fileDialogFilter(\"IncludeAll\", true) Description: Returns the pattern and description rows used by MATLAB file-selection dialogs and LabKit file panels. The thermal row includes .jpg, .jpeg, and .rjpg files. A matching extension does not prove that a JPEG contains radiometric data; use labkit.thermal.inspectFile to inspect its contents. Name-Value Arguments: IncludeAll - Logical or numeric scalar. When true, adds an \"All files\" row after the thermal-image row. Default: false. Outputs: filterSpec - One- or two-row cell array. Column 1 contains wildcard patterns and column 2 contains the text shown in the file dialog. Errors: MATLAB inputParser errors are raised for an unknown name, a missing name-value partner, or an IncludeAll value that is not a logical or numeric scalar. Example: filters = labkit.thermal.fileDialogFilter(\"IncludeAll\", true); assert(isequal(size(filters), [2 2])) See also labkit.thermal.supportedExtensions, labkit.thermal.inspectFile"},{"title":"labkit.thermal.inspectFile","url":"reference/api/labkit/thermal/inspectFile.html","kind":"reference","section":"reference","keywords":"labkit.thermal.inspectFile","text":"INSPECTFILE Check whether a file contains supported radiometric data. INSPECTFILE Check whether a file contains supported radiometric data. Usage: info = labkit.thermal.inspectFile(path) info = labkit.thermal.inspectFile(path, opts) Description: Performs the same format check and radiometric read as readFile, but returns the outcome instead of throwing when the extension, file contents, or calibration data are unsupported. Use this function to label files in a chooser or batch review before deciding which ones to read. Inputs: path - Character vector or string scalar naming one candidate file. opts - Optional scalar structure. See Options. Options: RequireExisting - Logical scalar forwarded to readFile. Default: true. TemperatureCorrection - \"environment\" or \"planck-basic\", forwarded to readFile. Default: \"environment\". Outputs: info - Scalar structure with path, name, supportedExtension, isThermal, format, identifier, and message. supportedExtension reports only the filename extension. isThermal is true only after a supported radiometric payload is read successfully. On failure, identifier and message contain the caught reader error. Failure Behavior: Unsupported extensions and all readFile failures return isThermal=false with identifier and message populated; those reader exceptions are not rethrown. path must still be convertible to scalar text, and malformed MATLAB values may raise the originating conversion error. Example: info = labkit.thermal.inspectFile(\"candidate.jpg\"); if info.isThermal fprintf(\"Readable thermal format: %s\\n\", info.format) else warning(\"%s\", info.message) end See also labkit.thermal.readFile, labkit.thermal.isSupportedPath"},{"title":"labkit.thermal.isSupportedPath","url":"reference/api/labkit/thermal/isSupportedPath.html","kind":"reference","section":"reference","keywords":"labkit.thermal.isSupportedPath","text":"ISSUPPORTEDPATH Test whether a path has a supported thermal extension. ISSUPPORTEDPATH Test whether a path has a supported thermal extension. Usage: tf = labkit.thermal.isSupportedPath(path) Description: Compares the filename extension with the extensions returned by labkit.thermal.supportedExtensions. The comparison is case-insensitive. This function does not access the file system and does not check whether a JPEG contains radiometric data. Use labkit.thermal.inspectFile when the file contents must be verified. Inputs: path - Character vector or string scalar containing a filename or path. Outputs: tf - Logical scalar. The value is true for .jpg, .jpeg, and .rjpg names and false for all other extensions. Failure Behavior: Unknown extensions return false without accessing the file system. path must be convertible to scalar text; unsupported MATLAB values or shapes may raise the originating string or fileparts conversion error. Example: labkit.thermal.isSupportedPath(\"capture.RJPG\") % returns true labkit.thermal.isSupportedPath(\"capture.png\") % returns false See also labkit.thermal.supportedExtensions, labkit.thermal.inspectFile"},{"title":"labkit.thermal.rawToTemperatureC","url":"reference/api/labkit/thermal/rawToTemperatureC.html","kind":"reference","section":"reference","keywords":"labkit.thermal.rawToTemperatureC","text":"RAWTOTEMPERATUREC Convert FLIR raw sensor values to degrees Celsius. RAWTOTEMPERATUREC Convert FLIR raw sensor values to degrees Celsius. Usage: temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration) temperatureC = labkit.thermal.rawToTemperatureC(raw, calibration, opts) [temperatureC, diagnostics] = labkit.thermal.rawToTemperatureC(...) Description: Applies the FLIR Planck calibration stored in a radiometric image. The default correction also compensates for emissivity, reflected radiation, atmospheric transmission, and an infrared window. Select \"planck-basic\" when the raw counts have already been corrected or when only the camera's Planck constants should be applied. Inputs: raw - Numeric array of raw sensor counts. The output has the same size. calibration - Scalar structure containing the five required Planck fields and, for environmental correction, the fields described below. opts - Optional scalar structure. See Options. Calibration Fields: PlanckR1 - Required finite scalar from the camera metadata. PlanckB - Required finite scalar from the camera metadata. PlanckF - Required finite scalar from the camera metadata. PlanckO - Required finite scalar from the camera metadata. PlanckR2 - Required finite scalar from the camera metadata. Emissivity - Object emissivity. Must be positive. Default: 1. ObjectDistanceM - Camera-to-object distance in metres. Negative values are treated as zero. Default: 1. ReflectedApparentTemperatureC - Reflected apparent temperature in degrees Celsius. Default: 20. AtmosphericTemperatureC - Atmospheric temperature in degrees Celsius. Default: 20. IRWindowTemperatureC - Infrared-window temperature in degrees Celsius. Defaults to ReflectedApparentTemperatureC. IRWindowTransmission - Infrared-window transmission. Must be positive. Default: 1. RelativeHumidity - Relative humidity as a fraction from 0 to 1 or as a percentage greater than 2. Default: 0.5. AtmosphericTransAlpha1 - First atmospheric alpha coefficient. Default: 0.006569. AtmosphericTransAlpha2 - Second atmospheric alpha coefficient. Default: 0.01262. AtmosphericTransBeta1 - First atmospheric beta coefficient. Default: -0.002276. AtmosphericTransBeta2 - Second atmospheric beta coefficient. Default: -0.00667. AtmosphericTransX - Atmospheric mixing coefficient. Default: 1.9. Options: Correction - String scalar selecting the conversion model. \"environment\" uses the environmental fields and records every fallback value. \"planck-basic\" uses only the five Planck fields. Default: \"environment\". Outputs: temperatureC - Double array in degrees Celsius with the same size as raw. Pixels for which the Planck expression is undefined are NaN. diagnostics - Scalar structure describing the conversion. correction is the selected mode; usedDefaults is true if an environmental fallback was used; defaultedFields lists those fields; parameterSources maps each environmental field to \"calibration\" or \"default\"; and message provides a readable summary. In \"planck-basic\" mode, no environmental fields are consumed and defaultedFields is empty. Errors: Throws labkit:thermal:MissingCalibration when a required Planck field is absent or not finite, and labkit:thermal:InvalidOptions when Correction is not one of the two supported values. Example: calibration = struct(\"PlanckR1\", 21106.77, \"PlanckB\", 1501, ... \"PlanckF\", 1, \"PlanckO\", -7340, \"PlanckR2\", 0.012545258); raw = [15000 15500; 16000 16500]; [temperatureC, diagnostics] = ... labkit.thermal.rawToTemperatureC(raw, calibration, ... struct(\"Correction\", \"planck-basic\")); See also labkit.thermal.readFile, labkit.thermal.renderImage"},{"title":"labkit.thermal.readFile","url":"reference/api/labkit/thermal/readFile.html","kind":"reference","section":"reference","keywords":"labkit.thermal.readFile","text":"READFILE Read radiometric image data and convert it to degrees Celsius. READFILE Read radiometric image data and convert it to degrees Celsius. Usage: record = labkit.thermal.readFile(path) record = labkit.thermal.readFile(path, opts) Description: Reads the embedded FLIR FFF RawThermalImage record from a radiometric JPEG or RJPEG. The ordinary visible JPEG pixels are not used as temperature values. Raw sensor counts are retained alongside the converted matrix and the calibration metadata used for conversion. Inputs: path - Character vector or string scalar naming one .jpg, .jpeg, or .rjpg file. The file must contain FLIR radiometric data. opts - Optional scalar structure. See Options. Options: RequireExisting - Logical scalar. When true, report a missing file before checking its extension. Default: true. TemperatureCorrection - String scalar selecting the temperature model. Allowed values are \"environment\" and \"planck-basic\". \"environment\" accounts for emissivity, object distance, reflected temperature, atmosphere, humidity, and an infrared window when the metadata is available. \"planck-basic\" applies only the camera's Planck constants to the raw counts. Default: \"environment\". Outputs: record - Scalar structure with fields path, name, format, raw, temperatureC, units, metadata, and message. raw is the decoded sensor matrix. temperatureC is a same-size double matrix in degrees Celsius, or NaN when the raw data cannot be converted. metadata includes the parsed FLIR tags and temperatureConversion diagnostics, including any environmental values that used documented defaults. Errors: Throws labkit:thermal:InvalidPath for a nonscalar path, labkit:thermal:FileNotFound for a missing file when RequireExisting is true, labkit:thermal:UnsupportedFile for another extension or a JPEG with no supported radiometric payload, and labkit:thermal:InvalidOptions for an invalid option value. Typical Call: opts = struct(\"TemperatureCorrection\", \"environment\"); record = labkit.thermal.readFile(\"capture.rjpg\", opts); imagesc(record.temperatureC) axis image colorbar See also labkit.thermal.inspectFile, labkit.thermal.readFiles, labkit.thermal.rawToTemperatureC"},{"title":"labkit.thermal.readFiles","url":"reference/api/labkit/thermal/readFiles.html","kind":"reference","section":"reference","keywords":"labkit.thermal.readFiles","text":"READFILES Read several radiometric image files. READFILES Read several radiometric image files. Usage: records = labkit.thermal.readFiles(paths) records = labkit.thermal.readFiles(paths, opts) [records, report] = labkit.thermal.readFiles(paths, opts) Description: Calls labkit.thermal.readFile for each path in input order. By default, the first unreadable file stops the operation. Set SkipInvalid to true when a batch should keep its readable records and return a failure entry for each skipped file. Inputs: paths - Character vector, string array, or cell array of character vectors and strings. Empty strings are removed. Output record order follows the remaining input order. opts - Optional scalar structure. See Options. Options: AllowEmpty - Logical scalar. When true, an empty path list returns empty records and a zero-count report. When false, an empty list throws labkit:thermal:NoPaths. Default: true. RequireExisting - Logical scalar forwarded to readFile. Default: true. SkipInvalid - Logical scalar. When true, an unreadable file is added to report.failures and processing continues. When false, its exception is rethrown immediately. Default: false. TemperatureCorrection - String scalar forwarded to readFile. Allowed values are \"environment\" and \"planck-basic\". Default: \"environment\". progressFcn - Empty or a function handle called once before each read and again after it loads or is skipped. The callback receives a scalar structure with stage, index, count, path, and name. stage is \"beforeRead\", \"afterRead\", or \"skipped\". Default: []. Outputs: records - Column structure array of successfully read thermal records. See labkit.thermal.readFile for the fields in each element. report - Scalar structure. requested is the number of normalized paths; loaded and skipped are counts. failures is a structure array with path, name, identifier, and message for each skipped file. Errors: In addition to readFile errors, throws labkit:thermal:InvalidOptions when opts or a callback is invalid and labkit:thermal:NoPaths when AllowEmpty is false and no usable path remains. Typical Call: opts = struct(\"SkipInvalid\", true, ... \"TemperatureCorrection\", \"environment\"); [records, report] = labkit.thermal.readFiles(files, opts); fprintf(\"Loaded %d of %d files.\\n\", report.loaded, report.requested) See also labkit.thermal.readFile, labkit.thermal.inspectFile"},{"title":"labkit.thermal.renderImage","url":"reference/api/labkit/thermal/renderImage.html","kind":"reference","section":"reference","keywords":"labkit.thermal.renderImage","text":"RENDERIMAGE Map a thermal matrix to an RGB image. RENDERIMAGE Map a thermal matrix to an RGB image. Usage: rgb = labkit.thermal.renderImage(values) rgb = labkit.thermal.renderImage(values, opts) Description: Linearly maps numeric values between the selected limits onto a thermal colour palette. Values below or above the limits are clipped to the first or last palette colour. NaN and Inf values use the first palette colour. This function changes only the display representation; it does not modify the source temperatures or apply a calibration. Inputs: values - Numeric M-by-N matrix containing temperatures or raw sensor values. opts - Optional scalar structure. See Options. Options: Limits - Two finite, increasing numeric values [low high]. When omitted, the finite minimum and maximum of values are used. An all-nonfinite matrix uses [0 1]. A constant matrix uses [value value+1]. Palette - String scalar naming the colour palette. Allowed values are \"turbo\", \"parula\", \"hot\", \"gray\", and \"iron\". Default: \"turbo\". Levels - Finite numeric scalar giving the palette length. Values are rounded to the nearest integer and must be at least 2. Default: 256. Outputs: rgb - M-by-N-by-3 double array with channel values in the interval [0, 1]. Errors: Throws labkit:thermal:InvalidOptions when Limits, Palette, or Levels is invalid. Example: temperatureC = [20 24 28; 32 36 40]; rgb = labkit.thermal.renderImage(temperatureC, ... struct(\"Limits\", [20 40], \"Palette\", \"iron\", \"Levels\", 256)); assert(isequal(size(rgb), [2 3 3])) See also labkit.thermal.rawToTemperatureC, labkit.thermal.readFile"},{"title":"labkit.thermal.supportedExtensions","url":"reference/api/labkit/thermal/supportedExtensions.html","kind":"reference","section":"reference","keywords":"labkit.thermal.supportedExtensions","text":"SUPPORTEDEXTENSIONS List the supported thermal filename extensions. SUPPORTEDEXTENSIONS List the supported thermal filename extensions. Usage: extensions = labkit.thermal.supportedExtensions() Description: Returns the filename extensions recognized by the thermal file-selection and reading functions. JPEG is a container format, so a listed extension does not guarantee that a file contains FLIR radiometric data. Use labkit.thermal.inspectFile to check a particular file. Outputs: extensions - String row vector [\".jpg\", \".jpeg\", \".rjpg\"]. Extensions are lowercase and include the leading period. Failure Behavior: The function accepts no caller input and performs no file-system access. It returns the fixed facade extension list without a recoverable failure state. Example: extensions = labkit.thermal.supportedExtensions(); pattern = \"*\" + strjoin(extensions, \";*\"); See also labkit.thermal.isSupportedPath, labkit.thermal.fileDialogFilter, labkit.thermal.inspectFile"},{"title":"labkit.thermal.version","url":"reference/api/labkit/thermal/version.html","kind":"reference","section":"reference","keywords":"labkit.thermal.version","text":"VERSION Return version information for the thermal API. VERSION Return version information for the thermal API. Usage: info = labkit.thermal.version() Description: Reports the version and compatibility range used when an app declares a dependency on labkit.thermal. This is the API contract version, not the MATLAB release or the version of a particular thermal camera format. Outputs: info - Scalar structure returned by labkit.contract.versionInfo. name is \"labkit.thermal\"; facade is \"thermal\"; current is the current semantic version; compatible lists supported requirement ranges; status describes API maturity; and notes summarizes the module. Failure Behavior: The function accepts no caller input. Invalid embedded facade metadata raises labkit:contract:InvalidVersionInfo; released metadata is validated by the contract test suite. Example: info = labkit.thermal.version(); fprintf(\"Thermal API %s (%s)\\n\", info.current, info.status) See also labkit.contract.versionInfo, labkit.contract.checkRequirements"},{"title":"nerve_response_analysis.analysisRun.analyzeRecording","url":"reference/api/nerve_response_analysis/analysisRun/analyzeRecording.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.analyzeRecording","text":"ANALYZERECORDING Analyze one RHS recording according to a protocol. ANALYZERECORDING Analyze one RHS recording according to a protocol. Usage: analysis = nerve_response_analysis.analysisRun.analyzeRecording(filepath) analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... filepath, protocol) analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... filepath, protocol, opts) Description: Indexes one Intan RHS file, maps protocol roles to amplifier channels, chooses an event source, detects pulse trains, and measures compound action potential features for every configured or inferred differential pair. Waveforms are read lazily over a bounded time range. The function does not open a GUI or write results. File, channel, and pair failures are recorded in analysis.issues whenever the remaining work can continue. This allows a batch analysis to retain successfully detected events or measured pairs instead of discarding the entire recording. Inputs: filepath - Path to one Intan RHS recording. protocol - Optional scalar protocol structure, normally loaded from an RHS Preview protocol JSON. An empty structure still allows stimulation- channel event detection and fallback amplifier detection. Default: struct(). opts - Optional scalar structure containing run and detector options. Protocol Fields: channels.roles - Structure array assigning logical roles to channels. Each entry uses id and may include label, nativeName, and match.anyNativeName. Matching is case-insensitive and ignores punctuation against both native and custom RHS channel names. channels.pairs - Structure array with id, label, positive, and negative role ids. Each pair is analyzed as positive-minus-negative. If pairs are absent, roles ending in \"_positive\" are paired with the corresponding \"_negative\" role when both resolve. A pair's optional mode field is retained in the protocol snapshot but does not change this calculation. Options: recordingId - Text identifier inserted into every event, train, metric, and issue row. Default: \"\". maxDurationSec - Maximum recording duration read from the start of the file. A nonpositive, nonfinite, or omitted value uses the indexed duration; values beyond the file are limited to that duration. Default: Inf. eventDetection - Structure accepted by nerve_response_analysis.analysisRun.detectEventTrains. Default: detector defaults. Event Source Priority: The first usable source is selected in this order: nonzero RHS stimulation current (maximum absolute value across stimulation channels); the amplifier channel assigned to role \"reference\"; channels named by configured response pairs; conventional positive/negative response roles; and finally the first amplifier channel. Amplifier sources are differentiated by detectEventTrains. A fallback source adds a warning to issues. Pair Measurement: Each pair reads its positive and negative amplifier channels and forms a differential response. When a reference role is available, it is supplied to common-mode correction. CAP features use the default windows documented by nerve_response_analysis.analysisRun.measureCapMetrics. Missing pair channels or failed reads add a warning and leave other pairs available. Outputs: analysis - Scalar recording-analysis structure. Analysis Fields: type, version - \"nerveResponseRecordingAnalysis\" and schema version 1. recordingId, filepath - Effective recording identity and source path. protocolSnapshot - Protocol value supplied by the caller. events - Event table from detectEventTrains with recordingId prepended when events exist. trains - Train table from detectEventTrains with recordingId prepended when trains exist. metrics - One row per event and analyzed pair. Columns are recordingId, pairId, pairLabel, followed by all measureCapMetrics columns. issues - Table with recordingId, severity (\"warning\" or \"error\"), and a reader-facing message. An unreadable file produces an error row and no further analysis; missing sources, events, or pairs produce warnings. Failure Behavior: Expected data and IO failures return a partial analysis structure rather than throwing. Invalid MATLAB types or unexpected parser/calculation errors may still throw; analyzeSession converts those exceptions into issue rows. Typical Call: protocol = jsondecode(fileread(\"nerve-response-protocol.json\")); opts = struct(\"recordingId\", \"R001\", \"maxDurationSec\", 30, ... \"eventDetection\", struct(\"stdMultiplier\", 3)); analysis = nerve_response_analysis.analysisRun.analyzeRecording( ... \"recording.rhs\", protocol, opts); disp(analysis.metrics) disp(analysis.issues) See also nerve_response_analysis.analysisRun.analyzeSession, nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.measureCapMetrics, labkit.rhs.indexFile, labkit.rhs.readWindow"},{"title":"nerve_response_analysis.analysisRun.analyzeSession","url":"reference/api/nerve_response_analysis/analysisRun/analyzeSession.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.analyzeSession","text":"ANALYZESESSION Analyze accepted RHS recordings and combine their results. ANALYZESESSION Analyze accepted RHS recordings and combine their results. Usage: analysis = nerve_response_analysis.analysisRun.analyzeSession(session) analysis = nerve_response_analysis.analysisRun.analyzeSession( ... session, protocol) analysis = nerve_response_analysis.analysisRun.analyzeSession( ... session, protocol, opts) Description: Selects accepted rows from an RHS Preview filter record, analyzes each file with analyzeRecording, and vertically concatenates event, train, metric, and issue tables. A failure in one recording becomes an issue row and does not stop later recordings. Inputs: session - Scalar structure whose recordings field is a table or structure array. Required recording columns are recordingId and filePath. label, comment, qcFlag, and keep are optional selection/provenance fields. protocol - Optional channel-role and pair protocol passed unchanged to each recording. See analyzeRecording. Default: struct(). opts - Optional scalar structure. Recording-level fields are forwarded to analyzeRecording after recordingId is replaced by the current row id. Recording Selection: If keep exists, rows with logical keep=true are selected. Otherwise label selects normalized \"good\" rows; accepted synonyms are good, keep, kept, true, 1, yes, and y, while bad/reject synonyms normalize to \"bad\". If label is absent but qcFlag exists, rows equal to \"accepted\" are selected. With no selection column, every row is treated as good. This precedence means keep overrides label and qcFlag when more than one is present. Options: maxRecordings - Positive finite limit applied after recording selection. Zero, nonpositive, nonfinite, or omitted input means no limit. Default: Inf. maxDurationSec - Forwarded to analyzeRecording for every selected file. eventDetection - Forwarded to analyzeRecording for every selected file. Outputs: analysis - Scalar session-analysis structure. Analysis Fields: type, version - \"nerveResponseSessionAnalysis\" and schema version 1. recordingCount - Total rows before acceptance filtering. analyzedCount - Number of selected rows after maxRecordings. This counts attempted recordings, including files that later produce issues. protocolSnapshot - Protocol supplied by the caller. events, trains, metrics - Combined nonempty tables returned by recording analyses. issues - Combined recording issues plus caught exceptions, with recordingId, severity, and message columns. Failure Behavior: A recording-level analysis exception is converted to one issue row and later recordings continue. Missing or unsupported session.recordings input becomes an empty session; a nonempty table without recordingId or filePath, incompatible selection columns, or unappendable result tables may still raise the originating table/field error before or between recordings. Example: recordings = table(strings(0,1), strings(0,1), ... 'VariableNames', {'recordingId', 'filePath'}); session = struct(\"recordings\", recordings); analysis = nerve_response_analysis.analysisRun.analyzeSession(session); assert(analysis.recordingCount == 0 && analysis.analyzedCount == 0) See also nerve_response_analysis.analysisRun.analyzeRecording, nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.measureCapMetrics"},{"title":"nerve_response_analysis.analysisRun.detectEventTrains","url":"reference/api/nerve_response_analysis/analysisRun/detectEventTrains.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.detectEventTrains","text":"DETECTEVENTTRAINS Detect pulse candidates and group them into trains. DETECTEVENTTRAINS Detect pulse candidates and group them into trains. Usage: [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains(timeSec, signal) [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, signal, opts) Description: Detects abrupt signal transitions and groups nearby candidates into pulse trains. Missing signal samples are linearly interpolated before the absolute first difference is calculated. Candidate peaks must exceed both a robust noise threshold and an optional absolute score, and nearby candidates are reduced to the one with the larger score. Reported event times may then be shifted to compensate for the delay between a derivative peak and physical stimulus onset. Inputs: timeSec - Numeric sample-time vector in seconds. signal - Numeric event-source vector with the same number of samples as timeSec. Units are arbitrary but must be consistent with minScore. opts - Optional scalar structure. Options may be supplied directly. A protocol-style structure may instead place source definitions and train policy under opts.eventDetection. Options: eventDetection - Optional scalar protocol structure containing sources and train fields. When omitted, the supplied opts structure itself is interpreted as the event-detection policy. Default: direct opts. sourceId - Text copied to event and train source columns. Default: the first derivative source id in eventDetection.sources, or \"analog_derivative\". stdMultiplier - Numeric scalar multiplier applied to robust score noise. A derivative source's threshold.multiplier takes precedence. Default: 2. minScore - Finite numeric scalar lower bound in signal-difference units. The effective threshold is max(minScore, stdMultiplier*noise). Default: 0. minPeakDistanceSec - Minimum separation between retained candidates. A derivative source's value takes precedence. Default: 0.005 seconds. groupGapSec - A gap larger than this value starts a new train. Supply it in opts.train or opts.eventDetection.train. Default: 0.100 seconds. minDetectedPulses - Minimum pulse count for a valid train. Default: 4. maxTrainDurationSec - Maximum first-to-last event duration for a valid train. Default: 0.060 seconds. isolationWindowSec - Minimum preceding and following inter-train gaps for isIsolated=true. Missing neighboring trains imply an infinite gap. Default: 0.500 seconds. requireIsolation - When true, isolation is also required for train validity. Default: false. stimShiftSec - Offset added to every retained raw event time. Default: -0.0005 seconds, moving derivative peaks 0.5 ms earlier. Detection Details: Robust noise is median(abs(score-median(score)))/0.6745, with ordinary standard deviation used when that estimate is zero or invalid. A local maximum is at least as large as its left neighbor and strictly larger than its right neighbor. Train validity requires pulseCount >= minDetectedPulses, durationSec <= maxTrainDurationSec, and isolation when requested. Outputs: events - One row per retained candidate. trains - One row per temporal group. Event Columns: trainId, pulseIndex - One-based group and within-group indices. timeSec - Shifted event time in seconds. rawTimeSec - Unshifted sample time in seconds. sampleIndex - One-based index into the input vectors. score - Absolute first-difference score. source - Effective sourceId. Train Columns: trainId, startSec, endSec, pulseCount, durationSec - Group identity and shifted-time geometry. isIsolated - True when both neighboring train gaps satisfy isolationWindowSec. isValid - Result of pulse-count, duration, and optional isolation rules. source - Effective sourceId. status - \"valid\" or \"needsReview\". Failure Behavior: Empty input or a signal with no qualifying candidates returns empty tables with stable column names. Errors: nerve_response_analysis:EventSizeMismatch - timeSec and signal lengths differ. Example: sampleRate = 10000; timeSec = (0:1/sampleRate:0.12).'; signal = zeros(size(timeSec)); pulseTimes = 0.03 + (0:4).' * 0.01; signal(round(pulseTimes*sampleRate)+1) = 5; opts = struct(\"sourceId\", \"stim\", \"stimShiftSec\", 0, ... \"train\", struct(\"minDetectedPulses\", 5)); [events, trains] = ... nerve_response_analysis.analysisRun.detectEventTrains( ... timeSec, signal, opts); assert(height(events) == 5 && trains.isValid(1)) See also nerve_response_analysis.analysisRun.measureCapMetrics, nerve_response_analysis.analysisRun.analyzeRecording"},{"title":"nerve_response_analysis.analysisRun.measureCapMetrics","url":"reference/api/nerve_response_analysis/analysisRun/measureCapMetrics.html","kind":"reference","section":"reference","keywords":"nerve_response_analysis.analysisRun.measureCapMetrics","text":"MEASURECAPMETRICS Measure event-locked CAP response features. MEASURECAPMETRICS Measure event-locked CAP response features. Usage: metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, eventTimesSec) metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, eventTimesSec, opts) Description: Measures baseline, noise, positive and negative response peaks, peak-to-peak amplitude, dominant-peak latency, and signal-to-noise ratio for each stimulus time. All time options and outputs are in seconds. Signal-derived values retain the units of signal; SNR is reported in decibels. Inputs: timeSec - Numeric sample-time vector in seconds. signal - Numeric response vector with the same length as timeSec. eventTimesSec - Numeric vector of stimulus times in seconds. One output row is produced for each value, in input order. opts - Optional scalar structure containing the measurement windows below. Options: segments - Optional scalar protocol structure. segments.capSearch may contain blankingAfterPulseSec and searchEndAfterPulseSec; nested values override direct options. Default: struct(). baselineWindowSec - Duration before each event used to estimate baseline. The inclusive baseline interval is [event-baselineWindowSec, event-blankingAfterPulseSec]. Default: 0.050 seconds. blankingAfterPulseSec - Time excluded immediately before and after the stimulus. It may also be supplied as opts.segments.capSearch. blankingAfterPulseSec. The nested value takes precedence. Default: 0.002 seconds. searchEndAfterPulseSec - End of the inclusive response-search interval, measured after the event. It may also be supplied in opts.segments.capSearch and takes the same precedence. Default: 0.008 seconds. Calculations: baselineMean is the finite-value mean in the baseline interval. noiseRms is the root mean square deviation from that mean. If the interval contains no samples, the recording-wide finite median becomes the baseline and noiseRms remains NaN. The response search is [event+blankingAfterPulseSec, event+searchEndAfterPulseSec]. Peaks are measured after baseline subtraction. peakTimeSec belongs to whichever of the positive or negative peaks has larger absolute amplitude, and latencySec = peakTimeSec-stimTimeSec. When noiseRms is positive and finite, snrDb = 20*log10(abs(peakToPeak)/noiseRms). Outputs: metrics - Table with one row per event. Metric Columns: eventIndex - One-based event order. stimTimeSec - Input stimulus time. baselineMean, noiseRms - Pre-stimulus baseline and noise estimate. peakPositive, peakNegative, peakToPeak - Baseline-subtracted extrema and their difference. peakTimeSec, latencySec - Dominant absolute peak time and event-relative latency. snrDb - Peak-to-peak SNR in decibels, or NaN when noise is unavailable or zero. status - \"ok\" when the search interval contains samples; \"noSamples\" otherwise. Rows without samples retain NaN response metrics. Errors: nerve_response_analysis:MetricSizeMismatch - timeSec and signal lengths differ. Example: timeSec = (0:0.0001:0.04).'; signal = zeros(size(timeSec)); signal(abs(timeSec-0.014) < 0.0002) = 2; signal(abs(timeSec-0.016) < 0.0002) = -1; opts = struct(\"baselineWindowSec\", 0.004, ... \"blankingAfterPulseSec\", 0.002, ... \"searchEndAfterPulseSec\", 0.008); metrics = nerve_response_analysis.analysisRun.measureCapMetrics( ... timeSec, signal, 0.010, opts); assert(metrics.status == \"ok\" && metrics.peakToPeak > 2.5) See also nerve_response_analysis.analysisRun.detectEventTrains, nerve_response_analysis.analysisRun.analyzeRecording"},{"title":"response_review_stats.analysisRun.alignSegments","url":"reference/api/response_review_stats/analysisRun/alignSegments.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.alignSegments","text":"ALIGNSEGMENTS Interpolate segment traces to a shared time grid. ALIGNSEGMENTS Interpolate segment traces to a shared time grid. Usage: aligned = response_review_stats.analysisRun.alignSegments(segments) aligned = response_review_stats.analysisRun.alignSegments(segments, opts) Description: Shifts each segment's time axis to a common alignment origin, constructs a shared uniformly spaced grid, and linearly interpolates every trace onto that grid. Duplicate source timestamps retain their first sample. Values outside a segment's aligned source range remain NaN. An optional baseline subtraction is performed after interpolation. Inputs: segments - Structure array with timeSec and values vectors of matching length and a name value for each segment. A segment-specific alignTimeSec may identify the source time that should become zero. opts - Optional scalar structure containing the alignment options below. Options: sampleIntervalSec - Positive output-grid interval in seconds. When omitted, the median of every finite positive source time difference is used. If no source supplies a usable difference, the fallback is 0.0001 seconds. windowSec - Two-element [start end] output interval in aligned seconds. When omitted, the function uses the intersection of all source ranges after alignment-time shifts. alignTimeSec - Global source time shifted to zero for segments without their own alignTimeSec field. Default: 0 seconds. baselineWindowSec - Optional inclusive [start end] interval in aligned seconds. The finite mean in this output-grid interval is subtracted from the entire corresponding segment. Default: no subtraction. Outputs: aligned - Scalar structure with these fields: Aligned Fields: timeSec - G-by-1 uniformly spaced aligned time vector in seconds. values - G-by-N double matrix with one interpolated segment per column. segmentNames - N-by-1 string vector copied from segments.name. status - \"ok\", or \"empty\" when segments is empty. Failure Behavior: Empty segments returns stable empty arrays with status=\"empty\". Nonempty elements must provide compatible timeSec, values, and name fields; invalid option intervals, disjoint derived ranges, or interpolation-incompatible data propagate the originating MATLAB field, colon, or interp1 error. Example: segments = struct( ... \"timeSec\", {[10; 11; 12], [20; 21; 22]}, ... \"values\", {[1; 2; 3], [4; 5; 6]}, ... \"name\", {\"first\", \"second\"}, ... \"alignTimeSec\", {10, 20}); aligned = response_review_stats.analysisRun.alignSegments( ... segments, struct(\"sampleIntervalSec\", 1)); assert(isequal(aligned.timeSec, [0; 1; 2])) assert(isequal(aligned.values, [1 4; 2 5; 3 6])) See also response_review_stats.analysisRun.measureAlignedSegments, response_review_stats.analysisRun.summarizeMetrics"},{"title":"response_review_stats.analysisRun.measureAlignedSegments","url":"reference/api/response_review_stats/analysisRun/measureAlignedSegments.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.measureAlignedSegments","text":"MEASUREALIGNEDSEGMENTS Measure peak-to-peak, noise, and SNR per segment. MEASUREALIGNEDSEGMENTS Measure peak-to-peak, noise, and SNR per segment. Usage: metrics = response_review_stats.analysisRun.measureAlignedSegments(aligned) metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, opts) Description: Baseline-corrects every aligned trace, measures its positive and negative extrema, and estimates baseline noise and peak-to-peak SNR. Each segment is measured independently and produces one output row. Peaks use only finite samples; NaN samples are omitted from baseline and noise means. Inputs: aligned - Structure returned by response_review_stats.analysisRun.alignSegments. timeSec is G-by-1, values is G-by-N, and segmentNames contains N names. opts - Optional scalar structure containing the measurement windows below. Options: baselineWindowSec - Inclusive [start end] interval in aligned seconds used to calculate and subtract the baseline mean. Default: [0.007 0.009]. noiseWindowSec - Two-element numeric [start end] interval used for noise RMS after baseline subtraction. Default: baselineWindowSec. measurementWindowSec - Inclusive interval used for positive and negative peaks. Empty input uses all aligned samples. Default: []. Calculations: PeakToPeak = Peak1Value-Peak2Value, where Peak1 is the finite maximum and Peak2 is the finite minimum in the measurement window. NoiseRMS is the root mean square deviation from the finite noise-window mean. When NoiseRMS is positive and PeakToPeak is finite, SNR_dB is 20*log10(abs(PeakToPeak)/NoiseRMS). A window without usable samples leaves its metrics as NaN. A missing baseline also makes that segment's corrected trace and downstream measurements NaN. Outputs: metrics - N-row table with SegmentName, BaselineStart_s, BaselineEnd_s, PeakToPeak, Peak1Time_s, Peak1Value, Peak2Time_s, Peak2Value, NoiseStart_s, NoiseEnd_s, NoiseRMS, and SNR_dB columns. Failure Behavior: Windows without usable finite samples produce NaN metrics for the affected segment. aligned must provide compatible timeSec, values, and segmentNames fields, and every supplied window must contain two numeric endpoints; malformed values propagate the originating field, indexing, or conversion error. Example: aligned = struct(\"timeSec\", (0:0.001:0.012).', ... \"values\", zeros(13, 1), \"segmentNames\", \"response\", \"status\", \"ok\"); aligned.values(7) = 2; aligned.values(11) = -1; opts = struct(\"baselineWindowSec\", [0 0.002], ... \"noiseWindowSec\", [0 0.002]); metrics = response_review_stats.analysisRun.measureAlignedSegments( ... aligned, opts); assert(metrics.PeakToPeak == 3) See also response_review_stats.analysisRun.alignSegments, response_review_stats.analysisRun.summarizeMetrics"},{"title":"response_review_stats.analysisRun.summarizeMetrics","url":"reference/api/response_review_stats/analysisRun/summarizeMetrics.html","kind":"reference","section":"reference","keywords":"response_review_stats.analysisRun.summarizeMetrics","text":"SUMMARIZEMETRICS Compute grouped metric counts and finite means. SUMMARIZEMETRICS Compute grouped metric counts and finite means. Usage: summary = response_review_stats.analysisRun.summarizeMetrics(metrics) Description: Produces compact descriptive statistics from response metric rows. Groups follow their first occurrence. The function performs no weighting, confidence interval, hypothesis test, or outlier removal. Inputs: metrics - Metric table. pairId selects groups when that variable exists; otherwise SegmentName is used, and otherwise all rows belong to \"all\". Amplitude may be named PeakToPeak or peakToPeak. SNR may be named SNR_dB or snrDb. Outputs: summary - Table with one row per group. Summary Columns: Group - Group name in stable first-occurrence order. Count - Number of input rows in the group, including rows whose metrics are nonfinite. MeanPeakToPeak - Arithmetic mean of finite group amplitudes, or NaN when the amplitude variable is absent or has no finite values. MeanSnrDb - Arithmetic mean of finite group SNR values in decibels, or NaN when the SNR variable is absent or has no finite values. Failure Behavior: Empty or non-table input returns one \"all\" row with Count=0 and NaN means. Example: metrics = table([\"cp\"; \"cp\"; \"ta\"], [3; 5; 2], [20; 22; 10], ... 'VariableNames', {'pairId', 'peakToPeak', 'snrDb'}); summary = response_review_stats.analysisRun.summarizeMetrics(metrics); assert(summary.Group(1) == \"cp\" && summary.MeanPeakToPeak(1) == 4) See also response_review_stats.analysisRun.measureAlignedSegments, response_review_stats.analysisRun.alignSegments"},{"title":"rhs_preview.analysisRun.readPreviewWindow","url":"reference/api/rhs_preview/analysisRun/readPreviewWindow.html","kind":"reference","section":"reference","keywords":"rhs_preview.analysisRun.readPreviewWindow","text":"READPREVIEWWINDOW Read the requested RHS preview window from app state. READPREVIEWWINDOW Read the requested RHS preview window from app state. Usage: [S, ok, logMessage] = rhs_preview.analysisRun.readPreviewWindow( ... S, selectedChannels, actionLabel) [S, ok, logMessage] = rhs_preview.analysisRun.readPreviewWindow( ... S, selectedChannels, actionLabel, preserveRoi) Description: Reads a bounded time window from the RHS file recorded in S and stores the returned waveform structure in S.preview. The requested start time is clamped to the indexed recording duration before labkit.rhs.readWindow is called. This function updates data and status fields only; it does not create graphics, open dialogs, or write files. Inputs: S - Scalar RHS Preview state structure. See State Fields for the values read or changed by this function. selectedChannels - Channel identifiers accepted by the channels option of labkit.rhs.readWindow. Supply at least one identifier from S.family. actionLabel - Text stored in S.lastAction after a successful read. This lets the caller distinguish an initial read from a refresh or a move to another part of the recording. preserveRoi - Optional logical scalar. true keeps the previous ROI after clamping it to the new time vector; false resets S.roiSec to [NaN NaN]. Default: false. Outputs: S - Updated state. On success, preview, roiSec, windowStartSec, statusMessage, and lastAction describe the completed read. ok - Logical scalar. true only when labkit.rhs.readWindow returns a status whose ok field is true. logMessage - Text summary for the app log. It reports the sample count on success, describes a read failure, or is empty when no read was attempted because a file or channel selection is missing. State Fields: rhsFile - Path to the indexed RHS file. Empty text prevents the read. family - RHS channel family passed to labkit.rhs.readWindow, such as \"amplifier\" or \"stim\". windowStartSec - Requested start time in seconds. It is replaced by the valid value returned by clampWindowStartSec. windowDurationSec - Requested duration in seconds. The read interval ends at windowStartSec + max(windowDurationSec, eps). index - File index used by clampWindowStartSec to enforce recording bounds. roiSec - Two-element ROI interval in seconds. It is preserved and clamped only when preserveRoi is true and the read contains time samples. preview - Waveform window returned by labkit.rhs.readWindow. A thrown read error clears this field; a returned failure status leaves the returned window here so its diagnostic content remains available. statusMessage - Reader-facing explanation of success, missing input, or a failed read. lastAction - actionLabel after success, or \"Preview read failed\" after a read error. Missing inputs leave the previous value unchanged. Failure Behavior: A missing file or empty channel selection returns ok=false without calling the reader. Exceptions from labkit.rhs.readWindow are caught, copied into statusMessage and logMessage, and clear preview. A nonthrowing failed read returns its status message and ok=false. State-shape errors, such as a missing required field, are programming errors and may still throw. Typical Call: channels = [\"A-000\", \"A-001\"]; [state, ok, message] = rhs_preview.analysisRun.readPreviewWindow( ... state, channels, \"Read preview\", true); See also labkit.rhs.readWindow, rhs_preview.analysisRun.clampWindowStartSec, rhs_preview.analysisRun.clampRoi"},{"title":"ttest_wizard.testRun.runGroupTTests","url":"reference/api/ttest_wizard/testRun/runGroupTTests.html","kind":"reference","section":"reference","keywords":"ttest_wizard.testRun.runGroupTTests","text":"RUNGROUPTTESTS Compare every group after the first with the first group. RUNGROUPTTESTS Compare every group after the first with the first group. Usage: results = ttest_wizard.testRun.runGroupTTests(groups, options) Description: Runs one explicit t-test for each group after the first, always using the first group as vector A/reference and the later group as vector B. It preserves group order, copies per-comparison labels and observations into canonical result snapshots, and performs no plotting or file IO. Inputs: groups - Struct vector with scalar text label and numeric-vector values fields. The first element is the reference group. Nonfinite values are reported through the affected runTTest result. options - Scalar struct with method, alternative, and alpha fields accepted by runTTest. Per-comparison labels are supplied from groups. Outputs: results - Column struct vector of canonical runTTest results in group order. Element k compares groups(1) minus groups(k+1). Options: method - Test label from testRun.choices or \"welch\", \"pooled\", or \"paired\". No default is applied; the field is required. alternative - Alternative label from testRun.choices or \"two_sided\", \"greater\", or \"less\". Direction is reference minus comparison. No default is applied; the field is required. alpha - Finite numeric scalar strictly between zero and one. No default is applied; the field is required. Units: Every group must use the same measurement unit. Means, standard deviations, differences, standard errors, and confidence bounds retain that unit. Test statistics, degrees of freedom, and p-values are dimensionless. Assumptions: Test choice, independence, and pairing remain the caller's scientific responsibility. A paired method pairs each comparison group separately with the first group by displayed row order. No multiple-comparison correction is applied. Failure Behavior: Fewer than two groups throws ttest_wizard:InsufficientGroups. A malformed group throws ttest_wizard:InvalidGroups. Individual scientific failures are returned through the stable runTTest result statuses. Errors: ttest_wizard:InsufficientGroups - groups has fewer than two elements. ttest_wizard:InvalidGroups - A group label is not scalar text or values is not a numeric vector. ttest_wizard:InvalidTestOptions - options is missing method, alternative, or alpha, or runTTest rejects one of their values. Example: groups = struct( ... \"label\", {\"Reference\", \"Treatment 1\", \"Treatment 2\"}, ... \"values\", {[1 2 3], [2 3 4], [1 2 4]}); options = struct(\"method\", \"welch\", ... \"alternative\", \"two_sided\", \"alpha\", 0.05); results = ttest_wizard.testRun.runGroupTTests(groups, options); assert(numel(results) == 2) See also ttest_wizard.testRun.runTTest"},{"title":"ttest_wizard.testRun.runTTest","url":"reference/api/ttest_wizard/testRun/runTTest.html","kind":"reference","section":"reference","keywords":"ttest_wizard.testRun.runTTest","text":"RUNTTEST Run one independent Welch, pooled, or paired t-test. RUNTTEST Run one independent Welch, pooled, or paired t-test. Usage: result = ttest_wizard.testRun.runTTest(vectorA, vectorB, options) Description: Calculates one App-owned t-test with Base MATLAB beta functions. Difference direction is always A minus B. Scientific validation failures return a result row with ok=false and a stable status instead of fabricating a statistic. Inputs: vectorA - Finite numeric vector with at least two values. vectorB - Finite numeric vector with at least two values. options - Scalar struct with method, alternative, alpha, labelA, and labelB. method accepts a label from testRun.choices or welch, pooled, or paired. alternative accepts a choice label or two_sided, greater, or less. alpha is a finite scalar strictly between zero and one. Outputs: result - Scalar struct created by emptyResult. Successful results include vector snapshots, summaries, A-minus-B estimate and confidence bounds, standard error, t statistic, degrees of freedom, and p-value. Units: A and B must use the same measurement unit. Means, standard deviations, the A-minus-B estimate, standard error, and confidence bounds retain that unit. The t statistic, degrees of freedom, and p-value are dimensionless. Assumptions: Independent tests require observations that are independent within and between A and B. The pooled method additionally assumes equal population variances. The paired method requires displayed A(k) and B(k) to describe the same experimental unit; it analyzes A(k)-B(k). Test choice and independence remain the caller's scientific responsibility. Failure Behavior: Invalid options throw ttest_wizard:InvalidTestOptions. Nonfinite vectors, sample sizes below two, unequal paired lengths, or zero standard error return ok=false with status invalid_input, insufficient_n, unequal_pairs, or zero_standard_error. Example: options = struct( ... \"method\", \"welch\", ... \"alternative\", \"two_sided\", ... \"alpha\", 0.05, ... \"labelA\", \"Condition A\", ... \"labelB\", \"Condition B\"); result = ttest_wizard.testRun.runTTest( ... [1.2 1.4 1.3 1.5], [1.8 1.7 2.0 1.9 1.6], options); assert(result.ok) See also ttest_wizard.testRun.choices, ttest_wizard.testRun.emptyResult"},{"title":"video_marker.coordinateExport.buildTable","url":"reference/api/video_marker/coordinateExport/buildTable.html","kind":"reference","section":"reference","keywords":"video_marker.coordinateExport.buildTable","text":"BUILDTABLE Convert confirmed video annotations to a coordinate table. BUILDTABLE Convert confirmed video annotations to a coordinate table. Usage: T = video_marker.coordinateExport.buildTable( ... annotations, skeleton, videoInfo, calibration, opts) Description: Exports a continuous range of confirmed frames in a rectangular table suitable for CSV files or direct MATLAB analysis. Coordinates can remain in pixels or be converted with the project's physical scale. The origin and vertical-axis direction are applied consistently to every keypoint while the stored annotation arrays remain unchanged. Inputs: annotations - Frame annotation structure. annotations.coords must be an F-by-P-by-2 array in [x y] pixel coordinates, and frameStatus must mark every exported frame as \"confirmed\". skeleton - Skeleton structure with P ordered pointIds. Each point ID is converted with matlab.lang.makeValidName for table variable names. videoInfo - Scalar structure with frameRate in frames per second. time_s is calculated as (frame_index-1)/frameRate. calibration - Scale structure with pixelsPerUnit and unit. It is required only when opts.unitMode is \"calibrated_physical\". opts - Coordinate export structure, normally created by video_marker.coordinateExport.options. Options: startFrame - Finite numeric scalar giving the first exported one-based frame index. It is rounded and limited to at least one. Default from options: 1. endFrame - Numeric scalar or Inf giving the last exported frame index, inclusive. It is rounded and limited to the annotation frame count. Default from options: Inf. unitMode - \"pixels\" retains pixel distances and reports \"px\"; \"calibrated_physical\" divides distances by calibration.pixelsPerUnit and reports calibration.unit. Default: \"pixels\". originMode - \"top_left_pixel_center\" subtracts [1 1], so the center of the upper-left pixel is [0 0]. \"first_point\" subtracts the first skeleton point's coordinates at startFrame from all frames. Default: \"top_left_pixel_center\". yAxisMode - \"down\" preserves image-row direction; \"up\" negates vertical displacement so positive y points upward. Default: \"up\". Outputs: T - Table with one row per exported frame. Fixed variables are frame_index, time_s, coordinate_unit, y_axis, origin_mode, origin_frame_index, origin_point_id, and pixels_per_unit. Every skeleton point adds __x and __y numeric variables in skeleton order. Errors: labkit_VideoMarker_app:InvalidFrameRange - The limited end frame precedes the limited start frame. labkit_VideoMarker_app:UnconfirmedFrameRange - Any selected frame is not confirmed. labkit_VideoMarker_app:MissingScaleCalibration - Physical units are requested without a positive finite pixelsPerUnit value. labkit_VideoMarker_app:InvalidOriginMode - originMode is unsupported. labkit_VideoMarker_app:InvalidYAxisMode - yAxisMode is unsupported. Example: skeleton = video_marker.skeletonDefinition.fromText(\"hip, knee\", \"hip-knee\"); annotations = video_marker.frameAnnotations.emptyAnnotations(2, 2); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 1, [11 21; 31 41], \"confirmed\"); annotations = video_marker.frameAnnotations.setFramePoints( ... annotations, 2, [13 25; 35 45], \"confirmed\"); info = struct(\"frameRate\", 10); opts = video_marker.coordinateExport.options( ... \"startFrame\", 1, \"endFrame\", 2, \"unitMode\", \"pixels\", ... \"originMode\", \"first_point\", \"yAxisMode\", \"up\"); T = video_marker.coordinateExport.buildTable( ... annotations, skeleton, info, struct(), opts); assert(isequal(T.hip__x, [0; 2]) && isequal(T.hip__y, [0; -4])) See also video_marker.coordinateExport.options, video_marker.frameAnnotations.emptyAnnotations, video_marker.skeletonDefinition.fromText"},{"title":"video_marker.motionEstimate.trackPoints","url":"reference/api/video_marker/motionEstimate/trackPoints.html","kind":"reference","section":"reference","keywords":"video_marker.motionEstimate.trackPoints","text":"TRACKPOINTS Track ordered image points between two consecutive video frames. TRACKPOINTS Track ordered image points between two consecutive video frames. Usage: [points, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, previousPoints) [points, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, previousPoints, priorDisplacement) Description: Tracks each point with deterministic base-MATLAB multiscale patch matching. A grayscale pyramid provides up to four resolution levels. At every level, a 7-by-7 mean-centered template is compared by normalized correlation in a plus-or-minus four-pixel search around the predicted location. A parabolic peak fit refines the final match to subpixel coordinates. Points are processed independently and preserve their input order. A flat, out-of-frame, or weakly correlated patch is not treated as a hard error: the output retains the clamped motion-prior prediction and reports zero confidence for that point. Inputs: previousImage - Finite numeric or logical grayscale or RGB source frame. currentImage - Finite frame with exactly the same size as previousImage. previousPoints - Finite N-by-2 array of [x y] coordinates in the previous frame, where x is the column and y is the row. N may be zero. priorDisplacement - Optional finite [dx dy] motion prediction. A 1-by-2 value is shared by all points; an N-by-2 array supplies one prior per point. Default: zeros(N,2). Outputs: points - N-by-2 tracked [x y] coordinates, limited to currentImage bounds. Failed matches contain the limited previousPoints+priorDisplacement. confidence - N-by-1 values in [0,1]. Accepted normalized-correlation scores are mapped with (score+1)/2; failed matches are zero. A correlation of at least 0.55 is required for acceptance. diagnostics - Scalar structure with engine, valid, and failureMessage. engine is \"multiscale_patch\". valid is an N-by-1 logical mask. failureMessage is empty when every point succeeds and explains partial tracking failure otherwise. Errors: labkit_VideoMarker_app:FrameSizeMismatch - Frame arrays differ in size. labkit_VideoMarker_app:InvalidTrackingPoints - previousPoints is not a finite N-by-2 array. labkit_VideoMarker_app:InvalidMotionPrior - priorDisplacement is neither a finite 1-by-2 nor finite N-by-2 array. labkit_VideoMarker_app:InvalidTrackingImage - A frame is not grayscale or RGB after conversion, or contains nonfinite values. Example: rng(7) previousImage = rand(60, 70); currentImage = circshift(previousImage, [-2 3]); [point, confidence, diagnostics] = ... video_marker.motionEstimate.trackPoints( ... previousImage, currentImage, [35 30]); assert(norm(point - [38 28]) < 0.5) assert(confidence > 0.5 && diagnostics.valid) See also video_marker.motionEstimate.predictForward, video_marker.frameAnnotations.setFramePoints"},{"title":"vt_resistance.analysisRun.computeResistance","url":"reference/api/vt_resistance/analysisRun/computeResistance.html","kind":"reference","section":"reference","keywords":"vt_resistance.analysisRun.computeResistance","text":"COMPUTERESISTANCE Estimate cathodic and anodic resistance from a VT pulse. COMPUTERESISTANCE Estimate cathodic and anodic resistance from a VT pulse. Usage: A = vt_resistance.analysisRun.computeResistance(item) A = vt_resistance.analysisRun.computeResistance(item, opts) Inputs: item - Scalar DTA item struct. item.curve, or the main curve selected from item.tables, must provide T in seconds, Vf in volts, and Im in amperes. Pt is optional; zero-based sample numbers are generated when absent. item.meta may contain pulse timing and current metadata. opts - Optional scalar struct described below. Default: struct(). Options: windowMode - Samples used for steady voltage/current medians: \"Full pulse median\" or \"Center 60% median\". The latter excludes the first and last 20% of each detected phase. Default: \"Full pulse median\". voltageMode - Resistance numerator: \"Baseline-corrected dV/I\" or \"Raw Vf/I\". Default: \"Baseline-corrected dV/I\". pulseMode - Pulse-detection policy: \"Metadata first, then auto\", \"Metadata only\", or \"Auto from Im only\". Default: \"Metadata first, then auto\". Outputs: A - Scalar result struct. Success is indicated by A.ok and A.message=\"OK\". Data-validation failures return ok=false and the fields calculated up to the failed stage. Result Fields: ok - True when the final mean absolute resistance is finite. message - \"OK\" or an explanation of the first failed stage. windowMode - Effective steady-window label. voltageMode - Effective voltage-numerator label. logOnFailure - True when the app should surface the failure in its log. t - Filtered time vector in seconds. Vf - Filtered voltage vector in volts. Im - Filtered current vector in amperes. pt - Filtered source point numbers or generated zero-based sample numbers. pulse - Pulse geometry returned by labkit.dta.detectPulses. detectMode - Detection method that produced pulse. detectMsg - Pulse-detection status message. cathMask - Logical samples in the selected cathodic steady window. anodMask - Logical samples in the selected anodic steady window. cathSteadyStart, cathSteadyEnd - Cathodic steady-window bounds in seconds. anodSteadyStart, anodSteadyEnd - Anodic steady-window bounds in seconds. Ic_est_A - Median cathodic current in amperes. Ia_est_A - Median anodic current in amperes. Vc_ss_V - Median cathodic steady voltage in volts. Va_ss_V - Median anodic steady voltage in volts. cathBaselineStart, cathBaselineEnd - Requested pre-pulse cathodic baseline bounds in seconds. anodBaselineStart, anodBaselineEnd - Requested post-pulse anodic baseline bounds in seconds. Vc_baseline_V - Median pre-pulse voltage, or 0 when unavailable. Va_baseline_V - Median post-pulse voltage; falls back to the cathodic baseline and then 0. cathBaselineWindow_s - Duration of the requested cathodic baseline window. anodBaselineWindow_s - Duration of the requested anodic baseline window. dVc_V - Vc_ss_V-Vc_baseline_V in volts. dVa_V - Va_ss_V-Va_baseline_V in volts. Rc_raw_ohm - Vc_ss_V/Ic_est_A in ohms. Ra_raw_ohm - Va_ss_V/Ia_est_A in ohms. Rc_dV_ohm - dVc_V/Ic_est_A in ohms. Ra_dV_ohm - dVa_V/Ia_est_A in ohms. Rc_ohm - Selected cathodic raw or baseline-corrected resistance. Ra_ohm - Selected anodic raw or baseline-corrected resistance. Rc_abs_ohm - Absolute Rc_ohm. Ra_abs_ohm - Absolute Ra_ohm. Ravg_abs_ohm - Mean of the finite absolute cathodic and anodic values. Description: computeResistance analyzes one detected biphasic voltage transient and reports phase-specific as well as averaged resistance. Apps may choose raw steady voltage or subtract the adjacent baseline before division. The function performs no plotting, file selection, or export. Calculations: Rows containing NaN in T, Vf, or Im are removed together. Pulse detection is delegated to labkit.dta.detectPulses. Steady values are phase-window medians. Division returns NaN when either operand is nonfinite or current is smaller than eps in magnitude. The reported average uses absolute phase resistances so opposite current signs do not cancel. Failure Behavior: Missing curve data, fewer than five remaining samples, failed pulse detection, undersampled steady windows, or nonfinite final resistance returns ok=false. Malformed structs and unsupported MATLAB value types may still throw from DTA access or numeric operations. Typical Call: [item, status] = labkit.dta.loadFile(\"transient.DTA\", \"chrono\"); assert(status.ok, status.message) opts = struct(\"windowMode\", \"Center 60% median\", ... \"voltageMode\", \"Baseline-corrected dV/I\", ... \"pulseMode\", \"Metadata first, then auto\"); A = vt_resistance.analysisRun.computeResistance(item, opts); assert(A.ok, A.message) fprintf(\"Mean resistance: %.4g ohm\\n\", A.Ravg_abs_ohm) See also labkit.dta.detectPulses, cic.analysisRun.computeCIC"}]
\ No newline at end of file
+[{"title":"LabKit Documentation","url":"index.html","kind":"overview","section":"general","keywords":"LabKit Documentation","text":"LabKit Documentation LabKit documentation is organized by the question you are trying to answer. Start with a task guide, then open an API reference only when you need exact MATLAB call syntax or returned data shapes. Choose A Starting Point I want to Start here Install, update, or open LabKit [Getting started](getting-started/README.md) Understand every launcher action or call it from MATLAB [LabKit Launcher](apps/labkit-core/launcher/README.md) Choose an app and understand its inputs and outputs [App guide](apps/README.md) Call a reusable `labkit.*` function [Public API reference](reference/README.md) Understand ownership and package boundaries [Architecture](development/build-apps/architecture.md) Create or modify an app [App development](development/build-apps/app-development.md) Run tests or diagnose performance [Testing](development/maintain-and-release/testing.md) Call packaging, profiling, codecheck, or documentation tools [Maintainer tools](development/tools/README.md) Understand documentation sources and generated HTML [Documentation system](development/maintain-and-release/documentation.md) Maintain a private app workspace [Private apps](development/maintain-and-release/private-apps.md) Prepare a release [Release process](development/maintain-and-release/release.md) Documentation Layers getting-started/ installation, launcher, updates, and first-run concepts apps/ one directory per family and one subdirectory per app including the LabKit Launcher under LabKit Core framework/ UI runtime concepts, behavior, and app-authoring contracts libraries/ one directory per reusable public MATLAB facade reference/ generated-function reference landing page development/ reader-task folders for app building, maintenance, and tools history/ chronological change records and the project history index Every Markdown file is published automatically. Its path owns navigation and its first level-one heading owns the page title. Public App manuals are matched to `labkit_launcher(\"list\")`; App-owned function pages are discovered from complete public MATLAB help contracts. Reference Conventions Public function pages follow the MATLAB reference pattern: summary and syntax description and behavior input arguments and options output arguments and data shapes algorithms or scientific semantics where relevant errors and limitations executable examples version history, See Also, and related topics Each app family has a landing page. Each concrete app owns a directory whose `README.md` is its Get Started and detailed behavior page; complex apps can add focused workflow, file-format, or algorithm topics beside it. App pages explain launch, task flow, interaction rules, inputs, outputs, persistence, scientific meaning, non-GUI APIs, errors, limitations, examples, related topics, and component history as applicable. They do not document internal callbacks. Framework and library landing pages follow the Qt module pattern: overview and ownership first, followed by grouped concepts, supported public members, detailed behavior, examples, and related modules. Generated function pages are owned by MATLAB source help blocks. Handwritten HTML is never a source. Project History And Support [Project history](history/README.md) lists all change records and connects them to the affected apps, framework, and libraries. [Support](../.github/SUPPORT.md) explains how to report workflow problems."},{"title":"EIS","url":"apps/electrochemistry/eis.html","kind":"app","section":"apps","keywords":"apps electrochemistry eis EIS","text":"EIS EIS overlays impedance data from one or more Gamry `ZCURVE` tables, supports Nyquist and Bode-style axis combinations, and exports the values currently selected for plotting. Requirements And Launch labkit_EIS_app Inputs Add one or more `.DTA` files containing a readable EIS `ZCURVE`. Files that do not contain the required curve are reported and omitted from the plot. The successfully decoded source list and its order are preserved in project state through portable references. Basic Workflow Add the EIS DTA files. Choose X and Y quantities. Choose **mΩ**, **Ω**, **kΩ**, or **MΩ** for impedance axes. New projects default to **kΩ**. Enable logarithmic X or Y scaling only for strictly positive plotted data. Use **Fit X/Y limits** to re-estimate independent limits from the current data, or **Use equal X/Y scale** when equal data units are wanted. Adjust marker, line, grid, and legend presentation. Export the current plot data CSV. Axis Quantities The available quantities are frequency, log10 frequency, time, point number, real impedance, imaginary impedance, negative imaginary impedance, impedance magnitude, phase, DC current, and DC voltage. Default axes are `Zreal` and `-Zimag`, displayed in kΩ for a new project. Use `Zreal` versus `-Zimag` for the conventional Nyquist orientation. Use frequency versus magnitude or phase for Bode-style views. The log-axis checkbox changes MATLAB axes scaling; choosing `log10(Freq)` changes the data coordinate itself. Do not apply both transformations unless that is explicitly intended. Plot Parameters Parameter Default Impedance unit kΩ Line width 1.4 Marker size 6 Show markers on Log X / Log Y off / off Legend / Grid on / on The app never infers an equal aspect ratio from the selected quantities: a Nyquist plot starts with independently fitted limits. Use **Use equal X/Y scale** only when equal data units are useful for the current comparison. Use **Fit X/Y limits** to return to independent limits after equal scaling or a manual zoom. Equal scaling expands a fitted limit when necessary so X and Y data units have the same on-screen length; it is a one-time reset and does not constrain later wheel zooming. Axis and styling changes preserve the current source set and the current viewport; the two view buttons explicitly replace that viewport. Output **Export current plot CSV** writes the selected X/Y values for each valid file on a shared row index. Each file retains its own X and Y pair, so unequal curve lengths do not imply interpolation. Impedance columns use the selected display unit and include an ASCII unit suffix such as `kohm` in the column name. A result manifest records the selected axes, impedance unit, plot parameters, source references, and output role. Use Without The GUI [item, status] = labkit.dta.loadFile(\"spectrum.DTA\", \"eis\"); assert(status.ok, status.message); units = eis.impedanceDisplay.catalog(); x = eis.analysisRun.valuesForAxis(item, \"Zreal\", units.choices(3)); y = eis.analysisRun.valuesForAxis(item, \"-Zimag\", units.choices(3)); plot(x, y, \"o-\"); axis equal `valuesForAxis` is app-owned and not currently part of the published app API catalog. The DTA loader and `getZCurve` are supported reusable APIs. Errors And Limitations Log axes omit or reject nonpositive coordinates according to MATLAB axes behavior; inspect the data rather than treating missing points as zero. Overlaying files does not normalize electrode area or fixture geometry. Changing the impedance display unit rescales impedance axes and exported impedance columns; it does not alter the DTA values stored in base ohms. Axis labels describe parsed DTA columns; they do not validate the experiment configuration recorded by the instrument. Related Topics [Electrochemistry family](../README.md) [DTA Library](../../../libraries/dta/README.md) [API Reference](../../../reference/README.md)"},{"title":"2.1: RHS apps and shared runtime stability","url":"history/records/2026/06/LK-20260621-v2-1-rhs-and-runtime-stability.html","kind":"history","section":"history","keywords":"LK-20260621-v2-1-rhs-and-runtime-stability 2026-06-21 7 feat compatible historical project evolution","text":"2.1: RHS apps and shared runtime stability id: LK-20260621-v2-1-rhs-and-runtime-stability date: 2026-06-21 sequence: 7 type: feat compatibility: compatible scope: historical project evolution Context After v2.0, the UI runtime supported several app families but still repeated important behavior around busy actions, path events, preview zoom, and layout ownership. A new neurophysiology family would amplify those inconsistencies if each app solved them independently. Decision and rationale Add RHS reading and review as a first-class app family while centralizing only the runtime mechanics shared across domains. Filtering choices and response analysis remained in the RHS workflows; transactions, path events, zoom, and layout ownership belonged to the UI layer. Changes Added RHS Preview, Nerve Response Analysis, and Response Review Stats. Folded the separate screening step into preview filtering so file review used one workflow rather than two overlapping stages. Added duplicate Batch Crop tasks for repeated crop layouts. Unified preview scroll zoom and centralized action busy transactions. Centralized path-event contracts and app layout ownership. Added a ZIP-based updater and stabilized image and app interactions before release. Reduced focused test discovery and guardrail cost as the repository grew. User and data impact Neurophysiology users gained file preview, response analysis, and review statistics apps. Existing users saw more consistent zoom, busy feedback, path updates, and responsive layout behavior. The updater provided a repository- owned way to install a packaged release. Compatibility and migration The RHS family was additive. UI implementations that maintained their own copies of busy, path, or layout mechanics were migrated to the shared runtime contracts. Validation The stage added RHS-focused tests, interaction regressions, compatibility input checks, path-event checks, and faster focused test routing. Tag `2.1` points to `76ddf7d0`. Evidence RHS app family `7ddc036f` and preview filtering `ca8a37dd`. Shared interaction mechanics `eb52eb17`, `f3e42b8e`, `1fe89ba2`. Layout ownership `3ce4c8f7`, `09baf7fe`. Updater `e4b02f3a`. 2.1 release stabilization `76ddf7d0`. Known limitations and follow-up The updater still depended on how the launcher was installed, and several image workflows reset zoom or recomputed previews during edits. The following 2.2 and 2.3 releases concentrated on self-contained launch and direct image manipulation."},{"title":"Adjacent navigation for history records","url":"history/records/2026/07/LK-20260716-history-adjacent-navigation.html","kind":"history","section":"history","keywords":"LK-20260716-history-adjacent-navigation 2026-07-16 64 docs compatible documentation `tools/docs/` `site/history/records/`","text":"Adjacent navigation for history records id: LK-20260716-history-adjacent-navigation date: 2026-07-16 sequence: 64 type: docs compatibility: compatible component: `documentation` scope: `tools/docs/` scope: `site/history/records/` Context The complete timeline linked every recorded change, but a reader who opened one detail page had to return to the index before continuing chronologically. Dates and filenames could not safely identify adjacent records because several changes may share one date. Decision and rationale Use validated history `sequence` metadata to add adjacent-record links at the end of every rendered history detail page. **Previous change** means the record with `sequence - 1`; **Next change** means `sequence + 1`. This preserves one unambiguous project-history order without binding authored content to Git. Changes Added responsive previous/next navigation immediately before the generated page footer. Showed only existing directions at the oldest and newest ends. Added regression coverage for link targets, ordering, and page placement. User and data impact Readers can traverse project history linearly without returning to the index. No MATLAB runtime, app state, scientific data, or exports change. Compatibility and migration Existing history URLs and record metadata remain valid. Regenerating `site/` adds navigation to every record page and updates the search index normally. Validation Renderer regression tests verify middle and endpoint records against source sequence metadata. The documentation consistency check independently rebuilds and byte-compares the tracked site. Evidence `renderLabKitHistorySequenceNavigation` owns adjacent link generation. `DocumentationRendererRegressionTest` verifies the generated navigation. Known limitations and follow-up Navigation is intentionally chronological across all project components. It does not filter to the current app or library; component-specific history remains available from each component manual."},{"title":"App SDK explicit contract replaces the retired UI runtime","url":"history/records/2026/07/LK-20260719-ui-explicit-contract-migration.html","kind":"history","section":"history","keywords":"LK-20260719-ui-explicit-contract-migration 2026-07-19 138 refactor breaking labkit.app labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app App Framework DIC Electrochem Gait Image Measurement LabKit Core Neurophysiology Statistics Wearable Project persistence Result provenance","text":"App SDK explicit contract replaces the retired UI runtime id: LK-20260719-ui-explicit-contract-migration date: 2026-07-19 sequence: 138 type: refactor compatibility: breaking component: `labkit.app` | `new -> 1.0.0` component: `labkit_DICPostprocess_app` | `1.4.7 -> 1.5.0` component: `labkit_DICPreprocess_app` | `1.5.8 -> 1.6.0` component: `labkit_ChronoOverlay_app` | `1.4.7 -> 1.5.0` component: `labkit_CIC_app` | `1.4.7 -> 1.5.0` component: `labkit_CSC_app` | `1.4.8 -> 1.5.0` component: `labkit_EIS_app` | `1.4.7 -> 1.5.0` component: `labkit_VTResistance_app` | `1.4.7 -> 1.5.0` component: `labkit_GaitAnalysis_app` | `2.0.8 -> 2.1.0` component: `labkit_BatchImageCrop_app` | `1.7.7 -> 1.8.0` component: `labkit_CurvatureMeasurement_app` | `1.4.6 -> 1.5.0` component: `labkit_FLIRThermal_app` | `1.4.8 -> 1.5.0` component: `labkit_FocusStack_app` | `1.5.6 -> 1.6.0` component: `labkit_ImageEnhance_app` | `1.6.7 -> 1.7.0` component: `labkit_ImageMatch_app` | `1.6.8 -> 1.7.0` component: `labkit_VideoMarker_app` | `1.5.7 -> 1.6.0` component: `labkit_FigureStudio_app` | `0.2.9 -> 0.3.0` component: `labkit_NerveResponseAnalysis_app` | `1.4.8 -> 1.5.0` component: `labkit_ResponseReviewStats_app` | `1.4.7 -> 1.5.0` component: `labkit_RHSPreview_app` | `1.4.6 -> 1.5.0` component: `labkit_TTestWizard_app` | `1.0.1 -> 1.1.0` component: `labkit_ECGPrint_app` | `1.4.6 -> 1.5.0` scope: App Framework scope: DIC scope: Electrochem scope: Gait scope: Image Measurement scope: LabKit Core scope: Neurophysiology scope: Statistics scope: Wearable scope: Project persistence scope: Result provenance Context The retired UI runtime removed substantial per-App lifecycle code, but Apps still registered callback tables, repeated bound values in presenters, authored standard file add/remove/clear behavior, and depended on nested event/service structs. A replacement SDK kernel had already established immutable semantic values, pre-GUI validation, transactional state/presentation commits, project documents, result manifests, resources, and portable sources. The migration then had to restore the complete behavior and visual contract of every tracked App before the retired production facade could be deleted. Decision and rationale Create `labkit.app` as the stable SDK rather than misnaming the expanded contract `labkit.ui` or adapting it back to Runtime V2 transport structs. Keep the public root small, partition authoring by capability, and concentrate complexity in a paved path: direct-callback `layout.*` nodes, strict bindings, runtime-completed `view.Snapshot` values, standard file lifecycle, fixed `CreateSession(project,context)`, and private native adapters. Migrate all 21 tracked Apps through capability waves, treating their previous controls, tabs, layout proportions, interactions, project behavior, results, debug samples, and workflow wording as product contracts rather than reducing the task to launch compatibility. Changes Added the private native MATLAB adapter with semantic component ownership, typed RuntimeKernel callbacks, native dialog results, complete-presentation reconciliation, and rollback to the previous native view after a failed renderer commit. Added `Definition.launch`, fixed renderer `(axes,model)` dispatch, semantic labels, runtime-owned file add/remove/clear and selection, and transient session rebuild after source collection changes. Added strict table view options, typed complete-data edits, and distinct `event.TableCellEdit`, `event.TableCellSelection`, and `event.ListSelection` values; the private adapter absorbs native MATLAB table-value differences. Fixed session construction to `CreateSession(project,context)` so Apps resolve opaque portable sources without reading their representation. Migrated Chrono Overlay to one directly bound export callback, four state bindings, one directly bound two-axis renderer, and a two-operation view snapshot. Partitioned the public SDK into `layout`, `view`, `event`, `project`, `result`, and `dialog`; layout nodes, option parsing, stores, adapters, and runtime execution remain hidden under `internal`. Reduced Chrono's noncomment layout/action/presenter code from 277 lines to 86 while preserving its DTA alignment, plot options, project schema, CSV columns, and result provenance. Migrated T-Test Wizard as the typed editable-table, feature-fragment, and multi-page workspace proof: table selections and edits have explicit payload classes, `+workbench` exposes product assembly, workflow packages own their layout/presentation/actions, and the private adapter owns concrete layout. Migrated VT Resistance to direct file and analysis-setting bindings, a complete summary/table/two-axis snapshot, and an App-owned result-package export. Plot renderers and scientific choices now live with their owning analysis capabilities instead of a technical UI package. Migrated Gait Analysis to capability-owned source adoption, option invalidation, deterministic analysis, step selection/navigation, three-axis rendering, CSV-set export, and result packaging. Migrated DIC Preprocess to two role-bound image sources, paired-anchor registration, managed crop and mask editors, two-axis rendering, edit replay, and result-package exports owned by its analysis, mask, and result capabilities. Migrated Batch Image Crop to framework-owned source selection plus App-owned duplicate crop tasks, direct crop-center and scale-reference interactions, capability snapshots, and result-package export. Added `labkit.app.project.sourceRecord` so pure payload migrations can convert legacy paths into portable sources without constructing the framework-owned reference representation. Corrected folder chooser dispatch to its one-path backend contract and applied table data before table selection during native reconciliation, so a selection may legally target rows introduced by the same snapshot. Preserved cell-valued interaction payloads in the transactional event queue and kept multi-target interaction bridge specifications scalar, enabling paired-anchor gestures across two semantic axes. Removed handler objects, callback tables, renderer registries, and their forwarding from the App authoring contract. Layout controls and plot areas reference concrete functions directly. Migrated the remaining electrochemistry, DIC, image-measurement, neurophysiology, core, wearable, and high-state workflows, including editable tables, workspace pages, file roles, multi-axis plots, managed point/rectangle/interval/scale interactions, long-lived video resources, project recovery, result packages, and synthetic diagnostic samples. Restored shared product presentation: versioned titles and dirty markers, startup progress and failure surfaces, guarded close behavior, utility menus, adjustable pane dividers, scroll and grow policies, numeric panners, adaptive action grids, usage text, plot navigation, pop-out/export tools, and viewport-preserving overlays. Internalized contract compilation, runtime construction, native platform plans, target inventories, and callback-context creation. `Definition` is the sole author-created root; `CallbackContext` is a sealed runtime-injected port; optional concepts remain grouped under purpose-specific packages. Deleted the complete retired `labkit.ui` production facade and the migration-only analyzer, prototypes, compatibility tools, and debt-only tests after source scans and focused App tests proved that no production consumer remained. User and data impact Chrono Overlay retains its input formats, pulse-gap alignment, plot meanings, parameter defaults, CSV table, and version-2 project payload. T-Test Wizard retains its source formats, group/test calculations, plot meaning, two CSV exports, and version-2 project payload. VT Resistance retains its pulse detection, resistance calculations, plot semantics, CSV schema, and version-1 project payload while recomputing the decoded batch under shared settings. Gait Analysis retains its Video Marker payload contract, project migrations, step segmentation, gait metrics, CSV set, and version-3 project payload. File identities and portable paths remain runtime-owned. DIC Preprocess retains its rigid registration, common crop, mask editing, image/mask exports, and version-1 project payload. Batch Image Crop retains duplicate tasks per source, fixed-pixel and physical crops, rotation/padding, scale calibration, scale-bar placement, image/CSV exports, and its version-2 payload. Existing payload migrations remain App-owned. The other 15 Apps retain their documented source formats, scientific calculations, units, thresholds, project payload versions, export schemas, and result meanings. Their product versions advance once from the `main` baseline to identify the new App SDK source contract and restored complete UI behavior. No project payload version was increased merely because the UI framework changed. Compatibility and migration `labkit.app` 1 is a source-breaking replacement contract for App definitions, presenters, callbacks, events, and interactions. Every tracked App migrated before the retired `labkit.ui` boundary was deleted; there is no runtime adapter or dual authoring surface. Public App entrypoint commands remain stable. Project documents retain their format and App payload versions independently of the facade transition. Validation Focused headless tests cover strict values, transactional runtime behavior, project save/restore, authoring defaults, and Chrono calculations/exports. Hidden GUI tests cover native semantic construction, typed control and table callbacks, bound side effects, standard file lifecycle, transient session rebuild, two-axis rendering, viewport preservation, renderer rollback, Chrono export, and project restore. VT Resistance focused tests cover resistance calculations, CSV compatibility, native layout, shared batch recomputation, two-axis rendering, result packaging, and project restore. Gait focused tests cover project migration, pose decoding, scientific calculations, CSV compatibility, typed table navigation, three-axis rendering, folder selection, result packaging, and project restore. DIC Preprocess focused tests cover project state, image loading, edit replay, registration/crop/mask helpers, export manifests, native two-axis layout, paired-anchor alignment, and managed crop interaction. Framework regression tests cover cell-valued event payloads and native multi-axis interaction bridging. Batch Image Crop focused tests cover crop geometry, padding, physical scaling, project migration, duplicate tasks, output planning/writes, native semantic layout, current-center editing, and standard result manifests. Focused framework and App tests additionally cover all 21 semantic layouts, typed events, managed interactions, project migration/recovery, result writing, synthetic sample packs, diagnostics, resource cleanup, window titles, startup success/failure, close behavior, and native adapter reconciliation. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [VT Resistance](../../../../apps/electrochemistry/vt-resistance/README.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Batch Image Crop](../../../../apps/image-measurement/batch-crop/README.md) [App catalog](../../../../apps/README.md) [LabKit App Framework](../../../../framework/README.md) [Build a Complete App](../../../../development/build-apps/complete-app.md) Known limitations and follow-up Automated GUI evidence does not replace developer-led validation of native dialogs, editable-table feel, pointer interaction, long-lived resource use, representative exports, visual quality, or scientific workflow suitability. That interactive validation remains a release input for the exact integrated commit. labkit.app.project.sourceRecord Create a portable source value during project migration."},{"title":"App SDK supports equal-data-unit plot fitting","url":"history/records/2026/07/LK-20260721-app-sdk-equal-data-unit-fit.html","kind":"history","section":"history","keywords":"LK-20260721-app-sdk-equal-data-unit-fit 2026-07-21 148 fix compatible labkit.app App Framework EIS Overlay","text":"App SDK supports equal-data-unit plot fitting id: LK-20260721-app-sdk-equal-data-unit-fit date: 2026-07-21 sequence: 148 type: fix compatibility: compatible component: `labkit.app` | `1.2.1 -> 1.2.2` scope: App Framework scope: EIS Overlay Context Apps needed an equal X/Y data-unit view for plots such as Nyquist diagrams, but setting an axes aspect mode directly also changed the allocated drawing area and persisted into later wheel and zoom interactions. Decision and rationale Keep viewport geometry in the framework fitter. An App can request equal data units while fitting the current plotted data, after which ordinary framework viewport preservation continues without an aspect lock. Changes Added the public `EqualDataUnits` option to `labkit.app.plot.fitAxesToGraphics`. Fit equal data units by expanding the smaller data span for the current axes pixel geometry, including logarithmic dimensions in log space. Removed the retired EIS axis-label rule; EIS now exposes explicit fit and equal-scale view actions. User and data impact The new option is additive. Existing fit calls keep independent X/Y limits. EIS projects and exports are unchanged; equal scaling is a transient view action and does not alter later zoom or wheel behavior. Compatibility and migration No project or result migration is required. Existing callers retain their independent fit behavior unless they opt into `EqualDataUnits=true`. Validation EIS GUI coverage exercises independent fitting, explicit equal scaling, and later viewport preservation. The App SDK plot helper tests cover finite data, logarithmic axes, and the public option contract. Evidence The fitting helper applies limits from finite plotted data and expands only the smaller working-space span for the axes pixel geometry. The EIS workflow test then verifies that a later framework redraw preserves the chosen viewport. Known limitations and follow-up Developer-led visual validation remains responsible for assessing layout balance on target displays and MATLAB releases. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data."},{"title":"App actions require explanatory hover help","url":"history/records/2026/07/LK-20260720-app-action-tooltips.html","kind":"history","section":"history","keywords":"LK-20260720-app-action-tooltips 2026-07-20 140 feat compatible labkit.app labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app App Framework All tracked Apps","text":"App actions require explanatory hover help id: LK-20260720-app-action-tooltips date: 2026-07-20 sequence: 140 type: feat compatibility: compatible component: `labkit.app` | `1.0.0 -> 1.1.0` component: `labkit_DICPostprocess_app` | `1.5.0 -> 1.5.1` component: `labkit_DICPreprocess_app` | `1.6.0 -> 1.6.1` component: `labkit_ChronoOverlay_app` | `1.5.0 -> 1.5.1` component: `labkit_CIC_app` | `1.5.0 -> 1.5.1` component: `labkit_CSC_app` | `1.5.0 -> 1.5.1` component: `labkit_EIS_app` | `1.5.0 -> 1.5.1` component: `labkit_VTResistance_app` | `1.5.0 -> 1.5.1` component: `labkit_GaitAnalysis_app` | `2.1.0 -> 2.1.1` component: `labkit_BatchImageCrop_app` | `1.8.0 -> 1.8.1` component: `labkit_CurvatureMeasurement_app` | `1.5.0 -> 1.5.1` component: `labkit_FLIRThermal_app` | `1.5.0 -> 1.5.1` component: `labkit_FocusStack_app` | `1.6.0 -> 1.6.1` component: `labkit_ImageEnhance_app` | `1.7.0 -> 1.7.1` component: `labkit_ImageMatch_app` | `1.7.0 -> 1.7.1` component: `labkit_VideoMarker_app` | `1.6.0 -> 1.6.1` component: `labkit_FigureStudio_app` | `0.3.0 -> 0.3.1` component: `labkit_NerveResponseAnalysis_app` | `1.5.0 -> 1.5.1` component: `labkit_ResponseReviewStats_app` | `1.5.0 -> 1.5.1` component: `labkit_RHSPreview_app` | `1.5.0 -> 1.5.1` component: `labkit_TTestWizard_app` | `1.1.0 -> 1.1.1` component: `labkit_ECGPrint_app` | `1.5.0 -> 1.5.1` scope: App Framework scope: All tracked Apps Context The native text-fit adapter copied button text through a column-shaped char conversion. MATLAB therefore received one newline between every character and rendered hover help as a narrow vertical strip. The generated tooltip also only repeated the visible label, so it did not explain the scientific or workflow consequence of an action. Decision and rationale Make explanatory hover help part of the semantic layout contract. Every `layout.button` produces a nonempty Tooltip, and tracked Apps must replace the label-based framework fallback with App-owned explanatory text. File-list actions expose dedicated tooltip fields and retain framework-owned defaults for generic folder, remove, and clear mechanics. Scientific meaning stays in the owning App rather than in the native adapter. All tracked Apps now describe what their actions consume, calculate, mutate, or export. The contract guardrail rejects App tooltips that only repeat the visible action label and also requires an explanatory input-selection tooltip. Changes Preserved char row vectors as one text line during native text fitting, eliminating the injected character-by-character newlines. Added `Tooltip` support with a nonempty label fallback to `layout.button` and dedicated file-list tooltips for choose, folder, recursive folder, remove, and clear actions. Added scientific and workflow-specific text for all 138 tracked App business buttons and all 26 App input selectors. Centralized the shared tooltip contract in the App Framework and App catalog instead of repeating it in every App manual. User and data impact Existing tracked Apps and third-party layouts retain their actions and calculations. Hovering now always shows readable text; tracked Apps additionally carry domain-specific explanations. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation App SDK unit coverage for required and compiled tooltip values. Cross-App definition guardrail for non-label business and input tooltips. Native adapter GUI coverage for exact tooltip text without injected newlines. DIC Postprocess GUI coverage for scientific action and Ncorr input help. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive checks should confirm tooltip timing, width, and line wrapping on supported MATLAB releases and desktop platforms."},{"title":"App alerts through UI facade","url":"history/records/2026/06/LK-20260630-app-alerts-through-ui-facade.html","kind":"history","section":"history","keywords":"LK-20260630-app-alerts-through-ui-facade 2026-06-30 22 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_ECGPrint_app App alerts through UI facade","text":"App alerts through UI facade id: LK-20260630-app-alerts-through-ui-facade date: 2026-06-30 sequence: 22 type: feat compatibility: compatible component: `labkit.ui` | `3.2.7 -> 3.2.8` component: `labkit_DICPostprocess_app` | `1.2.2 -> 1.2.3` component: `labkit_DICPreprocess_app` | `1.2.1 -> 1.2.2` component: `labkit_ChronoOverlay_app` | `1.2.0 -> 1.2.1` component: `labkit_CIC_app` | `1.2.0 -> 1.2.1` component: `labkit_CSC_app` | `1.2.0 -> 1.2.1` component: `labkit_EIS_app` | `1.2.0 -> 1.2.1` component: `labkit_VTResistance_app` | `1.2.0 -> 1.2.1` component: `labkit_BatchImageCrop_app` | `1.3.6 -> 1.3.7` component: `labkit_CurvatureMeasurement_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.2.4 -> 1.2.5` component: `labkit_ImageEnhance_app` | `1.3.3 -> 1.3.4` component: `labkit_ImageMatch_app` | `1.3.4 -> 1.3.5` component: `labkit_ECGPrint_app` | `1.2.1 -> 1.2.2` scope: App alerts through UI facade Context Apps opened alerts directly, which made hidden GUI tests unreliable and led to small differences in modal behavior across workflows. Decision and rationale Route user-visible alerts through one UI service that can display a modal message normally and record it safely during hidden tests. Keep each app responsible for the message and the decision that triggers it. Changes `labkit.ui` `3.2.7 -> 3.2.8` DIC, electrochem, image-measurement, and ECG apps patch bumped where alert routing changed. Routed app alerts through hidden-test-safe `labkit.ui.app.showAlert`. User and data impact Errors continued to appear as app alerts, but test and debug runs could capture them without blocking on an invisible dialog. No saved or exported data changed. Compatibility and migration App error conditions and messages remained app-owned. Only the display route changed, so user data and app project formats required no conversion. Validation Commit `8d7c83b1` migrated alert call sites and updated hidden workflow tests across the listed apps. Evidence Main commit `8d7c83b1`. Known limitations and follow-up The historical `labkit.ui.app.showAlert` entry point was later replaced by the injected `services.dialogs.alert` service in Runtime V2."},{"title":"App diagnostics and hardened UI workflows","url":"history/records/2026/06/LK-20260628-app-diagnostics-and-hardened-ui-workflows.html","kind":"history","section":"history","keywords":"LK-20260628-app-diagnostics-and-hardened-ui-workflows 2026-06-28 15 feat compatible labkit_launcher labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app App diagnostics and hardened UI workflows","text":"App diagnostics and hardened UI workflows id: LK-20260628-app-diagnostics-and-hardened-ui-workflows date: 2026-06-28 sequence: 15 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.1 -> 1.1.2` component: `labkit.ui` | `3.1.0 -> 3.1.2` component: `labkit.ui` | `3.1.2 -> 3.1.3` component: `labkit_BatchImageCrop_app` | `1.3.0 -> 1.3.1` component: `labkit_BatchImageCrop_app` | `1.3.1 -> 1.3.2` component: `labkit_FocusStack_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.2.0 -> 1.2.1` component: `labkit_ImageEnhance_app` | `1.2.1 -> 1.2.2` component: `labkit_ImageMatch_app` | `1.2.0 -> 1.2.1` component: `labkit_NerveResponseAnalysis_app` | `1.2.0 -> 1.2.1` component: `labkit_ResponseReviewStats_app` | `1.2.0 -> 1.2.1` component: `labkit_RHSPreview_app` | `1.2.0 -> 1.2.1` scope: App diagnostics and hardened UI workflows Context When an app stalled or caught an exception, screenshots and the visible Log tab often omitted the active callback and stack information needed to reproduce the problem. Decision and rationale Capture active operations, uncaught crashes, caught errors, and suspected stalls in structured local reports while hardening the UI paths that generate those events. Keep reports outside project data so diagnostics cannot alter results. Changes `labkit.ui` `3.1.0 -> 3.1.3` Batch Crop, Focus Stack, Image Enhance/Match, neurophysiology apps, and the launcher patch bumped where runtime behavior changed. Hardened LabKit UI workflows. Added crash reports, active-operation reports, caught-error reports, and stall diagnostics. User and data impact Debug runs produced evidence that could identify the failing operation and app state without copying laboratory inputs into the repository. Normal projects and exports kept their previous formats. Compatibility and migration The diagnostic files were additive and existing app inputs and saved results remained readable. Debug runs began producing more detailed local artifacts. Validation Commits `e966457b` and `f5bc6f98` updated launcher, framework, and affected app workflow tests for diagnostic creation and hardened callbacks. Evidence Main commits `e966457b` and `f5bc6f98`. Known limitations and follow-up Diagnostics report the state and stack available at capture time; they do not replace a reproducible input or a focused regression test."},{"title":"App file-selection and electrochem control fixes","url":"history/records/2026/07/LK-20260703-app-file-selection-and-electrochem-control-fixes.html","kind":"history","section":"history","keywords":"LK-20260703-app-file-selection-and-electrochem-control-fixes 2026-07-03 33 fix compatible labkit_CIC_app labkit_CIC_app labkit_CSC_app labkit_CSC_app labkit_VTResistance_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"App file-selection and electrochem control fixes id: LK-20260703-app-file-selection-and-electrochem-control-fixes date: 2026-07-03 sequence: 33 type: fix compatibility: compatible component: `labkit_CIC_app` | `1.3.1 -> 1.3.2` component: `labkit_CIC_app` | `1.3.2 -> 1.3.3` component: `labkit_CSC_app` | `1.3.1 -> 1.3.2` component: `labkit_CSC_app` | `1.3.2 -> 1.3.3` component: `labkit_VTResistance_app` | `1.3.1 -> 1.3.2` component: `labkit_VTResistance_app` | `1.3.2 -> 1.3.3` component: `labkit_BatchImageCrop_app` | `1.6.2 -> 1.6.3` component: `labkit_FLIRThermal_app` | `1.2.1 -> 1.2.2` component: `labkit_FocusStack_app` | `1.4.1 -> 1.4.2` component: `labkit_ImageEnhance_app` | `1.5.1 -> 1.5.2` component: `labkit_ImageMatch_app` | `1.5.1 -> 1.5.2` scope: historical project evolution Context Adding files to an existing multi-file session could replace or select the wrong entry in several apps. CIC, CSC, and VT Resistance also exposed manual plot controls that no longer matched their intended result-review workflow. Decision and rationale Preserve the complete file list when new selections are appended and select a newly added item predictably. Remove obsolete electrochem controls instead of keeping buttons whose effect was ambiguous or redundant. Changes CIC, CSC, VT Resistance, Batch Crop, FLIR Thermal, Focus Stack, Image Enhance, and Image Match patch bumped for appended file selections. CIC, CSC, and VT Resistance patch bumped again for manual plot-control removal. Preserved appended file selections. Removed electrochem manual plot controls that no longer matched the workflow. User and data impact Users could add another batch without losing files already loaded, across the listed electrochem and image apps. The electrochem workbenches became simpler; calculations and exported result schemas were unchanged. Compatibility and migration Existing loaded-file and result formats remained valid. Appended selections now preserved the active item, and the removed manual plot controls did not alter the underlying calculation options. Validation The file-selection commit added GUI regression coverage to every affected app. The control-removal commit updated the three electrochem GUI workflows and shared test helpers. Exact historical commands were not recorded. Evidence Main commits `6348185e` and `674d5d4b`. Known limitations and follow-up The change covered app-level append handling. Later framework work unified the native file-selection behavior across platforms."},{"title":"App manuals become reader-oriented and focused tests gain one wrapper","url":"history/records/2026/07/LK-20260720-reader-oriented-app-manuals-and-test-wrapper.html","kind":"history","section":"history","keywords":"LK-20260720-reader-oriented-app-manuals-and-test-wrapper 2026-07-20 142 docs compatible App documentation Documentation validation Agent testing workflow Agent skill maintenance","text":"App manuals become reader-oriented and focused tests gain one wrapper id: LK-20260720-reader-oriented-app-manuals-and-test-wrapper date: 2026-07-20 sequence: 142 type: docs compatibility: compatible scope: App documentation scope: Documentation validation scope: Agent testing workflow scope: Agent skill maintenance Context Concrete App manuals repeated shared hover-help and runtime architecture contracts. The repetition displaced workflow and scientific content, became stale when the framework changed, and made every App page appear more implementation-oriented than user-oriented. Focused test execution also required agents to repeatedly assemble low-level runner arguments; omitting GUI inclusion could produce a zero-match selection that looked like a test problem. Decision and rationale Keep shared behavior in the App catalog and framework manual. Concrete App pages describe only their inputs, workflow, App-specific interactions, scientific semantics, outputs, recovery, programmatic surface, and limitations. Protect that boundary with a documentation guardrail. Provide one platform-independent MATLAB wrapper with explicit operations for listing a file's canonical test names, running an exact file, running a canonical class or method within its owning file, and running a suite. Keep broad final gates on the existing `buildtool` interface. Treat recurring reasoning, command construction, discovery, and retry cost as skill-maintenance signals. Improvements must describe durable concepts and semantic operations rather than one product version, App, transient failure, or current repository snapshot. Changes Removed repeated hover-help and Framework Compatibility boilerplate from all tracked App manuals. Rewrote project-state passages around user-visible persistence, recovery, source relinking, and recalculation rather than internal factories, callbacks, and schema implementation. Clarified the App-manual authoring contract and added a guardrail against reintroducing shared framework boilerplate. Added `runLabKitTestTarget.m` to the test-planner skill with automatic repository discovery, GUI inference for exact files and GUI suites, hidden figures, focused artifacts, and fail-on-zero-match behavior. Made exact canonical selectors resolve their unique owning test file, while retaining an explicit file constraint for ambiguous ownership. Made local parallel build tasks retain structured shard state while relaying concise human-readable heartbeats, clear stale transient status at worker startup, and print only failing worker logs instead of remaining silent and then dumping every shard log. Moved child-process orchestration out of `buildfile.m` into a MATLAB/Java runner shared by local macOS, Linux, and Windows, removing generated POSIX scripts and adding an independent ETA to each shard summary. Hosted CI remains single-process pending explicit concurrent-license evidence. Partitioned broad local workers by unique test-class owner file so each worker discovers only its assigned files instead of repeating full-tree discovery. Batched App path registration while keeping every setup call sensitive to newly added App entrypoints, added quiet discovery probes, and removed one unreferenced GUI assertion helper. Updated the skill to list canonical names before exact method reruns and to distinguish selection failures from passing evidence. Added a root agent rule to improve skills and scripts when reusable automation can remove recurring inference or retry cost. User and data impact App behavior, calculations, saved data, and exports are unchanged. Existing manual content about scientific meaning and recovery remains available with less implementation noise. The wrapper is an agent convenience over the official test runner and does not add another CI or public build interface. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Documentation guardrails scan every concrete App manual for shared boilerplate. Documentation rendering and byte-for-byte site comparison cover all edited pages and this record. The wrapper lists and runs canonical GUI methods through the official runner, demonstrating automatic hidden-GUI selection. Test-runner profiling measured fresh full discovery before and after the path change at 5.83 and 4.82 seconds; path setup fell from 1.27 to 0.32 seconds. The retained rescan avoids a stale-cache failure when an App is added during a MATLAB session. Build contracts prove file partitions are disjoint, cover every selected test, accept deeply nested owning files, and preserve explicit failures for missing or ambiguous owners. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Continue reviewing App manuals when user-visible behavior changes; structural guardrails prevent known boilerplate but cannot judge scientific clarity."},{"title":"App tools and presentation controls become more deliberate","url":"history/records/2026/07/LK-20260720-app-tools-menu-and-screenshot-clipboard.html","kind":"history","section":"history","keywords":"LK-20260720-app-tools-menu-and-screenshot-clipboard 2026-07-20 141 feat compatible labkit.app App Framework All tracked Apps","text":"App tools and presentation controls become more deliberate id: LK-20260720-app-tools-menu-and-screenshot-clipboard date: 2026-07-20 sequence: 141 type: feat compatibility: compatible component: `labkit.app` | `1.1.0 -> 1.2.0` scope: App Framework scope: All tracked Apps Context Plot, Screenshot, Save State, and Load State appeared as separate root-level native menu items. Plot was a container while the other items executed immediately, so controls at the same visual level had inconsistent behavior and consumed unnecessary horizontal space. Screenshot also required a file dialog even when the user only needed an image for another application. Short status summaries consumed the same height as detail panels, and Apps could preserve plot zoom but had no declarative one-shot reset revision. Decision and rationale Install one framework-owned **Tools** root with purpose-based **Plots**, **Screenshot**, and conditional **Project State** submenus. Keep scientific workflow controls in the App layout while grouping domain-neutral native utilities predictably. Use MATLAB's existing `copygraphics` clipboard path so the capability remains platform-neutral and introduces no external runtime. Changes Moved plot utilities beneath **Tools > Plots**. Added **Tools > Screenshot > Copy to Clipboard** for the complete App surface and retained file export as **Save to File...**. Moved project persistence beneath **Tools > Project State** when the App owns a project document. Preserved existing executable action tags for compatibility with GUI automation and added stable tags for the new menu containers and clipboard action. Added a line-count hint for compact status summaries and a general plot-view revision that accepts renderer-fitted limits once while preserving user zoom during ordinary redraws. Updated the getting-started and framework manuals. User and data impact App calculations, project files, plots, and exports are unchanged. Existing action tags continue to identify save screenshot, save state, load state, and plot commands, but users now reach them through the Tools hierarchy. Clipboard copy uses the operating system clipboard and may be unavailable in a noninteractive MATLAB session. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Hidden native-adapter GUI coverage verifies the menu hierarchy, labels, conditional Project State menu, stable action tags, and clipboard callback wiring. Focused framework tests validate the App SDK version and native runtime. Documentation rendering and consistency checks cover the updated manuals and history record. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive checks should confirm clipboard image fidelity and native menu appearance on supported MATLAB desktop platforms."},{"title":"App validation no longer relies on sibling App paths","url":"history/records/2026/07/LK-20260717-isolated-app-validation.html","kind":"history","section":"history","keywords":"LK-20260717-isolated-app-validation 2026-07-17 128 test compatible Public App path isolation Changed-file validation routing GUI CI evaluation","text":"App validation no longer relies on sibling App paths id: LK-20260717-isolated-app-validation date: 2026-07-17 sequence: 128 type: test compatibility: compatible scope: Public App path isolation scope: Changed-file validation routing scope: GUI CI evaluation Context The ordinary public test setup adds every App entry folder so family and cross-App contract suites can run efficiently. That broad path can conceal an accidental dependency on a sibling App package. Static source scans rejected direct sibling calls, and individual Gait/private launch tests used isolated paths, but there was no uniform executable contract for every public App or automatic changed-file route to that contract. Decision and rationale Keep the convenient broad path for ordinary suites, but add one fast independent proof. For each public App, restore MATLAB's default path, add only the repository root and the owning App root, load its complete definition, check declared facade compatibility, and write its synthetic debug sample. Run this contract whenever public App source changes. Static call scanning and dynamic isolation cover different risks and remain separate. The former identifies the forbidden source reference; the latter proves metadata, framework requirements, and debug generation work in the same path shape as a single-App package. Changes Added one isolated-path contract covering all 20 public Apps. Routed every App source change to that contract in both conservative and fast changed-file plans. Added a planner regression so future routing changes cannot silently drop isolation coverage. Documented why independent private workspaces must run their own compatibility checks without inheriting public App roots. Evaluated, but did not add, a hidden-GUI job to every pull request. User and data impact There is no App or saved-data behavior change. A new sibling package dependency, stale facade requirement, incomplete definition, or broken debug sample now fails in a focused non-GUI contract instead of passing because all Apps happened to be on the developer's MATLAB path. Compatibility and migration No migration is required. Existing test commands remain unchanged; the changed-file planner adds the focused isolation selection automatically. Validation The all-App isolation contract completed in about 14 seconds locally. Existing changed-file evidence measured representative hidden image workflows at about 18 seconds each and a Gait hidden launch at about 30 seconds, before MATLAB startup and license acquisition. Since those synthetic GUI checks still do not prove native dialogs, pointer feel, or visual quality, ordinary CI keeps the fast non-GUI contract while App-specific GUI checks remain changed-file, scheduled, manual, and release validation. The private workspace's independent suite passes after removing public App roots from its runner path. Evidence [Testing and validation](../../../../development/maintain-and-release/testing.md) [Architecture](../../../../development/build-apps/architecture.md) [App development](../../../../development/build-apps/app-development.md) Known limitations and follow-up MATLAB process-global function caches cannot replace package-boundary source review, so the static sibling-call guard remains required. Hidden GUI tests remain bounded semantic checks rather than substitutes for manual interaction and visual assessment."},{"title":"Base-MATLAB image compatibility","url":"history/records/2026/07/LK-20260713-base-matlab-image-compatibility.html","kind":"history","section":"history","keywords":"LK-20260713-base-matlab-image-compatibility 2026-07-13 45 feat compatible labkit.image labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app Base-MATLAB image compatibility","text":"Base-MATLAB image compatibility id: LK-20260713-base-matlab-image-compatibility date: 2026-07-13 sequence: 45 type: feat compatibility: compatible component: `labkit.image` | `1.1.0 -> 1.2.0` component: `labkit_DICPostprocess_app` | `1.3.4 -> 1.3.5` component: `labkit_DICPreprocess_app` | `1.3.5 -> 1.3.6` component: `labkit_FocusStack_app` | `1.4.7 -> 1.4.8` component: `labkit_ImageEnhance_app` | `1.5.6 -> 1.5.7` component: `labkit_ImageMatch_app` | `1.5.6 -> 1.5.7` scope: Base-MATLAB image compatibility Context Several image workflows appeared toolbox-optional but still called Image Processing Toolbox functions for conversion, grayscale luminance, resizing, smoothing, alignment, or warping. Development machines with the toolbox hid those calls, so a nominally supported Base MATLAB installation could fail only after the user entered a particular workflow. Decision and rationale Own the essential image operations used by LabKit and make their Base MATLAB paths the tested behavior. Optional toolbox acceleration could remain only when the call was explicit, a repository-owned fallback existed, and parity tests protected the app-consumed result. Changes `labkit.image` `1.1.0 -> 1.2.0` `labkit_DICPreprocess_app` `1.3.5 -> 1.3.6` `labkit_DICPostprocess_app` `1.3.4 -> 1.3.5` `labkit_FocusStack_app` `1.4.7 -> 1.4.8` `labkit_ImageEnhance_app` `1.5.6 -> 1.5.7` `labkit_ImageMatch_app` `1.5.6 -> 1.5.7` Added `labkit.image.toDouble` and `labkit.image.toLuma`, and replaced hard Image Processing Toolbox calls in shared image facade code and image-app workflow paths with base-MATLAB implementations. DIC preprocessing now uses a toolbox-free phase-correlation translation path for automatic alignment and a base-MATLAB rigid warp for control-point alignment. DIC postprocessing, Focus Stack, Image Enhance, and Image Match now use app-local or facade-owned image normalization, resizing, smoothing, and luma helpers instead of requiring toolbox functions. Added a project hygiene guardrail that rejects unguarded toolbox image helper calls under `apps/` and `+labkit/`, while still allowing explicit optional toolbox paths with fallbacks. User and data impact DIC alignment and overlays, Focus Stack, Image Enhance, and Image Match could run without Image Processing Toolbox. Users with the toolbox retained compatible workflows, while CI now exercised the installation that previously failed late and silently. Compatibility and migration Existing app workflows and exported schemas are preserved. Optional toolbox acceleration paths remain allowed only when a base-MATLAB fallback is present. Validation The commit expanded DIC, Focus Stack, Image Enhance, Image Match, and image- facade unit suites and added `ToolboxDependencyGuardrailTest` to detect new unguarded calls. GUI layout tests covered the affected DIC workflows. The exact historical command was not recorded. Evidence Mainline commit `bcd5f51f`. Known limitations and follow-up Base MATLAB compatibility does not mean every optional accelerated algorithm is numerically identical by construction. Where a toolbox branch affects scientific values, representative parity and idempotency tests remain required until the repository-owned implementation fully replaces it."},{"title":"Batch Crop assigns state to workflow capabilities","url":"history/records/2026/07/LK-20260716-batch-crop-structure.html","kind":"history","section":"history","keywords":"LK-20260716-batch-crop-structure 2026-07-16 101 refactor compatible labkit_BatchImageCrop_app Image Measurement App structure","text":"Batch Crop assigns state to workflow capabilities id: LK-20260716-batch-crop-structure date: 2026-07-16 sequence: 101 type: refactor compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.2 -> 1.7.3` scope: Image Measurement scope: App structure Context Batch Crop split static metadata across separate files, spread project creation, validation, migration, and session construction through a generic lifecycle package, and placed source decoding, crop-task operations, geometry, scale calibration, export planning, and transient caches together under `+appState`. The App also retained an unused scale-unit adapter and a startup callback whose only job duplicated the shared source-adjacent output-folder default. Decision and rationale Use the compact Runtime V2 definition and one project contract, then assign remaining helpers to the workflow capability that owns their meaning. Crop geometry, scale calibration, and export planning are real App complexity and remain App-owned; generic lifecycle and state buckets are structural debt and are removed. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderers, and optional capabilities in `definition.m`. Concentrated durable creation, validation, and the version-1 upgrade in the single `projectSpec.m` entry; root `createSession.m` rebuilds transient state and lazily loads the selected source. Replaced embedded image data and direct path fields with canonical source records and `sourceId` task references. Assigned file reconstruction to `+sourceFiles`, task operations to `+cropTasks`, coordinate and cache behavior to `+cropGeometry`, calibration semantics to `+scaleCalibration`, and export state to `+resultFiles`. Removed the generic lifecycle/state packages, separate metadata files, redundant startup callback, and unused scale-unit wrapper. User and data impact Multiple crop tasks per source, draggable and resizable ROIs, zoom-preserving preview edits, rotation, edge-continuous padding, pixel and physical crop modes, per-source calibration, scale bars, manifests, and exports keep their existing behavior. Empty launch no longer computes an output folder; importing sources still selects the source-adjacent default. Developers can now find each calculation and state transition under its owning workflow concept without learning a generic App state package. Compatibility and migration Version-1 saved projects upgrade automatically to schema version 2. The migration discards reconstructible embedded pixels, creates portable source records for legacy paths, and preserves crop-task settings. Runtime source reconciliation handles moved or missing required files. Validation Focused unit tests cover project creation, validation and migration, source reading, crop geometry, scale planning, export planning, and manifest output. The hidden GUI workflow loads an image, manipulates the crop workflow, and exports a synthetic crop through the real Runtime callbacks. Evidence [Batch Image Crop](../../../../apps/image-measurement/batch-crop/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Image Library](../../../../libraries/image/README.md) Known limitations and follow-up Automated GUI tests do not replace manual judgment of ROI pointer feel, resampling quality, scale-reference placement, or export suitability for a particular quantitative workflow. Other Apps that still use generic lifecycle or state packages remain scheduled for the same ownership review."},{"title":"Batch Crop file workflow feedback","url":"history/records/2026/06/LK-20260628-batch-crop-file-workflow-feedback.html","kind":"history","section":"history","keywords":"LK-20260628-batch-crop-file-workflow-feedback 2026-06-28 14 feat compatible labkit.ui labkit_BatchImageCrop_app Batch Crop file workflow feedback","text":"Batch Crop file workflow feedback id: LK-20260628-batch-crop-file-workflow-feedback date: 2026-06-28 sequence: 14 type: feat compatibility: compatible component: `labkit.ui` | `3.0.1 -> 3.1.0` component: `labkit_BatchImageCrop_app` | `1.2.0 -> 1.3.0` scope: Batch Crop file workflow feedback Context In a multi-file Batch Crop session, the window and preview did not consistently show which list item supplied the current image and crop settings. Decision and rationale Make the selected file part of the shared window-title context and update Batch Crop feedback whenever selection changes. Keep the title derived from the file panel so the app does not maintain a second selection label. Changes `labkit.ui` `3.0.1 -> 3.1.0` Batch Crop `1.2.0 -> 1.3.0` Added selected-file title context. Improved Batch Crop file workflow feedback. User and data impact The active filename became visible while reviewing or editing a crop task, reducing the chance of applying settings to the wrong image. Crop geometry and exported data were unchanged. Compatibility and migration Batch Crop tasks and exports kept their existing format. The change affected file-selection context and feedback in the window only. Validation Commit `61e8edd3` updated shared title-context and Batch Crop GUI workflow coverage. Evidence Main commit `61e8edd3`. Known limitations and follow-up Later file-panel work extended the same title context and append behavior to the rest of the app fleet."},{"title":"Binding-only controls and default resource cleanup","url":"history/records/2026/07/LK-20260717-binding-and-resource-defaults.html","kind":"history","section":"history","keywords":"LK-20260717-binding-and-resource-defaults 2026-07-17 122 refactor compatible labkit.ui labkit_VideoMarker_app Runtime V2 action registration Runtime V2 resource ownership","text":"Binding-only controls and default resource cleanup id: LK-20260717-binding-and-resource-defaults date: 2026-07-17 sequence: 122 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.5 -> 7.4.6` component: `labkit_VideoMarker_app` | `1.5.4 -> 1.5.5` scope: Runtime V2 action registration scope: Runtime V2 resource ownership Context Runtime V2 already supported controls whose complete behavior is writing one bound state path, but Video Marker still registered an empty action for its skeleton-preset selector. The private resource registry also had a default cleanup policy, while the injected App service required every call to supply a fourth cleanup function. Video Marker therefore kept a second empty function for its plain decoded-video resource. Decision and rationale Use the existing binding-only contract for state-only controls and expose the resource registry's existing default cleanup through the injected service. Apps should declare an action only when a user event has workflow behavior beyond committing the bound value. Plain structs and values require no App-owned cleanup placeholder. Changes Removed Video Marker's empty skeleton-preset change action and left the dropdown as a direct session binding. Made the injected `services.resources.set(scope,id,value)` form select the Runtime's default cleanup, while retaining the optional fourth custom cleanup function. Removed Video Marker's empty video-resource cleanup function. Added Runtime GUI coverage for binding-only state commits and resources registered without a custom cleanup. User and data impact There is no workflow, scientific, project-data, or saved-file change. Selecting a skeleton preset still updates the session immediately, and **Use preset** still performs the only durable skeleton edit. Video cache replacement and session cleanup retain the same observable behavior. Compatibility and migration The change is source-compatible. Existing four-argument resource registration continues to use the supplied cleanup function. No project migration is required. Validation The focused Runtime V2 GUI test exercises both service forms and proves that a binding without `Event` commits state without invoking an App action. Video Marker structure and targeted behavior tests cover the simplified definition. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents bound controls and injected resource ownership. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) describes the preset and decoded-video resource behavior. Known limitations and follow-up Other bound events still require App actions when they normalize values, invalidate results, update previews, or log workflow changes. They should be reviewed by behavior rather than removed merely because their handlers are short."},{"title":"CIC consolidates its project contract without losing lazy loading","url":"history/records/2026/07/LK-20260716-cic-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-cic-project-spec 2026-07-16 87 refactor compatible labkit_CIC_app Electrochemistry Project lifecycle","text":"CIC consolidates its project contract without losing lazy loading id: LK-20260716-cic-project-spec date: 2026-07-16 sequence: 87 type: refactor compatibility: compatible component: `labkit_CIC_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context CIC split static metadata across three files and its first-version project across a generic lifecycle package. Its session factory also encodes an important performance policy: restore only the first source for preview rather than parsing an entire large batch. Decision and rationale Consolidate product metadata in `definition.m` and durable schema behavior in `projectSpec.m`. Keep `createSession.m` separate and explicit because lazy source decoding is real transient reconstruction and a user-visible performance boundary, not lifecycle boilerplate. Changes Moved command metadata, version, update date, and requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved lazy preview restoration to one package-root session factory. Removed separate metadata files and the generic lifecycle package. Kept pulse detection, CIC formulas, units, limits, plots, and exports unchanged. User and data impact Launch, source selection, large-batch behavior, computation, save/load, and CSV exports behave unchanged. The App structure is smaller while the reason for its lazy session factory is now documented next to the code. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing CIC projects require no migration. Validation Unit tests cover the project contract, CIC calculation, units, failed rows, CSV schema, and presentation. The hidden GUI workflow covers real launch, source selection, calculation, save/load, and export through Runtime V2. Evidence [CIC](../../../../apps/electrochemistry/cic/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The session currently reads `reference.originalPath` directly. That internal portable-reference representation will be replaced by a simple framework source-path service across all Apps rather than patched only in CIC."},{"title":"CIC uses Runtime-owned source and workflow mechanics","url":"history/records/2026/07/LK-20260716-cic-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-cic-runtime-source-reconciliation 2026-07-16 112 refactor compatible labkit_CIC_app Electrochemistry App structure Runtime adoption","text":"CIC uses Runtime-owned source and workflow mechanics id: LK-20260716-cic-runtime-source-reconciliation date: 2026-07-16 sequence: 112 type: refactor compatibility: compatible component: `labkit_CIC_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context CIC intentionally registers paths before decoding them so large batches remain lazy, but it still generated, appended, and deleted portable source records in App code. It also wrote directly to `session.workflow.logLines`, requiring its session factory to reproduce Runtime-owned empty workflow and view buckets. Those details increased the amount of framework structure an App maintainer had to understand without expressing CIC analysis behavior. Decision and rationale CIC continues to own the ordered lazy path set, current-index policy, deferred decoding, scientific options, and result workflow. It now passes that ordered path set to `services.project.reconcileSources` after add, removal, and clear operations. Runtime preserves matching identities and allocates new unique ones. Analysis messages use `services.workflow.log`, the same service as other workflow messages. `createSession` returns only CIC-specific selection and decoded-cache state; Runtime creates missing transient buckets before actions run. Changes Removed App-local source-ID generation and source append helpers. Reconciled registered paths through the Runtime project service. Routed analysis messages through the Runtime workflow service. Removed empty workflow and view boilerplate from `createSession`. Added GUI checks for stable and unique identities across batch add, save, reopen, removal, and re-addition. Advanced the CIC App version to 1.4.4. User and data impact Lazy decoding, file order, selected preview, analysis values, plots, exports, logs, and project files retain their behavior. Source identities remain stable for retained files and collision-free for newly registered files. Scientific calculations and output schemas are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing source records and their identities are preserved when reopened. Validation The CIC hidden GUI workflow covers three-source lazy registration, selection, whole-batch recomputation, export, save, clear, reopen, remove, re-add, source identity, viewport refresh, and final clear. Focused calculation/view, App structure, version, documentation, and history tests cover the surrounding contracts. Evidence [Charge-Injection Capacity](../../../../apps/electrochemistry/cic/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up CSC and VT Resistance still own similar source registration helpers. Their result indexing and lazy-load behavior must be checked independently before adopting the Runtime service."},{"title":"CSC consolidates its product and project contracts","url":"history/records/2026/07/LK-20260716-csc-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-csc-project-spec 2026-07-16 88 refactor compatible labkit_CSC_app Electrochemistry Project lifecycle","text":"CSC consolidates its product and project contracts id: LK-20260716-csc-project-spec date: 2026-07-16 sequence: 88 type: refactor compatibility: compatible component: `labkit_CSC_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context CSC split product metadata across three files and its durable schema across a generic lifecycle package even though it has no historical payload migration. The additional entry points did not own distinct electrochemical behavior. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because reloading CV/CT curves and restoring file/cycle selection is genuine transient work. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved decoded curve and selection restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Kept integration formulas, sign splitting, scan-rate handling, area normalization, plots, and export schemas unchanged. User and data impact Launch, source and cycle selection, save/load, calculations, plots, and both export paths behave unchanged. The durable contract is now readable in one place without hiding scientific behavior in the framework. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing CSC projects require no migration. Validation Unit tests cover CT/CV calculations, edge-cycle policy, normalization, result tables, export schemas, project/session contracts, and presentation. The hidden GUI workflow covers real launch, file/cycle selection, calculation, save/load, plot overlays, and exports. Evidence [CSC](../../../../apps/electrochemistry/csc/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Decoded source restoration still reaches into portable-reference internals. That cross-App framework boundary remains scheduled for one shared source-path service rather than an App-specific wrapper."},{"title":"CSC delegates decoded-source identity to Runtime V2","url":"history/records/2026/07/LK-20260716-csc-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-csc-runtime-source-reconciliation 2026-07-16 113 refactor compatible labkit_CSC_app Electrochemistry App structure Runtime adoption","text":"CSC delegates decoded-source identity to Runtime V2 id: LK-20260716-csc-runtime-source-reconciliation date: 2026-07-16 sequence: 113 type: refactor compatibility: compatible component: `labkit_CSC_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context CSC eagerly decodes accepted CV/CT files and keeps decoded item order aligned with durable source order. Its action file nevertheless generated source IDs, appended records, and deleted records by index. The session factory also declared empty workflow and view buckets already guaranteed by Runtime V2. Those mechanics were framework persistence responsibilities rather than CSC curve-selection or scientific behavior. Decision and rationale Continue using successfully decoded item order as the App-owned source order, then reconcile durable records through `services.project.reconcileSources` after additions, removals, and clearing. Runtime preserves records for retained paths and allocates collision-free IDs for new paths. CSC continues to own decoding, active file and curve selection, plot defaults, calculation, exports, and failure messages. Return only the CSC-specific selection fields and decoded item cache from `createSession`; Runtime supplies missing transient buckets. Changes Removed App-local source-ID generation and source append helpers. Reconciled source records from the decoded item list. Replaced the growing failure array with a single first-failure record while retaining per-file workflow logs. Removed empty workflow and view session boilerplate. Added GUI checks for canonical buckets and stable, unique identities across add, save, reopen, removal, and re-addition. Advanced the CSC App version to 1.4.4. User and data impact File order, selected curve, plot resets, CSC calculations, reload, exports, logs, and saved projects retain their behavior. Retained source identities no longer depend on App-local append/delete logic. Scientific values and result schemas are unchanged. Compatibility and migration The current project payload remains version 1 and needs no migration. Existing portable records retain their IDs when the corresponding paths remain loaded. Validation The CSC hidden GUI workflow covers two-file decoding, file and cycle selection, plot changes, calculation display, export, save, clear, reopen, remove, re-add, source identity, and final plot clearing. Focused CSC calculation/view and repository contract tests cover the remaining behavior. Evidence [Charge-Storage Capacity](../../../../apps/electrochemistry/csc/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up VT Resistance remains the last electrochem batch App with the same local source-ID and append pattern and requires a separate behavior audit."},{"title":"CSC export and viewport policy","url":"history/records/2026/07/LK-20260703-csc-export-and-viewport-policy.html","kind":"history","section":"history","keywords":"LK-20260703-csc-export-and-viewport-policy 2026-07-03 35 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app CSC export and viewport policy","text":"CSC export and viewport policy id: LK-20260703-csc-export-and-viewport-policy date: 2026-07-03 sequence: 35 type: feat compatibility: compatible component: `labkit.ui` | `4.0.0 -> 4.1.0` component: `labkit_DICPostprocess_app` | `1.3.2 -> 1.3.3` component: `labkit_DICPreprocess_app` | `1.3.2 -> 1.3.3` component: `labkit_ChronoOverlay_app` | `1.3.2 -> 1.3.3` component: `labkit_CIC_app` | `1.3.4 -> 1.3.5` component: `labkit_CSC_app` | `1.3.4 -> 1.3.6` component: `labkit_EIS_app` | `1.3.2 -> 1.3.3` component: `labkit_VTResistance_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.6.4 -> 1.6.5` component: `labkit_CurvatureMeasurement_app` | `1.3.2 -> 1.3.3` component: `labkit_FLIRThermal_app` | `1.2.3 -> 1.2.4` component: `labkit_FocusStack_app` | `1.4.3 -> 1.4.4` component: `labkit_ImageEnhance_app` | `1.5.3 -> 1.5.4` component: `labkit_ImageMatch_app` | `1.5.3 -> 1.5.4` component: `labkit_NerveResponseAnalysis_app` | `1.3.2 -> 1.3.3` component: `labkit_ResponseReviewStats_app` | `1.3.2 -> 1.3.3` component: `labkit_RHSPreview_app` | `1.3.2 -> 1.3.3` component: `labkit_ECGPrint_app` | `1.3.3 -> 1.3.4` scope: CSC export and viewport policy Context CSC could export the selected cycle, but comparing activation and stability across a recording required all cycles in one dataset. In parallel, app layouts made their own assumptions about minimum window size and scrollable content, so the same control panel could behave differently in a small viewport. Decision and rationale Add an explicit all-cycle CSC export without changing the existing selected- cycle result. Define viewport behavior in the UI contract so each app declares its content needs while the framework decides when scrolling or minimum sizing is necessary. Changes `labkit.ui` `4.0.0 -> 4.1.0` All supported apps received aligned patch bumps. Added CSC all-cycle export. Added viewport policy support and aligned app contracts with the UI 4.x line. User and data impact CSC users could export every cycle for downstream comparison while retaining the existing focused export. Across the workbench, small windows followed one scrolling and sizing policy instead of clipping controls according to the app that happened to create them. Compatibility and migration The selected-cycle CSC export remained available and the all-cycle table was a new option. Existing app definitions inherited the default viewport behavior until they declared more specific needs. Validation Commit `a69829c6` added a dedicated CSC export suite, extended the CSC GUI layout test, and added workbench viewport coverage. Evidence Main commit `a69829c6`. Known limitations and follow-up All-cycle CSV output was a separate export choice rather than a new session format. Later CSC work refined column naming and voltage/current organization."},{"title":"Callback-safe native layout synchronization","url":"history/records/2026/07/LK-20260721-callback-safe-layout-synchronization.html","kind":"history","section":"history","keywords":"LK-20260721-callback-safe-layout-synchronization 2026-07-21 152 fix compatible labkit.app App Framework Hidden GUI validation","text":"Callback-safe native layout synchronization id: LK-20260721-callback-safe-layout-synchronization date: 2026-07-21 sequence: 152 type: fix compatibility: compatible component: `labkit.app` | `1.2.3 -> 1.2.4` scope: App Framework scope: Hidden GUI validation Context The App SDK synchronizes native layout before calculating pixel-dependent plot geometry and while publishing busy, startup, close-prompt, and popout views. A full MATLAB `drawnow` also executes pending callbacks, which can introduce unrelated UI or timer work during those internal rendering operations. Decision and rationale Keep the required native graphics synchronization, but defer callbacks while the framework is calculating or publishing its own view. Long-running interactive launcher and profiler waits continue to process callbacks so users can close their windows. Changes Use callback-deferred graphics updates for equal-scale fitting and fixed-aspect preview geometry. Use callback-deferred updates while the native App runtime publishes busy, startup, failure, close-prompt, and popout-style state. Verify equal-scale EIS fitting, preview-canvas layout, and native runtime lifecycle behavior with hidden GUI tests. User and data impact Apps retain the same layouts and fitted limits while framework rendering no longer processes unrelated pending callbacks mid-operation. Projects, source records, results, and exports are unchanged. Compatibility and migration This compatible patch does not alter the public API or require project/result migration. Validation Focused hidden-GUI EIS equal-axis, Figure Studio preview, and native adapter lifecycle checks pass locally. The complete platform matrix remains the final validation for native GUI scheduling behavior. Evidence MATLAB documents that full `drawnow` both updates figures and processes pending callbacks, while the `nocallbacks` forms defer those callbacks. The framework uses callback-deferred synchronization only where it must read settled native geometry or publish internal view state. Known limitations and follow-up Automated hidden GUI checks do not substitute for manual visual and interaction assessment on supported MATLAB desktop platforms."},{"title":"Canonical role-based source collections","url":"history/records/2026/07/LK-20260716-canonical-role-based-sources.html","kind":"history","section":"history","keywords":"LK-20260716-canonical-role-based-sources 2026-07-16 73 refactor compatible labkit_RHSPreview_app labkit_NerveResponseAnalysis_app project payload schema source roles and relinking","text":"Canonical role-based source collections id: LK-20260716-canonical-role-based-sources date: 2026-07-16 sequence: 73 type: refactor compatibility: compatible component: `labkit_RHSPreview_app` | `1.4.0 -> 1.4.1` component: `labkit_NerveResponseAnalysis_app` | `1.4.0 -> 1.4.1` scope: project payload schema scope: source roles and relinking Context RHS Preview and Nerve Response Analysis stored external files in separate app-specific fields such as `rhsSource`, `filterSources`, and `protocolSource`. The records already carried explicit roles, but Runtime V2 could only discover the canonical `project.inputs.sources` collection during portable save/load and missing-file relinking. Decision and rationale Store every external dependency in one canonical collection and select records by their app-owned role. The framework remains domain-neutral, while each app continues to distinguish preview recordings, filter recordings, filter JSON, and optional protocols without duplicating persistence mechanics. Changes Advanced both project payload schemas from version 1 to version 2. Added ordered migrations that combine the former role-specific fields without changing source record contents or order. Added app-local role selection and replacement operations used consistently by lifecycle creation, actions, presenters, exports, and validation. Kept at most one primary recording/protocol per relevant app while allowing RHS Preview to retain an ordered collection of filter recordings. User and data impact Project reopen, result provenance, and future missing-file recovery can now see all selected dependencies. Preview, filtering, event detection, CAP metrics, and output formats are unchanged. Compatibility and migration Existing version 1 payloads remain readable and preserve every source record. The old fields are removed only from the migrated in-memory project; saving writes the version 2 canonical collection. Validation Focused unit tests verify role-preserving migrations and project versions. Focused GUI workflows verify RHS indexing, multiple filter-file management, project save/reopen, filter analysis, export, and transient cache rebuilding. Evidence [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) documents its three source roles. [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) documents filter/protocol restoration and relinking. [App Framework](../../../../framework/README.md) defines the canonical project source collection. Known limitations and follow-up This change makes every source discoverable but does not yet rebase relative paths at the final project save destination. That framework serialization change is tracked as the next persistence batch."},{"title":"Canonical single-source app projects","url":"history/records/2026/07/LK-20260716-canonical-single-source-projects.html","kind":"history","section":"history","keywords":"LK-20260716-canonical-single-source-projects 2026-07-16 72 refactor compatible labkit_GaitAnalysis_app labkit_CurvatureMeasurement_app labkit_ECGPrint_app labkit_ResponseReviewStats_app project payload schema source relinking","text":"Canonical single-source app projects id: LK-20260716-canonical-single-source-projects date: 2026-07-16 sequence: 72 type: refactor compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.0 -> 2.0.1` component: `labkit_CurvatureMeasurement_app` | `1.4.0 -> 1.4.1` component: `labkit_ECGPrint_app` | `1.4.0 -> 1.4.1` component: `labkit_ResponseReviewStats_app` | `1.4.0 -> 1.4.1` scope: project payload schema scope: source relinking Context The Runtime V2 project contract discovers external files through the `project.inputs.sources` collection. Four single-source apps instead stored their record under a singular `project.inputs.source` field. Their own actions could open that path, but framework save/load relinking could not discover it. Decision and rationale Use the same canonical source collection for every single-source app. The plural field is a collection even when it currently contains zero or one record, so persistence and relinking do not need app-specific field knowledge. Changes Moved Gait Analysis, Curvature Measurement, ECG Print, and Response Review Stats source ownership to `project.inputs.sources`. Advanced each payload schema by one version and added the corresponding ordered migration. Updated action, presenter, session, validation, export, and manual references to the canonical collection. Corrected the Gait family overview to describe its current strict Video Marker project input instead of retired generic MAT/table inputs. User and data impact New saves expose their source records to the common portable-project resolver. Scientific calculations, UI choices, annotations, parameters, and result values are unchanged. Compatibility and migration Existing payloads remain readable. Loading copies the former singular field to the canonical collection and removes the retired field. A later save writes the new payload version; source record contents are not transformed. Validation Focused unit tests verify each field migration, current project version, and existing calculations. Focused hidden-GUI tests verify that each app still launches and follows its source workflow with the renamed durable field. Evidence [App Framework](../../../../framework/README.md) defines the canonical source collection used by project persistence. [Gait Analysis](../../../../apps/gait/gait-analysis/README.md), [Curvature Measurement](../../../../apps/image-measurement/curvature/README.md), [ECG Print](../../../../apps/wearable/ecg-print/README.md), and [Response Review Stats](../../../../apps/neurophysiology/response-review-stats/README.md) describe their upgraded project behavior. Known limitations and follow-up This record covers single-source apps. Multi-role neurophysiology projects are audited separately because preserving source roles requires a richer migration than renaming one field."},{"title":"Chrono Overlay adopts one version-aware project migration entry","url":"history/records/2026/07/LK-20260716-chrono-overlay-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-chrono-overlay-project-spec 2026-07-16 86 refactor compatible labkit_ChronoOverlay_app Electrochemistry Project lifecycle","text":"Chrono Overlay adopts one version-aware project migration entry id: LK-20260716-chrono-overlay-project-spec date: 2026-07-16 sequence: 86 type: refactor compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context Chrono Overlay split product metadata across three files and represented its only payload upgrade as a separately named `migrateProjectV1ToV2.m` file in a generic lifecycle package. Adding another schema version would have added yet another file and exposed migration sequencing to the App definition. Decision and rationale Make `definition.m` the product declaration and `projectSpec.m` the sole durable-schema entry. Its `Migrate(project, fromVersion)` callback owns App semantics for one version step; Runtime V2 owns the loop, version increment, validation after every step, and unsupported-envelope handling. Changes Consolidated project creation, validation, and migration in one project spec. Replaced the per-version migration file with one version-aware local function. Moved transient DTA reconstruction to a package-root session factory. Removed separate requirements, version, and generic lifecycle files. Kept DTA parsing, pulse-gap alignment, interpolation, plots, and exports unchanged. User and data impact Launch, plotting, save/load, v1 project upgrade, and CSV export behavior remain unchanged. Maintainers now add future schema cases in one ordered migration entry instead of editing both definition wiring and file inventories. Compatibility and migration Version-1 payloads still remove the obsolete decoded `inputs.items` field so portable source records remain the only durable input. Version-2 payloads are unchanged and need no migration. Validation Unit tests call the new migration entry with a synthetic v1 payload and verify validation, session reconstruction, pulse alignment, interpolation, and presenter output. The hidden GUI workflow covers real launch, source loading, plotting, and export through the consolidated definition. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Only v1-to-v2 is currently required. Future cases remain App-owned in the same function; they must not bypass the framework's stepwise validation loop."},{"title":"Chrono Overlay removes App-owned source identity bookkeeping","url":"history/records/2026/07/LK-20260716-chrono-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-chrono-runtime-source-reconciliation 2026-07-16 111 fix compatible labkit_ChronoOverlay_app Electrochemistry App structure Project identity","text":"Chrono Overlay removes App-owned source identity bookkeeping id: LK-20260716-chrono-runtime-source-reconciliation date: 2026-07-16 sequence: 111 type: fix compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Project identity Context Chrono Overlay generated a new source ID from the current record count. After removing an earlier source, adding another file could therefore reuse an ID still owned by a retained record. Runtime validation correctly rejects duplicate IDs, but the App should not have been maintaining this persistence invariant itself. The App also duplicated source append/removal helpers and empty transient workflow/view buckets already supplied by Runtime V2. Decision and rationale After every successful add, removal, or clear operation, derive the ordered path list from the decoded items and delegate durable-record reconciliation to `services.project.reconcileSources`. Chrono Overlay continues to own decoding, pulse-gap alignment, item order, selection, logs, and failure wording. Runtime owns stable identity reuse and collision-free allocation. Only App-specific selection and decoded cache data are returned by `createSession`; Runtime canonicalizes the remaining transient buckets. Changes Removed App-local source record append, removal, and count-based ID logic. Reconciled source records from the accepted decoded item order. Removed empty workflow and view boilerplate from the session factory. Replaced the dynamically growing failure list with one first-failure record while retaining per-file log messages. Added a GUI regression sequence that removes the first of two sources, adds it again, checks unique IDs, saves, clears, and reopens the project. Advanced the Chrono Overlay App version to 1.4.4. User and data impact Normal file loading, ordering, selection, plots, exports, and saved project payloads retain their behavior. The remove-then-add sequence no longer risks a duplicate source ID and failed callback. Persistence IDs remain internal and do not change curve values or scientific output. Compatibility and migration The current payload remains version 2 and no new migration is needed. Existing records retain their IDs when their paths remain in the project. Validation The Chrono Overlay hidden GUI workflow covers two-file loading, selection, remove and re-add, unique identity, export, save, clear, reopen, option redraw, and final plot clearing. Focused unit, structure, version, documentation, and history tests protect the calculation and product contracts. Evidence [Chrono Overlay](../../../../apps/electrochemistry/chrono-overlay/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up CIC, CSC, and VT Resistance still have equivalent App-local source identity helpers and require their own ordering and behavior audits before conversion."},{"title":"Clean-room cross-platform CI","url":"history/records/2026/07/LK-20260717-clean-room-cross-platform-ci.html","kind":"history","section":"history","keywords":"LK-20260717-clean-room-cross-platform-ci 2026-07-17 131 ci compatible project validation and release automation","text":"Clean-room cross-platform CI id: LK-20260717-clean-room-cross-platform-ci date: 2026-07-17 sequence: 131 type: ci compatibility: compatible scope: project validation and release automation Context The previous MATLAB workflow combined ordinary CI, scheduled reports, release validation, and raw tag creation. Its optional Base MATLAB job also mixed static source checks with installed-product dependency analysis, even though that analysis can only report products present on the runner. The design made it difficult to tell which evidence was required and did not exercise every candidate outside the developer's operating system. Decision and rationale Make a Toolbox-free MATLAB installation the baseline for all required CI behavior. Let one CI workflow run complete headless and hidden-GUI tasks on Linux, macOS, and Windows. Keep coverage as an explicit local report. Release creation remains a developer-initiated process after manual App validation, requires a successful main-push CI run for the exact commit, and stops at a draft for final human review. This directly tests the promised runtime instead of trying to infer the same fact from a runner with optional products installed. Changes Replaced the monolithic MATLAB workflow with one ordinary/reusable CI workflow and one manual release workflow. Made cross-platform Base MATLAB headless and hidden-GUI validation mandatory for pull requests and main pushes, and required exact same-commit CI evidence before release. Removed the installed-product dependency-analysis task and all CI Toolbox installation. Kept static known-Toolbox call detection and representative shadowed-helper workflows inside the ordinary headless suite. Kept coverage out of CI because it has no failure threshold and repeats the non-GUI tests; maintainers can still request the local report explicitly. Replaced raw Git-ref creation with a validated annotated tag, tag-blob launcher staging, remote asset verification, and a draft GitHub Release. User and data impact No App runtime, calculation, project, or saved-data behavior changes. CI may now expose operating-system, path, case-sensitivity, layout, or missing- dependency failures that a configured development machine did not reveal. Compatibility and migration Maintainers should use `Continuous Integration` for required checks, `buildtool coverage` for an on-demand local report, and the manual `Release` workflow only after interactive App validation. The retired `buildtool baseMatlab` task has no compatibility alias because clean Base MATLAB execution is now the environment of every required build task rather than a separate partial check. Validation The CI policy contract checks trigger separation, read-only ordinary permissions, three-platform matrices, absence of `products:` installation, public build-task use, timeouts, diagnostics, exact-sha CI evidence, manual release confirmation, tag-blob asset generation, and draft-only publication. Build-task and Toolbox guardrails verify the simplified catalog and retained static/fallback checks. Evidence `.github/workflows/ci.yml` and `release.yml` define the executable pipeline. `tests/cases/contract/project/ci/CiValidationPolicyGuardrailTest.m` protects the new boundaries. Known limitations and follow-up Hidden GUI tests cannot drive native dialogs or judge visual and pointer quality. Developer-led manual App testing therefore remains mandatory before starting a release. CI also detects only Toolbox calls reached by tests or covered by the maintained static-call set; temporary product use still needs its declared fallback, idempotency, and parity evidence."},{"title":"Close guards and caught-exception diagnostics","url":"history/records/2026/06/LK-20260630-close-guards-and-caught-exception-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260630-close-guards-and-caught-exception-diagnostics 2026-06-30 21 feat compatible labkit.ui labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Close guards and caught-exception diagnostics","text":"Close guards and caught-exception diagnostics id: LK-20260630-close-guards-and-caught-exception-diagnostics date: 2026-06-30 sequence: 21 type: feat compatibility: compatible component: `labkit.ui` | `3.2.6 -> 3.2.7` component: `labkit_DICPostprocess_app` | `1.2.1 -> 1.2.2` component: `labkit_BatchImageCrop_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.3.5 -> 1.3.6` component: `labkit_CurvatureMeasurement_app` | `1.2.1 -> 1.2.2` component: `labkit_FocusStack_app` | `1.2.2 -> 1.2.3` component: `labkit_FocusStack_app` | `1.2.3 -> 1.2.4` component: `labkit_ImageEnhance_app` | `1.3.2 -> 1.3.3` component: `labkit_ImageMatch_app` | `1.3.2 -> 1.3.3` component: `labkit_ImageMatch_app` | `1.3.3 -> 1.3.4` component: `labkit_NerveResponseAnalysis_app` | `1.2.3 -> 1.2.4` component: `labkit_ResponseReviewStats_app` | `1.2.2 -> 1.2.3` component: `labkit_RHSPreview_app` | `1.2.1 -> 1.2.2` component: `labkit_ECGPrint_app` | `1.2.0 -> 1.2.1` scope: Close guards and caught-exception diagnostics Context Many app callbacks caught an exception, showed its message, and returned. That protected the UI but discarded the stack and callback context needed to diagnose the failure. Image apps also tracked unsaved or incomplete work in slightly different ways when deciding whether a window could close. Decision and rationale Report caught exceptions to the existing debug trace before presenting the user-facing error, and promote the file-index and close-guard mechanics that were shared by several image apps. Preserve app ownership of what counts as dirty or incomplete work. Changes `labkit.ui` `3.2.6 -> 3.2.7` DIC, Batch Crop, Curvature, Focus Stack, Image Match, neurophysiology apps, and ECG Print patch bumped for diagnostics or close-guard work. Reported caught app-runner exceptions through framework debug diagnostics. Promoted file-entry index helpers. Connected dirty/incomplete workflow state to close guards. User and data impact An app could still recover from a failed load, calculation, or export, while its debug report retained the exception evidence. Closing an image workflow with unfinished state produced a consistent warning instead of silently discarding work. Scientific results and saved schemas were unchanged. Compatibility and migration Existing app state and result files remained valid. Closing an incomplete workflow gained an additional confirmation, and debug reports gained exception details. Validation The exception-reporting commit expanded app/library compatibility checks across the affected runners. The close-guard commit added public-surface and compatibility coverage for promoted file indices and app close behavior. Exact historical commands were not recorded. Evidence Main commits `c0028a81` and `a81853ef`. Known limitations and follow-up Runtime V2 later replaced direct debug-log and close-guard calls with lifecycle and diagnostic services, but retained the requirement that caught exceptions remain observable."},{"title":"Consistent electrochemistry batch analysis","url":"history/records/2026/07/LK-20260713-electrochem-batch-consistency.html","kind":"history","section":"history","keywords":"LK-20260713-electrochem-batch-consistency 2026-07-13 46 fix compatible labkit_CIC_app labkit_VTResistance_app Consistent electrochemistry batch analysis","text":"Consistent electrochemistry batch analysis id: LK-20260713-electrochem-batch-consistency date: 2026-07-13 sequence: 46 type: fix compatibility: compatible component: `labkit_CIC_app` | `1.3.7 -> 1.3.8` component: `labkit_VTResistance_app` | `1.3.7 -> 1.3.8` scope: Consistent electrochemistry batch analysis Context CIC and VT Resistance could retain per-file derived values from different control settings, and CIC could sample outside the recorded time range while displaying a delay without units. Decision and rationale Treat analysis controls as one batch contract and recompute every file before display/export so rows cannot silently mix stale and current parameters. Changes Recompute all loaded files when shared controls change and once more before CSV export. Label CIC delay in microseconds, reject out-of-range sampling, and export the area and delay used for each result. Added family regression coverage and audited sibling electrochem apps. User and data impact Batch rows now represent one consistent area, delay, pulse-detection, resistance-window, and voltage-mode configuration. Invalid delay choices fail instead of extrapolating a misleading value. Compatibility and migration CIC CSV adds trailing `Area_cm2` and `Delay_us` columns. Existing columns keep their names and order; VT Resistance exports remain schema-compatible. Validation Electrochem unit and GUI tests cover batch recomputation, display units, out-of-range handling, and export-time refresh. Evidence The guarded calculations and export builders are app-owned. Commit `67ea2286` introduced the consistent batch-analysis and export behavior. Known limitations and follow-up The fix enforces internal batch consistency but does not choose scientifically appropriate area, delay, or resistance windows for the user."},{"title":"Core, neurophysiology, and ECG project validation ownership","url":"history/records/2026/07/LK-20260716-core-neuro-ecg-project-validation.html","kind":"history","section":"history","keywords":"LK-20260716-core-neuro-ecg-project-validation 2026-07-16 121 refactor compatible labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app remaining public App project schemas App maintenance cost","text":"Core, neurophysiology, and ECG project validation ownership id: LK-20260716-core-neuro-ecg-project-validation date: 2026-07-16 sequence: 121 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.6 -> 0.2.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.4 -> 1.4.5` component: `labkit_ResponseReviewStats_app` | `1.4.4 -> 1.4.5` component: `labkit_RHSPreview_app` | `1.4.4 -> 1.4.5` component: `labkit_ECGPrint_app` | `1.4.4 -> 1.4.5` scope: remaining public App project schemas scope: App maintenance cost Context The remaining public project validators still surrounded their own style, recording-role, metric-window, protocol, ECG, and result rules with repeated canonical bucket and source-record format checks. Decision and rationale Complete the public-App validation ownership split. Runtime owns canonical bucket structs and source-record internals. Each App continues to require its source collection and to enforce its own fields, role/cardinality rules, numeric limits, tables, annotations, and results. Changes Removed 66 net lines of repeated framework structure checks from Figure Studio, the three neurophysiology Apps, and ECG Print. Preserved Nerve Response Analysis's fixed source ID-role pairs and RHS Preview's recording/protocol/filter roles and cardinalities. Added focused project-spec contracts for default acceptance, missing-source rejection, and the retained neurophysiology role rules. User and data impact Valid projects, migrations, signal calculations, previews, and exports are unchanged. Framework-shape failures now have one Runtime owner; domain-schema failures remain attributable to the App that defines them. Compatibility and migration No payload format changed and no migration is required. All supported saved projects continue through their existing centralized `projectSpec.m` migration entry. Validation Focused project-spec tests cover all five defaults and missing-source cases, plus invalid Nerve and RHS source roles. Representative unit and hidden-GUI workflows cover project reconstruction, preview, analysis, and export behavior. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Neurophysiology Apps](../../../../apps/neurophysiology/README.md) [ECG Print](../../../../apps/wearable/ecg-print/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Project validator boilerplate is now removed across the public App fleet. Further App simplification must target independently evidenced action, presentation, or cache patterns rather than weakening domain validation."},{"title":"Cross-platform validation exits cleanly after settled plot fitting","url":"history/records/2026/07/LK-20260721-cross-platform-validation-exit.html","kind":"history","section":"history","keywords":"LK-20260721-cross-platform-validation-exit 2026-07-21 151 fix compatible labkit.app App Framework Continuous Integration","text":"Cross-platform validation exits cleanly after settled plot fitting id: LK-20260721-cross-platform-validation-exit date: 2026-07-21 sequence: 151 type: fix compatibility: compatible component: `labkit.app` | `1.2.2 -> 1.2.3` scope: App Framework scope: Continuous Integration Context Windows headless validation completed every test and report but retained a progress heartbeat resource until the GitHub Actions step timed out. Hosted macOS GUI validation also measured equal-scale plot limits before native layout had settled, producing a platform-specific false failure. Decision and rationale Make lifecycle ownership explicit at the test-runner boundary and measure equal-data-unit limits only after MATLAB has applied pending native layout. This keeps CI diagnostics available while ensuring successful validation exits promptly, without changing the App SDK surface or EIS workflow meaning. Changes Explicitly release each progress plugin and its heartbeat timer when a test runner returns, including nested focused runs. Verify focused runs leave no tagged heartbeat timer behind. Settle native layout before equal-data-unit fitting and assert EIS scaling from the post-action axes allocation. User and data impact Users receive the same transient equal-scale EIS view, now calculated against the displayed axes allocation. Projects, source records, results, and exports are unchanged. Compatibility and migration The patch release is compatible with all 1.x App SDK requirements. No project or result migration is required. Validation Focused nested-runner coverage verifies heartbeat cleanup after return. Focused EIS GUI coverage verifies independent fitting and equal-scale limits. GitHub Actions will re-run the complete platform matrices after delivery. Evidence The timed-out Windows job reported all 468 headless tests and `** Finished headless` more than twenty minutes before the action timeout. The macOS GUI artifact consistently identified the pre-layout equal-scale pixel assertion. Known limitations and follow-up The Windows hosted runner remains the final verification environment for process teardown. Manual visual assessment of plot layout remains outside hidden-GUI automation."},{"title":"Cross-version dialogs and Batch Crop duplication keep portable shapes","url":"history/records/2026/07/LK-20260729-cross-version-dialog-and-duplicate-repair.html","kind":"history","section":"history","keywords":"LK-20260729-cross-version-dialog-and-duplicate-repair 2026-07-29 162 fix compatible labkit.app labkit_BatchImageCrop_app labkit_FigureStudio_app Windows MATLAB file-dialog filter compatibility Batch Crop duplicate task shape alignment diagnostic text fallback Figure Studio R2022b graphics export and layout R2022b-safe diagnostic attribute identifiers oldest/latest MATLAB CI compatibility matrix","text":"Cross-version dialogs and Batch Crop duplication keep portable shapes id: LK-20260729-cross-version-dialog-and-duplicate-repair date: 2026-07-29 sequence: 162 type: fix compatibility: compatible component: `labkit.app` | `2.0.1 -> 2.0.3` component: `labkit_BatchImageCrop_app` | `1.9.0 -> 1.9.1` component: `labkit_FigureStudio_app` | `0.7.0 -> 0.7.1` scope: Windows MATLAB file-dialog filter compatibility scope: Batch Crop duplicate task shape alignment scope: diagnostic text fallback scope: Figure Studio R2022b graphics export and layout scope: R2022b-safe diagnostic attribute identifiers scope: oldest/latest MATLAB CI compatibility matrix Context MATLAB R2024b on Windows rejected string-valued cells passed to `uiputfile` while exporting a diagnostic ZIP. Batch Crop also inserted a duplicate with vertical concatenation even when the native file-list source binding supplied a row struct array, so duplicating from a multi-image list could fail with a dimension mismatch. Decision and rationale The private MATLAB adapter now uses its existing dialog-filter normalizer for ordinary input and output dialogs. Batch Crop normalizes its four parallel task collections to columns at the duplicate callback boundary before inserting the new task. These are the narrow owners of the platform and App shape contracts; no new public API or saved-project migration is needed. The App SDK also canonicalizes text at the portable-source, source-resolution, and native file-label boundaries: a character vector is one scalar value, while string and cellstr collections become columns where the owning contract requires a collection. Apps continue to own ordinary file reading and writing; an architecture guardrail only prevents them from bypassing `fileList` or `CallbackContext` for native file and folder dialogs. Diagnostic export now treats a ZIP failure as a degraded but recoverable Runtime outcome. It writes the already-sanitized in-memory session records to one text file beside the chosen destination when possible, or in MATLAB's user-writable temporary folder otherwise. This remains private Runtime behavior because Apps neither select nor interpret diagnostic formats. Figure Studio now selects graphics primitives at its result-file boundary. R2025a and newer retain `exportgraphics` figure padding; supported older releases use `print`, including its SVG device. Invisible export figures are anchored before their drawable pixel size is assigned, then layout converges against the native renderer's measured text extents. A final figure-coordinate fit catches older Windows renderers that update ruler-label extents only after accepting the offscreen geometry. If R2022b clamps that invisible figure to the desktop despite the requested size, Figure Studio recomputes the data frame from the accepted drawable canvas after reserving measured label and tick insets. Font size remains unchanged, while the constrained plot frame yields enough real outer whitespace for ordinary ruler labels. Titles and axis labels are included explicitly because R2022b does not consistently expose those ruler decorators through descendant text discovery. The compatibility floor also owns identifier limits. Newer MATLAB releases allow names longer than R2022b's 63-character ceiling, so the diagnostic attribute-key contract and repository test names use the R2022b ceiling rather than the development runtime's `namelengthmax`. Changes Converted native input/output dialog filters to character-cell tables before calling `uigetfile` or `uiputfile`. Canonicalized scalar character paths and IDs before collection reshaping, including Unicode, duplicate, drive-letter, and UNC-shaped regression cases. Added a repository guardrail requiring Apps to use the SDK-owned native file dialog boundaries without introducing a generic public file API. Made Batch Crop duplicate insertion preserve one column-aligned task, source, image-cache, and path-cache row per list entry. Added focused regression coverage for the R2024b-compatible filter value and a multi-image row-shaped duplicate state. Expanded every validation profile from one fixed MATLAB release to the supported R2022b floor and the latest release available to the official setup action. macOS remains a latest-release platform sentinel. Grouped the three validation profiles by platform and release so each matrix entry installs MATLAB once while each profile retains a fresh batch session. Bound the R2022b entries to Ubuntu 22.04 and Windows Server 2022 runner images supported by that release instead of testing unsupported latest operating-system images. Gave Linux MATLAB sessions an X virtual framebuffer so hidden-GUI tests do not depend on release-specific behavior when no display server is active. Added a single-file diagnostic fallback for native dialog, ZIP staging, ZIP creation, and publish failures, with the resulting path reported to the user. Kept Figure Studio raster and SVG export operational on R2022b, where `exportgraphics` has neither figure padding nor SVG support. Reserved measured label and tick margins in hidden export canvases. The normal path preserves the configured plot frame and a typography-derived outer margin. Older Windows `print` releases reserve one hardcopy line box plus a half-em white margin; if Windows refuses the requested outer size, the final fit reduces only the rendered data frame to keep that space inside the accepted canvas. Aligned diagnostic struct keys and repository function names with the R2022b 63-character identifier ceiling, eliminating warnings hidden by the newer development runtime's larger limit. Prepared MATLAB's conventional personal folder on Linux runners so repeated reset-path probes do not emit environment-only `userpath` warnings. Retained deterministic production-rendered PNGs below each test profile's `visual-evidence/` folder, included them in the existing platform artifact, and kept automated assertions over the same files. Replaced three disconnected raw test summaries with one platform-level compatibility report that explains each profile, success caveats, actionable failure diagnostics, slow-test signals, and exact artifact ownership. Cancelled or skipped profiles now report an incomplete conclusion instead of mislabeling missing evidence as a compatibility failure. User and data impact Windows users can choose a destination for diagnostic ZIP bundles, projects, screenshots, plots, and other Runtime output dialogs. Batch Crop can duplicate an image task from a multi-image list without changing source images or prior task settings. If diagnostic ZIP export still fails, users receive the path to a readable plain-text fallback containing the surviving sanitized session history. Figure Studio keeps complete ruler labels and styled figure output across the supported MATLAB release boundary. A Windows desktop that refuses the requested outer canvas can constrain the rendered data-frame size without changing the saved project setting. Compatibility and migration The repair is backward compatible. Public App SDK signatures, Batch Crop project payload version 3, scientific crop calculations, and exported result schemas are unchanged. Validation Focused headless specifications cover the Batch Crop duplicate callback and the native dialog-filter value, diagnostic ZIP-to-text degradation, and Figure Studio result layout. The ruler-label regression uses the production PNG export path and checks its bottom and left whitespace rather than treating hidden-window geometry as export evidence. Hidden-GUI workflow coverage also performs a real PNG export. CI runs every full profile on Linux, macOS, and Windows against R2022b and the latest available MATLAB release, while macOS runs the latest release. The R2022b jobs use fixed supported runner images; latest MATLAB uses current runner images, and Linux GUI validation runs with a virtual display. The JUnit artifact records MathWorks qualification and exception diagnostics instead of a generic failure placeholder, and dependency-free Python regressions protect both successful and failed summary layouts. Documentation consistency and the final changed-file gate cover the integrated version and history updates. Evidence The repair branch records focused MATLAB test artifacts and the final changed-file validation result. The supplied sanitized log identifies the original `MATLAB:catenate:dimensionMismatch` and `MATLAB:character:CellsMustContainChars` failures. Known limitations and follow-up Automated tests cannot prove the appearance and interaction quality of the native Windows save dialog. A manual MATLAB R2024b Windows check remains the final platform acceptance boundary."},{"title":"Curvature consolidates migration and analysis state","url":"history/records/2026/07/LK-20260716-curvature-project-structure.html","kind":"history","section":"history","keywords":"LK-20260716-curvature-project-structure 2026-07-16 97 refactor compatible labkit_CurvatureMeasurement_app Image Measurement App structure","text":"Curvature consolidates migration and analysis state id: LK-20260716-curvature-project-structure date: 2026-07-16 sequence: 97 type: refactor compatibility: compatible component: `labkit_CurvatureMeasurement_app` | `1.4.2 -> 1.4.3` scope: Image Measurement scope: App structure Context Curvature split static metadata across files, exposed four generic lifecycle functions including a filename-encoded migration, and stored five computation tasks/results under `+appState`. Session restoration also read the nested portable source path directly. Decision and rationale Use one project specification with a version-aware migration entry. Runtime V2 calls `Migrate(project,fromVersion)` for each missing version; Curvature keeps only the actual version-1 transformation. Put fit and length task/result shapes beside the analysis functions that consume them. Changes Consolidated product metadata and requirements in `definition.m`. Consolidated project creation, validation, and the v1 source migration in `projectSpec.m`. Moved decoded-image reconstruction to root `createSession.m`. Moved all five generic state helpers to `+analysisRun`. Removed separate metadata and lifecycle files. Replaced direct portable-reference access with the Runtime path accessor. Kept managed anchor editing, scale calibration, curve fitting, length measurement, overlay rendering, and exports unchanged. User and data impact Opening current or version-1 projects, tracing/removing anchors, calibrating scale, fitting curvature, measuring length, and exporting results behave unchanged. Developers now find every durable-version rule in one file and every numerical result/task next to the numerical implementation. Compatibility and migration The current payload stays at version 2. Version-1 `inputs.source` is still moved to `inputs.sources` exactly once. No new payload migration is introduced. Validation Unit tests cover current project/session construction, v1 migration, fit and length numerics, physical/pixel scales, densification, invalid points, result tables, and deterministic task fingerprints. The hidden GUI suite covers image loading, anchor editing guidance, managed interactions, viewport behavior, scale calibration, results, and save/load. Evidence [Curvature Measurement](../../../../apps/image-measurement/curvature/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other versioned Apps still use filename-per-step migrations and will move to one project entry during their own behavior audits."},{"title":"DIC Postprocess adopts one product and project declaration","url":"history/records/2026/07/LK-20260716-dic-postprocess-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-dic-postprocess-project-spec 2026-07-16 83 refactor compatible labkit_DICPostprocess_app DIC Project lifecycle","text":"DIC Postprocess adopts one product and project declaration id: LK-20260716-dic-postprocess-project-spec date: 2026-07-16 sequence: 83 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.1 -> 1.4.2` scope: DIC scope: Project lifecycle Context DIC Postprocess split static product metadata across `definition.m`, `requirements.m`, and `version.m`, while its version-1 durable schema occupied three generic `+appLifecycle` files. None of those separations represented a separate scientific capability or independently evolving contract. Decision and rationale Make `definition.m` the product declaration and `projectSpec.m` the only durable-schema entry. Keep `createSession.m` separate because it restores file-backed strain, image, mask, and prepared overlay caches that must not be serialized into the project. Changes Moved command metadata, version, update date, and facade requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved transient reconstruction to one explicitly named package-root entry. Removed the two metadata files and generic lifecycle package. Replaced the stale GUI-free manual example with an executable call matching the real input structure, complete parameter contract, and three outputs. Kept all calculation, parameter, action, presentation, and export behavior unchanged. User and data impact Ncorr loading, overlay preparation, statistics, save/load, and exports behave unchanged. Maintainers can now find the complete product contract in one file and the complete durable schema in one adjacent file. Compatibility and migration The command, project ID, payload version, fields, defaults, validation rules, and source records are unchanged. Existing DIC Postprocess projects require no migration. Validation Unit tests exercise Ncorr loading, cached session reconstruction, numerical preparation, summary/export behavior, and presentation. The hidden GUI flow launches the real App and generates overlays and a summary through Runtime V2. Evidence [DIC Postprocess](../../../../apps/dic/dic-postprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The action file remains a meaningful workflow boundary because it coordinates input selection, calculation, export, diagnostics, and dialogs. Its naming and possible smaller capability boundaries will be compared across the DIC family instead of changed in isolation."},{"title":"DIC Preprocess consolidates product and project declarations","url":"history/records/2026/07/LK-20260716-dic-preprocess-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-dic-preprocess-project-spec 2026-07-16 84 refactor compatible labkit_DICPreprocess_app DIC Project lifecycle","text":"DIC Preprocess consolidates product and project declarations id: LK-20260716-dic-preprocess-project-spec date: 2026-07-16 sequence: 84 type: refactor compatibility: compatible component: `labkit_DICPreprocess_app` | `1.5.1 -> 1.5.2` scope: DIC scope: Project lifecycle Context DIC Preprocess split static product metadata across three files and split one version-1 durable schema across a generic lifecycle package. That structure made a maintainer traverse six declarations before reaching any registration, crop, or mask behavior. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the only durable-schema entry. Keep `createSession.m` explicit because decoding source images and replaying alignment/crop steps reconstructs transient cache state rather than defining persistence. Changes Moved command metadata, version, update date, and facade requirements into the definition. Consolidated project defaults and validation behind one project spec. Moved transient image and edit replay to one package-root session factory. Removed the metadata files and generic lifecycle package. Kept registration, crop, mask, history, managed interactions, export, and scientific parameter behavior unchanged. User and data impact Manual and automatic alignment, zoom-preserving point/ROI editing, crop and mask history, project save/load, and exports behave unchanged. The durable and transient state boundaries are now visible from two adjacent files. Compatibility and migration The command, project ID, payload version, fields, defaults, validation, source records, edit steps, and mask history are unchanged. Existing projects require no migration. Validation Unit tests cover project/session reconstruction, state history, registration, crop/mask calculations, source IO, exports, and presenter models. The hidden GUI workflow covers real launch, manual point matching, ROI crop, mask editing, viewport preservation, and generated outputs. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The `+appState` package mixes edit-history and mask-geometry capabilities. It is retained for this compatibility-preserving checkpoint and will be split or renamed by responsibility after its callers and tests are audited."},{"title":"DIC Preprocess replaces its generic state bucket","url":"history/records/2026/07/LK-20260716-dic-preprocess-state-capabilities.html","kind":"history","section":"history","keywords":"LK-20260716-dic-preprocess-state-capabilities 2026-07-16 85 refactor compatible labkit_DICPreprocess_app DIC App ownership","text":"DIC Preprocess replaces its generic state bucket id: LK-20260716-dic-preprocess-state-capabilities date: 2026-07-16 sequence: 85 type: refactor compatibility: compatible component: `labkit_DICPreprocess_app` | `1.5.2 -> 1.5.3` scope: DIC scope: App ownership Context The generic `+appState` package mixed three unrelated concepts: durable align/crop undo history, mask editing and undo, and a predicate over transient source-image cache state. The name exposed storage mechanics without helping a maintainer find the behavior they needed. Decision and rationale Group these operations by the capability that owns their invariants. `editHistory` owns align/crop snapshots and reset behavior, `maskEditing` owns mask canvas composition and undo snapshots, and `sourceFiles` owns the loaded image-pair predicate. None is promoted to LabKit because the project fields, undo limits, image semantics, and operation order are specific to DIC Preprocess. Changes Moved four edit-history operations into `+editHistory`. Moved five mask composition/history operations into `+maskEditing`. Moved the image-pair readiness predicate into `+sourceFiles`. Updated actions, presenters, preview requests, and direct state tests to use the capability paths. Removed the generic `+appState` package without adding an adapter. User and data impact All controls, calculations, undo limits, project fields, masks, alignment/crop history, and viewport behavior are unchanged. Code navigation now starts from the user capability rather than an undifferentiated state bucket. Compatibility and migration This is an app-internal ownership change. Project payloads and public GUI-free scientific APIs are unchanged, so saved projects require no migration. Validation State tests cover history trimming, reset/restore behavior, mask composition, and image-pair readiness through the new capability paths. The full DIC Preprocess unit and hidden GUI workflows verify the unchanged callers. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up `definitionActions.m` remains a large but cohesive workflow coordinator. Its callbacks will be compared with other interaction-heavy image Apps before any further extraction; file length alone is not a reason to create another technical package."},{"title":"DIC and Gait project validation ownership","url":"history/records/2026/07/LK-20260716-dic-gait-project-validation-ownership.html","kind":"history","section":"history","keywords":"LK-20260716-dic-gait-project-validation-ownership 2026-07-16 120 refactor compatible labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_GaitAnalysis_app DIC and Gait project schemas App maintenance cost","text":"DIC and Gait project validation ownership id: LK-20260716-dic-gait-project-validation-ownership date: 2026-07-16 sequence: 120 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.4 -> 1.4.5` component: `labkit_DICPreprocess_app` | `1.5.5 -> 1.5.6` component: `labkit_GaitAnalysis_app` | `2.0.5 -> 2.0.6` scope: DIC and Gait project schemas scope: App maintenance cost Context DIC Postprocess, DIC Preprocess, and Gait repeated Runtime's canonical project and source-record shape checks around their real domain constraints. The duplication made it harder to see which saved fields these Apps actually own. Decision and rationale Retain each App's required source collection and domain schema while relying on Runtime for canonical bucket structs and source-record internals. DIC continues to own alignment, crop, mask, summary, and parameter fields; Gait continues to own pose-analysis options, numeric limits, and result structure. Changes Removed 26 net lines of repeated framework structure checks from the three project specifications. Preserved every App-required source field and domain-specific validator. Added GUI-free DIC and Gait contracts that accept default projects and reject projects missing their required source collection. User and data impact Valid DIC and Gait projects, migrations, calculations, and exports are unchanged. Malformed framework structures still fail in Runtime and malformed domain fields still fail in the owning App. Compatibility and migration No payload format changed and no migration is required. Supported older projects continue through the same App-owned migration callbacks. Validation Focused project-spec tests cover default acceptance and missing-source rejection. Hidden GUI workflows cover representative DIC load/edit/analysis and Gait project load/step-analysis behavior. Evidence [DIC Apps](../../../../apps/dic/README.md) [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up This boundary cleanup does not change the scientific gait analysis or image registration algorithms. Their domain complexity remains App-owned."},{"title":"DIC uses framework-owned optional source slots","url":"history/records/2026/07/LK-20260716-dic-source-slots.html","kind":"history","section":"history","keywords":"LK-20260716-dic-source-slots 2026-07-16 93 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app DIC Runtime source boundary","text":"DIC uses framework-owned optional source slots id: LK-20260716-dic-source-slots date: 2026-07-16 sequence: 93 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.0 -> 7.4.1` component: `labkit_DICPostprocess_app` | `1.4.2 -> 1.4.3` component: `labkit_DICPreprocess_app` | `1.5.3 -> 1.5.4` scope: DIC scope: Runtime source boundary Context DIC Preprocess and Postprocess use stable semantic source slots such as `referenceImage`, `movingImage`, `maskImage`, and `dicMat`. Those slots are legitimately absent before the user selects a file. Each App therefore carried an identical `pathForId.m` wrapper that returned empty text for an unset slot and otherwise inspected the nested portable reference. The first source accessor required every requested ID to exist. Applying that rule would have preserved the duplicate wrapper instead of lowering App cost. Decision and rationale Make ID-based source lookup preserve the requested shape and return an empty string for a semantic slot that has not been added. Continue to reject malformed records and references. This matches ordinary presenter and session behavior without hiding corrupt project data. Changes Refined `sourcePaths(sources,ids)` for optional semantic slots. Removed both DIC `pathForId.m` wrappers and their duplicate schema knowledge. Migrated DIC loading, summaries, presenters, save defaults, and export paths to the Runtime accessor. Preserved alignment, crop, mask, anchor, rendering, calculation, and export behavior. User and data impact An empty DIC project still presents empty file controls and enables them as the workflow requires. Selected and relinked files load through the same paths. Developers no longer implement a source-ID lookup helper for each App. Compatibility and migration The accessor change relaxes one error case and is compatible within UI 7. No saved project shape changed and no DIC payload migration is required. Validation Runtime unit tests cover mixed present and absent IDs. DIC unit and hidden-GUI tests cover empty startup, file selection, point matching, ROI and mask edits, viewport-preserving interaction, project state, and result export. Evidence [DIC Preprocess](../../../../apps/dic/dic-preprocess/README.md) [DIC Postprocess](../../../../apps/dic/dic-postprocess/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other families still contain direct portable-reference reads. They will move to the same accessor before the repository-wide no-leak contract is enabled."},{"title":"DTA facade and app ownership boundaries","url":"history/records/2026/05/LK-20260529-dta-facade-and-app-boundaries.html","kind":"history","section":"history","keywords":"LK-20260529-dta-facade-and-app-boundaries 2026-05-29 2 refactor compatible historical project evolution","text":"DTA facade and app ownership boundaries id: LK-20260529-dta-facade-and-app-boundaries date: 2026-05-29 sequence: 2 type: refactor compatibility: compatible scope: historical project evolution Context The first extraction pass separated calculations from the imported GUIs, but the electrochem apps still depended on a broad internal package and shared session helpers. A new app could reuse a parser only by learning implementation details that had nothing to do with reading a DTA file. Decision and rationale Make DTA loading the reusable boundary and keep workflow decisions in the app that owns them. The public layer would load one file, discover files, or load a folder and return documented report structures. Plot labels, callback order, session state, result summaries, and export choices would remain app concerns. This distinction became an early form of LabKit's present architecture: a small library presents stable data operations, while an app owns the laboratory workflow assembled around those operations. Changes Added GUI-independent DTA loading and adopted it in the EIS and Chrono Overlay apps. Added discovery and folder-loading operations, including validation for empty selections, missing folders, and expected experiment kinds. Documented and tested the report schemas returned by batch operations. Moved EIS, Chrono Overlay, CSC, VT Resistance, and CIC out of the original `gamrywb` namespace and into app-owned entry points. Returned calculation details, labels, callback flow, and exports to their owning apps when they were not genuinely reusable. Introduced boundary tests to prevent UI, data, IO, analysis, and utility packages from growing through accidental cross-dependencies. User and data impact Electrochem users kept the same principal workflows, but file-loading failures became more explicit and batch loading gained a defined result report. App authors could use the DTA layer without starting a GUI or depending on the layout of an existing app. No laboratory file format or scientific result schema was intentionally changed by the ownership cleanup. Compatibility and migration The supported app commands replaced direct use of the old `gamrywb` package. Code that reached into that package's internal helpers needed to move either to the DTA facade or into the app that owned the behavior. Validation The period added DTA schema tests, empty- and missing-folder tests, architectural boundary checks, and behavior-focused calculation tests. The repository did not yet have the later unified MATLAB test command, so no single historical validation invocation is available. Evidence GUI-free loading facade `88b19851` and initial app adoption `789ef507`, `0ccc3ad6`. App ownership migration `1ee8e82d` through `1b6042bf`. Discovery and folder loading `c06946d9`, `82f4146d`. Report documentation and contract tests `669eea38`, `e04292c0`. Boundary and failure-handling work from `ad2b6c74` through `7506fa32`. Known limitations and follow-up The public surface was still organized around the first electrochem use cases. The next stage renamed the workbench, narrowed the package map further, and tested whether the same app-first approach could support image and biosignal workflows."},{"title":"Debug sample packs","url":"history/records/2026/07/LK-20260701-debug-sample-packs.html","kind":"history","section":"history","keywords":"LK-20260701-debug-sample-packs 2026-07-01 28 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Debug sample packs","text":"Debug sample packs id: LK-20260701-debug-sample-packs date: 2026-07-01 sequence: 28 type: feat compatibility: compatible component: `labkit.ui` | `3.3.1 -> 3.4.0` component: `labkit_DICPostprocess_app` | `1.2.4 -> 1.3.0` component: `labkit_DICPreprocess_app` | `1.2.2 -> 1.3.0` component: `labkit_ChronoOverlay_app` | `1.2.1 -> 1.3.0` component: `labkit_CIC_app` | `1.2.1 -> 1.3.0` component: `labkit_CSC_app` | `1.2.1 -> 1.3.0` component: `labkit_EIS_app` | `1.2.1 -> 1.3.0` component: `labkit_VTResistance_app` | `1.2.1 -> 1.3.0` component: `labkit_BatchImageCrop_app` | `1.5.1 -> 1.6.0` component: `labkit_CurvatureMeasurement_app` | `1.2.4 -> 1.3.0` component: `labkit_FLIRThermal_app` | `1.1.2 -> 1.2.0` component: `labkit_FocusStack_app` | `1.3.0 -> 1.4.0` component: `labkit_ImageEnhance_app` | `1.4.1 -> 1.5.0` component: `labkit_ImageMatch_app` | `1.4.1 -> 1.5.0` component: `labkit_NerveResponseAnalysis_app` | `1.2.4 -> 1.3.0` component: `labkit_ResponseReviewStats_app` | `1.2.3 -> 1.3.0` component: `labkit_RHSPreview_app` | `1.2.4 -> 1.3.0` component: `labkit_ECGPrint_app` | `1.2.2 -> 1.3.0` scope: Debug sample packs Context Debug reports could identify a failed callback, but reproducing the failure still depended on the original laboratory file and the user's exact sequence of actions. Those inputs were often unavailable to the person investigating the report and were unsuitable as permanent test fixtures. Decision and rationale Let every app create a small, synthetic sample pack that exercises its normal loading path without containing laboratory data. Store the generated inputs and expected output location beside the debug artifacts so a report can be replayed from one self-contained folder. Changes `labkit.ui` `3.3.1 -> 3.4.0` All supported apps moved into the `1.3.x`, `1.4.x`, `1.5.x`, or `1.6.x` debug-sample-pack lines. Added app-owned debug sample packs. Added debug artifact sample and output folders. User and data impact Debug mode gained a repeatable demonstration dataset for each supported app. These packs were synthetic and intended for diagnosis, not as substitutes for real experiment files or as reference scientific results. Compatibility and migration The sample packs were additive debug assets. Existing app inputs and outputs were unchanged, and no user file was copied into a pack. Validation Commit `279befbc` added coverage tests for every app family, focused tests for DIC, electrochem, image, RHS, and wearable sample writers, and layout checks that exercised the generated packs. Evidence Main commit `279befbc`. Known limitations and follow-up Sample packs were deliberately app-owned because each workflow knows which inputs make a useful reproduction. They do not contain or archive a user's original files."},{"title":"Debug workflows, launcher tools, and changelog governance","url":"history/records/2026/07/LK-20260707-debug-workflows-launcher-tools-and-changelog-governance.html","kind":"history","section":"history","keywords":"LK-20260707-debug-workflows-launcher-tools-and-changelog-governance 2026-07-07 39 feat compatible labkit_launcher labkit_launcher labkit_launcher labkit.ui labkit.ui labkit_DICPreprocess_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_FigureStudio_app Debug workflows, launcher tools, and changelog governance","text":"Debug workflows, launcher tools, and changelog governance id: LK-20260707-debug-workflows-launcher-tools-and-changelog-governance date: 2026-07-07 sequence: 39 type: feat compatibility: compatible component: `labkit_launcher` | `1.2.4 -> 1.2.5` component: `labkit_launcher` | `1.2.5 -> 1.2.6` component: `labkit_launcher` | `1.2.6 -> 1.2.7` component: `labkit.ui` | `5.0.0 -> 5.0.1` component: `labkit.ui` | `5.0.1 -> 5.0.2` component: `labkit_DICPreprocess_app` | `1.3.4 -> 1.3.5` component: `labkit_BatchImageCrop_app` | `1.6.6 -> 1.6.7` component: `labkit_FocusStack_app` | `1.4.5 -> 1.4.6` component: `labkit_FigureStudio_app` | `0.1.4 -> 0.1.5` scope: Debug workflows, launcher tools, and changelog governance Context The first complete debug sample packs exposed several false failures. A modal file chooser could be mistaken for a stalled callback, DIC could read an older ROI snapshot than the editor displayed, duplicate crops needed confirmation before export, and Focus Stack's folder workflow was hard to reach from its sample instructions. The launcher was also becoming a practical maintenance and distribution tool. Code Analyzer reports needed a durable home outside launcher source, individual apps needed an offline package format, and private app workspaces needed to remain discoverable without entering the public repository. Finally, the growing changelog needed to explain the direction of the project, not merely repeat tags and commit subjects. Decision and rationale Use v3.1.0 as a stabilization release for reproducible debug workflows. Treat native modal dialogs as active UI, read live editor state when it is newer than the stored snapshot, and make required gestures visible in the app. Move code analysis and packaging into dedicated tools, let the launcher invoke them, and keep private app discovery separate from public release contents. Changes Release tag `v3.1.0` `labkit_launcher` `1.2.4 -> 1.2.7` `labkit.ui` `5.0.1 -> 5.0.2` `labkit_FigureStudio_app` `0.1.4 -> 0.1.5` `labkit_DICPreprocess_app` `1.3.4 -> 1.3.5` `labkit_BatchImageCrop_app` `1.6.6 -> 1.6.7` `labkit_FocusStack_app` `1.4.5 -> 1.4.6` DIC Preprocess ROI mask export now reads the live ROI editor anchors when building a mask, so preview/save do not misreport a drawn ROI as empty when editor state is newer than the app state snapshot. DIC Preprocess keeps the double-click ROI anchor workflow and makes the double-click requirement explicit in the visible details text. Batch Image Crop duplicate tasks now redraw with finite preview overlay coordinates while still requiring users to confirm the duplicated crop center before export. Figure Studio quick PNG/JPG/SVG export actions use runtime-compatible handler signatures. Focus Stack exposes a direct `Choose folder` action for loading all supported images from a focus-stack folder. Debug trace diagnostics no longer write stalled-callback crash reports while a file chooser modal is active. Moved the launcher Code Analyzer scan into `tools/codecheck`, which writes timestamped JSON/HTML report pairs under `artifacts/code-check/` without overwriting earlier runs. Added launcher actions and a deployment tool that package one selected LabKit app into a standalone zip, either as source `.m` files or encoded `.p` files. Added launcher discovery for local private app workspaces under `private_apps/apps/` and roots named by `LABKIT_PRIVATE_APP_ROOTS`. Clarified the public changelog model as a project evolution map organized by reader-facing evolution entries, with release tags and commits kept as anchors and evidence rather than the primary structure. User and data impact The supplied debug packs could be followed without producing a false crash report or an apparently disabled workflow. DIC mask export used the anchors currently visible in the editor, duplicate crops remained blocked until their centers were confirmed, and Focus Stack offered a direct folder action. Maintainers gained timestamped HTML and JSON Code Analyzer reports. A selected app could be packaged for production or offline use without tests, unrelated apps, or repository metadata. Private apps could live beside the checkout, appear in the ordinary launcher, and retain their own repository and history. Compatibility and migration DIC ROI editing still uses double-click to add anchors; no interaction-mode migration is required. Existing file-panel image selection remains available in Focus Stack. Code Analyzer report consumers should read the timestamped `artifacts/code-check/matlab_code_issues_*.json` files. Full LabKit checkout installs are unchanged. Single-app packages can start through either the packaged launcher or the direct run file; P-code packages require MATLAB to run the generated `.p` files. Public apps, public releases, and public CI remain scoped to `apps/`. Validation PR #34 updated debug sample, ROI, crop, folder-loading, launcher-tool, deployment, and private-workspace tests before its squash merge. Tag `v3.1.0` points to release commit `9db01952`; the exact local command sequence was not recorded in this page. Evidence PR #34 squash merge `9db01952` and release tag `v3.1.0`. Known limitations and follow-up The first deployment tool packaged one app at a time. Multi-app bundles were added later. The changelog model introduced here was subsequently replaced by component-linked history pages generated with the documentation site."},{"title":"Declarative app runtime","url":"history/records/2026/07/LK-20260703-declarative-app-runtime.html","kind":"history","section":"history","keywords":"LK-20260703-declarative-app-runtime 2026-07-03 32 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Declarative app runtime","text":"Declarative app runtime id: LK-20260703-declarative-app-runtime date: 2026-07-03 sequence: 32 type: refactor compatibility: compatible component: `labkit.ui` | `3.4.4 -> 3.4.5` component: `labkit_DICPostprocess_app` | `1.3.0 -> 1.3.1` component: `labkit_DICPreprocess_app` | `1.3.0 -> 1.3.1` component: `labkit_ChronoOverlay_app` | `1.3.0 -> 1.3.1` component: `labkit_CIC_app` | `1.3.0 -> 1.3.1` component: `labkit_CSC_app` | `1.3.0 -> 1.3.1` component: `labkit_EIS_app` | `1.3.0 -> 1.3.1` component: `labkit_VTResistance_app` | `1.3.0 -> 1.3.1` component: `labkit_BatchImageCrop_app` | `1.6.1 -> 1.6.2` component: `labkit_CurvatureMeasurement_app` | `1.3.0 -> 1.3.1` component: `labkit_FLIRThermal_app` | `1.2.0 -> 1.2.1` component: `labkit_FocusStack_app` | `1.4.0 -> 1.4.1` component: `labkit_ImageEnhance_app` | `1.5.0 -> 1.5.1` component: `labkit_ImageMatch_app` | `1.5.0 -> 1.5.1` component: `labkit_NerveResponseAnalysis_app` | `1.3.0 -> 1.3.1` component: `labkit_ResponseReviewStats_app` | `1.3.0 -> 1.3.1` component: `labkit_RHSPreview_app` | `1.3.0 -> 1.3.1` component: `labkit_ECGPrint_app` | `1.3.1 -> 1.3.2` scope: Declarative app runtime Context Apps had adopted common shells and controls, but each runner still assembled callbacks, startup steps, refresh behavior, and debug hooks in its own order. Understanding an app meant reading a long construction procedure before reaching the scientific workflow. Decision and rationale Describe an app through a definition containing its layout, actions, state, startup, and rendering contracts, then let the runtime perform the common wiring. Keep action bodies, calculations, and result structures in the app so the definition exposes workflow structure without becoming a generic analysis language. Changes `labkit.ui` `3.4.4 -> 3.4.5` All supported apps received patch bumps. Migrated apps to declarative workflow runtime. User and data impact The visible workflows were intended to remain the same. For maintainers, each app gained a recognizable definition and lifecycle, while repeated setup moved out of its runner. Debug sample packs and focused app tests continued to call the same app-owned operations. Compatibility and migration Users kept the same app entry points and data files. App implementations moved to definitions and runtime actions; unsupported code that reached into old runner construction details needed to adopt the declarative APIs. Validation The migration changed 545 files and updated the affected app, layout, export, calculation, sample-pack, and package-boundary suites in the same commit. The commit message records final profiler and debug evidence; the exact local command was not preserved in this history. Evidence Main commit `568b3e9b`. Known limitations and follow-up The first declarative vocabulary still used the mixed `app`, `spec`, `view`, `tool`, and `diag` package names. The UI 4 and UI 5 passes refined how groups, plots, interactions, and lifecycle responsibilities were named."},{"title":"Default LabKit close protection","url":"history/records/2026/07/LK-20260709-default-labkit-close-protection.html","kind":"history","section":"history","keywords":"LK-20260709-default-labkit-close-protection 2026-07-09 43 fix compatible labkit.ui labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app Default LabKit close protection","text":"Default LabKit close protection id: LK-20260709-default-labkit-close-protection date: 2026-07-09 sequence: 43 type: fix compatibility: compatible component: `labkit.ui` | `5.0.2 -> 5.0.3` component: `labkit_FocusStack_app` | `1.4.6 -> 1.4.7` component: `labkit_ImageEnhance_app` | `1.5.5 -> 1.5.6` component: `labkit_ImageMatch_app` | `1.5.5 -> 1.5.6` scope: Default LabKit close protection Context Only apps that installed a dirty-state guard warned before closing. A newly created public or private app could therefore discard work silently until its author implemented and tested a separate guard. Repeated close shortcuts could also race with a modal confirmation. Decision and rationale Make close confirmation a default property of every framework-owned app window and remove the optional app-facing guard API. Keep one in-window prompt active at a time; a repeated close shortcut confirms that prompt instead of opening another one. Changes `labkit.ui` `5.0.2 -> 5.0.3` `labkit_FocusStack_app` `1.4.6 -> 1.4.7` `labkit_ImageEnhance_app` `1.5.5 -> 1.5.6` `labkit_ImageMatch_app` `1.5.5 -> 1.5.6` LabKit runtime figures now show an in-window confirmation prompt before any framework-owned app window closes, even when the app has not marked itself dirty. Removed the app-facing `labkit.ui.runtime.setCloseGuard` API and migrated existing app close-guard dirty checks to the framework default behavior. Repeating or holding the app close shortcut while the in-window prompt is active confirms the close. User and data impact Every public or private LabKit app asked before its window closed, even if the app had no custom dirty-state logic. Users gained a consistent protection against accidental closure at the cost of one confirmation step. Compatibility and migration Closing LabKit apps now requires one confirmation step by default. App code that calls `labkit.ui.runtime.setCloseGuard` must remove that call; close confirmation is framework-owned. Validation Commit `0c9f472b` expanded the UI busy-state GUI suite for initial close, cancel, confirmation, and repeated-shortcut behavior, then updated public- surface and app-compatibility checks. Evidence Mainline commit `0c9f472b`. Known limitations and follow-up This policy protected the window, not the semantic completeness of each saved project. Runtime V2 later combined default close handling with app-owned dirty state and lifecycle services."},{"title":"Destination-rebased source references","url":"history/records/2026/07/LK-20260716-destination-rebased-source-references.html","kind":"history","section":"history","keywords":"LK-20260716-destination-rebased-source-references 2026-07-16 74 fix compatible labkit.ui project serialization named save, explicit autosave, and recovery","text":"Destination-rebased source references id: LK-20260716-destination-rebased-source-references date: 2026-07-16 sequence: 74 type: fix compatibility: compatible component: `labkit.ui` | `6.0.4 -> 6.0.5` scope: project serialization scope: named save, explicit autosave, and recovery Context Runtime source records were created before an App knew its future project-file location, so they began with an empty `relativePath`. Project-envelope creation copied those records unchanged. As a result, the resolver supported relative references, but ordinary Runtime V2 saves did not actually generate them. Decision and rationale Rebase source references at the serialization boundary using the actual MAT destination. A named project, app-owned autosave, and framework recovery file can live in different folders, so each write must calculate its own relative path rather than reusing a path cached in live state. Changes Passed the real destination path into every Runtime V2 envelope writer. Copied the durable project and refreshed reference schema, relative path, original path, and filename immediately before serialization. Preserved additive app/reference fields while replacing standard path fields. Kept resolved absolute paths in live state so current-session readers do not need to interpret portable references. User and data impact A project and its source directory can now move together to another root or machine and reopen through the saved relative relationship even when the old absolute path no longer exists. Existing projects without a relative path remain readable through their original-path and relink fallbacks. Compatibility and migration The envelope and source-reference schema versions are unchanged. No payload migration is needed: the next named save, explicit autosave, or recovery write adds the destination-correct relative path. Validation The Runtime V2 project GUI test saves a source/project directory tree, verifies the generated relative path, moves the tree, and reloads from the new root. Video Marker verifies that its source-adjacent explicit autosave is rebased from the autosave destination. Existing atomic-save, relink, recovery, and additive field tests remain in the same focused suites. Evidence [Runtime and Data Model](../../../../framework/guides/runtime.md) explains when relative paths are created and how they are resolved. [UI Framework](../../../../framework/README.md) describes Runtime V2 project ownership and persistence. Known limitations and follow-up The portable-reference creation and resolution algorithms remain public in UI 6 for compatibility. They are implementation mechanics rather than App-facing workflow APIs and are reviewed with the remaining Runtime public surface for a single future major-boundary cleanup."},{"title":"Documentation navigation follows topic hierarchy","url":"history/records/2026/07/LK-20260717-hierarchical-documentation-navigation.html","kind":"history","section":"history","keywords":"LK-20260717-hierarchical-documentation-navigation 2026-07-17 132 docs compatible MATLAB documentation site navigation","text":"Documentation navigation follows topic hierarchy id: LK-20260717-hierarchical-documentation-navigation date: 2026-07-17 sequence: 132 type: docs compatibility: compatible scope: MATLAB documentation site navigation Context The documentation site exposed its major areas in the top bar, but each area's left sidebar was a flat list. Related app families, development tasks, and API packages were not visually distinguished, so readers could not readily see the path from an area to a topic and then to a specific page. Decision and rationale Render contextual hierarchy from the existing page and catalog metadata. App families, libraries, development tasks, and MATLAB packages become labeled sidebar branches. This preserves one structured source of navigation truth while making downstream topics visible from their area landing pages. Changes Grouped app navigation by family and indented app manuals below family pages. Grouped function, framework, and development navigation by topic. Grouped API siblings under their owning MATLAB package. Defined the second `nav` entry in `docs/site.json` as the branch label and ordered branches by their earliest page. Added responsive sidebar styling and renderer regression coverage for the hierarchy. User and data impact Readers can move from a top-level area to a topic branch and then to a specific manual without searching a flat list. Documentation content, URLs, MATLAB behavior, and scientific data are unchanged. Compatibility and migration Existing page URLs and top-level navigation remain compatible. Documentation authors should assign new multi-page topics a stable second-level `nav` label in `docs/site.json`. Validation The documentation renderer regression verifies Development topic groups, App family branches, and API package labels. `docsCheck` rebuilds and compares the complete generated site. Evidence The generated area landing pages contain labeled branch headings and parent/child link classes derived from tracked page and app catalog metadata. Known limitations and follow-up Single-page areas do not display an artificial empty branch. History records remain in the generated timeline because reproducing the full record set in the sidebar would make navigation less usable."},{"title":"Documentation rendering avoids repeated model capture","url":"history/records/2026/07/LK-20260717-documentation-render-performance.html","kind":"history","section":"history","keywords":"LK-20260717-documentation-render-performance 2026-07-17 130 perf compatible MATLAB documentation renderer","text":"Documentation rendering avoids repeated model capture id: LK-20260717-documentation-render-performance date: 2026-07-17 sequence: 130 type: perf compatibility: compatible scope: MATLAB documentation renderer Context Two project documentation tests each rebuild the complete tracked site. Profiling showed that most render time was not file generation or tree comparison: every inline Markdown fragment created an anonymous function that captured the complete documentation model before scanning links. Decision and rationale Pass the shared model and current page directly into token protection and dispatch code, image, and link rendering by token type. This preserves the renderer contract and generated bytes while avoiding thousands of large closure allocations. Changes Replaced per-fragment renderer closures with direct token dispatch. Retained the same code, image, link, emphasis, and nested inline-code behavior. Added a focused regression guard against restoring the measured closure pattern. User and data impact Generated HTML is byte-for-byte unchanged. Documentation rebuilds, consistency checks, and the launcher documentation action complete substantially sooner. Compatibility and migration `renderLabKitDocs`, `checkLabKitDocs`, `buildtool docs`, and `buildtool docsCheck` retain their existing syntax, outputs, and failure behavior. No migration is required. Validation On the same MATLAB R2025a host and complete `checkLabKitDocs` scenario, profiler total time decreased from 46.52 seconds to 9.66 seconds, approximately 79 percent. The after-profile run also completed the byte comparison against the tracked site. Evidence The focused renderer regression protects inline link behavior and the allocation fix. `docsCheck` independently rebuilds all narrative and API pages and compares every generated file. Known limitations and follow-up Absolute times vary by machine and filesystem. API help rendering is now the largest measured renderer-owned cost and should be changed only after a separate profile identifies a safe optimization."},{"title":"Documentation search ranks page intent and scopes history explicitly","url":"history/records/2026/07/LK-20260720-ranked-and-scoped-documentation-search.html","kind":"history","section":"history","keywords":"LK-20260720-ranked-and-scoped-documentation-search 2026-07-20 146 docs compatible Documentation system tools/docs/ site search","text":"Documentation search ranks page intent and scopes history explicitly id: LK-20260720-ranked-and-scoped-documentation-search date: 2026-07-20 sequence: 146 type: docs compatibility: compatible scope: Documentation system scope: tools/docs/ scope: site search Context The generated documentation search treated every term in a page title, body, and rendered component-history links as equivalent. A shared history record could therefore make an unrelated App page appear before the App named by a search. Readers also had no way to restrict a query to history records. Decision and rationale Keep component-history links visible on their related pages, but index history records as their own searchable documents. Give title and explicit keywords more weight than body text, then provide a visible section filter for readers who want Apps, APIs, history, or another documentation area. Changes Added section and keyword fields to generated search entries. Excluded rendered component-history link text from narrative page search bodies while retaining complete history-record search entries. Added deterministic field-weighted ranking, result excerpts, and section filtering to the offline-safe client search. Added regression coverage for search-entry separation, filters, and ranking contracts. User and data impact Searching a named App now favors that App and its family over unrelated pages that share a historical record. Readers can search only History when tracing a decision. No App behavior, calculations, projects, exports, or laboratory data changes. Compatibility and migration The generated site remains usable from file:// without network fetches. Existing documentation URLs, history records, and page-level change-history links remain unchanged. Validation Focused documentation search contract tests verify the generated index and client controls. Documentation rendering and consistency checks regenerate the tracked site and validate the history record. Evidence renderLabKitDocs now emits separated search fields. The client script ranks title, keyword, and body matches independently and filters by the generated section field. Known limitations and follow-up Search is intentionally dependency-free and uses exact lexical matching. Typo-tolerant suggestions can be considered later only if they preserve the current deterministic title-first ordering."},{"title":"Documentation structure becomes the navigation contract","url":"history/records/2026/07/LK-20260719-path-derived-documentation.html","kind":"history","section":"history","keywords":"LK-20260719-path-derived-documentation 2026-07-19 136 refactor compatible labkit_launcher Documentation renderer Documentation maintenance","text":"Documentation structure becomes the navigation contract id: LK-20260719-path-derived-documentation date: 2026-07-19 sequence: 136 type: refactor compatibility: compatible component: `labkit_launcher` | `1.5.1 -> 1.5.2` scope: Documentation renderer scope: Documentation maintenance Context The local HTML site duplicated documentation ownership across Markdown files, `site.json`, two catalog JSON files, and generated output. Moving or adding a page therefore required several coordinated edits. Markdown links also embedded many relative paths without a repository-owned way to repair links after a move. Decision and rationale Treat the `docs/` directory structure as the narrative navigation contract. Discover public Apps from `labkit_launcher(\"list\")`, associate each App with its unique path-conventional manual, and discover public API pages from complete MATLAB help contracts. Keep ordinary relative Markdown links so source pages remain useful in GitHub and add a checker that can repair a broken link only when its destination is unambiguous. The generated `site/` remains the offline and local HTML product. It is still produced exclusively by the repository renderer. Changes Removed `docs/site.json` and the App and API catalog JSON files. Reorganized development and framework guides into path-owned navigation groups and moved the API landing page to `docs/reference/README.md`. Made the renderer discover Markdown pages, public Apps, and complete public MATLAB help without duplicated registries. Added `maintainLabKitDocLinks` for checking and uniquely repairing relative Markdown links after file moves. Changed launcher documentation lookup to derive the selected App manual and generated HTML path from App discovery and documentation structure. Removed obsolete compatibility redirect pages instead of preserving legacy documentation routes. User and data impact Local HTML documentation and launcher documentation actions remain available. Contributors add or move a page by placing Markdown in the intended directory and running the link maintainer and renderer; no navigation catalog needs a matching edit. Scientific behavior and project data are unchanged. Compatibility and migration Existing current documentation links are rewritten to their new relative paths. Old generated documentation routes are intentionally retired and are not maintained as redirect pages. Rebuild `site/` after updating a checkout. Validation Focused documentation contract and renderer regression tests cover discovery, navigation, link repair, public API help, and launcher documentation lookup. The documentation checker validates the generated site after a clean local render. Evidence [Documentation Build Tools](../../../../development/tools/documentation.md) [Maintaining LabKit Documentation](../../../../development/maintain-and-release/documentation.md) [LabKit Launcher](../../../../apps/labkit-core/launcher/README.md) [API Reference](../../../../reference/README.md) Known limitations and follow-up Automatic link repair deliberately refuses ambiguous destinations. Authors must resolve those cases explicitly so the checker never guesses between pages with the same filename or title."},{"title":"Durable Video Marker source metadata","url":"history/records/2026/07/LK-20260716-video-marker-durable-metadata.html","kind":"history","section":"history","keywords":"LK-20260716-video-marker-durable-metadata 2026-07-16 70 feat compatible labkit_VideoMarker_app project payload schema downstream gait inputs","text":"Durable Video Marker source metadata id: LK-20260716-video-marker-durable-metadata date: 2026-07-16 sequence: 70 type: feat compatibility: compatible component: `labkit_VideoMarker_app` | `1.4.1 -> 1.5.0` scope: project payload schema scope: downstream gait inputs Context Video Marker previously kept frame rate, frame count, duration, and image size only in transient decoded-video state. Its MAT project preserved coordinates but a GUI-independent downstream analysis could not recover time semantics without reopening the original video or inventing a frame rate. Decision and rationale Make immutable decoded-video facts part of the durable Video Marker project. The portable source record continues to own the file path; the metadata record contains only finite numeric facts required to interpret saved annotations. Changes Advanced the Video Marker project payload from version 1 to version 2. Added durable frame count, frame rate, duration, height, and width. Populate metadata when a video or marker CSV is opened and seed rebuilt sessions from saved metadata before optional source decoding. Refresh durable metadata from the reopened source before an explicit **Save autosave**, so a loaded version 1 autosave upgrades in place. Validate metadata values and annotation/frame-count consistency. Added an ordered v1-to-v2 migration that recovers frame count from saved annotation coordinates and leaves unknowable source facts at zero. User and data impact New named projects and source-adjacent autosaves contain enough timing and geometry information for Gait Analysis to treat the MAT document as its authoritative input. Paths are not duplicated into the metadata record. Compatibility and migration Version 1 LabKit project envelopes remain readable. Opening one marks it for an upgrade; saving writes payload version 2. A frame rate absent from version 1 cannot be inferred from coordinates alone and remains zero until the source is opened by the current Video Marker app. Validation Video Marker unit tests verify metadata selection, absence of transient paths, frame-count recovery, payload validation, and mismatched annotation rejection. The existing GUI suite covers video opening, marker CSV import, project saves, and recovery loading. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the persisted fields and downstream use. Gait Analysis input tests exercise the current payload contract separately. Known limitations and follow-up Projects whose old payload never recorded a frame rate must reopen their source video before producing a fully self-describing current project. Opening that source through the saved reference and pressing **Save autosave** performs the upgrade without another location prompt."},{"title":"ECG Print uses one project contract","url":"history/records/2026/07/LK-20260716-ecg-print-structure.html","kind":"history","section":"history","keywords":"LK-20260716-ecg-print-structure 2026-07-16 106 refactor compatible labkit_ECGPrint_app Wearable App structure","text":"ECG Print uses one project contract id: LK-20260716-ecg-print-structure date: 2026-07-16 sequence: 106 type: refactor compatibility: compatible component: `labkit_ECGPrint_app` | `1.4.2 -> 1.4.3` scope: Wearable scope: App structure Context ECG Print kept product metadata in separate requirement and version functions, and split one durable schema across project creation, validation, migration, and session reconstruction files under a generic lifecycle package. Session and presentation code also read the Runtime-owned portable-reference structure directly. Decision and rationale Use the compact Runtime V2 App structure. The definition owns product metadata and optional capabilities, one project contract owns durable schema evolution, and one root session factory rebuilds decoded signal state. ECG-specific parsing, analysis, presentation, and result packages already express real workflow capabilities and remain App-owned. Changes Consolidated command identity, display metadata, version, requirements, layout, actions, presenter, renderer, and debug capability in `definition`. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved transient recording and analysis reconstruction to root `createSession` and reused that factory when clearing a failed decode. Replaced nested portable-reference reads with semantic `sourcePaths` lookup. Removed the generic lifecycle package and separate requirement/version files. Updated the App package structure guardrail so actions, presentation, project persistence, and transient sessions are optional capabilities; Apps that adopt root `projectSpec` cannot regress to split lifecycle metadata. User and data impact Recording import, parsing controls, channel selection, filtering, peak detection, segmentation, template construction, SNR measurements, plots, exports, manifests, project reopening, and user wording are unchanged. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still move their singular source record into the canonical source collection before validation. Runtime V2 now invokes the single migration entry and owns iteration to the current payload version. Current version-2 project files require no data transformation. Validation Focused GUI-free tests cover the migration callback, definition contract, import parsing, analysis products, presentation models, and result tables. The hidden GUI workflow covers launch, recording import, analysis, plots, both exports, project save, and session reconstruction after load. Project guardrails cover the compact optional-capability structure, embedded metadata, version/history ownership, and generated documentation consistency. Evidence [ECG Print](../../../../apps/wearable/ecg-print/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up Automated tests do not judge ECG morphology, peak quality, or the visual suitability of exported waveforms for a particular experiment. Remaining Runtime V2 App migrations and the stale agent guidance require separate review."},{"title":"EIS consolidates its product and project contracts","url":"history/records/2026/07/LK-20260716-eis-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-eis-project-spec 2026-07-16 89 refactor compatible labkit_EIS_app Electrochemistry Project lifecycle","text":"EIS consolidates its product and project contracts id: LK-20260716-eis-project-spec date: 2026-07-16 sequence: 89 type: refactor compatibility: compatible component: `labkit_EIS_app` | `1.4.1 -> 1.4.2` scope: Electrochemistry scope: Project lifecycle Context EIS split static product metadata across three files and one first-version project schema across a generic lifecycle package. Those files did not own separate impedance or plotting contracts. Decision and rationale Make `definition.m` the complete product declaration and `projectSpec.m` the sole durable-schema entry. Keep `createSession.m` explicit because restoring decoded ZCURVE items and source selection is genuine transient work. Changes Consolidated command metadata, version, update date, and requirements in the definition. Consolidated project defaults and validation behind one project spec. Moved decoded curve and selection restoration to a package-root session factory. Removed separate metadata files and the generic lifecycle package. Delayed log-scale assignment until filtered positive data establishes valid automatic limits, and corrected the GUI regression fixture to use a legal positive manual range while testing stale-zoom replacement. Kept impedance values, Nyquist/Bode axis semantics, log scaling, zoom behavior, and export schemas unchanged. User and data impact Launch, multi-file selection, plotting, zoom, save/load, and export behavior remain unchanged. Project and transient state ownership are now adjacent and explicit. Compatibility and migration The project remains version 1 with identical fields, defaults, validation, and source records. Existing EIS projects require no migration. Validation Unit tests cover ZCURVE parsing, axis values, Nyquist mode, log scaling, project/session contracts, presentation, and export columns. The hidden GUI workflow covers real launch, file loading, plot interaction, save/load, and export. Evidence [EIS](../../../../apps/electrochemistry/eis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Source restoration still consumes portable-reference internals. The planned shared source-path service will remove that leak across all Apps together."},{"title":"EIS delegates source identity reconciliation to Runtime V2","url":"history/records/2026/07/LK-20260716-eis-runtime-source-reconciliation.html","kind":"history","section":"history","keywords":"LK-20260716-eis-runtime-source-reconciliation 2026-07-16 110 refactor compatible labkit_EIS_app Electrochemistry App structure Runtime adoption","text":"EIS delegates source identity reconciliation to Runtime V2 id: LK-20260716-eis-runtime-source-reconciliation date: 2026-07-16 sequence: 110 type: refactor compatibility: compatible component: `labkit_EIS_app` | `1.4.3 -> 1.4.4` scope: Electrochemistry scope: App structure scope: Runtime adoption Context EIS already stored only portable source records in its durable project, but its action file still generated source IDs, appended records, and removed records with App-local helpers. That duplicated Runtime V2's source reconciliation service and made the App responsible for an identity invariant owned by project persistence. The EIS session factory also repeated empty workflow and view buckets even though Runtime V2 canonicalizes missing transient buckets. Decision and rationale Use `services.project.reconcileSources` as the single source-record owner after successful file additions, removals, and clearing. The EIS workflow continues to decide which decoded files are accepted and their order; Runtime preserves matching source identities and allocates collision-free identities for new files. Return only EIS-specific selection and decoded-cache state from `createSession`. Runtime supplies the empty workflow and view buckets before the state becomes visible to actions or presenters. Changes Removed EIS-local source-ID generation, append, and removal helpers. Reconciled durable source records from the successfully decoded EIS items. Removed empty workflow and view boilerplate from the session factory. Added GUI assertions for canonical Runtime buckets, unique source IDs, and identity preservation across additions and project reopen. Advanced the EIS App version to 1.4.4. User and data impact File selection, decoding, plotting, export, save, and reopen behavior are unchanged. Existing saved source IDs remain stable when their paths match. Newly added records receive Runtime-managed IDs. IDs are persistence identities and are not displayed as scientific results. Compatibility and migration The project payload remains version 1 and needs no migration. Existing project files continue to load through their stored portable references. Validation The EIS hidden GUI workflow covers two-file loading from separate folders, selection, plotting, export, project save, clear, and reopen. Focused unit and contract tests cover EIS calculations, App structure, version ownership, documentation, and history ordering. Evidence [EIS](../../../../apps/electrochemistry/eis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up Other multi-file Apps still contain equivalent local source-list helpers. They should adopt the same Runtime service only after their role and ordering semantics are verified by their own focused tests."},{"title":"EIS uses explicit plot-view actions","url":"history/records/2026/07/LK-20260721-eis-explicit-plot-views.html","kind":"history","section":"history","keywords":"LK-20260721-eis-explicit-plot-views 2026-07-21 147 fix compatible labkit_EIS_app EIS Overlay","text":"EIS uses explicit plot-view actions id: LK-20260721-eis-explicit-plot-views date: 2026-07-21 sequence: 147 type: fix compatibility: compatible component: `labkit_EIS_app` | `1.5.1 -> 1.5.2` scope: EIS Overlay Context EIS inferred an equal X/Y aspect ratio from specific Nyquist axis selections. That implicit choice could shrink the usable plotting area and make a redraw look unlike the user's current view. Decision and rationale Keep plot semantics in EIS, but use the App framework's viewport revision and data-fitting capability for view behavior. Equal data units are now an explicit one-time view reset rather than a rule tied to axis labels or a constraint on later zooming. Changes Removed the EIS axis-label rule that automatically selected `axis equal`. Added **Fit X/Y limits** for independent data-fitted limits. Added **Use equal X/Y scale** for an explicit equal-data-unit view without changing the plot size allocated by the App layout. Kept ordinary zoom and wheel viewports under the framework-managed viewport preservation path. User and data impact Nyquist plots now begin with independent limits and a stable plot area. Equal scaling preserves the App layout's plot size. The new view buttons change only transient presentation; EIS sources, projects, calculations, and exports are unchanged. Compatibility and migration Existing saved projects remain valid. The selected axes and Log X/Y options keep their existing meaning; the equal-aspect choice is intentionally not saved with the project. Validation The EIS GUI workflow test covers both buttons, independent fitting, explicit equal aspect, and preservation of an ordinary manually chosen viewport. Evidence The renderer now uses `labkit.app.plot.fitAxesToGraphics` for both independent fits and equal-data-unit limits, and receives a new view revision only from an explicit view action. Known limitations and follow-up Automated GUI coverage cannot assess visual balance on every monitor and MATLAB release. Verify the Nyquist layout interactively before a release. labkit.app.plot.fitAxesToGraphics Fit axes limits to finite plotted X/Y data."},{"title":"Electrochem Apps stop reading portable references","url":"history/records/2026/07/LK-20260716-electrochem-source-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-electrochem-source-boundary 2026-07-16 92 refactor compatible labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app Electrochemistry Runtime source boundary","text":"Electrochem Apps stop reading portable references id: LK-20260716-electrochem-source-boundary date: 2026-07-16 sequence: 92 type: refactor compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.2 -> 1.4.3` component: `labkit_CIC_app` | `1.4.2 -> 1.4.3` component: `labkit_CSC_app` | `1.4.2 -> 1.4.3` component: `labkit_EIS_app` | `1.4.2 -> 1.4.3` component: `labkit_VTResistance_app` | `1.4.2 -> 1.4.3` scope: Electrochemistry scope: Runtime source boundary Context The Electrochem Apps already delegated portable source creation, save-time rebasing, and load-time relinking to Runtime V2. Their session factories, source loaders, actions, and presenters nevertheless read the runtime's nested path field directly. CIC and VT Resistance also carried duplicate local path extraction functions. Decision and rationale Use the public Runtime source-path accessor at every App boundary. This keeps lazy-loading and batch-selection policies App-owned while removing knowledge of the portable-reference storage schema. Changes Replaced direct nested-reference reads in all five Electrochem Apps. Removed duplicate CIC and VT Resistance path extraction functions. Preserved source order and lazy first-item decoding. Kept DTA parsing, formulas, thresholds, plots, exports, and error wording unchanged. User and data impact File selection, project reopen/relink, calculation, preview, and export behavior are unchanged. App code now expresses only its workflow use of paths; Runtime V2 owns how those paths are stored and resolved. Compatibility and migration No project schema or saved source record changed. Existing projects require no migration and remain compatible with the same UI 7 contract range. Validation The Electrochem unit and hidden-GUI suites cover project reconstruction, DTA loading, selection, plotting, computation, export, and saved-state workflows. Package and version guardrails verify the shared API and component metadata. Evidence [Electrochemistry Apps](../../../../apps/electrochemistry/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other App families still contain direct reads and will migrate in their own behavior-tested commits before a repository-wide no-leak guard is enabled."},{"title":"Electrochemistry source-field validation boundary","url":"history/records/2026/07/LK-20260716-electrochem-source-field-validation.html","kind":"history","section":"history","keywords":"LK-20260716-electrochem-source-field-validation 2026-07-16 118 fix compatible labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app electrochemistry project schemas malformed project rejection","text":"Electrochemistry source-field validation boundary id: LK-20260716-electrochem-source-field-validation date: 2026-07-16 sequence: 118 type: fix compatibility: compatible component: `labkit_ChronoOverlay_app` | `1.4.5 -> 1.4.6` component: `labkit_CIC_app` | `1.4.5 -> 1.4.6` component: `labkit_CSC_app` | `1.4.5 -> 1.4.6` component: `labkit_EIS_app` | `1.4.5 -> 1.4.6` component: `labkit_VTResistance_app` | `1.4.5 -> 1.4.6` scope: electrochemistry project schemas scope: malformed project rejection Context Runtime owns the format of a portable source record but intentionally allows a project whose `inputs` bucket has no `sources` field. Static and embedded-data Apps may not use external sources. The first electrochemistry validator reduction correctly removed record-format duplication but also stopped declaring that these five file-backed Apps require a source collection. Decision and rationale Keep field presence with the App schema and record shape with Runtime. This is the narrow ownership split: the App decides whether sources are required; Runtime decides what every supplied source record means. Changes Restored the `inputs.sources` presence requirement in all five electrochemistry project validators. Added a GUI-free unit contract that accepts each default project and rejects the same project after its App-required source collection is removed. Kept canonical bucket and source-record field validation out of the Apps. User and data impact Valid projects are unchanged. A malformed electrochemistry project missing its entire source collection is rejected by the App validator before session reconstruction instead of failing later while decoded data are rebuilt. Compatibility and migration No saved format changed and no migration is required. Every project created or saved by these Apps already contains `inputs.sources`. Validation The focused project-spec test covers all five accepted defaults and all five missing-source rejection cases. The Runtime project and electrochemistry GUI tests continue to cover canonical record validation and working file flows. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) separates framework-owned record shape from App-owned fields and roles. The [Electrochemistry App manuals](../../../../apps/electrochemistry/README.md) identify the required App source collection. Known limitations and follow-up The same distinction must be applied explicitly while reducing validators in other App families; a generic source format does not imply that every App has the same required source fields or roles."},{"title":"Explicit application autosave","url":"history/records/2026/07/LK-20260716-explicit-autosave.html","kind":"history","section":"history","keywords":"LK-20260716-explicit-autosave 2026-07-16 68 feat compatible labkit.ui labkit_VideoMarker_app Runtime V2 project recovery Video Marker session workflow","text":"Explicit application autosave id: LK-20260716-explicit-autosave date: 2026-07-16 sequence: 68 type: feat compatibility: compatible component: `labkit.ui` | `6.0.2 -> 6.0.3` component: `labkit_VideoMarker_app` | `1.3.0 -> 1.4.0` scope: Runtime V2 project recovery scope: Video Marker session workflow Context Runtime V2 already wrote debounced recovery generations after durable edits, but an app could only expose the named-project save operation. Video Marker users who wanted to force the current recovery point had no direct control and using **Save State** introduced a destination prompt for an unnamed project. Decision and rationale Expose the existing framework-owned recovery writer as an injected action service. The service accepts the current action state, writes the same bounded recovery generations as the timer, and deliberately leaves the named project path and dirty status unchanged. Video Marker exposes that operation as **Save autosave** in its Session panel. Changes Factored the atomic recovery-generation writer out of the timer scheduler. Added `services.project.saveAutosave(state)` for an immediate, pathless recovery write. Added the Video Marker **Save autosave** button and visible workflow log acknowledgement. Kept ordinary **Save State** as the separate operation for choosing or updating a named project file. User and data impact Clicking **Save autosave** does not open a native file dialog. It updates the current app/document recovery file and retains one previous generation. It does not make the recovery file the active named project and does not suppress later unsaved-close protection. Compatibility and migration Existing projects, recovery files, and automatic autosave behavior remain compatible. Apps that do not expose the new service behave exactly as before. Validation The focused Video Marker GUI workflow verifies that the button immediately creates a recovery file while the runtime document path remains empty. The existing Runtime V2 project test continues to cover debouncing, bounded generations, and recovery loading. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) documents the injected recovery operation and its distinction from named saves. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the Session button and no-dialog behavior. Known limitations and follow-up An autosave is a recovery aid, not a user-named archival project. Users still use **Save State** when they need a deliberate project filename or location."},{"title":"Explicit layout action contract","url":"history/records/2026/07/LK-20260717-explicit-layout-action-contract.html","kind":"history","section":"history","keywords":"LK-20260717-explicit-layout-action-contract 2026-07-17 123 refactor compatible labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Runtime V2 layout callback resolution public App maintenance cost","text":"Explicit layout action contract id: LK-20260717-explicit-layout-action-contract date: 2026-07-17 sequence: 123 type: refactor compatibility: compatible component: `labkit_DICPostprocess_app` | `1.4.5 -> 1.4.6` component: `labkit_DICPreprocess_app` | `1.5.6 -> 1.5.7` component: `labkit_ChronoOverlay_app` | `1.4.6 -> 1.4.7` component: `labkit_CIC_app` | `1.4.6 -> 1.4.7` component: `labkit_CSC_app` | `1.4.6 -> 1.4.7` component: `labkit_EIS_app` | `1.4.6 -> 1.4.7` component: `labkit_VTResistance_app` | `1.4.6 -> 1.4.7` component: `labkit_GaitAnalysis_app` | `2.0.6 -> 2.0.7` component: `labkit_BatchImageCrop_app` | `1.7.5 -> 1.7.6` component: `labkit_CurvatureMeasurement_app` | `1.4.5 -> 1.4.6` component: `labkit_FLIRThermal_app` | `1.4.5 -> 1.4.6` component: `labkit_FocusStack_app` | `1.5.4 -> 1.5.5` component: `labkit_ImageEnhance_app` | `1.6.5 -> 1.6.6` component: `labkit_ImageMatch_app` | `1.6.5 -> 1.6.6` component: `labkit_VideoMarker_app` | `1.5.5 -> 1.5.6` component: `labkit_FigureStudio_app` | `0.2.7 -> 0.2.8` component: `labkit_NerveResponseAnalysis_app` | `1.4.5 -> 1.4.6` component: `labkit_ResponseReviewStats_app` | `1.4.5 -> 1.4.6` component: `labkit_RHSPreview_app` | `1.4.5 -> 1.4.6` component: `labkit_ECGPrint_app` | `1.4.5 -> 1.4.6` scope: Runtime V2 layout callback resolution scope: public App maintenance cost Context Every public App layout carried the same local `callbackValue` helper. The helper returned an empty value when a callback field was absent, so a misspelled or unregistered action could survive startup as a control that silently did nothing. Twenty copies also obscured that Runtime already creates the complete callback table from each App definition. Decision and rationale Layouts reference Runtime-generated callbacks directly. MATLAB field access therefore validates the action ID while the data-only layout is built. Runtime remains the single callback-adapter owner; Apps own only their semantic action registries and layout references. Changes Removed the duplicated callback fallback from all 20 public App layouts. Replaced static lookups with direct `callbacks.actionId` references and kept dynamic field access only in three small controls whose action names are supplied by the surrounding layout. Added a fleet-wide contract that creates every App definition and layout with the Runtime callback inventory. Updated direct neurophysiology layout tests to supply explicit callbacks derived from their action registries. User and data impact Correctly registered controls behave as before. A broken App definition now fails during layout construction instead of presenting an inert control. No scientific calculation, project payload, saved file, or interaction semantics changed. Compatibility and migration No project migration is required. This is an App-development contract tightening: custom layouts must register every referenced semantic action. Validation The focused fleet contract builds every public App layout and catches missing or misspelled callback fields. The App package guardrail rejects reintroduced silent callback fallbacks, and focused workflow-layout tests cover direct construction outside a running figure. Evidence [Runtime layout and action rules](../../../../framework/guides/runtime.md#layout-and-action-rules) [Apps](../../../../apps/README.md) [App development](../../../../development/build-apps/app-development.md) Known limitations and follow-up This change deliberately does not merge short semantic actions or dynamic control helpers merely to reduce line count. Those remain candidates only when a repeated behavior has one stable, domain-neutral owner."},{"title":"FLIR Thermal separates durable annotations from decoded sources","url":"history/records/2026/07/LK-20260716-flir-thermal-structure.html","kind":"history","section":"history","keywords":"LK-20260716-flir-thermal-structure 2026-07-16 98 refactor compatible labkit_FLIRThermal_app Image Measurement App structure","text":"FLIR Thermal separates durable annotations from decoded sources id: LK-20260716-flir-thermal-structure date: 2026-07-16 sequence: 98 type: refactor compatibility: compatible component: `labkit_FLIRThermal_app` | `1.4.2 -> 1.4.3` scope: Image Measurement scope: App structure Context FLIR Thermal split static metadata across files, spread a version-1 project over a generic lifecycle package, grouped decoded records, durable annotations, and numerical reading updates under `+appState`, and read nested portable reference fields in session, actions, and presentation. Its startup callback also selected an output directory before the user loaded a source. Decision and rationale Use the compact Runtime V2 contract while preserving the boundary between decoded radiometric data and the lightweight annotations safe to persist. Assign each remaining helper to the capability it describes instead of moving thermal semantics into the framework. Changes Consolidated product metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated durable project creation and validation in `projectSpec.m` and moved selected-image reconstruction to root `createSession.m`. Moved decoded item shape to `+sourceFiles`, point/ROI updates to `+analysisRun`, and persistent per-image readings to `+thermalAnnotations`. Removed separate metadata files, generic lifecycle/state packages, and the redundant App startup callback. Replaced all direct portable-reference field access with the Runtime source path accessor. User and data impact Radiometric decoding, Celsius conversion, range controls, point and ROI readings, viewport-preserving overlays, project reopen, and exports keep their existing behavior. Empty launch no longer chooses an output folder; adding sources still establishes the same source-adjacent default. Developers can find transient decoded matrices, scientific readings, and durable annotations under their actual owners without learning a generic state layer. Compatibility and migration The durable payload remains version 1 with identical fields and defaults. Existing projects require no migration. The removed App-specific debug line is replaced by the Runtime's standard debug-startup message. Validation Five unit methods cover radiometric import, thermal values, calibration diagnostics, range controls, independent ROIs, rendering mappings, manifests, and exported matrices. Two hidden GUI workflows cover multi-file selection, session caching, managed region interaction, project save/load reconstruction, shared ranges, and batch export. Evidence [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Thermal Library](../../../../libraries/thermal/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Image Measurement Apps still use generic lifecycle/state packages and will be reviewed individually. Automated GUI tests do not replace manual judgment of pointer feel, visual calibration, or camera-specific accuracy."},{"title":"FLIR display tuning","url":"history/records/2026/07/LK-20260703-flir-display-tuning.html","kind":"history","section":"history","keywords":"LK-20260703-flir-display-tuning 2026-07-03 36 feat compatible labkit_CSC_app labkit_FLIRThermal_app labkit_FLIRThermal_app labkit_FLIRThermal_app FLIR display tuning","text":"FLIR display tuning id: LK-20260703-flir-display-tuning date: 2026-07-03 sequence: 36 type: feat compatibility: compatible component: `labkit_CSC_app` | `1.3.6 -> 1.3.7` component: `labkit_FLIRThermal_app` | `1.2.4 -> 1.2.5` component: `labkit_FLIRThermal_app` | `1.2.5 -> 1.2.6` component: `labkit_FLIRThermal_app` | `1.2.6 -> 1.2.7` scope: FLIR display tuning Context The initial FLIR renderer used a fixed color transfer. Images with a narrow or skewed temperature distribution could therefore hide useful contrast even when the numeric range was correct. CSC voltage/current CSV output also needed clearer cycle organization for direct downstream use. Decision and rationale Separate the displayed color mapping from the temperature values. Apply gamma only while rendering the normalized image and expose it as a user control; keep the temperature matrix, point readings, ROI means, and exports numerically unchanged. Refine CSC CSV structure at its result writer rather than in the UI. Changes `labkit_FLIRThermal_app` `1.2.4 -> 1.2.7` `labkit_CSC_app` `1.3.6 -> 1.3.7` Refined CSC CV export. Added FLIR gamma color mapping and made gamma adjustable. User and data impact FLIR users could reveal contrast in hotter or cooler parts of an image without changing the underlying temperatures. The same gamma was used in displayed and exported color renderings. CSC users received a more directly usable voltage/current table. Compatibility and migration Existing FLIR inputs remained valid, and gamma affected rendered color only. Temperature matrices and measurement values did not require conversion. Validation The commits extended CSC export assertions, FLIR rendering and output tests, and the FLIR layout test for the gamma control. Exact historical commands were not recorded. Evidence Main commits `ee5b8f79`, `65dbf5ae`, and `f076561e`. Known limitations and follow-up Gamma changes visual contrast only. Quantitative interpretation must use the temperature values and scale, not colors sampled from the rendered image."},{"title":"Facade contract baseline and release validation hardening","url":"history/records/2026/06/LK-20260623-facade-contract-baseline-and-release-validation-hardening.html","kind":"history","section":"history","keywords":"LK-20260623-facade-contract-baseline-and-release-validation-hardening 2026-06-23 11 ci compatible labkit.biosignal labkit.dta labkit.rhs labkit.ui labkit.ui labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_CurvatureMeasurement_app Facade contract baseline and release validation hardening","text":"Facade contract baseline and release validation hardening id: LK-20260623-facade-contract-baseline-and-release-validation-hardening date: 2026-06-23 sequence: 11 type: ci compatibility: compatible component: `labkit.biosignal` | `new -> 1.0.0` component: `labkit.dta` | `new -> 1.0.0` component: `labkit.rhs` | `new -> 1.0.0` component: `labkit.ui` | `new -> 2.0.0` component: `labkit.ui` | `2.0.0 -> 2.1.0` component: `labkit.ui` | `2.2.0 -> 2.2.1` component: `labkit_DICPostprocess_app` | `1.0.0 -> 1.0.1` component: `labkit_DICPreprocess_app` | `1.0.0 -> 1.0.1` component: `labkit_CurvatureMeasurement_app` | `1.0.0 -> 1.0.1` scope: Facade contract baseline and release validation hardening Context Reusable packages exposed public MATLAB functions, but apps had no machine-checkable way to state which API versions they required. Release and CI checks also needed one consistent build-task entry path. Decision and rationale Give each reusable package version metadata and let apps declare compatible ranges that are checked at launch and in tests. Route MATLAB CI shards through the same build tasks used by maintainers so release validation exercises the documented commands. Changes `labkit.biosignal` `1.0.0` `labkit.dta` `1.0.0` `labkit.rhs` `1.0.0` `labkit.ui` `2.0.0 -> 2.2.1` DIC Pre/Post and Curvature `1.0.0 -> 1.0.1` Release tags `v2.4.1` and `v2.4.2` Added facade contract metadata and requirement checks. Hardened app lifecycle and release validation contracts. Routed MATLAB CI shards through build tasks. User and data impact An incompatible app/package combination could fail with a direct requirement message instead of producing a later missing-function error. Existing project and result data were unaffected. Compatibility and migration The contract metadata described the existing public facades and was additive for app callers. Apps with an incompatible declared requirement now failed at launch with a version diagnostic instead of continuing unpredictably. Validation The listed commits introduced contract, lifecycle, and CI build-task tests. Release tags `v2.4.1` and `v2.4.2` identify the shipped checkpoints; exact local commands were not recorded. Evidence Main commits `a25b79f9`, `3673e548`, `49d9f41b`, and `7e39b558`. Known limitations and follow-up App and launcher display versions were added in the separate version-metadata change later the same day."},{"title":"Figure Studio adopts one product definition","url":"history/records/2026/07/LK-20260716-figure-studio-single-definition.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-single-definition 2026-07-16 79 refactor compatible labkit_FigureStudio_app labkit.ui LabKit Core App structure","text":"Figure Studio adopts one product definition id: LK-20260716-figure-studio-single-definition date: 2026-07-16 sequence: 79 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.1 -> 0.2.2` component: `labkit.ui` | `7.2.0 -> 7.2.1` scope: LabKit Core scope: App structure Context Figure Studio declared its command, names, family, App version, and LabKit requirement in two files separate from the runtime definition. Its entrypoint joined all three factories even though they described one product. Decision and rationale Use Figure Studio as the first reviewed App migration to the single-definition contract. This validates the reduced structure on an App that also accepts a typed axes handoff, without changing its plotting or export behavior. Changes Moved exact product metadata and the UI requirement into `definition.m`. Reduced the public entrypoint to one definition plus its existing typed request adapter. Removed the redundant `requirements.m` and `version.m` files. Taught the version guard to use `AppVersion` in a migrated definition while comparing the first migration against the previous `version.m` baseline. Fixed the shared source reconciler so it creates the canonical empty source array without first constructing an invalid empty-ID placeholder record. Added `services.results.emptyOutputs()` and used it for Figure Studio's variable-length export manifest instead of constructing an invalid empty-ID placeholder output. User and data impact Launch, axes handoff, style controls, project payload, FIG reading, plot snapshot extraction, and all export formats retain their existing behavior. Maintainers now update one product contract instead of three files. Compatibility and migration The App command and project ID are unchanged. App version metadata requests still return the same fields through the public entrypoint. Existing project payloads remain at version 1 and require no data migration. Validation Focused tests cover ordinary launch, axes handoff, style changes, project round-trip, exports, launcher discovery, requirements/version requests, and version/history governance. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) documents the unchanged workflow and new definition ownership. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the shared product contract. Known limitations and follow-up Figure Studio still has a meaningful version-1 project schema, rebuildable plot session cache, and startup resource installation. These will move to the projectSpec/action capability model after the framework supports that model; they were not deleted merely to reduce file count."},{"title":"Figure Studio consolidates its project schema","url":"history/records/2026/07/LK-20260716-figure-studio-project-spec.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-project-spec 2026-07-16 82 refactor compatible labkit_FigureStudio_app LabKit Core Project lifecycle","text":"Figure Studio consolidates its project schema id: LK-20260716-figure-studio-project-spec date: 2026-07-16 sequence: 82 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.2 -> 0.2.3` scope: LabKit Core scope: Project lifecycle Context Figure Studio's version-1 project creation and validation lived in separate files under a generic `+appLifecycle` package. The package name described a framework phase rather than the durable capability those functions owned. Decision and rationale Consolidate the complete durable schema behind `projectSpec.m`. Keep `createSession.m` separate at the App package root because it performs a different job: rebuilding transient selection and decoded plot cache from a validated project. Changes Added one project declaration with local create and validate functions. Moved session reconstruction to one explicitly named package-root entry. Removed the three-file generic `+appLifecycle` package. Left meaningful startup, action, presentation, source, style, and export capabilities unchanged. User and data impact Figure loading, axes handoff, styling, project save/load, session restoration, and exports behave unchanged. A maintainer can now understand the entire durable schema in one file. Compatibility and migration The App command, project ID, payload version, fields, validation, and source record format are unchanged. Existing Figure Studio projects need no migration. Validation The existing unit and hidden GUI suites cover FIG reading, source style, project round-trip, axes handoff, canvas resizing, quick export, and data package export through the consolidated project declaration. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The startup hook still owns axes handoff and resize-resource installation. It will be evaluated with other App startup hooks before any shared mount/action capability is introduced."},{"title":"Figure Studio derives its publication preset from the nine-panel reference","url":"history/records/2026/07/LK-20260721-figure-studio-nine-panel-calibration.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-nine-panel-calibration 2026-07-21 155 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio derives its publication preset from the nine-panel reference id: LK-20260721-figure-studio-nine-panel-calibration date: 2026-07-21 sequence: 155 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.1 -> 0.6.4` scope: LabKit Core Context The previous Figure Studio publication profile was described as a visual calibration even though its dimensions had been taken from a separate manual FIG example. It also allowed native figure chrome to reduce the exported drawable height. Decision and rationale Use only the maintained 3-by-3 scientific reference. Normalize every panel by its detected axes frame, measure typography and strokes in pixels, and select one robust profile from all nine panels. Keep those measurements app-local and size the chromeless export client area directly so the configured inner frame survives raster and vector export. Changes Recalibrated the reference plot frame, typography, semantic strokes, frame, grid, legend border, and legend token length from panels A through I. Set the data stroke to 6.5 pt after registered raster checks matched panel A exactly and reduced the remaining one-pixel deficits in panels E and H. Removed menu and toolbar chrome from temporary export figures and preserved the complete figure bounds through `exportgraphics`. Ignored zero-area text extents when computing outer margins so native log rulers cannot inflate an export canvas to tens of thousands of pixels. Added the calibrated 900 px plot width to the selectable sizes and made it the new-project default. Retained source token geometry for **FIG default** while applying the measured long token only to the LabKit preset. Replaced dynamic-array analyzer suppressions with bounded collection and documented the 72-to-96-PPI range used by cross-platform visual metrics. User and data impact New LabKit-style projects open at the nine-panel reference scale. Existing scientific values, limits, source graphics, FIG files, and saved projects are not recalculated. Exports retain the requested inner-frame aspect instead of losing native-window height. Compatibility and migration The style record remains extensible and existing projects stay readable. Switching an existing document to **LabKit figure** opts into the new profile; **FIG default** continues to use source presentation values. Validation Pixel-frame, OCR glyph-height, colored-stroke, and registered-overlay checks against all nine reference panels. Focused Figure Studio result-file and project tests. Hidden-GUI Figure Studio workflow tests. Native logarithmic-axis margin regression. Linux-compatible visual-ratio and analyzer-policy regressions. Repository `changedFast` validation. Evidence After width registration, the nine detected inner-frame height errors range from -1.71 to +2.16 pixels; panel A is exact. Shared OCR glyph heights have a median scale error below one percent. Reference and generated comparison rasters remain untracked validation artifacts. Known limitations and follow-up Raster antialiasing varies slightly by renderer and platform. Exact scientific curve coordinates are source data and are not part of the style preset."},{"title":"Figure Studio edits one native subplot panel at a time","url":"history/records/2026/07/LK-20260721-figure-studio-native-panel-editing.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-native-panel-editing 2026-07-21 153 feat compatible labkit_FigureStudio_app Figure Studio","text":"Figure Studio edits one native subplot panel at a time id: LK-20260721-figure-studio-native-panel-editing date: 2026-07-21 sequence: 153 type: feat compatibility: compatible component: `labkit_FigureStudio_app` | `0.4.0 -> 0.6.0` scope: Figure Studio Context Figure Studio reconstructed a limited portable snapshot from the first axes in a FIG. This could omit composite graphics such as traditional `boxplot` groups, gave no reliable way to choose a subplot from a mixed figure, and made export canvas controls look like live plot-resize controls. Decision and rationale Treat an opened FIG as a transient native document resource and select one axes from it by an ordered subplot panel choice. The selected axes remains the authoritative hierarchy for preview and editable/image export; the portable snapshot remains an explicit secondary data-package representation. This gives ordinary MATLAB charts a broader preservation path without promising that an arbitrary custom graphics class is portable. Use a fixed, visually calibrated single-panel default (1600 by 1333, 6:5). The workbench now displays a raster rendering of that real export canvas, fit as one image into the available workspace. Resizing the window never feeds back into style sizing; supersampling remains export-only. Provide an explicit automatic X/Y-limit recovery action instead of trying to infer limits during unrelated style changes. Changes Added ordered mixed-FIG subplot-panel selection; preview, styling, limits, and exports all operate on the selected axes only. Added private native-source cloning for popout handoff and FIG import, with cleanup through the existing document-resource lifecycle. Preserved native child hierarchies in preview and editable FIG export, including traditional grouped `boxplot` graphics and hidden-handle visual children. Preserved source tick labels and tick configuration when copying native axes. Replaced free-form preview resizing with source/fixed export-size choices, aspect choices, export supersampling, and a stable true-export preview. Added **Recalculate X/Y limits** and project persistence for explicit limit overrides. Migrated project schema 3 to remember the selected panel index. User and data impact Existing projects migrate automatically. A saved mixed FIG project restores the same selected panel when its source is available. Popout projects retain a portable snapshot after reload; their private native source is never saved. No scientific calculation, source file, or source axes is modified. Compatibility and migration Schema 1--3 projects migrate to schema 4. Schema 3 adds `panelIndex = 1`; earlier migrations retain their existing style and limit conversions. Validation Project-schema tests cover migrations through schema 4. Hidden-GUI tests cover four-panel FIG selection, native boxplot preservation for FIG import and popout handoff, export, limit recovery, and a stable export-canvas preview through workspace resizing. Focused visual review checks a generated single-panel scientific probe at the default 1600-by-1333 reference size. Evidence The focused test artifacts and generated local visual probe are supporting evidence. No external laboratory data was used. Known limitations and follow-up Developer-led visual review remains necessary for unusual custom charts, native dialogs, and publication-specific annotation overlap. Portable data packages intentionally support fewer object types than native FIG export."},{"title":"Figure Studio fixes publication-frame preview and axis controls","url":"history/records/2026/07/LK-20260721-figure-studio-plot-frame-preview.html","kind":"history","section":"history","keywords":"LK-20260721-figure-studio-plot-frame-preview 2026-07-21 154 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio fixes publication-frame preview and axis controls id: LK-20260721-figure-studio-plot-frame-preview date: 2026-07-21 sequence: 154 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.0 -> 0.6.1` scope: LabKit Core Context Figure Studio had treated a requested output size as a whole figure canvas. That made labels and titles consume the configured plot area, and resizing the preview could make the displayed typography unrelated to the eventual export. Copied UIAxes also needed a portable reconstruction path for reliable export. Decision and rationale Treat the requested width and aspect as the inner axes frame. Keep all publication-style calibration within Figure Studio, then calculate an outer figure from the live rendered text extents so visual styling cannot reduce the configured data area. Changes Defined the plot width and aspect controls as the inner axes frame; export now calculates an outer figure around that fixed frame from visible labels, ticks, titles, legends, and annotations. Calibrated the LabKit publication preset from the maintained single-panel visual measurements, including category-specific typography and strokes. Kept the preview as an interactive axes, reflowing its display scale when its allocated area changes without changing saved/export settings. Added explicit X/Y minimum and maximum controls, plus visible-data recalculation with a finite-data envelope expanded by 50 percent. Rebuilt UIAxes sources through the portable renderer for conventional export, retaining displayed scientific axis exponents. User and data impact Existing Figure Studio projects remain compatible. Existing style sizes retain their meaning as a plot frame; the enclosing export figure may grow to avoid clipping external text. No scientific calculations or source figure data are changed. Compatibility and migration The project schema migrates existing style records without changing their stored data. UIAxes exports use the portable display snapshot only for the screen-specific aspect geometry that cannot be transferred to normal axes. Validation Focused Figure Studio result-file and source-axes tests. Hidden-GUI Figure Studio layout tests, including UIAxes export and preview reflow behavior. Pixel-scale visual metrics for the calibrated single-panel output. Evidence The calibrated profile fixes the inner frame at 1237 by 942 pixels. Its generated output reports that same axes size while the enclosing figure grows only for required outer text and annotations. Known limitations and follow-up Manual review remains appropriate for native MATLAB graphics that cannot be represented by the portable package."},{"title":"Figure Studio names its post-layout initialization capability","url":"history/records/2026/07/LK-20260716-figure-studio-initializer.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-initializer 2026-07-16 109 refactor compatible labkit_FigureStudio_app LabKit Core App structure","text":"Figure Studio names its post-layout initialization capability id: LK-20260716-figure-studio-initializer date: 2026-07-16 sequence: 109 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.4 -> 0.2.5` scope: LabKit Core scope: App structure Context Figure Studio was the only public App with a generic `startup.m` file. Its definition already declared the function through Runtime V2's optional `Start` capability, but the filename obscured who invoked it and made the function look like an independent lifecycle entrypoint. Decision and rationale Keep the Runtime-owned `Start` hook because Figure Studio genuinely needs a post-layout phase: it consumes an optional axes handoff, resolves the managed preview axes, and registers a cleanup-owned resize resource. Rename the App callback to `initializeWorkbench` so its capability and invocation boundary are explicit. Do not move GUI/service work into the GUI-free session factory or reintroduce App-owned timers and readiness state. Changes Renamed the Start callback from generic `startup` to `initializeWorkbench` and declared the new handle in `definition`. Documented the callback timing and the request, preview, resource, dialog, workflow, and debug services supplied by Runtime V2. Tightened the App structure guardrail so compact Apps cannot add a generic `startup.m` file outside their declared capability vocabulary. Advanced the Figure Studio App version to 0.2.5. User and data impact Normal launch, default output folder selection, popout axes handoff, source style adoption, preview canvas resize behavior, debug logging, project data, and exports are unchanged. No project migration is required. Compatibility and migration The public command and Runtime definition contract are unchanged. `startup` was an App-internal callback referenced only by the definition; callers should launch the App rather than invoke either callback directly. Validation The Figure Studio hidden GUI suite covers ordinary launch, style controls, FIG loading, preview resize, quick/package export, axes handoff, project save/load, stable canvas sizing, and popout-to-Studio transfer. It also asserts that the definition exposes the semantic initializer handle. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Development](../../../../development/build-apps/app-development.md) Known limitations and follow-up The Start capability remains necessary for post-layout resources; removing it would require a different framework service phase rather than a file move. App and agent guidance still need a repository-wide terminology audit."},{"title":"Figure Studio preserves common graphics and styles semantic categories","url":"history/records/2026/07/LK-20260720-figure-studio-semantic-restyling.html","kind":"history","section":"history","keywords":"LK-20260720-figure-studio-semantic-restyling 2026-07-20 145 feat compatible labkit_FigureStudio_app labkit.app Figure Studio App Framework GUI validation Test runner","text":"Figure Studio preserves common graphics and styles semantic categories id: LK-20260720-figure-studio-semantic-restyling date: 2026-07-20 sequence: 145 type: feat compatibility: compatible component: `labkit_FigureStudio_app` | `0.3.1 -> 0.4.0` component: `labkit.app` | `1.2.0 -> 1.2.1` scope: Figure Studio scope: App Framework GUI validation scope: Test runner Context Figures sent from Apps, reopened from FIG files, or recreated from Figure Studio data packages could omit common composite graphics such as bars, error bars, areas, and rectangles. Legends were reconstructed only from display names, and the preset exposed one broad line-width control. This made restyling incomplete and could enlarge unrelated text enough to overlap data. Separately, the official hidden-GUI policy was applied while native App windows were constructed but the final show operation could make direct-entrypoint tests visible. Decision and rationale Preserve the supported visible plot structure before applying a presentation preset. Style text and strokes by semantic role so users can establish a coherent baseline and then refine categories that have different visual weights. A preset may style an existing legend, but it retains source placement by default because moving or creating a legend can cover data. Keep GUI-test visibility in the private native adapter. App authors and test methods should not maintain a second hiding convention, and no new public API is needed for a framework-owned launch consistency rule. Keep broad tests in one MATLAB process. A complete local comparison showed that three workers increased both wall-clock and CPU time, so portability work around child-process orchestration would preserve cost without user benefit. Changes Added bar, error-bar, area, and display-only rectangle extraction and reconstruction for previews, FIG imports, and portable recreation scripts. Preserved constant-line labels without interpreting scientific text as a MATLAB line specification. Preserved existing legend text, visibility, placement, orientation, columns, font, interpreter, and border through every supported reconstruction path. Preserved explicit axis ticks, tick labels, rotations, locations, and tick geometry instead of asking MATLAB to infer them again during reconstruction. Included visible hidden-handle graphics in snapshots while retaining their exclusion from automatic legend discovery. Made the LabKit preset the initial style for direct plot handoff while retaining the captured source style as **FIG default**. Added separate controls for title, axis-label, tick, annotation, legend, data, uncertainty, graphic-boundary, reference, and axes styling, plus source/horizontal/45-degree X tick labels. Calibrated the LabKit preset against a representative standard Figure: a 720-by-600 reference canvas, 24/20/15 pt semantic text tiers, 1.1/1.2 pt semantic strokes, Helvetica when available, boxed axes and legend, and a 600 dpi export at the default scale. Scaled text and strokes dynamically from each style's reference canvas while compensating for preview fitting, and reduced UI steps to 0.5 pt for fonts, 0.1 pt for strokes, and 10 px for canvas dimensions. Migrated schema 1 projects to the expanded semantic style model while preserving every previously saved style value. Made the final native show operation honor hidden and minimized GUI validation modes. Bound heartbeat work at timer creation so path-isolation contracts cannot remove a method needed by an active progress callback. Removed internal worker planning, partitioning, process launch, shard selectors, and speculative JUnit shard estimates. Local and CI broad tasks now share the same single-process runner. User and data impact Existing schema 1 documents migrate automatically to schema 2, keeping saved values and supplying defaults only for newly introduced style categories. Supported figures retain more visible content, while unsupported MATLAB chart classes continue to produce explicit warnings. Normal App launches remain visible; only official hidden or minimized validation modes change final window presentation. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Result-file tests exercise extraction, FIG reopening, recreation-script execution, legend metadata, constant-line labels, semantic styling, and idempotent preset application. Figure Studio source-axis and hidden-GUI suites cover source defaults, LabKit handoff defaults, the new control surface, preview stability, and popout handoff. Project tests cover schema 1 style migration. Framework GUI coverage verifies that the final show operation retains hidden mode, and a direct DIC Preprocess entrypoint test exercises the full launch path. A same-machine cold-process comparison measured 470 seconds wall time and 876 seconds CPU time for the serial headless gate versus 490 seconds wall time and 1,215 seconds CPU time for three workers. Worker artifacts also confirmed that heartbeat updates survived the isolated-path contract. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led visual review remains responsible for aesthetic judgment, unusual third-party chart classes, native window behavior, and plot-specific overlap after manual legend placement."},{"title":"Figure Studio preserves overlays above source images","url":"history/records/2026/07/LK-20260722-figure-studio-image-overlay-stacking.html","kind":"history","section":"history","keywords":"LK-20260722-figure-studio-image-overlay-stacking 2026-07-22 156 fix compatible labkit_FigureStudio_app LabKit Core","text":"Figure Studio preserves overlays above source images id: LK-20260722-figure-studio-image-overlay-stacking date: 2026-07-22 sequence: 156 type: fix compatibility: compatible component: `labkit_FigureStudio_app` | `0.6.4 -> 0.6.5` scope: LabKit Core Context Figure Studio copied every visible child from native MATLAB axes, but reversed the source child vector before copying it into the interactive preview. Because MATLAB stores axes children from front to back and `copyobj` preserves vector order, an opaque image became the frontmost child and hid line, marker, and text overlays that had been drawn above it. Decision and rationale Pass the native axes child order through unchanged. The source axes remains the authority for stacking, and Figure Studio must not infer a new order from graphics classes or special-case images. Changes Preserved native front-to-back child order when copying an axes into the interactive preview. Added a synthetic image, line, and text stack regression at the renderer boundary. Added a hidden-GUI regression covering both FIG-file import and axes handoff. User and data impact Image-backed plots now show their visible overlays in Figure Studio and in preview-derived exports. Source files, scientific values, coordinates, object styles, and project data are unchanged. Compatibility and migration No project migration is required. Existing projects receive the corrected stacking when their native source is rebuilt. Validation Exact source-axes stacking regression. Hidden-GUI FIG import and axes-handoff stacking regression. Visual inspection of an authorized local diagnostic FIG through both paths; the real file remains untracked and is not a test fixture. Evidence Both real-source paths retained all 12 visible graphics children with the opaque image last and the 11 overlay children in front. Temporary preview rasters confirmed the overlays were visible. Known limitations and follow-up Custom graphics classes still depend on MATLAB supporting their native parent transition. The portable data package remains narrower than the native preview and export path."},{"title":"Figure Studio removes source-reference knowledge","url":"history/records/2026/07/LK-20260716-figure-studio-source-boundary.html","kind":"history","section":"history","keywords":"LK-20260716-figure-studio-source-boundary 2026-07-16 94 refactor compatible labkit_FigureStudio_app LabKit Core Apps Runtime source boundary","text":"Figure Studio removes source-reference knowledge id: LK-20260716-figure-studio-source-boundary date: 2026-07-16 sequence: 94 type: refactor compatibility: compatible component: `labkit_FigureStudio_app` | `0.2.3 -> 0.2.4` scope: LabKit Core Apps scope: Runtime source boundary Context Figure Studio read both the current path and stored filename from the nested portable source reference. It repeated those reads in session reconstruction, actions, file-list presentation, and selection lookup. Decision and rationale Resolve source paths once through Runtime V2 at each workflow boundary. Derive the displayed filename from the resolved path so the App needs no knowledge of any portable-reference field. Changes Migrated FIG session reconstruction, action loading, file presentation, and selection lookup to `labkit.ui.runtime.sourcePaths`. Derived the opened-file status name through `fileparts`. Removed every direct portable-reference field read from Figure Studio. Preserved imported axes data, default style adoption, source order, current selection, editing, and export behavior. User and data impact FIG selection and display behave unchanged. Figure Studio now owns only how it uses a resolved FIG file; Runtime V2 owns how that file remains portable. Compatibility and migration No project or source-record field changed. Existing Figure Studio projects require no migration. Validation Source-axes and result-file unit tests cover import and export semantics. The hidden GUI suite covers empty startup, FIG addition and selection, style presentation, save/load, and output workflows. Evidence [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The repository-wide portable-reference guard remains deferred until all remaining App families have migrated."},{"title":"File-panel layout stabilization","url":"history/records/2026/06/LK-20260630-file-panel-layout-stabilization.html","kind":"history","section":"history","keywords":"LK-20260630-file-panel-layout-stabilization 2026-06-30 19 fix compatible labkit.ui labkit.ui File-panel layout stabilization","text":"File-panel layout stabilization id: LK-20260630-file-panel-layout-stabilization date: 2026-06-30 sequence: 19 type: fix compatibility: compatible component: `labkit.ui` | `3.2.3 -> 3.2.4` component: `labkit.ui` | `3.2.4 -> 3.2.5` scope: File-panel layout stabilization Context The single-file panel allocated too much height to its rows and could render inconsistently as the containing app resized. This was especially noticeable in file-heavy apps, where the panel competed with analysis controls and plots. Decision and rationale Give the panel explicit, compact row sizing and protect that geometry with GUI layout tests. The change stayed in the shared file-panel builder because the same visual defect appeared wherever the component was used. Changes `labkit.ui` `3.2.3 -> 3.2.5` Stabilized and compacted single file-panel layout. User and data impact File names and actions occupied less vertical space, leaving more room for the workflow below them. The change affected layout only; selections and loaded file data were unchanged. Compatibility and migration App definitions and selected files required no conversion. The shared panel kept the same controls and callbacks with revised row geometry. Validation Both commits extended `GuiLayoutUiDeclarativeAppTest` with the expected panel geometry. Exact historical test commands were not recorded. Evidence Main commits `7f8df1cd` and `02b2f1b6`. Known limitations and follow-up Later runtime generations replaced this private UI 3.x builder, but retained the principle that shared components own and test their responsive geometry."},{"title":"File-panel migration","url":"history/records/2026/06/LK-20260624-file-panel-migration.html","kind":"history","section":"history","keywords":"LK-20260624-file-panel-migration 2026-06-24 12 refactor breaking labkit.dta labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app File-panel migration","text":"File-panel migration id: LK-20260624-file-panel-migration date: 2026-06-24 sequence: 12 type: refactor compatibility: breaking component: `labkit.dta` | `1.0.0 -> 2.0.0` component: `labkit.ui` | `2.2.1 -> 3.0.0` component: `labkit_DICPostprocess_app` | `1.0.1 -> 1.2.0` component: `labkit_DICPreprocess_app` | `1.0.1 -> 1.2.0` component: `labkit_ChronoOverlay_app` | `1.0.0 -> 1.2.0` component: `labkit_CIC_app` | `1.0.0 -> 1.2.0` component: `labkit_CSC_app` | `1.0.0 -> 1.2.0` component: `labkit_EIS_app` | `1.0.0 -> 1.2.0` component: `labkit_VTResistance_app` | `1.0.0 -> 1.2.0` component: `labkit_BatchImageCrop_app` | `1.0.0 -> 1.2.0` component: `labkit_CurvatureMeasurement_app` | `1.0.1 -> 1.2.0` component: `labkit_FocusStack_app` | `1.0.0 -> 1.2.0` component: `labkit_ImageEnhance_app` | `1.0.0 -> 1.2.0` component: `labkit_ImageMatch_app` | `1.0.0 -> 1.2.0` component: `labkit_NerveResponseAnalysis_app` | `1.0.0 -> 1.2.0` component: `labkit_ResponseReviewStats_app` | `1.0.0 -> 1.2.0` component: `labkit_RHSPreview_app` | `1.0.0 -> 1.2.0` component: `labkit_ECGPrint_app` | `1.0.0 -> 1.2.0` scope: File-panel migration Context Apps implemented file selection through separate task-input adapters, which produced inconsistent lists, selection events, and status feedback. The DTA session helper also coupled parsing to that older UI model. Decision and rationale Move supported apps to one reusable file-panel model and make DTA loading a GUI-free file/curve API. Accept the breaking package versions because retaining both task-input and file-panel paths would preserve ambiguous callback behavior. Changes `labkit.dta` `1.0.0 -> 2.0.0` `labkit.ui` `2.2.1 -> 3.0.0` All supported apps moved from `1.0.x` into the `1.2.0` workflow line. Replaced task inputs with file panels. Removed the old DTA session helper surface. User and data impact File lists, current selection, add/remove actions, and status display became consistent across the migrated apps. App code written against the removed task inputs or DTA session helpers required an update. Compatibility and migration This was a breaking workflow migration. Older app code expecting task inputs or the removed DTA session helpers needed migration. Validation Commit `b145c904` migrated app GUI workflows and package compatibility tests to the new file-panel and DTA APIs. Evidence Main commit `b145c904`. Known limitations and follow-up This first file-panel generation was later refined for append behavior, native dialog edge cases, and Runtime V2 events."},{"title":"Focus Stack adopts the compact App contract","url":"history/records/2026/07/LK-20260716-focus-stack-structure.html","kind":"history","section":"history","keywords":"LK-20260716-focus-stack-structure 2026-07-16 95 refactor compatible labkit_FocusStack_app Image Measurement App structure","text":"Focus Stack adopts the compact App contract id: LK-20260716-focus-stack-structure date: 2026-07-16 sequence: 95 type: refactor compatibility: compatible component: `labkit_FocusStack_app` | `1.5.1 -> 1.5.2` scope: Image Measurement scope: App structure Context Focus Stack split static metadata across `requirements.m` and `version.m`, spread a version-1 project over a generic lifecycle package, grouped three calculation concepts under `+appState`, and ran a startup callback only to fill an output path and emit an App-specific debug line. It also duplicated the portable-reference path loop in session, actions, and presentation. Decision and rationale Use the compact Runtime V2 contract: one product definition, one durable project specification, and one explicit session factory. Put calculation result shape, presets, and run fingerprinting beside the computation. Resolve output folders only when file registration or export actually needs them. Changes Consolidated command metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated project creation and validation in `projectSpec.m`. Moved transient image reconstruction to root `createSession.m`. Moved three `+appState` helpers into their owning `+analysisRun` capability. Removed the metadata files, generic lifecycle/state packages, and redundant App startup callback. Replaced all direct portable-reference reads with the Runtime path accessor. User and data impact Focus loading, registration, fusion, previews, duplicate-run detection, project reopen, and exports keep the same scientific behavior. A new App no longer mutates its durable output folder during startup. After inputs are selected, output defaults remain source-adjacent as before. The App package loses six structural files and one generic package while its real workflow capabilities become easier to locate. Compatibility and migration The durable payload remains version 1 with identical fields, defaults, and validation. Existing projects require no migration. The removed startup debug line is replaced by the Runtime's standard debug-startup message. Validation Unit tests cover the definition/project/session contract, numerical fusion, registration, deterministic run fingerprints, summaries, readers, and invalid inputs. The hidden GUI test covers debug startup, file and folder import, fusion, presentation, export manifest, save/load reconstruction, and append. Evidence [Focus Stack](../../../../apps/image-measurement/focus-stack/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up The remaining Image Measurement Apps still use the older structural packages and will be reviewed individually rather than copied from this App blindly."},{"title":"Framework guides and function reference keep distinct routes","url":"history/records/2026/07/LK-20260717-framework-guide-and-function-routes.html","kind":"history","section":"history","keywords":"LK-20260717-framework-guide-and-function-routes 2026-07-17 134 fix compatible Documentation information architecture","text":"Framework guides and function reference keep distinct routes id: LK-20260717-framework-guide-and-function-routes date: 2026-07-17 sequence: 134 type: fix compatibility: compatible scope: Documentation information architecture Context An initial Framework documentation alignment also classified individual `labkit.ui.*` and `labkit.contract.*` API pages as Framework pages. Entering those pages from the global Functions index then changed the active top-level area, which mixed conceptual ownership with page type and made return paths hard to predict. Decision and rationale Keep conceptual and workflow guides under Framework. Keep every exact MATLAB function contract under Functions. Visible API group labels still explain that `labkit.ui` is the Framework implementation, but the top navigation remains stable while browsing function reference pages. Changes Moved the canonical compatibility guide to `framework/contracts.html`. Kept a tracked legacy page at `libraries/contracts/index.html` that links to the new guide and the Functions reference. Restored Functions ownership for all individual UI and contract API pages. Updated framework, library, and regression-test links to the canonical guide. User and data impact Readers now follow one predictable route for Framework guides and another for exact function syntax. Existing contract-guide bookmarks still reach a compatibility page. MATLAB code and scientific data are unchanged. Compatibility and migration The old guide URL remains as a compatibility landing page. Public MATLAB symbols remain unchanged. Validation Renderer regression verifies the Framework guide route, legacy landing page, and Functions-active runtime API page. `docsCheck` verifies the complete site. Evidence The generated Framework landing page links directly to `framework/contracts.html`, while `labkit.ui.runtime.launch` highlights Functions and labels its sibling group Framework Runtime. Known limitations and follow-up The legacy page is a visible handoff rather than an HTTP redirect because the tracked static-site renderer does not currently emit redirect responses."},{"title":"Framework owns UI and compatibility documentation","url":"history/records/2026/07/LK-20260717-framework-documentation-ownership.html","kind":"history","section":"history","keywords":"LK-20260717-framework-documentation-ownership 2026-07-17 133 docs compatible App Framework documentation","text":"Framework owns UI and compatibility documentation id: LK-20260717-framework-documentation-ownership date: 2026-07-17 sequence: 133 type: docs compatibility: compatible scope: App Framework documentation Context The documentation described `labkit.ui` as the App Framework but presented its public API and the compatibility contracts through the generic Functions area. The visible information architecture therefore disagreed with the framework terminology used by app development guides. Decision and rationale Make Framework the documentation owner for `labkit.ui.*` and `labkit.contract.*`. Compatibility requirements belong in the Framework because App definitions and startup consume them. Keep the stable `labkit.contract` MATLAB namespace because it checks UI and domain facades alike; moving it below `labkit.ui` would create a misleading dependency and break existing App definitions. Changes Added compatibility contracts as a Framework sidebar branch. Classified UI and contract API pages under the Framework top navigation. Replaced raw UI package headings with visible Framework API labels while retaining fully qualified MATLAB symbols. Removed the separate Contracts entry from the Functions guide table. User and data impact Readers now enter framework runtime, layout, plotting, interaction, debugging, and compatibility material through one consistent Framework route. MATLAB calls, saved projects, scientific results, and URLs remain compatible. Compatibility and migration No code migration is required. Existing `labkit.ui.*` and `labkit.contract.*` symbols are unchanged. Validation Renderer regression tests verify Framework ownership for the contracts guide and a runtime API page. `docsCheck` rebuilds and compares the complete site. Evidence Generated UI and contract pages highlight Framework in the product navigation, and the Framework landing sidebar links the compatibility guide. Known limitations and follow-up The global API reference still indexes all public symbols together so exact MATLAB functions remain searchable from one place."},{"title":"Framework public APIs become an explicit last resort","url":"history/records/2026/07/LK-20260720-public-api-escalation-order.html","kind":"history","section":"history","keywords":"LK-20260720-public-api-escalation-order 2026-07-20 144 docs compatible Architecture guidance Agent skills","text":"Framework public APIs become an explicit last resort id: LK-20260720-public-api-escalation-order date: 2026-07-20 sequence: 144 type: docs compatibility: compatible scope: Architecture guidance scope: Agent skills Context A convenient framework helper can appear attractive for one App-specific need, but every public name becomes a long-lived compatibility and documentation commitment. Conversely, forcing unrelated behavior into an existing API can turn a focused contract into an ambiguous option bucket. Decision and rationale Use a consistent escalation order for new App behavior: keep it App-local, extend an existing focused API when the addition is natural, add private framework/runtime capability when shared implementation does not require an App-authored contract, and add a new public API only for stable use by multiple Apps or when the nearest existing API would otherwise lose a clear single purpose. This makes a new public API the final solution without prohibiting one when a genuine reusable boundary has emerged. Changes Added the escalation order to the root, framework, and App agent contracts. Updated the App builder and boundary guard skills to require evidence for multiple consumers or an anti-bucket clarity need before introducing a new public name. Updated the documentation maintainer skill to distinguish documentation of an existing focused extension from review of a genuinely new public surface. User and data impact Runtime behavior, public signatures, App calculations, projects, and exports are unchanged. The rule affects future design choices and review evidence. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Skill package validation covers every edited skill. Documentation history rendering and consistency checks cover this record. Repository searches confirm the escalation order appears at the three ownership layers and in the relevant skills. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up When a proposed public API crosses the stated threshold, record its consumers, ownership rationale, alternatives considered, and compatibility contract in the component history."},{"title":"Gait Analysis active-swing workflow","url":"history/records/2026/07/LK-20260716-gait-analysis-active-swing-workflow.html","kind":"history","section":"history","keywords":"LK-20260716-gait-analysis-active-swing-workflow 2026-07-16 71 feat breaking labkit_GaitAnalysis_app Video Marker input contract treadmill swing segmentation gait visualization and exports","text":"Gait Analysis active-swing workflow id: LK-20260716-gait-analysis-active-swing-workflow date: 2026-07-16 sequence: 71 type: feat compatibility: breaking component: `labkit_GaitAnalysis_app` | `1.1.1 -> 2.0.0` scope: Video Marker input contract scope: treadmill swing segmentation scope: gait visualization and exports Context The migrated Gait app accepted loosely shaped coordinate files but did not recover the legacy workflow's step segmentation, per-step skeleton report, or complete translation and angle measurements. A coordinate file alone also could not prove its frame rate, skeleton order, or calibration. Decision and rationale Treat a current Video Marker payload-version-2 project or autosave as the sole file input. It is the first durable artifact that owns coordinates, timing, skeleton, calibration, and annotation provenance together. Segment the legacy treadmill active swing from a prominent foot-X maximum (lift-off) to the following minimum (landing), including a final completed swing that has no later lift-off. Changes Load only the named `labkitProject` variable and reject generic tables, arbitrary MAT variables, legacy marker payloads, and missing frame rate. Show all overlaid skeleton trajectories immediately after import. Detect active swings with app-owned prominence, peak-height, and temporal separation logic; compute cycle/stance measures when a following lift-off exists. Present one selected swing with connected landmarks, joint/segment traces, five endpoint translations, and joint minimum, maximum, and range of motion. Export per-frame, coordinate, step, summary, and provenance tables with lift-off/landing and `swing_time_s` terminology. Migrate version-1 Gait option names and invalidate its scientifically incompatible cached result. User and data impact Users first inspect all trajectories, then run analysis and move between segmented swings. Existing Gait project settings are migrated, but old cached results are recalculated. Older or generic pose files must be converted by opening and saving the source in current Video Marker. Compatibility and migration This is an intentional input and output-schema break. The durable project migration preserves equivalent thresholds while renaming stride/contact-era fields to active-swing terms. Video Marker project payload version 2 is required. Validation Focused unit tests cover source metadata recovery, rejection of incomplete and legacy MAT files, lift-off/landing pairs, retention of the final completed swing, migration of old option names, scientific table fields, and exports. The focused GUI test covers trajectory orientation, analysis, step preview, export, and project reopen. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) documents the source, calculation, visualization, and export contracts. [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the durable payload used as input. Known limitations and follow-up The detector represents image-kinematic treadmill events, not force-plate contact. Frame annotation status is preserved but is not yet an automatic step exclusion rule."},{"title":"Gait Analysis app","url":"history/records/2026/07/LK-20260714-gait-analysis-app.html","kind":"history","section":"history","keywords":"LK-20260714-gait-analysis-app 2026-07-14 55 feat compatible labkit_GaitAnalysis_app Gait Analysis app","text":"Gait Analysis app id: LK-20260714-gait-analysis-app date: 2026-07-14 sequence: 55 type: feat compatibility: compatible component: `labkit_GaitAnalysis_app` | `new -> 1.0.0` scope: Gait Analysis app Context Existing gait work used a script chain after pose tracking: import coordinates, smooth marker traces, make per-step figures, compute step and joint metrics, select useful steps, and export tables for downstream statistics. That work belongs in its own app family because the downstream task is not image annotation, signal import, or electrochemistry; it is gait-specific pose analysis from already tracked coordinates. Decision and rationale Add an independent Gait Analysis app instead of extending Video Marker or recreating a model-training workflow. The app accepts several coordinate-table shapes, keeps gait event detection and metric definitions app-local, and exports simple CSV tables that can be consumed by plotting or statistical programs. Changes Added `labkit_GaitAnalysis_app` under the new Gait family. Added CSV/TSV/TXT and MAT pose-coordinate import, including generic `point_x`/`point_y` and LabKit `point__x`/`point__y` column shapes. Added smoothing, foot-relative step-event detection, hip/knee/ankle angle calculation, segment lengths, per-step translations, stride length, step time, ROM, summary metrics, and trajectory/angle/step-event previews. Added CSV set export for frame metrics, step metrics, summary metrics, and per-frame coordinates that keep raw pixel columns alongside optional scale-calibrated and first-frame-origin-shifted columns. User and data impact Users can analyze gait from multiple pose-coordinate sources without tying the workflow to a specific tracking model. The exported tables are plain CSV and separate frame-level, coordinate, step-level, and summary data for downstream overlay, plotting, or statistics. Compatibility and migration The app is additive and does not change Video Marker, image measurement apps, public `+labkit` facades, or existing exports. Existing script outputs can be imported when they provide wide coordinate columns or MAT `coords` and `pointNames`. Validation `GaitAnalysisTest` covers coordinate import, synthetic step detection, metric tables, MAT import, coordinate export calibration/origin semantics, coordinate CSV readback, and CSV set export. `GuiLayoutGaitAnalysisTest` covers the hidden GUI launch and semantic control contract. These tests were added with the app in commit `49863964`. Evidence Initial Gait Analysis app `49863964`. Known limitations and follow-up The first version analyzes one tracked subject at a time and does not perform tracking, model training, group-level statistics, EMG/CAP synchronization, multi-limb phase analysis, or automatic step quality classification."},{"title":"Gait Analysis keeps schema history in one project contract","url":"history/records/2026/07/LK-20260716-gait-analysis-structure.html","kind":"history","section":"history","keywords":"LK-20260716-gait-analysis-structure 2026-07-16 104 refactor compatible labkit_GaitAnalysis_app Gait App structure","text":"Gait Analysis keeps schema history in one project contract id: LK-20260716-gait-analysis-structure date: 2026-07-16 sequence: 104 type: refactor compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.3 -> 2.0.4` scope: Gait scope: App structure Context Gait Analysis spread creation, validation, session reconstruction, and two ordered schema upgrades through a generic lifecycle package. Its generic state package actually contained four analysis concepts: defaults, source-derived options, result construction, and duplicate-run fingerprints. Session and presentation code also inspected the Runtime's nested source-reference fields. Decision and rationale Concentrate project history behind one `projectSpec.m`, keep transient pose reconstruction in root `createSession.m`, and assign all former state helpers to `+analysisRun`. This preserves the real complexity—Video Marker parsing, step segmentation, kinematics, QC, and exports—while removing structural categories that did not explain the workflow. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderer, and debug capability in `definition.m`. Replaced two public migration files with one migration callback that selects the version-1 or version-2 transformation from `fromVersion`. Moved default options, pose-derived option resolution, empty results, and deterministic task fingerprints from `+appState` to `+analysisRun`. Replaced direct portable-reference access with semantic `sourcePaths` lookup in session reconstruction and presentation. Removed generic lifecycle/state packages and separate requirement/version files, and updated GUI-free documentation calls to the owning package. User and data impact Current Video Marker MAT loading, full-trajectory inspection, active-swing segmentation, one-step review, joint angles, translations, timing, QC, duplicate-run detection, CSV export, project save, and project reopen retain their behavior. The image-coordinate Y direction and time-series coordinate direction remain deliberately distinct. Developers can now follow project history through one entry and find all analysis policy under the package that owns the scientific workflow. Compatibility and migration Durable schema version 3 is unchanged. Version-1 option renames and result invalidation still run before the version-2 source collection upgrade. Runtime V2 now owns the loop and validates the resulting current payload. Validation Focused tests cover current Video Marker input, timing/scale/role extraction, active-swing segmentation, final-swing retention, gait parameters, CSV round-trip, and both historical migrations. The hidden GUI workflow covers MAT import, trajectory preview, analysis, export, project save, and decoded pose reconstruction after reopen. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not establish scientific validity for recordings outside the documented treadmill model or replace manual inspection of tracked points and segmented events. Neurophysiology and wearable Apps with generic lifecycle packages remain scheduled for the same review."},{"title":"Gait consumes the Video Marker file contract without a sibling App dependency","url":"history/records/2026/07/LK-20260717-gait-app-path-isolation.html","kind":"history","section":"history","keywords":"LK-20260717-gait-app-path-isolation 2026-07-17 124 fix compatible labkit_GaitAnalysis_app Gait App packaging Video Marker file contract","text":"Gait consumes the Video Marker file contract without a sibling App dependency id: LK-20260717-gait-app-path-isolation date: 2026-07-17 sequence: 124 type: fix compatibility: compatible component: `labkit_GaitAnalysis_app` | `2.0.7 -> 2.0.8` scope: Gait scope: App packaging scope: Video Marker file contract Context Gait Analysis correctly parsed saved Video Marker MAT documents in production, but its synthetic debug writer created those documents by calling `video_marker.projectSpec`, `video_marker.skeletonDefinition`, and `video_marker.frameAnnotations`. Tests added every App root to the MATLAB path, while the launcher adds only the selected App root. Debug startup could therefore pass tests and fail in a real isolated launch. Decision and rationale Treat the saved `labkitProject` payload as the boundary between the producer and consumer. Gait owns its parser, diagnostics, and synthetic consumer fixture. Video Marker owns serialization and schema validation. One explicit integration test may load both Apps to prove their current file contracts agree, but neither Gait production nor debug code may call the Video Marker package. Changes Rebuilt the Gait debug sample from the documented payload fields without loading Video Marker source code. Split consumer parser tests from a producer-consumer integration test that constructs a current Video Marker project, validates it, saves it, and reads it through Gait. Added an isolated-path debug test using only the repository root and Gait App root. Added a repository guardrail that rejects executable calls from one App package into a sibling App package while allowing documentation references and integration tests. Documented the saved-data boundary in the App development guide and Gait manual. User and data impact Gait debug startup and its packaged App behavior no longer depend on every public App being present on the MATLAB path. Users continue to open the same current Video Marker project and autosave MAT files; parsing, timing, calibration, gait calculations, and exports are unchanged. Developers receive an immediate guardrail failure if a shared test path hides a new sibling-App runtime dependency. Compatibility and migration No project or output migration is required. Video Marker payload version 2 remains the accepted input contract. Validation Focused validation covers isolated debug sample generation, consumer parsing, current Video Marker producer-to-Gait import, Gait calculations and hidden GUI workflow, and the App package boundary guardrail. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) [Video Marker](../../../../apps/image-measurement/video-marker/README.md) [App Development](../../../../development/build-apps/app-development.md#cross-app-data-contracts) Known limitations and follow-up The integration test protects the current saved schema, not scientific validity of manually marked trajectories. Those data and workflow checks remain App-owned."},{"title":"Gait trajectory image-coordinate preview","url":"history/records/2026/07/LK-20260716-gait-image-coordinates.html","kind":"history","section":"history","keywords":"LK-20260716-gait-image-coordinates 2026-07-16 67 fix compatible labkit_GaitAnalysis_app trajectory preview","text":"Gait trajectory image-coordinate preview id: LK-20260716-gait-image-coordinates date: 2026-07-16 sequence: 67 type: fix compatibility: compatible component: `labkit_GaitAnalysis_app` | `1.1.0 -> 1.1.1` scope: trajectory preview Context Marker coordinates use the image convention with an upper-left origin and Y increasing downward. Gait Analysis plotted those values on MATLAB's default Cartesian axes, so the trajectory preview appeared vertically flipped relative to the source video. Decision and rationale Preserve the imported coordinate values and scientific calculations. Reverse only the trajectory preview's Y axis so its visual orientation matches Video Marker and Image Marker. Time-series angle and step plots explicitly retain the conventional upward Y direction. Changes Render trajectory previews with a reversed Y axis. Restore the normal Y direction when switching to Angles or Steps. Document the preview coordinate convention. User and data impact Tracked motion now appears in the same orientation as the source image. Stored coordinates, calculations, tables, and exports are unchanged. Compatibility and migration The change is display-only and requires no project or data migration. Validation The hidden Gait Analysis workflow test checks both trajectory and time-series axis directions while exercising source load and analysis. Evidence [Gait Analysis](../../../../apps/gait/gait-analysis/README.md) documents the image-coordinate preview convention. Known limitations and follow-up The preview assumes imported marker coordinates follow the documented image coordinate convention. Generic coordinate files with a Cartesian convention must be converted by their producer before import."},{"title":"Headless validation follows current documentation and App contracts","url":"history/records/2026/07/LK-20260719-headless-validation-repair.html","kind":"history","section":"history","keywords":"LK-20260719-headless-validation-repair 2026-07-19 137 fix compatible labkit_TTestWizard_app Continuous integration Documentation validation Statistics","text":"Headless validation follows current documentation and App contracts id: LK-20260719-headless-validation-repair date: 2026-07-19 sequence: 137 type: fix compatibility: compatible component: `labkit_TTestWizard_app` | `1.0.0 -> 1.0.1` scope: Continuous integration scope: Documentation validation scope: Statistics Context The first main-branch validation after the path-derived documentation change still asserted several retired documentation paths. The same run exposed T-Test Wizard code-quality findings and a framework GUI test file that had grown beyond its review budget. Decision and rationale Align validation with the current path-owned documentation structure and keep Runtime-owned empty session buckets out of App factories. Resolve the static analysis findings without changing calculations, export values, or visible workflow behavior. Split the workspace-specific GUI coverage by capability so each test file remains focused. Changes Updated documentation guardrails to use the current testing, Runtime, and complete-App guide paths. Removed an empty `view` placeholder that Runtime already supplies. Replaced dynamic array growth in group-label and group-reassignment helpers with bounded collection. Centralized the box-plot label and documented the plot-jitter and significance-display constants. Split workspace-page and dynamic-table GUI coverage into its own focused test file. User and data impact T-Test Wizard calculations, project data, CSV values, plots, and workflows are unchanged. The App version advances to 1.0.1 to record the internal validation repair. Compatibility and migration Existing version-1 projects remain compatible and require no data migration. Runtime continues to provide `selection`, `workflow`, `view`, and `cache` in the final session state. Validation Focused contract and T-Test core tests cover the original headless failures. Hidden GUI tests cover the T-Test workflow and the extracted Runtime workspace coverage. Documentation rendering and changed-file gates validate the final main-branch handoff. Evidence [T-Test Wizard](../../../../apps/statistics/ttest-wizard/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Testing LabKit](../../../../development/maintain-and-release/testing.md) Known limitations and follow-up Interactive native-dialog behavior remains a developer-led manual validation responsibility; hidden GUI automation does not replace it."},{"title":"History records use one canonical authored format","url":"history/records/2026/07/LK-20260721-canonical-history-record-format.html","kind":"history","section":"history","keywords":"LK-20260721-canonical-history-record-format 2026-07-21 149 docs compatible Project History Documentation Search","text":"History records use one canonical authored format id: LK-20260721-canonical-history-record-format date: 2026-07-21 sequence: 149 type: docs compatibility: compatible scope: Project History scope: Documentation Search Context History records mixed a transport-oriented schema marker with several related but inconsistent metadata conventions. Authors had to infer valid fields and values from renderer internals and agent guidance. Decision and rationale Use one compact, authored record format without a schema field. A single public format page defines every legal field, value, ordering rule, and narrative section so a record can be written and reviewed from repository documentation. Changes Rewrote every existing record into the canonical metadata form, preserved its identity, date, sequence, narrative, and component history links, and added a strict renderer parser. Scopes now feed the documentation search index. User and data impact Readers retain the complete history timeline and component links while gaining more useful search terms. This documentation-only change does not alter saved projects, experimental data, or App behavior. Compatibility and migration The source format changes incompatibly for record authors: remove `schema` and replace an introduction entry with a `component` transition using `new ->`. Existing records are migrated in this same change, so no compatibility reader is retained. Validation The documentation renderer validates every migrated record, rebuilds the site, and documentation guardrails verify the canonical source and generated output. Evidence The renderer parser is the executable enforcement of the published format; the record format page provides its human-readable counterpart. Known limitations and follow-up No known follow-up is required. Future history changes use this format directly rather than adding compatibility metadata variants."},{"title":"Image App project validation ownership","url":"history/records/2026/07/LK-20260716-image-project-validation-ownership.html","kind":"history","section":"history","keywords":"LK-20260716-image-project-validation-ownership 2026-07-16 119 refactor compatible labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app image App project schemas App maintenance cost","text":"Image App project validation ownership id: LK-20260716-image-project-validation-ownership date: 2026-07-16 sequence: 119 type: refactor compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.4 -> 1.7.5` component: `labkit_CurvatureMeasurement_app` | `1.4.4 -> 1.4.5` component: `labkit_FLIRThermal_app` | `1.4.4 -> 1.4.5` component: `labkit_FocusStack_app` | `1.5.3 -> 1.5.4` component: `labkit_ImageEnhance_app` | `1.6.4 -> 1.6.5` component: `labkit_ImageMatch_app` | `1.6.4 -> 1.6.5` component: `labkit_VideoMarker_app` | `1.5.3 -> 1.5.4` scope: image App project schemas scope: App maintenance cost Context The seven image workflow validators repeated Runtime's canonical project-bucket checks and, in six cases, the standard portable source-record field list. Those checks obscured the App-specific rules that actually matter: crop task relationships, parameter ranges, annotation shapes, video metadata, skeleton dimensions, and result schemas. Decision and rationale Keep the existence of each App's required source collection and every domain-specific relationship in `projectSpec.m`. Rely on Runtime for the five canonical bucket structs and the internal shape of every supplied source record. This preserves strict malformed-project rejection while making each validator read as its App schema. Changes Removed 71 net lines of repeated Runtime structure checks from the seven image App project specifications. Preserved required `inputs.sources` fields and all item/source, annotation/source, skeleton/frame, parameter, and result constraints. Added a GUI-free contract that accepts all seven default projects and rejects each project when its App-required source collection is removed. User and data impact Valid projects, migrations, image calculations, annotations, and exports are unchanged. Malformed source records continue to fail in Runtime; malformed App fields continue to fail in the owning App validator. Compatibility and migration No payload format changed and no migration is required. The refactor changes which layer reports framework-owned structure failures, not which valid data the Apps accept. Validation The focused project-spec contract covers default acceptance and missing-source rejection for every affected App. Existing focused unit and hidden-GUI tests cover the domain relationships and representative load, edit, analyze, and export workflows. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines canonical project and source validation. Each affected [Image Measurement App](../../../../apps/image-measurement/README.md) manual lists its remaining project and session responsibilities. Known limitations and follow-up This change does not shorten action handlers or scientific workflow code. Those remain App-owned unless a separately measured repeated behavior proves to be stable and domain-neutral."},{"title":"Image Apps stop creating invalid placeholder outputs","url":"history/records/2026/07/LK-20260716-image-manifest-output-arrays.html","kind":"history","section":"history","keywords":"LK-20260716-image-manifest-output-arrays 2026-07-16 80 fix compatible labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_ImageEnhance_app labkit_ImageMatch_app Image Measurement Result manifests","text":"Image Apps stop creating invalid placeholder outputs id: LK-20260716-image-manifest-output-arrays date: 2026-07-16 sequence: 80 type: fix compatibility: compatible component: `labkit_BatchImageCrop_app` | `1.7.1 -> 1.7.2` component: `labkit_FLIRThermal_app` | `1.4.1 -> 1.4.2` component: `labkit_ImageEnhance_app` | `1.6.1 -> 1.6.2` component: `labkit_ImageMatch_app` | `1.6.1 -> 1.6.2` scope: Image Measurement scope: Result manifests Context Four image Apps preallocated variable-length result arrays by first asking the Runtime output factory to create a record with an empty ID. Runtime V2 rejects empty result IDs, so otherwise valid export workflows could stop before their standard manifest was written. Decision and rationale Use the canonical empty result array introduced by `labkit.ui` and append only validated real outputs. This preserves strict IDs without requiring each App to duplicate the manifest struct shape. Changes Updated Batch Crop crop outputs. Updated FLIR image, colorbar, and temperature-CSV outputs. Updated Image Enhance batch outputs. Updated Image Match batch outputs. User and data impact The exported images, numeric CSV data, filenames, roles, statuses, and manifest schemas are unchanged. Exports no longer fail during internal output-array initialization. Compatibility and migration No saved project or export schema migration is required. Existing result files remain readable. Validation Each affected App's hidden synthetic GUI export workflow verifies its domain output plus the standard LabKit manifest. Version and component-history guardrails cover all four products. Evidence [Batch Crop](../../../../apps/image-measurement/batch-crop/README.md) [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Image Match](../../../../apps/image-measurement/image-match/README.md) Known limitations and follow-up The Apps still need their separate single-definition and projectSpec reviews; this record only fixes the shared manifest construction defect."},{"title":"Image Enhance assigns state helpers to workflow capabilities","url":"history/records/2026/07/LK-20260716-image-enhance-structure.html","kind":"history","section":"history","keywords":"LK-20260716-image-enhance-structure 2026-07-16 99 refactor compatible labkit_ImageEnhance_app Image Measurement App structure","text":"Image Enhance assigns state helpers to workflow capabilities id: LK-20260716-image-enhance-structure date: 2026-07-16 sequence: 99 type: refactor compatibility: compatible component: `labkit_ImageEnhance_app` | `1.6.2 -> 1.6.3` scope: Image Measurement scope: App structure Context Image Enhance split static product metadata across files, spread a version-1 project over a generic lifecycle package, and placed decoded items, durable annotations, numerical histories, preview scaling, UI normalization, and export fingerprints together under `+appState`. Source reconstruction and presentation also read nested portable-reference fields directly. Decision and rationale Use the compact Runtime V2 definition and project contracts, then assign each remaining concept to the workflow capability that owns its behavior. Preserve the important preview/export boundary: pixel-radius parameters scale for the downsampled preview while full-size export continues to use source coordinates. Changes Consolidated product metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated durable creation and validation in `projectSpec.m` and moved transient reconstruction to root `createSession.m`. Assigned decoded items and lazy selected-image loading to `+sourceFiles`, histories and preview replay to `+analysisRun`, durable per-image data to `+enhancementAnnotations`, UI value clamping to `+userInterface`, and export fingerprints to `+resultFiles`. Removed separate metadata files, generic lifecycle/state packages, and the redundant App startup callback. Replaced direct portable-reference access with the Runtime path accessor. Corrected the GUI-free manual example to write the first returned image from the documented cell-array result. User and data impact Shared and per-image histories, white ROI calibration, pending previews, preview-coordinate radius scaling, original-resolution processing, duplicate export detection, project reopen, and outputs keep their existing behavior. Empty launch no longer chooses an output folder; importing sources retains the source-adjacent default. Developers can locate source decoding, calculations, durable annotations, and exports without learning one generic state package. Compatibility and migration The durable payload remains version 1 with identical fields and defaults, so existing projects require no migration. The Runtime's standard debug-startup message replaces the removed App-specific line. Validation The focused unit suite covers numerical tools, white ROI behavior, preview radius scaling, source reading, full-size exports, manifests, per-image steps, task fingerprints, and project/presenter contracts. The hidden GUI workflow covers file append and selection, independent histories, tool application, export, save/load, and transient cache reconstruction. Evidence [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Image Library](../../../../libraries/image/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Apps still using generic lifecycle/state packages will be reviewed individually. Automated GUI tests do not replace manual judgment of visual quality, ROI pointer feel, or enhancement suitability for quantitative images."},{"title":"Image Match adopts capability-owned structure","url":"history/records/2026/07/LK-20260716-image-match-structure.html","kind":"history","section":"history","keywords":"LK-20260716-image-match-structure 2026-07-16 96 refactor compatible labkit_ImageMatch_app Image Measurement App structure","text":"Image Match adopts capability-owned structure id: LK-20260716-image-match-structure date: 2026-07-16 sequence: 96 type: refactor compatibility: compatible component: `labkit_ImageMatch_app` | `1.6.2 -> 1.6.3` scope: Image Measurement scope: App structure Context Image Match split metadata and project lifecycle across eight structural files, used an App startup callback only for an output-folder default and debug line, and grouped unrelated source, analysis, and export structures under `+appState`. Session, actions, and presentation also read portable-reference path fields directly. Decision and rationale Adopt one definition, one project spec, and one session factory. Place each data constructor beside the capability that owns its meaning: loaded image items with source reading, match steps with analysis, and idempotent export tasks with result writing. Changes Consolidated command metadata, version, requirements, and optional capabilities in `definition.m`. Consolidated version-1 project creation and validation in `projectSpec.m`. Moved selected-image reconstruction to root `createSession.m`. Replaced `+appState` with capability-owned source, analysis, and result functions. Removed the metadata files, generic lifecycle package, and redundant startup callback. Replaced all portable-reference field reads with the Runtime path accessor. Corrected the GUI-free manual example to use the real `applyPipeline` argument order and cell output. User and data impact Reference/source selection, lazy preview loading, match history, calculation, duplicate-export detection, project reopen, and export formats behave unchanged. A new project no longer writes an environment-specific output path during startup; source selection still establishes the same adjacent default. The directory now answers where an item, step, or export task belongs without requiring knowledge of a generic state layer. Compatibility and migration The durable payload remains version 1 with identical fields and validation. Existing Image Match projects require no migration. Validation Unit tests cover the definition/project/session contract, matching methods, pipeline replay, preview scaling, result tables, export formats, manifests, and task fingerprints. The hidden GUI suite covers reference/source loading, selection, history, preview, export, and save/load workflows. Evidence [Image Match](../../../../apps/image-measurement/image-match/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Other Image Measurement Apps retain older lifecycle or state packages and will be reviewed against their own capabilities."},{"title":"Image app workflow improvements","url":"history/records/2026/07/LK-20260701-image-app-workflow-improvements.html","kind":"history","section":"history","keywords":"LK-20260701-image-app-workflow-improvements 2026-07-01 27 feat compatible labkit_launcher labkit.image labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_FLIRThermal_app labkit_ImageEnhance_app labkit_ImageMatch_app Image app workflow improvements","text":"Image app workflow improvements id: LK-20260701-image-app-workflow-improvements date: 2026-07-01 sequence: 27 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.5 -> 1.1.6` component: `labkit.image` | `1.0.0 -> 1.1.0` component: `labkit.ui` | `3.2.10 -> 3.3.0` component: `labkit.ui` | `3.3.0 -> 3.3.1` component: `labkit_BatchImageCrop_app` | `1.4.0 -> 1.5.0` component: `labkit_BatchImageCrop_app` | `1.5.0 -> 1.5.1` component: `labkit_FLIRThermal_app` | `1.0.0 -> 1.1.0` component: `labkit_FLIRThermal_app` | `1.1.0 -> 1.1.2` component: `labkit_ImageEnhance_app` | `1.4.0 -> 1.4.1` component: `labkit_ImageMatch_app` | `1.4.0 -> 1.4.1` scope: Image app workflow improvements Context Image apps used full-resolution data for several previews even when the screen could not display that detail. Controls also accepted ranges that were awkward for the current image, and FLIR point and ROI measurements were not yet a complete direct-manipulation workflow. Decision and rationale Set an explicit preview budget, derive sensible control bounds from the loaded image, and preserve full-resolution data for calculations and exports. Expand the FLIR app around point and ROI temperature tools rather than adding more separate analysis dialogs. Changes `labkit.image` `1.0.0 -> 1.1.0` `labkit.ui` `3.2.10 -> 3.3.1` Batch Crop `1.4.0 -> 1.5.1` FLIR Thermal `1.0.0 -> 1.1.2` `labkit_launcher` `1.1.5 -> 1.1.6` Added preview-budget helpers. Improved image app range and preview controls. Improved image measurement workflows. User and data impact Large images produced bounded previews while exports continued to use source resolution. Batch Crop controls reflected the crop and scale being edited, and FLIR users could place temperature points or regions directly on the image and read their values in the detail panel. Compatibility and migration Existing image inputs and app exports remained valid. Preview budgeting changed display resolution only; calculations and exports continued to use the source data required by each workflow. Validation The two commits expanded Batch Crop export tests, FLIR operation and layout tests, image-facade preview tests, busy-state tests, scale-bar gesture checks, and launcher coverage. Exact historical commands were not recorded. Evidence Main commits `15a798ba` and `70bfcfd4`. Known limitations and follow-up Preview budgeting improved responsiveness but did not by itself remove every eager image read. Batch Crop file selection was made lazy in the following profiling work."},{"title":"Interactive recovery of missing project sources","url":"history/records/2026/07/LK-20260716-runtime-source-relink.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-source-relink 2026-07-16 66 fix compatible labkit.ui project loading portable source references","text":"Interactive recovery of missing project sources id: LK-20260716-runtime-source-relink date: 2026-07-16 sequence: 66 type: fix compatibility: compatible component: `labkit.ui` | `6.0.1 -> 6.0.2` scope: project loading scope: portable source references Context Saved projects could describe required external files with portable source records, but an unresolved path produced a framework error unless the owning app implemented the advanced `Project.RelinkSources` hook. Production apps using standard source records therefore had no common recovery interaction when a project moved between folders, drives, or operating systems. Decision and rationale Make missing-file recovery the default Runtime V2 behavior for canonical source records. The runtime already owns project loading, injected dialogs, and transactional state replacement, so it can provide one consistent prompt without duplicating native chooser code in every app. The advanced app hook remains available for nonstandard source schemas. Changes Report the saved filename and source role when automatic resolution fails. Let the user locate each missing required file with one native file chooser. Rebuild the selected source reference relative to the loaded project before constructing the app session. Mark a successfully repaired document as unsaved so the replacement path can be retained on the next save. Mark migrated payloads, snapshots, and declared legacy variables as unsaved; **Save State** then upgrades the opened MAT path to the current project format through the existing atomic replacement. Treat cancellation as an atomic load cancellation without changing the live project or view. User and data impact Projects whose external files remain in their recorded locations open exactly as before. When a required file moved, the user can locate it instead of seeing only an unresolved-source error. The runtime never searches arbitrary folders or silently substitutes a different file. Merely opening an old document does not rewrite it; saving the migrated document upgrades that same path. Compatibility and migration The change is compatible with current project envelopes, declared legacy imports, and existing custom `Project.RelinkSources` callbacks. Selecting a replacement updates only the candidate project; saving writes the current project format. Validation The hidden Runtime V2 project test covers successful default relinking, relative-reference rebuilding, dirty-document state, cancellation rollback, and the existing custom relink hook. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) documents the default missing-source interaction. Runtime source resolution reruns before fresh-session construction and state commit. Known limitations and follow-up The default flow asks once for each unresolved source. Apps with a specialized multi-file schema may continue to provide a custom relinking callback."},{"title":"LabKit name and the first multi-domain app families","url":"history/records/2026/05/LK-20260530-app-family-expansion.html","kind":"history","section":"history","keywords":"LK-20260530-app-family-expansion 2026-05-30 3 feat compatible historical project evolution","text":"LabKit name and the first multi-domain app families id: LK-20260530-app-family-expansion date: 2026-05-30 sequence: 3 type: feat compatibility: compatible scope: historical project evolution Context By the end of the electrochem extraction, the repository had reusable DTA operations and several app entry points, but its naming and package layout still reflected the original Gamry workbench. It was not yet clear whether the design could serve workflows with different data, interaction, and visualization needs. Decision and rationale Rename the project LabKit and treat apps, rather than one scientific domain, as the main deliverables. Keep a limited set of reusable packages underneath them, then exercise that structure with image registration, region editing, curvature measurement, wearable data import, and ECG viewing. The purpose was not to turn LabKit into one analysis program. It was to make a common MATLAB foundation useful to several independent laboratory tools without erasing the workflow decisions that made each tool understandable. Changes Renamed the workbench namespace to `labkit` and reduced the public package surface to app-facing UI and domain operations. Standardized the basic workbench shell and DTA file panels across the electrochem apps. Added DIC preprocessing and postprocessing workflows with iterative crop, mask, registration, zoom, and preview interactions. Added the Curvature Measurement app and reused its anchor-curve editor across image workflows where the interaction was genuinely the same. Added a biosignal facade and the ECG Print app, including wearable CSV import and signal-column diagnostics. Added MATLAB CI and reorganized tests by source responsibility. User and data impact LabKit now served electrochem, image, and biosignal work from distinct app entry points. Image users gained direct manipulation of regions and anchors; biosignal users gained a viewer for imported wearable recordings. Existing DTA files remained external inputs and were not converted into a central project format. Compatibility and migration The namespace rename was an implementation migration for repository code. The new image and biosignal apps were additive. Scripts that referenced the former workbench namespace needed to adopt `labkit` or an app entry point. Validation Focused DIC interaction tests, biosignal import tests, layout tests, and the first MATLAB CI workflow were added during this stage. The exact historical command used for each commit was not recorded. Evidence Public-surface cleanup and LabKit rename `9bd8ec8f` through `179885e1`. Unified workbench and file panels `2de7dd0f` through `1be52b9d`. DIC family `aa96ae88` through `fd808a56`. Curvature Measurement `9c1f3798`, `8bcde252`. Biosignal and ECG work `d7c31369` through `b7cabd52`. Initial MATLAB CI `5102640c`. Known limitations and follow-up The new apps exposed interaction problems that ordinary callback tests did not capture well, including popout axes, anchor editing, busy-state reentry, and image zoom ownership. Those issues drove the managed interaction work that followed before v1.0."},{"title":"Launcher adopts the App SDK diagnostic launch contract","url":"history/records/2026/07/LK-20260720-launcher-app-sdk-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260720-launcher-app-sdk-diagnostics 2026-07-20 139 feat compatible labkit_launcher LabKit Core App Framework","text":"Launcher adopts the App SDK diagnostic launch contract id: LK-20260720-launcher-app-sdk-diagnostics date: 2026-07-20 sequence: 139 type: feat compatibility: compatible component: `labkit_launcher` | `1.5.2 -> 1.6.0` scope: LabKit Core scope: App Framework Context The Launcher still passed the retired `RequestAdapter` startup seam after Apps had moved to `labkit.app.Definition`. Normal launch therefore failed before the selected App could create its window. Removing that argument restored startup, but temporarily also removed the user's diagnostic launch path. Decision and rationale Treat the App entrypoint and `Definition.launch` as the only Launcher-to-App runtime boundary. A normal launch uses the SDK defaults. A debug launch passes one typed `labkit.app.diagnostic.Options` value that requests verbose persisted events and the App-owned anonymous synthetic sample. The Launcher continues to read catalog metadata from the single app-owned `definition.m` source without executing the SDK. This preserves its recovery role when an installed framework is incomplete or damaged. Changes Replaced retired runtime-adapter injection with direct App SDK entrypoint calls for normal launch and profiling. Restored **Open Debug** using verbose typed diagnostics and each App's `BuildDebugSample` contract. Added one isolated session folder per debug launch under `artifacts/diagnostics/launcher/`, with the event stream, manifests, and anonymous fixture artifacts. Kept Launcher UI state in a Launcher-owned view record instead of the retired framework registry name. Updated Definition metadata discovery for current name-value syntax while retaining the older literal form for repair-oriented catalog compatibility. Excluded hidden class-folder implementation methods from public API and generated-documentation discovery. User and data impact Existing normal launch, profiling, packaging, update, and documentation actions keep their public behavior. Debug launch now creates explicit diagnostic artifacts and opens the App with its synthetic scenario; it no longer invokes the retired string-mode or request-adapter contracts. Compatibility and migration No additional migration applies beyond the compatibility information in the preceding impact section. Validation Focused Launcher GUI, catalog, progress, profiling, and documentation contracts. App SDK diagnostic integration through DIC Preprocess, including `events.jsonl` and `sample-pack.json` evidence. Documentation source synchronization and public API discovery guardrails. Evidence The validation details above are the supporting evidence for this record. Known limitations and follow-up Developer-led interactive validation should confirm the visible debug launch, the selected App's synthetic state, and the usefulness of the generated diagnostic bundle on the deployment MATLAB version. labkit.app.Definition Compile and launch one immutable App SDK contract."},{"title":"Launcher and maintainer tool manuals","url":"history/records/2026/07/LK-20260716-launcher-and-maintainer-manuals.html","kind":"history","section":"history","keywords":"LK-20260716-launcher-and-maintainer-manuals 2026-07-16 63 docs compatible labkit_launcher `docs/apps/labkit-core/launcher/` `docs/development/tools/` `docs/development/maintain-and-release/testing.md`","text":"Launcher and maintainer tool manuals id: LK-20260716-launcher-and-maintainer-manuals date: 2026-07-16 sequence: 63 type: docs compatibility: compatible component: `labkit_launcher` scope: `docs/apps/labkit-core/launcher/` scope: `docs/development/tools/` scope: `docs/development/maintain-and-release/testing.md` Context Launcher behavior was split between Getting Started, architecture notes, source comments, and historical records. Maintainer tools under `tools/` had callable MATLAB entry points but no coherent manual set; profiling details were embedded in the testing command matrix while packaging and codecheck behavior were mostly discoverable only from source. Decision and rationale Document the user-operated launcher beside LabKit Core apps, even though its self-repair implementation remains a root-level standalone file. Document source-checkout tools under Development because they support maintenance rather than app workflows. Keep the existing Testing page as the test-system owner, add a conceptual execution map there, and move profiler call details into the maintainer tool reference. This structure lets readers start from the product they operate or the maintenance task they intend, without mirroring every source directory or mixing tool APIs into reusable `labkit.*` function reference. Changes Added a LabKit Launcher manual covering window actions, programmatic calls, discovery, installation, recovery, tools, limitations, and development milestones. Added a maintainer-tool landing page and detailed references for Code Analyzer reports, app deployment packages, documentation builds, and performance profiling. Added a test-system execution map explaining build tasks, runner discovery, test layers, selectors, plugins, artifacts, and manual-validation boundaries. Connected Getting Started, Apps, LabKit Core, Development, Architecture, Documentation, Testing, Release, the root README, and generated navigation to the new manuals. User and data impact Users can now understand launcher controls and history from the Apps section. Maintainers can copy direct MATLAB calls and inspect every supported option and output without reading tool implementation files. No app workflow, scientific calculation, saved project, input file, or exported result changes. Compatibility and migration This is an additive documentation change. Existing page URLs remain valid. The new launcher and tool pages are generated from Markdown and included in search; no handwritten HTML or third-party documentation package is added. Validation Documentation contracts verify source ownership, generated launcher history, tool entry-point coverage, local links, search indexing, and byte-for-byte agreement between structured sources and the tracked site. Evidence `docs/apps/labkit-core/launcher/README.md` owns the launcher manual. `docs/development/tools/` owns direct maintainer-tool references. `ProjectSurfaceDocumentationTest` protects placement and generated content. Known limitations and follow-up Private app documentation remains in each private repository. Internal private helpers beneath tool folders are intentionally described through their public entry point rather than receiving standalone reference pages."},{"title":"Launcher app version history","url":"history/records/2026/07/LK-20260713-launcher-app-version-history.html","kind":"history","section":"history","keywords":"LK-20260713-launcher-app-version-history 2026-07-13 53 feat compatible labkit_launcher Launcher app version history","text":"Launcher app version history id: LK-20260713-launcher-app-version-history date: 2026-07-13 sequence: 53 type: feat compatibility: compatible component: `labkit_launcher` | `1.3.0 -> 1.4.0` scope: Launcher app version history Context The structured changelog could be parsed by maintainers, but launcher users could not inspect the history of the selected app. Earlier normalization also attached broad release descriptions without recording every app version event, which made histories such as CIC appear to begin years of versions too late. Decision and rationale Expose component-filtered history in the launcher and make exact component events the durable lookup key. A user should be able to move from the current app catalog directly to its evolution record without reading Git history or understanding changelog internals. Changes Added `labkit_launcher(\"history\", appCommand)` for programmatic history lookup and a Version History viewer for the selected launcher app. Added explicit component introduction events and reconstructed every tracked launcher, facade, and app version transition from `origin/main` history. Added continuous-history validation so missing introductions, gaps, and current-version mismatches fail the release guardrail. User and data impact Launcher users can inspect dated version transitions, compatibility, rationale, impact, and evidence for the selected app. App calculations and user data are unchanged. Compatibility and migration The launcher command and button are additive. Existing list, version, package, maintenance, and app-launch workflows remain available. Validation `LauncherGuiTest` covers programmatic filtering and the hidden GUI viewer; `ChangelogGuardrailTest` validates complete version chains against current metadata. These checks were included in the combined mainline change `4a6c41dd`. Evidence Component history, launcher viewer, and changelog validation were included in `4a6c41dd`. Historical component events were reconstructed from version-file changes on the then-current `origin/main` history. Known limitations and follow-up History begins when version metadata was first tracked for each component. Behavior before that point can only be inferred from older source commits and is not assigned invented version numbers."},{"title":"Launcher code-analysis export","url":"history/records/2026/07/LK-20260701-launcher-code-analysis-export.html","kind":"history","section":"history","keywords":"LK-20260701-launcher-code-analysis-export 2026-07-01 29 feat compatible labkit_launcher Launcher code-analysis export","text":"Launcher code-analysis export id: LK-20260701-launcher-code-analysis-export date: 2026-07-01 sequence: 29 type: feat compatibility: compatible component: `labkit_launcher` | `1.1.6 -> 1.2.0` scope: Launcher code-analysis export Context The launcher offered a Code Analyzer action, but its earlier implementation carried substantial custom export code. That made a maintenance tool harder to maintain than the MATLAB analysis it was exposing. Decision and rationale Use MATLAB's native issue representation and export path, and keep the launcher responsible only for choosing the target and destination. This reduced custom conversion logic while preserving a launcher-level entry point for the report. Changes `labkit_launcher` `1.1.6 -> 1.2.0` Exported launcher Code Analyzer issues natively. User and data impact The launcher could export Code Analyzer findings in a MATLAB-supported form without requiring a separate script. This action inspected source code only and did not start or modify an app project. Compatibility and migration Existing launcher actions remained available. Newly exported issue files used MATLAB's native representation rather than the removed custom conversion. Validation `LauncherGuiTest` gained coverage for the native issue-export path and the implementation in `8fd3ddff` removed more launcher code than it added. Evidence Main commit `8fd3ddff`. Known limitations and follow-up Code analysis remained a maintainer tool. It did not become a prerequisite for launching an app or a replacement for the repository's automated checks."},{"title":"Launcher installation and App startup expose actionable progress","url":"history/records/2026/07/LK-20260726-launcher-install-and-startup-feedback.html","kind":"history","section":"history","keywords":"LK-20260726-launcher-install-and-startup-feedback 2026-07-26 160 fix compatible labkit_launcher Launcher double-click startup feedback Duplicate App launch prevention Standalone install and repair target selection Stable main and release-tag download selection","text":"Launcher installation and App startup expose actionable progress id: LK-20260726-launcher-install-and-startup-feedback date: 2026-07-26 sequence: 160 type: fix compatibility: compatible component: `labkit_launcher` | `1.7.0 -> 1.7.1` scope: Launcher double-click startup feedback scope: Duplicate App launch prevention scope: Standalone install and repair target selection scope: Stable main and release-tag download selection Context Opening an App from the Launcher was synchronous. Although the callback assigned an opening message before calling the App entry point, MATLAB could enter the expensive startup call before repainting the Launcher. Users therefore saw no visible acknowledgement, could interpret a legitimate startup as a hang, and could issue repeated double-clicks without knowing which stage was active. Existing GUI coverage asserted only the final opened state. The standalone `labkit_launcher.m` also claimed it could bootstrap an otherwise empty folder, while its safety check and regression test rejected that exact case. Its repair window exposed neither a target folder nor a download-source choice, and did not distinguish a new installation from replacement of an existing one. Decision and rationale Treat startup acknowledgement as part of the Launcher interaction contract. Publish and paint a small number of honest stages before the expensive App entry point, disable launch surfaces while the action is active, and preserve the concrete entry-point failure when startup does not complete. Stages identify the current ownership boundary without inventing percentage progress that the Launcher cannot measure. Keep the standalone entry self-contained but make its narrow responsibility complete: choose and validate a target before network access, distinguish installation from repair, offer stable/main/explicit-tag archives, confirm replacement, validate the extracted candidate, and retain transactional rollback. The installed Launcher continues to own rich version browsing and App catalog behavior. Changes Added visible path-preparation and App-window-initialization stages. Changed the launch button text and pointer while startup is active. Disabled the App table and all action buttons until startup finishes. Restored the normal controls after success or failure. Extended the native Launcher test to inspect status and control state at the instant the selected App entry point begins. Replaced the one-button repair window with target, source, a fetched published-version selector, confirmation, truthful operation stages, and status details. The UI neither requires users to type repository tags nor claims percentage progress for work it cannot measure. Allowed safe installation into a new, empty, or standalone-launcher-only folder while rejecting filesystem roots, Git checkouts, and unrelated nonempty folders before download. Added current-main and selected published-release downloads without importing the installed version manager into the standalone file. User and data impact Users receive immediate acknowledgement after a button press or row double-click, can see whether startup is preparing the path or waiting in the named App entry point, and cannot accidentally queue another launch through the Launcher while the first is active. Scientific data and App projects are unchanged. A user who downloads only `labkit_launcher.m` can now choose where to install, understand whether the operation is a new installation or repair, and select a published stable version or deliberate development source. Unsafe target folders fail before network or replacement activity. Compatibility and migration The launcher command, catalog, table fields, visual layout, update tools, and App entry points are unchanged. The patch is compatible with existing installations and Apps. The standalone window adds choices but preserves latest stable as its recommended default. Validation The focused hidden-GUI Launcher specification verifies the starting message, disabled table, wait pointer, temporary button label, selected command, final opened message, and restoration of controls. Documentation consistency and launcher version/history guardrails cover the published contract. Bootstrap specifications verify standalone UI choices, root-only and selected empty-folder installation, stable-release fallback, exact main and tag archive URLs, candidate validation, Git and unrelated-folder rejection, preservation, rollback, and actionable network failure. Evidence The direct-main fix commit, focused Launcher dispatch result, generated documentation check, and exact main-push CI run are the delivery evidence. Known limitations and follow-up The Launcher can identify that control has entered a named App entry point, but cannot report finer App-owned initialization phases until that App creates its Runtime diagnostics. Native dialog appearance, folder-picker behavior, and perceived responsiveness remain manual checks."},{"title":"Launcher manager and stale callback fix","url":"history/records/2026/06/LK-20260626-launcher-manager-and-stale-callback-fix.html","kind":"history","section":"history","keywords":"LK-20260626-launcher-manager-and-stale-callback-fix 2026-06-26 13 fix compatible labkit_launcher labkit_launcher labkit.ui Launcher manager and stale callback fix","text":"Launcher manager and stale callback fix id: LK-20260626-launcher-manager-and-stale-callback-fix date: 2026-06-26 sequence: 13 type: fix compatibility: compatible component: `labkit_launcher` | `1.0.0 -> 1.1.0` component: `labkit_launcher` | `1.1.0 -> 1.1.1` component: `labkit.ui` | `3.0.0 -> 3.0.1` scope: Launcher manager and stale callback fix Context The launcher could update LabKit but did not offer an explicit choice among a recent release, a tag, and the main branch. Separately, image drag tools could leave old callbacks attached after an interaction ended. Decision and rationale Add a managed version selector backed by an install manifest, and make image interaction cleanup restore or release every temporary callback. Both changes reduce hidden state that otherwise survives beyond the user's selected action. Changes `labkit_launcher` `1.0.0 -> 1.1.1` `labkit.ui` `3.0.0 -> 3.0.1` Added the launcher version manager and managed-manifest requirement. Released stale image drag callbacks. User and data impact Users could deliberately install a supported release or development revision. Ending an image drag no longer left the figure responding to an obsolete tool. Saved app data did not change. Compatibility and migration Existing launcher installations remained usable. Managed installations needed the launcher manifest for version replacement; image editors required no saved- data conversion after their stale callbacks were released. Validation The listed commits added launcher-manager/manifest coverage and regression checks for image callback cleanup. Exact historical commands were not recorded. Evidence Main commits `fe8654c9`, `ef89cf77`, and `3d23b7f1`. Known limitations and follow-up Version selection still depended on network access for remote revisions; already installed local versions remained usable offline."},{"title":"Launcher reads the single App definition","url":"history/records/2026/07/LK-20260716-launcher-definition-metadata.html","kind":"history","section":"history","keywords":"LK-20260716-launcher-definition-metadata 2026-07-16 78 refactor compatible labkit_launcher App discovery Product metadata ownership","text":"Launcher reads the single App definition id: LK-20260716-launcher-definition-metadata date: 2026-07-16 sequence: 78 type: refactor compatibility: compatible component: `labkit_launcher` | `1.5.0 -> 1.5.1` scope: App discovery scope: Product metadata ownership Context The launcher discovered entrypoints independently from App runtime creation, but obtained versions by parsing a separate package `version.m`. That kept the old metadata file alive even after Runtime V2 gained a single definition contract. Decision and rationale Keep discovery lightweight and self-contained while changing its source of truth. The launcher now reads `AppVersion` and `Updated` literals from the App's `definition.m`; it does not execute the definition or start the App. Changes Preferred `definition.m` product metadata during public and private App discovery. Added a synthetic private-App catalog test with no `version.m` file. Retained a documented internal fallback only for Apps awaiting migration. User and data impact Launcher catalogs continue to show version and update dates. A migrated App does not need a second metadata file solely for discovery. Compatibility and migration The change is compatible. Existing Apps remain discoverable through the temporary fallback; migrated Apps use only their definition. The fallback is removed after the last App migration. Validation The launcher catalog test creates an isolated App definition, points private discovery at it, and verifies the returned version and date without a `version.m` file. Evidence [LabKit Launcher](../../../../apps/labkit-core/launcher/README.md) explains definition-backed discovery. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the App product metadata fields. Known limitations and follow-up P-code packaging metadata will be audited with the deployment workflow before the transitional parser fallback is removed."},{"title":"Launcher update reliability","url":"history/records/2026/07/LK-20260701-launcher-update-reliability.html","kind":"history","section":"history","keywords":"LK-20260701-launcher-update-reliability 2026-07-01 25 fix compatible labkit_launcher labkit_launcher Launcher update reliability","text":"Launcher update reliability id: LK-20260701-launcher-update-reliability date: 2026-07-01 sequence: 25 type: fix compatibility: compatible component: `labkit_launcher` | `1.1.3 -> 1.1.4` component: `labkit_launcher` | `1.1.4 -> 1.1.5` scope: Launcher update reliability Context The self-contained launcher could install a ZIP release, but the replacement path performed more filesystem work than necessary and duplicated logic for locating, unpacking, and replacing the workbench. Slow replacement made a successful update look stalled; duplicated recovery paths made failures harder to reason about. Decision and rationale Minimize the files touched during an update and give ZIP replacement one straight-line implementation. Preserve the existing installation until the download and unpack steps have produced a usable replacement. Changes `labkit_launcher` `1.1.3 -> 1.1.5` Sped up launcher zip updates. Simplified launcher zip replacement. User and data impact ZIP updates completed with less waiting and fewer intermediate operations. A failure before replacement left the existing checkout available, while a successful update continued to present the same launcher entry point. Compatibility and migration Existing managed installations remained updateable. The replacement algorithm changed internally and did not require users to reinstall a working checkout. Validation Both commits extended `LauncherGuiTest` around download, replacement, and recovery behavior. The exact historical commands were not recorded. Evidence Main commits `ebf86cf2` and `becf9391`. Known limitations and follow-up The updater still depended on the release artifact being trustworthy. Later release work added stronger tag-to-asset verification and update diagnostics."},{"title":"Legacy import and first app workbench","url":"history/records/2026/05/LK-20260528-initial-app-workbench-foundation.html","kind":"history","section":"history","keywords":"LK-20260528-initial-app-workbench-foundation 2026-05-28 1 feat compatible historical project evolution","text":"Legacy import and first app workbench id: LK-20260528-initial-app-workbench-foundation date: 2026-05-28 sequence: 1 type: feat compatibility: compatible scope: historical project evolution Context LabKit began with a direct import of older MATLAB analysis and GUI code. The electrochem workflows mixed file parsing, calculations, session state, plots, and exports inside large entry scripts, and several root commands exposed the same legacy implementation through different paths. Decision and rationale Separate the imported code by responsibility before adding new features. Move Gamry parsing and pulse detection into reusable DTA operations; keep CIC, CSC, VT resistance, EIS, and chrono calculations with their workflows; then expose each workflow through one package-backed app command. Extract shared UI pieces only after the app behavior is visible in more than one workflow. Changes Imported the legacy MATLAB tree without rewriting its scientific formulas. Extracted chrono, EIS, and CV/CT DTA parsers plus pulse detection and named synthetic fixtures. Separated chrono alignment/export, CIC, CSC, VT resistance, and EIS result operations from their GUI assembly. Added named app entry points and package-backed runners, then removed the duplicate root legacy entry points and legacy GUI directory. Introduced the first shared two-pane/tabbed shells, file panels, plot controls, log panels, and axes helpers used by the electrochem apps. User and data impact Users gained named electrochem app commands instead of opening the old GUI scripts directly. Calculations and exported values were intended to remain unchanged while code ownership moved; laboratory files remained external inputs rather than being copied into a central LabKit database. Compatibility and migration Component/app version files did not exist yet. Old root GUI entry points were removed as package-backed app commands became the supported launch path. Validation The first regression evidence included named DTA fixtures, calculation tests, an optional GUI smoke runner, and GUI compatibility checks. A single standard test command had not yet been established. Evidence Initial import `5973bde0`. Parser and calculation extraction from `fc70f9b2` through `9f7c6e4e`. App entrypoint and package migration from `40f46561` through `4d58066c`. First shared app-shell work from `f05f4a35` through `61a8cb87`. Known limitations and follow-up This stage improved structure but still contained several broad shared UI helpers and session abstractions that were narrowed during the following DTA facade and app-boundary work."},{"title":"Legacy import preserves portable references without exposing their schema","url":"history/records/2026/07/LK-20260716-opaque-source-import.html","kind":"history","section":"history","keywords":"LK-20260716-opaque-source-import 2026-07-16 102 feat compatible labkit.ui App Framework Project portability","text":"Legacy import preserves portable references without exposing their schema id: LK-20260716-opaque-source-import date: 2026-07-16 sequence: 102 type: feat compatibility: compatible component: `labkit.ui` | `7.4.2 -> 7.4.3` scope: App Framework scope: Project portability Context The GUI-free source-record factory accepted filepaths, but an App importing an older MAT variable could already receive a portable reference containing the only usable relative location. Recreating the record from its original path would discard that portability; preserving it required the importer to copy the Runtime's private nested reference schema. Decision and rationale Keep one public source factory and allow its source value to be either a normal filepath or an existing portable reference passed as one opaque struct. The Runtime validates and canonicalizes the latter. Apps still never construct or read nested reference fields. Changes Extended `labkit.ui.runtime.sourceRecord` with an opaque-reference overload. Added strict validation for the supported portable-reference schema, scalar text fields, filename, and usable relative or original path. Canonicalized imported references so unrelated legacy fields do not enter a current project. Documented the distinction between normal filepath creation and legacy reference preservation. User and data impact Moved legacy projects retain their relative source fallback during upgrade. App importers no longer need to duplicate Runtime serialization fields or choose between architectural ownership and data portability. Compatibility and migration Existing filepath calls and injected source-record services are unchanged. The overload accepts only the current supported reference schema; malformed or newer references fail before they can enter a project. Validation Focused Runtime tests cover normal path creation, opaque-reference canonicalization, relative-path preservation, missing fields, unsupported schema versions, source path access, and the public package surface. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Framework](../../../../framework/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up Opaque-reference import is intended for trusted project importers, not as a general App-defined serialization extension. Video Marker is the first legacy importer scheduled to consume the overload."},{"title":"Local validation uses one focused route and CI owns full coverage","url":"history/records/2026/07/LK-20260721-local-validation-routing.html","kind":"history","section":"history","keywords":"LK-20260721-local-validation-routing 2026-07-21 150 test compatible Test routing Continuous Integration","text":"Local validation uses one focused route and CI owns full coverage id: LK-20260721-local-validation-routing date: 2026-07-21 sequence: 150 type: test compatibility: compatible scope: Test routing scope: Continuous Integration Context The repository exposed both `changedFast` and a conservative `changed` task. For documentation, agent guidance, and CI workflow changes they selected the same complete project suite, so running both locally duplicated work that the cross-platform CI already repeated more comprehensively. Decision and rationale Keep one local changed-file task, `changedFast`, for a focused pre-push check. PR and main-push CI remain the complete validation gate because they run the full headless and hidden-GUI suites on every supported platform. Changes Removed the public conservative `changed` task and its runner entry. Guidance, templates, workflows, and CI helpers now route to their owning documentation or CI guardrails instead of the complete project suite. Broad GUI candidate selection remains reduced to representative workflows for the local route. User and data impact App behavior, saved projects, analysis results, and laboratory data do not change. Contributors receive faster local feedback while the same complete platform coverage remains mandatory before delivery. Compatibility and migration Use `buildtool changedFast` for the local pre-push checkpoint. The retired `buildtool changed` command is intentionally unavailable; use CI results for complete validation rather than substituting another local broad run. Validation Build-task catalog and route-contract tests verify the single public local route and its owner-specific selections. The changed-file gate exercises the selected guardrails, while CI continues to run full headless and hidden-GUI coverage. Evidence The CI workflow invokes `headless` and `gui` on Linux, macOS, and Windows. The validation planner records a reason for every selected local guardrail. Known limitations and follow-up Local `changedFast` is intentionally not a substitute for CI or manual App review. If a new shared surface lacks an owning test route, add that route rather than broadening unrelated local checks."},{"title":"MATLAB-compatible image conversion API","url":"history/records/2026/07/LK-20260713-matlab-compatible-image-conversion.html","kind":"history","section":"history","keywords":"LK-20260713-matlab-compatible-image-conversion 2026-07-13 47 refactor breaking labkit.image labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app MATLAB-compatible image conversion API","text":"MATLAB-compatible image conversion API id: LK-20260713-matlab-compatible-image-conversion date: 2026-07-13 sequence: 47 type: refactor compatibility: breaking component: `labkit.image` | `1.2.0 -> 2.0.0` component: `labkit_DICPostprocess_app` | `1.3.5 -> 1.3.6` component: `labkit_BatchImageCrop_app` | `1.6.7 -> 1.6.8` component: `labkit_CurvatureMeasurement_app` | `1.3.4 -> 1.3.5` component: `labkit_FocusStack_app` | `1.4.8 -> 1.4.9` component: `labkit_ImageEnhance_app` | `1.5.7 -> 1.5.8` component: `labkit_ImageMatch_app` | `1.5.7 -> 1.5.8` scope: MATLAB-compatible image conversion API Context The `toDouble`, `toLuma`, and `toRgbDouble` names combined class conversion, channel shaping, and clipping in ways that differed from familiar MATLAB APIs. Decision and rationale Use MATLAB-compatible names and call contracts for replacement functions, and keep orthogonal RGB shaping explicit so users do not need to learn a composite LabKit normalization rule. Changes Added base-MATLAB `labkit.image.im2double` and `labkit.image.rgb2gray`. Kept channel shaping in `ensureRgb` and made clipping explicit at call sites. Removed the ambiguous conversion helpers and centralized Rec.601 ownership. User and data impact Base-MATLAB users receive familiar image conversion behavior without hidden toolbox requirements. Existing app image results retain their intended ranges and channel shapes through explicit pipelines. Compatibility and migration This is a breaking facade rename. External callers replace removed helper names with `im2double`, `rgb2gray`, and `ensureRgb` as separately needed. Validation Image facade, downstream app, toolbox-shadow, and base-MATLAB ownership tests cover class conversion, luma values, and representative workflows. Evidence The current API contracts are documented in the [Image Library](../../../../libraries/image/README.md). Commit `e3f71c2d` aligned the conversion functions with MATLAB call behavior. Known limitations and follow-up The compatibility layer intentionally covers the LabKit-used MATLAB contracts, not every Image Processing Toolbox function. labkit.image.im2double Convert image data to double using MATLAB's im2double contract. labkit.image.rgb2gray Convert RGB data using MATLAB's rgb2gray call contract."},{"title":"Managed image interactions and diagnostic tracing","url":"history/records/2026/06/LK-20260604-managed-image-interactions.html","kind":"history","section":"history","keywords":"LK-20260604-managed-image-interactions 2026-06-04 4 feat compatible historical project evolution","text":"Managed image interactions and diagnostic tracing id: LK-20260604-managed-image-interactions date: 2026-06-04 sequence: 4 type: feat compatibility: compatible scope: historical project evolution Context Image and ECG apps had made the workbench broader, but they also revealed a class of GUI failures that did not appear in parser or calculation tests. An anchor editor could re-enter a callback, a scale bar could lose ownership, an axes popout could stop reflecting the source, and a long operation could leave controls in an ambiguous state. Decision and rationale Move repeatable interaction mechanics into the UI foundation while leaving image-specific geometry and workflow state in each app. Give long-running actions an explicit busy-state guard, make image axes and tools managed resources, and record callback progress in diagnostic traces that could be inspected after a failure. Changes Added axes popout support and corrected image aspect and menu refresh behavior. Added shared UI control helpers and a reusable ECG peak detector facade. Added the Focus Stack app with automatic and manual image selection. Improved anchor insertion, curvature controls, and scale-bar ownership. Added a busy-state guard to prevent overlapping actions. Introduced a managed image-axes runtime, layered UI facades, and app debug trace logging. Split large app entry points into app-owned private workflow helpers rather than widening the public framework API. User and data impact Users gained Focus Stack and more predictable image editing. Popout views, anchors, scale bars, and long operations provided clearer feedback and were less likely to leave an app in a broken intermediate state. Debug traces made it possible to distinguish a slow or failed callback from an unresponsive window. The changes affected UI state and presentation; they did not intentionally alter existing scientific results. Compatibility and migration Apps gradually moved from direct axes and interaction setup to the managed UI facades. App-private geometry helpers remained private, so the framework did not promise a public API for every interaction detail. Validation The period added public API option documentation, app-private helper tests, interaction regression coverage, and focused checks for UI layout ownership. No single historical validation command covers the complete stage. Evidence Axes popout and control helpers `1e9022c4` through `2136009f`. ECG detector and filtering work `e8450652` through `adb7dfbf`. Focus Stack `08518e91`, `cc4cdf92`. Busy guard `ac36bd54`. Image interaction and scale-bar work `f58b8559` through `5304fa20`. Managed axes, traces, and layered UI facades `27bdbdd8`, `e86e4ea3`, `da0663f1`, `c4cae633`. Known limitations and follow-up The interaction layer was still evolving and the test infrastructure mixed custom runners with newer MATLAB tests. The v1.0 stabilization pass addressed test entry points, package ownership, and remaining compatibility aliases."},{"title":"Managed scientific and conversion constants","url":"history/records/2026/07/LK-20260713-managed-calculation-constants.html","kind":"history","section":"history","keywords":"LK-20260713-managed-calculation-constants 2026-07-13 48 refactor compatible labkit.dta labkit.rhs labkit.biosignal labkit_ChronoOverlay_app labkit_CSC_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Managed scientific and conversion constants","text":"Managed scientific and conversion constants id: LK-20260713-managed-calculation-constants date: 2026-07-13 sequence: 48 type: refactor compatibility: compatible component: `labkit.dta` | `2.0.0 -> 2.0.1` component: `labkit.rhs` | `1.0.0 -> 1.0.1` component: `labkit.biosignal` | `1.0.0 -> 1.0.1` component: `labkit_ChronoOverlay_app` | `1.3.5 -> 1.3.6` component: `labkit_CSC_app` | `1.3.9 -> 1.3.10` component: `labkit_NerveResponseAnalysis_app` | `1.3.4 -> 1.3.5` component: `labkit_ResponseReviewStats_app` | `1.3.4 -> 1.3.5` scope: Managed scientific and conversion constants Context Scientific coefficients, device-format gains, unit conversions, and numeric tolerances were sometimes left as unexplained literals or repeated across callers, making origin and change impact difficult to audit. Decision and rationale Give each semantic calculation constant one named owner and a nearby source or purpose comment, while exempting ordinary indices and explicit UI geometry that do not encode scientific meaning. Changes Named and documented Rec.601, sRGB/CIE, DTA/RHS gains, SI conversions, tolerances, and empirical policies across facades and apps. Centralized repeated CSC charge-density, CIC display-unit, and curvature tolerance contracts. Added repository-wide magic-number and rectangle-interaction guardrails. User and data impact Calculation results are preserved, but future changes now expose the constant's meaning and provenance instead of silently editing an unexplained literal. Compatibility and migration No user migration is required. Public function call contracts and output schemas are unchanged by this record. Validation `MagicNumberGovernanceTest`, rectangle governance, module compatibility tests, and affected app tests cover centralized ownership and preserved numeric behavior. Evidence Source comments use the `Constant:` marker and guardrail diagnostics name the unmanaged file and line. Commit `125338c0` introduced the calculation-constant checks and the corresponding source annotations. Known limitations and follow-up The scanner targets semantically suspicious precision and notation; review is still required for simple integers or short decimals whose meaning is hidden."},{"title":"Migration helper cleanup","url":"history/records/2026/06/LK-20260630-migration-helper-cleanup.html","kind":"history","section":"history","keywords":"LK-20260630-migration-helper-cleanup 2026-06-30 23 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_BatchImageCrop_app labkit_BatchImageCrop_app labkit_ImageEnhance_app labkit_RHSPreview_app labkit_RHSPreview_app historical project evolution","text":"Migration helper cleanup id: LK-20260630-migration-helper-cleanup date: 2026-06-30 sequence: 23 type: refactor compatibility: compatible component: `labkit.ui` | `3.2.8 -> 3.2.9` component: `labkit_DICPostprocess_app` | `1.2.3 -> 1.2.4` component: `labkit_BatchImageCrop_app` | `1.3.7 -> 1.3.8` component: `labkit_BatchImageCrop_app` | `1.3.8 -> 1.3.9` component: `labkit_ImageEnhance_app` | `1.3.4 -> 1.3.5` component: `labkit_RHSPreview_app` | `1.2.2 -> 1.2.3` component: `labkit_RHSPreview_app` | `1.2.3 -> 1.2.4` scope: historical project evolution Context Several apps still split one small decision across multiple temporary helper files created during earlier package migrations. That made simple state and preview behavior harder to follow without creating a reusable API. Decision and rationale Consolidate related values behind one clearly named operation and delete pass-through helpers whose only purpose was reducing file length. Preserve the visible app behavior while making each calculation or state summary traceable from its caller. Changes DIC Post, Batch Crop, and RHS Preview patch bumped. Retired migration helper debt. Consolidated RHS preview window bounds, Batch Crop scale state, and Image Enhance export helpers. User and data impact No workflow or result format changed. The cleanup reduced internal indirection in Image Enhance export, Batch Crop scale summaries, RHS Preview window bounds, and a small set of DIC/electrochem helpers. Compatibility and migration The consolidated helpers preserved the app-facing state and result structures. No project or export format changed as the duplicate implementations were removed. Validation Commits `7f73b71b`, `e3349af6`, `733fb951`, `98a2b02c`, and `391540a7` added or updated focused tests for each consolidated operation. Evidence Main commits `7f73b71b`, `e3349af6`, `733fb951`, `98a2b02c`, and `391540a7`. Known limitations and follow-up This was a behavior-preserving cleanup. Later workflow-first packages replaced some of the package names shown in the historical commits."},{"title":"Minimal App contract and current authoring manuals","url":"history/records/2026/07/LK-20260716-minimal-app-contract.html","kind":"history","section":"history","keywords":"LK-20260716-minimal-app-contract 2026-07-16 115 docs compatible labkit-project App authoring Runtime V2 contract guardrails","text":"Minimal App contract and current authoring manuals id: LK-20260716-minimal-app-contract date: 2026-07-16 sequence: 115 type: docs compatibility: compatible component: `labkit-project` scope: App authoring scope: Runtime V2 contract guardrails Context Runtime V2 already supplied default project, session, action, presenter, and startup capabilities, but the complete-App tutorial and several current manuals still taught the retired split metadata and generic lifecycle package shape. The App structure guardrail also prohibited those files only when an App already owned `projectSpec.m`, leaving static Apps unprotected. Decision and rationale Make progressive capability the documented and tested authoring contract. A static App consists of a thin entrypoint, one definition, and one data-only layout. Actions, presentation, durable project state, transient reconstruction, and startup are added independently only when the product needs them. Changes Rewrote the complete-App tutorial around the three-file starting point and one `projectSpec.m` create/validate/migrate entry. Updated architecture, framework, private-App, library, testing, release, and history manuals plus scoped App rules and the App-builder skill to use definition-owned metadata and progressive optional capabilities. Added a hidden-GUI launch test whose definition omits every optional Runtime V2 component. Corrected the tutorial's numeric field declaration after the real launch test exposed its implicit text-control mismatch. Made the App package guardrail reject retired metadata, lifecycle, state, startup, and per-version migration files for every App shape. User and data impact App behavior and scientific results do not change. A developer can begin with three source files and add each larger architectural concept only when a real workflow requires it. Public and private App documentation now teach the same contract. Compatibility and migration This change documents and protects the already migrated Runtime V2 structure. It does not alter saved project payloads or remove the temporary low-level legacy project-migration reader used by framework tests. Validation Focused contract and GUI tests cover both the forbidden retired structures and an actual launch from the minimal definition. A documentation contract checks the tutorial, scoped App rules, and App-builder skill together. Documentation consistency checks and a regenerated site validate navigation and history metadata. Evidence [Build a Complete App](../../../../development/build-apps/complete-app.md) gives the progressive file-by-file tutorial. [Architecture](../../../../development/build-apps/architecture.md) defines the current package shape and ownership boundaries. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) specifies every optional component and callback. Known limitations and follow-up The minimal contract proves framework defaults, not that every complex App has already reached its lowest maintainable complexity. App-by-App workflow and framework-boundary audits continue separately."},{"title":"Minimal App definitions","url":"history/records/2026/07/LK-20260716-minimal-app-definition.html","kind":"history","section":"history","keywords":"LK-20260716-minimal-app-definition 2026-07-16 76 feat compatible labkit.ui App authoring Runtime V2 definition defaults","text":"Minimal App definitions id: LK-20260716-minimal-app-definition date: 2026-07-16 sequence: 76 type: feat compatibility: compatible component: `labkit.ui` | `7.0.0 -> 7.1.0` scope: App authoring scope: Runtime V2 definition defaults Context Runtime V2 already normalized missing project and session buckets, but every definition still had to provide project create/validate callbacks, at least one action, and a presenter. A static App therefore needed lifecycle files and placeholder functions that owned no behavior. Decision and rationale Make lifecycle components proportional to product behavior. Every App still declares a stable ID, title, and semantic layout. The framework supplies empty canonical state and no-op dynamic behavior until the App opts into durable data, transient cache, actions, or presentation. Changes Made `Project`, `Actions`, and `Present` optional in `labkit.ui.runtime.define`; `CreateSession` was already optional. Added a framework-owned version-1 empty project specification and empty presenter model. Allowed an empty action registry when no layout event or startup phase references an action. Added a GUI contract test that launches a definition containing only `Id`, `Title`, and `Layout` and verifies both canonical state roots. Rewrote App-development entry guidance around capability tiers instead of a fixed list of placeholder files. User and data impact Existing Apps behave unchanged. A new static App does not need `createProject.m`, `createSession.m`, `validateProject.m`, `definitionActions.m`, or a presenter. Those components are introduced only when the App gains corresponding behavior. Compatibility and migration The change is additive within UI 7. Existing complete definitions remain valid. Saved project and source schemas do not change. Validation The minimal-definition GUI test exercises real launch, canonical project and session creation, layout construction, empty presentation, and readiness. Definition validation and public documentation guardrails cover both minimal and full definitions. Evidence [App Framework](../../../../framework/README.md) introduces the minimal definition. [Runtime and Lifecycle](../../../../framework/guides/runtime.md) lists required and optional definition components. [App Development](../../../../development/build-apps/app-development.md) maps files to capabilities instead of treating them as universal boilerplate. Known limitations and follow-up Apps that own a project schema still use explicit project specification. The next lifecycle cleanup consolidates each App's project factory, validator, and ordered migration steps behind one `projectSpec.m` entry point rather than scattered version-step files."},{"title":"Multi-app launcher packages","url":"history/records/2026/07/LK-20260709-multi-app-launcher-packages.html","kind":"history","section":"history","keywords":"LK-20260709-multi-app-launcher-packages 2026-07-09 42 feat compatible labkit_launcher Multi-app launcher packages","text":"Multi-app launcher packages id: LK-20260709-multi-app-launcher-packages date: 2026-07-09 sequence: 42 type: feat compatibility: compatible component: `labkit_launcher` | `1.2.7 -> 1.3.0` scope: Multi-app launcher packages Context The deployment tool could package one app, but laboratory workflows often used a small related set. Creating one ZIP per app duplicated the shared LabKit runtime, while manually merging ZIP files risked inconsistent entry files and manifests. The launcher's selected row also could not double as a multi-select without making Open and Debug ambiguous. Decision and rationale Add a separate Package checkbox to each launcher row. Keep the ordinary row selection for Open and Debug, and let checked rows produce one bundle with one entry file per app and a manifest describing the complete package. Changes `labkit_launcher` `1.2.7 -> 1.3.0` Project deployment tooling, multi-app bundle support. Added an independent `Package` checkbox column to the launcher app table so users can choose multiple apps without changing the row selected for Open or Debug. `Package Checked` and `Checked P-code` now create one zip containing every checked app, one direct entry file per app, and a multi-app manifest. Kept single-app package names, result fields, and manifest schema compatible when only one app is supplied to `packageLabKitApp`. User and data impact Users could distribute a related group of apps in one source or P-code ZIP without including the rest of the workbench. Opening and debugging still acted on the highlighted app, so preparing a package did not change normal launcher navigation. Compatibility and migration Existing direct calls that package one app continue to produce the original single-app package contract. Validation The commit expanded deployment unit tests, launcher packaging GUI coverage, selection helpers, changed-file routing, and build-task efficiency checks. The exact historical command was not recorded. Evidence Mainline commit `8a23a52`. Known limitations and follow-up Bundles intentionally included selected apps and their LabKit dependencies, not tests, full documentation, or unrelated private workspaces. P-code packages still required a compatible MATLAB runtime."},{"title":"Nerve Response Analysis uses fixed source identities","url":"history/records/2026/07/LK-20260716-nerve-response-structure.html","kind":"history","section":"history","keywords":"LK-20260716-nerve-response-structure 2026-07-16 107 refactor compatible labkit_NerveResponseAnalysis_app Neurophysiology App structure","text":"Nerve Response Analysis uses fixed source identities id: LK-20260716-nerve-response-structure date: 2026-07-16 sequence: 107 type: refactor compatibility: compatible component: `labkit_NerveResponseAnalysis_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context Nerve Response Analysis split one project schema across generic lifecycle files and kept two additional lifecycle helpers to filter and replace source records by role. Product metadata remained in separate requirement/version functions, while session and presentation code read nested portable-reference fields directly. Decision and rationale Use `filterRecord` and `protocol` as the two stable App-owned source IDs and matching roles. Runtime V2 already supports semantic path lookup by ID and an injected upsert service, so role-filter wrappers duplicate framework behavior without adding scientific meaning. Keep event detection, recording analysis, CAP measurement, presentation, and result writing in their existing concrete workflow packages. Changes Consolidated command identity, display metadata, version, requirements, project, session, layout, actions, presenter, renderer, and debug capability through the single definition. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved parsed JSON and transient workflow reconstruction to root `createSession`. Replaced App-owned role filtering/replacement helpers with `sourcePaths` and the injected `upsertSource` service. Strengthened validation so the two source IDs are unique, supported, and match their declared roles. Removed the generic lifecycle package and separate requirement/version files. User and data impact Filter/protocol selection, run limits, event and train detection, CAP metrics, issue handling, previews, output-folder defaults, JSON export, manifest output, reset, and project reopening are unchanged. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still combine their separate filter and protocol records into the canonical source collection. Runtime V2 invokes the single migration entry and owns iteration. Current version-2 files already written with the documented fixed IDs load without data transformation. Validation Focused unit tests cover migration, source identity validation, event trains, differential/common-mode calculations, CAP metrics, filter labels, and the legacy keep-column input. The hidden GUI workflow covers filter selection, analysis, counts/issues presentation, JSON and manifest export, project save, reset, reopen, and reanalysis. Evidence [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not establish that a selected protocol, stimulation source, or timing window is physiologically correct. RHS Preview still uses the older split lifecycle structure and requires its own independent convergence review."},{"title":"One project migration entry per App","url":"history/records/2026/07/LK-20260716-single-project-migration-entry.html","kind":"history","section":"history","keywords":"LK-20260716-single-project-migration-entry 2026-07-16 81 feat compatible labkit.ui Runtime V2 projects App authoring","text":"One project migration entry per App id: LK-20260716-single-project-migration-entry date: 2026-07-16 sequence: 81 type: feat compatibility: compatible component: `labkit.ui` | `7.2.1 -> 7.3.0` scope: Runtime V2 projects scope: App authoring Context Runtime V2 already applied project migrations sequentially, but each App had to expose a cell array containing every historical version-step function. Schema growth therefore increased files and wiring even though the framework owned the actual migration loop. Decision and rationale Give each persistent App one migration entry. `Migrate(project,fromVersion)` upgrades exactly one step; Runtime V2 supplies each missing version in order, validates every intermediate payload, and continues to the current version. Changes Added the scalar `Project.Migrate` callback. Kept per-step serialization validation, newer-version rejection, atomic load behavior, final project validation, session rebuild, and dirty upgrade state unchanged. Converted the framework's version-1-to-3 round-trip fixture to one callback with two version cases. Retained `Project.Migrations` only as a temporary bridge for existing Apps. User and data impact Saved projects behave as before. App maintainers can keep create, validate, and every schema transition as local functions in one `projectSpec.m` instead of maintaining a growing migration-file registry. Compatibility and migration The contract is additive within UI 7. Existing Apps continue to load during family-sized migration. An App must not declare both migration forms. Validation The Runtime V2 project test opens a version-1 payload in a version-3 App, executes both steps through one callback, verifies final data and source relinking, saves the current payload version, and checks failure atomicity. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents the project declaration and loop. [App Development](../../../../development/build-apps/app-development.md) describes the single project file. Known limitations and follow-up Existing Apps still need their lifecycle functions consolidated into `projectSpec.m`. The legacy migration-array bridge will be removed after the last family is migrated."},{"title":"Output folder prompts","url":"history/records/2026/06/LK-20260630-output-folder-prompts.html","kind":"history","section":"history","keywords":"LK-20260630-output-folder-prompts 2026-06-30 20 feat compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_BatchImageCrop_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Output folder prompts","text":"Output folder prompts id: LK-20260630-output-folder-prompts date: 2026-06-30 sequence: 20 type: feat compatibility: compatible component: `labkit.ui` | `3.2.5 -> 3.2.6` component: `labkit_DICPostprocess_app` | `1.2.0 -> 1.2.1` component: `labkit_DICPreprocess_app` | `1.2.0 -> 1.2.1` component: `labkit_BatchImageCrop_app` | `1.3.3 -> 1.3.4` component: `labkit_FocusStack_app` | `1.2.1 -> 1.2.2` component: `labkit_ImageEnhance_app` | `1.3.1 -> 1.3.2` component: `labkit_ImageMatch_app` | `1.3.1 -> 1.3.2` component: `labkit_NerveResponseAnalysis_app` | `1.2.1 -> 1.2.3` component: `labkit_ResponseReviewStats_app` | `1.2.1 -> 1.2.2` scope: Output folder prompts Context Exporting from different apps opened `uigetdir` directly, chose starting folders differently, and could not substitute a noninteractive chooser in a test. The repeated dialog code also made cancellation handling inconsistent. Decision and rationale Provide one output-folder prompt that selects a safe default, returns an explicit cancellation flag, remembers a successful folder, and accepts an injected chooser for tests. Apps would still decide when an output folder was needed and what they wrote there. Changes `labkit.ui` `3.2.5 -> 3.2.6` DIC apps, Batch Crop, Focus Stack, Image Enhance/Match, Nerve Response, and Response Review patch bumped. Added `promptOutputFolder`. Migrated output-folder prompts with chooser injection and safe defaults. User and data impact Output dialogs began in a useful folder and cancellation returned cleanly to the app. The selected folder was remembered as a preference; no output was created until the owning app performed its export. Compatibility and migration Existing output locations and exported file formats remained valid. Apps moved from direct folder dialogs to the shared prompt without changing what they wrote after selection. Validation Commit `c5055b98` added `AppHookHelpersTest` coverage for defaults, cancellation, successful selection, and chooser injection, then updated the affected app compatibility checks. Evidence Main commit `c5055b98`. Known limitations and follow-up Runtime V2 later moved dialog access into injected services, preserving the same separation between app decisions and platform dialog mechanics."},{"title":"Owner-minimal validation routing compiles canonical test groups","url":"history/records/2026/07/LK-20260722-owner-minimal-validation-routing.html","kind":"history","section":"history","keywords":"LK-20260722-owner-minimal-validation-routing 2026-07-22 157 test compatible Test routing Test ownership","text":"Owner-minimal validation routing compiles canonical test groups id: LK-20260722-owner-minimal-validation-routing date: 2026-07-22 sequence: 157 type: test compatibility: compatible scope: Test routing scope: Test ownership Context Changed-file validation could widen an App capability change to a family-level test folder, depend on implicit contract discovery, or execute the same test through overlapping folder routes. The migration ledger recorded the required zero-debt replacement: explicit App owners, framework consumer closure, and one execution of each selected official test. Decision and rationale Treat route selection and execution as separate concerns. Preserve every semantic owner route with its reason, discover the matching official tests, then union canonical identities into one non-GUI and one hidden-GUI execution group. This keeps coverage reviewable without paying for duplicate execution. Changes Moved App-specific tests into explicit `appContract`, `workbench`, source capability, `smoke`, and `isolatedPath` owners; moved cross-App audits to project ownership. Removed family-root and overlap-based route suppression. Added canonical test-union compilation, deterministic feature-tagged smoke selection, direct facade-consumer routing, and safe all-smoke fallback when required feature metadata is absent. Preserved physical suite-folder case during selector discovery and made an isolated App sample derive the repository root from the shared test setup, so owner paths behave consistently on Linux, macOS, and Windows. Focused ordinary App-manual, history, and library-documentation routes while retaining complete contracts for renderer and policy changes. Added dynamic guards for every public App's owned contracts and smoke proof, valid source-capability scopes, unique canonical execution, and focused documentation routes. Test classes also reject migrated test-shaped methods stranded in non-discoverable static-private blocks. User and data impact No App behavior, saved project, scientific result, or export changes. Local `changedFast` feedback is smaller and deterministic; CI remains the complete headless and hidden-GUI validation gate. Compatibility and migration No saved-data migration is required. The active test-routing migration ledger is retired; future routing changes must extend the durable ownership rules and their guardrails rather than restore compatibility fallbacks. Validation Focused build-routing guardrails, App ownership contracts, and moved cross-App contracts. Same-machine MATLAB R2025a before/after benchmark for a representative Figure Studio source-capability change. One final `buildtool changedFast` run for the completed diff. Evidence The planner prints all semantic routes and the canonical execution count, including the number of repeated selections removed. The final local gate and CI provide the executable evidence for the exact merged diff. For a change to the Figure Studio `sourceAxes/copyToPreview` capability, the pre-migration planner at the retained mainline baseline selected 46 test executions across three runner groups: 45 unique tests plus one repeated isolated-path contract. Those groups took 120.396 seconds after 5.284 seconds of planning. The migrated planner retained four explicit semantic routes, selected 11 unique tests with no repeated execution, and compiled them into one headless and one hidden-GUI group. Those groups took 51.134 seconds after 6.212 seconds of planning. Test execution time fell by 69.262 seconds (57.5%) while retaining the source-capability unit tests, the owning App's isolated path contract, its launch smoke test, and the relevant source-axes GUI tests. Known limitations and follow-up Hidden GUI smoke tests validate bounded launch/layout behavior, not native dialogs, pointer feel, visual quality, or scientific interpretation; those remain manual review responsibilities."},{"title":"Preview-area per-axis wheel zoom","url":"history/records/2026/07/LK-20260709-preview-area-per-axis-wheel-zoom.html","kind":"history","section":"history","keywords":"LK-20260709-preview-area-per-axis-wheel-zoom 2026-07-09 44 feat compatible labkit.ui Preview-area per-axis wheel zoom","text":"Preview-area per-axis wheel zoom id: LK-20260709-preview-area-per-axis-wheel-zoom date: 2026-07-09 sequence: 44 type: feat compatibility: compatible component: `labkit.ui` | `5.0.3 -> 5.0.4` scope: Preview-area per-axis wheel zoom Context The preview area applied the same two-dimensional wheel zoom to every registered axes. That was appropriate for an image or main plot, but awkward for a narrow histogram or color-scale axes: horizontal zoom changed a dimension whose layout was meant to remain fixed. Decision and rationale Let a preview-area definition choose `xy`, `x`, or `y` wheel zoom for each axes. Preserve `xy` as the default so existing apps keep their behavior, while side axes can opt into only the dimension that carries data. Changes `labkit.ui` `5.0.3 -> 5.0.4` Added a `scrollZoomAxes` preview-area layout option so apps can declare whether each preview axis should mouse-wheel zoom in `xy`, `x`, or `y`. Preview-area side axes can now remain horizontally stable while still allowing app-selected vertical wheel zoom. User and data impact A histogram or temperature scale could remain horizontally stable while still supporting vertical exploration. Main images and plots continued to zoom in both dimensions unless their app explicitly chose another mode. Compatibility and migration Existing preview areas keep default `xy` wheel zoom unless they opt into another per-axis setting. Validation The commit extended UI layout unit tests and axes-workbench GUI coverage for the default and per-axis modes. The exact historical command was not recorded. Evidence Mainline commit `3c143eb`. Known limitations and follow-up The option controlled wheel navigation only. Toolbar zoom, pan, linked axes, and app-specific limit policies remained separate concerns."},{"title":"Profiling and validation speedups","url":"history/records/2026/07/LK-20260702-profiling-and-validation-speedups.html","kind":"history","section":"history","keywords":"LK-20260702-profiling-and-validation-speedups 2026-07-02 30 ci compatible labkit_launcher labkit_launcher labkit.ui labkit.ui labkit_BatchImageCrop_app labkit_ECGPrint_app Profiling and validation speedups","text":"Profiling and validation speedups id: LK-20260702-profiling-and-validation-speedups date: 2026-07-02 sequence: 30 type: ci compatibility: compatible component: `labkit_launcher` | `1.2.0 -> 1.2.1` component: `labkit_launcher` | `1.2.1 -> 1.2.2` component: `labkit.ui` | `3.4.0 -> 3.4.1` component: `labkit.ui` | `3.4.1 -> 3.4.2` component: `labkit_BatchImageCrop_app` | `1.6.0 -> 1.6.1` component: `labkit_ECGPrint_app` | `1.3.0 -> 1.3.1` scope: Profiling and validation speedups Context Startup and file-selection complaints could not be resolved reliably from wall clock impressions alone. At the same time, focused changes paid avoidable test discovery cost, GUI tests waited longer than the behavior required, and Batch Crop read every selected image before the user requested a preview or export. Decision and rationale Add a profiler that records launcher, startup, callback, and close costs with source attribution. Use its evidence to remove repeated UI updates and eager image reads. Route changed-file validation to the smallest owning suites and bound GUI waits without weakening their behavioral assertions. Changes `labkit_launcher` `1.2.0 -> 1.2.2` `labkit.ui` `3.4.0 -> 3.4.2` `labkit_BatchImageCrop_app` `1.6.0 -> 1.6.1` `labkit_ECGPrint_app` `1.3.0 -> 1.3.1` Added LabKit profiling and build-managed test routing to the launcher. Reduced GUI profiling overhead and deferred Batch Crop image reads until preview/export. Compressed validation runtime with bounded GUI waits. User and data impact Maintainers could launch a profiled target and receive a summarized report instead of interpreting MATLAB's raw profile table. Selecting many Batch Crop files returned sooner because images were decoded only for the current preview or final export. Routine validation also completed with less discovery and wait overhead. Compatibility and migration Profiling and test routing were maintainer tools. Batch Crop kept its task and export schemas; selected images were simply decoded later in the workflow. Validation The profiling tool received a dedicated unit suite. Later commits added launcher profiler coverage, deferred-read tests, debug-trace mirroring checks, routing guardrails, and bounded GUI-wait tests. Commit `25912c54` records successful `buildtool changed`, integration validation, and remote MATLAB tests. Evidence Main commits `c07dfc0a`, `74025fee`, `eadcca82`, `25912c54`, and `fcfc36d8`. Known limitations and follow-up These changes reduced measured work but did not eliminate the white window shown before all startup setup completed. The following startup pass changed when windows were painted and when scroll navigation was installed."},{"title":"Project restore distinguishes missing sources from damaged sources","url":"history/records/2026/07/LK-20260717-strict-project-session-restore.html","kind":"history","section":"history","keywords":"LK-20260717-strict-project-session-restore 2026-07-17 125 fix compatible labkit.ui labkit_FigureStudio_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageMatch_app labkit_ImageEnhance_app labkit_NerveResponseAnalysis_app Runtime project restore App session reconstruction","text":"Project restore distinguishes missing sources from damaged sources id: LK-20260717-strict-project-session-restore date: 2026-07-17 sequence: 125 type: fix compatibility: compatible component: `labkit.ui` | `7.4.6 -> 7.4.7` component: `labkit_FigureStudio_app` | `0.2.8 -> 0.2.9` component: `labkit_FLIRThermal_app` | `1.4.6 -> 1.4.7` component: `labkit_FocusStack_app` | `1.5.5 -> 1.5.6` component: `labkit_ImageMatch_app` | `1.6.6 -> 1.6.7` component: `labkit_ImageEnhance_app` | `1.6.6 -> 1.6.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.6 -> 1.4.7` scope: Runtime project restore scope: App session reconstruction Context Runtime already resolved missing required source paths and offered interactive relinking before it rebuilt an App session. Several session factories then caught every decoder exception and returned an empty cache. A file that still existed but was damaged, unsupported, or exposed a programming error could therefore look like an unresolved source or an empty project. Decision and rationale Keep path resolution and cancellation in Runtime, and make reconstruction of an existing source strict. A session factory may accept an explicitly absent optional source, but it must not reinterpret an exception from an existing file. Runtime wraps reconstruction failures with `inputs.sources` IDs, roles, and filenames, records the exception in diagnostics, and preserves the prior state and presentation. Changes Removed broad exception recovery from Figure Studio, Focus Stack, Image Match, Image Enhance, FLIR Thermal, and Nerve Response Analysis session reconstruction. Made FLIR restore use strict thermal decoding while retaining skip-and-report behavior for interactive batch import. Added Runtime diagnostic context and a field-specific `ProjectSessionRestoreFailed` error without replacing the decoder cause. Added atomic corrupt-source, diagnostic-delivery, cross-App factory, and no-broad-catch regression coverage. User and data impact Missing required files still open the relink workflow, and cancelling it leaves the current project untouched. Existing corrupt or unsupported files now stop the load with the relevant project source identities and filenames instead of showing an empty preview. The prior document remains usable. App developers can leave ordinary decoder failures uncaught in `createSession.m`; Runtime owns the user-visible load failure and diagnostic record. Compatibility and migration No saved-project migration is required. Project envelopes and source-reference formats are unchanged. This change only corrects failure handling while reconstructing transient state. Validation Focused validation covers successful project round trips, missing-source relink, cancellation rollback, existing corrupt source rollback, diagnostic delivery, all affected session factories, and the repository guardrail that forbids broad catches in `createSession.m`. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md#session-actions-presentation-and-renderers) [Figure Studio](../../../../apps/labkit-core/figure-studio/README.md) [FLIR Thermal](../../../../apps/image-measurement/flir-thermal/README.md) [Focus Stack](../../../../apps/image-measurement/focus-stack/README.md) [Image Match](../../../../apps/image-measurement/image-match/README.md) [Image Enhance](../../../../apps/image-measurement/image-enhance/README.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) Known limitations and follow-up Automated hidden GUI tests verify state rollback and semantic error delivery; they do not replace manual review of native dialog wording or damaged third-party file variants."},{"title":"Protected image enhancement workflows","url":"history/records/2026/06/LK-20260629-protected-image-enhancement-workflows.html","kind":"history","section":"history","keywords":"LK-20260629-protected-image-enhancement-workflows 2026-06-29 16 feat compatible labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"Protected image enhancement workflows id: LK-20260629-protected-image-enhancement-workflows date: 2026-06-29 sequence: 16 type: feat compatibility: compatible component: `labkit_ImageEnhance_app` | `1.2.2 -> 1.3.0` component: `labkit_ImageMatch_app` | `1.2.1 -> 1.3.0` scope: historical project evolution Context The existing appearance adjustments could change a subject together with its background. Image Enhance and Image Match needed modes that protected subject detail while correcting bright backgrounds and tone. Decision and rationale Add subject-preserving enhancement and protected tone matching as explicit methods with their own parameters, descriptions, and tests. Keep these methods inside the image apps because they encode workflow-specific decisions rather than generic image primitives. Changes Image Enhance `1.2.2 -> 1.3.0` Image Match `1.2.1 -> 1.3.0` Added Subject-preserving enhance to Image Enhance. Added Protected tone to Image Match. Added calculation and GUI coverage for both methods. User and data impact Users could brighten or normalize a background with less change to the main subject. The new methods were optional; existing histories and other matching methods retained their previous behavior. Compatibility and migration Existing enhancement steps and Image Match inputs remained valid. The new protected methods were additional choices and did not rewrite saved source images. Validation Commit `1768dd57` added focused Image Enhance and Image Match unit tests and updated both GUI workflow tests. Evidence Main commit `1768dd57`. Known limitations and follow-up The protected methods reduce unwanted subject changes but cannot infer semantic foreground perfectly. Their result still requires visual review."},{"title":"Public API help is complete, discoverable, and behavior checked","url":"history/records/2026/07/LK-20260717-complete-public-api-help-contract.html","kind":"history","section":"history","keywords":"LK-20260717-complete-public-api-help-contract 2026-07-17 127 docs compatible labkit.biosignal labkit.dta labkit.rhs labkit.thermal labkit.image labkit.ui labkit_DICPreprocess_app labkit_DICPostprocess_app labkit_CSC_app labkit_BatchImageCrop_app labkit_FLIRThermal_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app Public MATLAB help Generated API reference Documentation contract tests","text":"Public API help is complete, discoverable, and behavior checked id: LK-20260717-complete-public-api-help-contract date: 2026-07-17 sequence: 127 type: docs compatibility: compatible component: `labkit.biosignal` | `1.0.2 -> 1.0.3` component: `labkit.dta` | `2.0.2 -> 2.0.3` component: `labkit.rhs` | `1.0.2 -> 1.0.3` component: `labkit.thermal` | `1.1.1 -> 1.1.2` component: `labkit.image` | `2.0.1 -> 2.0.2` component: `labkit.ui` | `7.5.0 -> 7.5.1` component: `labkit_DICPreprocess_app` | `1.5.7 -> 1.5.8` component: `labkit_DICPostprocess_app` | `1.4.6 -> 1.4.7` component: `labkit_CSC_app` | `1.4.7 -> 1.4.8` component: `labkit_BatchImageCrop_app` | `1.7.6 -> 1.7.7` component: `labkit_FLIRThermal_app` | `1.4.7 -> 1.4.8` component: `labkit_ImageMatch_app` | `1.6.7 -> 1.6.8` component: `labkit_VideoMarker_app` | `1.5.6 -> 1.5.7` component: `labkit_NerveResponseAnalysis_app` | `1.4.7 -> 1.4.8` component: `labkit_ResponseReviewStats_app` | `1.4.6 -> 1.4.7` scope: Public MATLAB help scope: Generated API reference scope: Documentation contract tests Context LabKit's generated function pages came from MATLAB help, but the contract test covered a hand-maintained subset of packages and only checked broad section presence. A public function, implemented option, default, legal value, failure mode, or related API could therefore be omitted while the documentation suite still passed. Several App calculation pages exposed exactly those gaps. Decision and rationale Treat the callable public surface as discoverable source truth. The contract now scans every non-private function below `+labkit` plus each App API declared in the API catalog. It derives standard option reads from implementation code and requires those fields to have documented values and defaults, while required options remain explicitly distinguishable from optional ones. Every public function also states failure behavior and a `See also` relationship. This keeps a generated page useful for standalone MATLAB callers instead of merely repeating a short summary. Changes Completed help for the Contract, Biosignal, DTA, RHS, Thermal, Image, UI, and cataloged App calculation surfaces. Added exact option/default/legal-value checks, package-wide discovery, failure-behavior checks, and related-API checks to the documentation contract. Added a synthetic regression proving the guard detects an undocumented option, an omitted default, a vague value domain, missing failure behavior, and a missing related-API link. Made `labkit.image.resizeToFit` reject unsupported interpolation method names instead of silently treating them as bilinear. Linked owning library and App manuals to the detailed generated reference. User and data impact Readers can now determine valid calls, defaults, option domains, outputs, empty-result behavior, errors, and adjacent functions from the generated page for a public symbol. Developers adding a public function or a standard option read receive a focused contract failure when the corresponding help is incomplete. Compatibility and migration No saved project or data migration is required. Existing valid calls are unchanged. A call to `labkit.image.resizeToFit` with a method other than `\"bilinear\"` or `\"nearest\"` now raises `labkit:image:InvalidResizeMethod`; such values were never part of the documented contract. Validation The exact `PublicApiDocumentationContractTest` suite passes all eight tests, including execution of cataloged examples and the new synthetic defect regression. Image facade unit tests cover both supported resize methods and the unsupported-method error. Generated-site consistency and changed-scope validation are run after this record and its component manuals are rendered. Evidence [Public API index](../../../../reference/README.md) [LabKit App Framework](../../../../framework/README.md) [App catalog](../../../../apps/README.md) [Documentation maintenance](../../../../development/maintain-and-release/documentation.md) Known limitations and follow-up Implementation-derived option discovery intentionally recognizes the repository's standard option access patterns; unusual dynamic field access still requires review. Private helpers remain outside the public-page contract, and interactive `Typical Call:` sketches are not executed as file-independent examples. labkit.image.resizeToFit Resize an image to fit within maximum row/column limits."},{"title":"Public library help contracts and reference validation","url":"history/records/2026/07/LK-20260716-public-api-help-contracts.html","kind":"history","section":"history","keywords":"LK-20260716-public-api-help-contracts 2026-07-16 62 docs compatible labkit.biosignal labkit.dta labkit.image labkit.rhs labkit.thermal public MATLAB help generated API reference documentation contract tests","text":"Public library help contracts and reference validation id: LK-20260716-public-api-help-contracts date: 2026-07-16 sequence: 62 type: docs compatibility: compatible component: `labkit.biosignal` | `1.0.1 -> 1.0.2` component: `labkit.dta` | `2.0.1 -> 2.0.2` component: `labkit.image` | `2.0.0 -> 2.0.1` component: `labkit.rhs` | `1.0.1 -> 1.0.2` component: `labkit.thermal` | `1.1.0 -> 1.1.1` scope: public MATLAB help scope: generated API reference scope: documentation contract tests Context The public library reference identified functions and basic syntax but did not consistently explain option values, units, returned structures, failure behavior, or the distinction between executable examples and calls that need a user-supplied file. Readers still had to inspect implementation files to use several GUI-free APIs correctly. Decision and rationale Treat each public MATLAB help block as the source-level call contract and render that contract into one API page per function. Detailed library validation is kept separate from project-navigation validation so both remain focused and within the repository's effective-code-line budget. The patch versions record a compatible documentation-contract improvement. Function signatures, calculations, returned data, and error identifiers are unchanged. Changes Expanded public help with syntax, inputs, outputs, option names and legal values, defaults, units, errors, examples or typical calls, and related APIs. Distinguished self-contained `Example:` sections from interactive or file-dependent `Typical Call:` sections. Added generated-reference checks for thermal conversion and rendering, DTA schemas and pulse modes, and RHS lazy reads and waveform units. Corrected stale biosignal documentation links in package guidance and an implementation contract. Made label-ownership and package-boundary scans ignore prose-only substring collisions while continuing to reject literals and forbidden MATLAB tokens in executable code. User and data impact Users can discover the supported call contract through both `help` and the generated HTML reference without launching an app. No file format, saved project, calculation, or exported value changes. Compatibility and migration This is a patch-level compatible change. Existing calls require no migration. Validation Documentation consistency checks validate generated sections and executable examples. Project guardrails validate version/history ownership, literal ownership, package boundaries, and effective code-line budgets. Evidence Public help blocks and generated API pages are the maintained contract. Known limitations and follow-up Interactive calls and calls requiring laboratory files remain `Typical Call:` sketches until the repository provides synthetic, non-sensitive fixtures that make them executable examples."},{"title":"RHS Preview separates source roles from reference mechanics","url":"history/records/2026/07/LK-20260716-rhs-preview-structure.html","kind":"history","section":"history","keywords":"LK-20260716-rhs-preview-structure 2026-07-16 108 refactor compatible labkit_RHSPreview_app Neurophysiology App structure","text":"RHS Preview separates source roles from reference mechanics id: LK-20260716-rhs-preview-structure date: 2026-07-16 sequence: 108 type: refactor compatibility: compatible component: `labkit_RHSPreview_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context RHS Preview split one durable project across generic lifecycle files and used two additional lifecycle helpers to select or replace records by role. It also constructed variable filter-source IDs itself and read Runtime-owned reference fields in session, actions, and presentation code. Product metadata remained in separate requirement/version functions. Decision and rationale Keep the three App-owned roles because they express real workflow meaning: one preview recording, one optional protocol, and an ordered collection of filter recordings. Use Runtime V2 for portable path access, fixed-source upserts, and stable reconciliation of the variable collection. Keep only the small App-owned role-to-path selection policy under `sourceFiles`; do not introduce a new framework public role API for one App-specific collection shape. Changes Consolidated command identity, display metadata, version, requirements, project, session, layout, actions, presenter, renderer, and debug capability through the single definition. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec` behind one `Migrate` callback. Moved indexing, initial preview reads, filter discovery, and transient view reconstruction to root `createSession`. Added `sourceFiles.pathsForRole` to select App-owned source roles while delegating reference decoding to `labkit.ui.runtime.sourcePaths`. Replaced fixed-source construction with `upsertSource` and variable filter ID construction with `reconcileSources`, preserving filter order and stable identities across rediscovery. Removed the generic lifecycle package and separate requirement/version files. User and data impact RHS indexing, lazy waveform windows, channel selection, managed ROI/scroll interaction, protocol editing, multi-file filter labels, refresh/removal, exports, manifests, reset, and project reopening retain their behavior. New filter selections use Runtime-generated stable IDs; those IDs are internal project identities and do not change exported recording paths or labels. The App version advances to 1.4.3; durable payload version remains 2. Compatibility and migration Version-1 projects still combine their separate recording, protocol, and filter source fields before validation. Existing version-2 source IDs and ordering are preserved when their resolved files remain selected. Runtime V2 owns migration iteration and later saves remain version 2. Validation Focused unit tests cover source migration, role-path ordering, summary/detail models, channel-role drafts, filter labels, and preview bounds. The hidden GUI workflow covers indexing and drawing a synthetic RHS file, channel tables, two-file filter discovery, protocol/filter export, manifests, project save, reset, reopen, and reconstruction of both preview and filter sources. Evidence [RHS Preview](../../../../apps/neurophysiology/rhs-preview/README.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) Known limitations and follow-up Automated tests do not assess native file-dialog feel, physical channel-role correctness, or waveform interpretation. The remaining special startup path and repository guidance still require separate convergence review."},{"title":"Release validation gate and GUI CI hardening","url":"history/records/2026/07/LK-20260708-release-validation-gate-and-gui-ci-hardening.html","kind":"history","section":"history","keywords":"LK-20260708-release-validation-gate-and-gui-ci-hardening 2026-07-08 40 ci compatible historical project evolution","text":"Release validation gate and GUI CI hardening id: LK-20260708-release-validation-gate-and-gui-ci-hardening date: 2026-07-08 sequence: 40 type: ci compatibility: compatible scope: historical project evolution Context Release-candidate tags did not yet require one explicit summary gate over the headless, coverage, and GUI jobs. Some GUI assertions also depended on exact pixel ordering or timing that varied across CI display backends. Decision and rationale Require every release test job before publication, while testing semantic grid structure instead of platform-specific pixel rounding. Increase the shared GUI idle allowance so slower hosted displays can finish registered UI work. Changes Project validation workflow, no component version change. Release candidate tags now run the full MATLAB test workflow gate before publication: headless tests, coverage, GUI tests, and a release summary gate. GUI layout tests now assert structural grid contracts instead of platform-dependent flattened pixel ordering or width comparisons. Shared GUI test idle waiting allows slower CI display backends more time to finish registered UI work. User and data impact Published release candidates gained a single pass/fail validation signal. Application behavior and data formats did not change; the GUI suite became less sensitive to harmless platform layout differences. Compatibility and migration No known manual migration. Validation Commit `f359518` updated CI policy tests, declarative UI tests, launcher GUI tests, and shared wait behavior. Evidence Mainline commit `f359518`. Known limitations and follow-up The gate covers automated checks only. Interactive workflow feel and scientific visual review remain outside hosted CI."},{"title":"Release validation includes Base MATLAB compatibility","url":"history/records/2026/07/LK-20260717-release-validation-hardening.html","kind":"history","section":"history","keywords":"LK-20260717-release-validation-hardening 2026-07-17 129 ci compatible scheduled and release-candidate validation","text":"Release validation includes Base MATLAB compatibility id: LK-20260717-release-validation-hardening date: 2026-07-17 sequence: 129 type: ci compatibility: compatible scope: scheduled and release-candidate validation Context LabKit already exposed `buildtool baseMatlab`, but GitHub Actions did not run the broad MATLAB product-ownership analysis. A release candidate could therefore pass headless, coverage, and GUI jobs while the explicit Base MATLAB compatibility gate remained a local-only check. Decision and rationale Run the existing public `baseMatlab` task in scheduled and manually dispatched workflows, and require it before a validated release tag can be created. Ordinary pull requests and main pushes retain the smaller headless gate because the static call scan and representative fallback workflows already run there. Changes Added a dedicated Base MATLAB compatibility job with JUnit, active-test, log, and artifact reporting. Added that job to the release-test dependency gate. Corrected the CI contract test so it discovers actual `tasks:` values and checks them against the executable build-task catalog. Updated artifact upload jobs to the current Node.js 24 action generation. User and data impact No App behavior or saved data changes. A release tag is now blocked when MATLAB resolves production source to an undeclared MathWorks product. Compatibility and migration The public `buildtool baseMatlab` command is unchanged. GitHub-hosted runners meet the action runtime requirement; no self-hosted runner migration is introduced. Validation The CI policy contract verifies job scope, public-task ownership, diagnostics, timeouts, parent checkout depth, and release-gate dependencies. The Base MATLAB task remains covered by its own dependency guardrail. Evidence `.github/workflows/matlab-tests.yml` is the executable release gate; `tests/cases/contract/project/ci/CiValidationPolicyGuardrailTest.m` protects its routing and dependency structure. Known limitations and follow-up The product-ownership analysis depends on MATLAB's dependency report and cannot replace manual scientific or interactive App validation."},{"title":"Response Review Stats uses one project contract","url":"history/records/2026/07/LK-20260716-response-review-structure.html","kind":"history","section":"history","keywords":"LK-20260716-response-review-structure 2026-07-16 105 refactor compatible labkit_ResponseReviewStats_app Neurophysiology App structure","text":"Response Review Stats uses one project contract id: LK-20260716-response-review-structure date: 2026-07-16 sequence: 105 type: refactor compatibility: compatible component: `labkit_ResponseReviewStats_app` | `1.4.2 -> 1.4.3` scope: Neurophysiology scope: App structure Context Response Review Stats kept project creation, validation, its one source-schema migration, and session reconstruction in four generic lifecycle files. Session and presentation code also read the Runtime's nested source path directly, and the reset action depended on the generic lifecycle package. Decision and rationale Use the standard compact Runtime V2 structure: one definition, one project contract, and one root session factory. Metric alignment, measurement, summaries, source parsing, exports, and presentation already had clear owners and remain unchanged. Changes Consolidated product metadata, version, requirements, layout, actions, presentation, renderer, and debug capability in `definition.m`. Concentrated project creation, validation, and the version-1 source upgrade in `projectSpec.m`. Moved transient metric/alignment reconstruction to root `createSession.m` and reused it for the reset action. Replaced direct portable-reference reads with semantic `sourcePaths` lookup. Removed the generic lifecycle package and separate requirement/version files. User and data impact Analysis JSON and segment CSV loading, automatic window recomputation, aligned preview, metric summaries, reset, output-folder choice, CSV export, and result manifest behavior are unchanged. Developers now see the entire durable schema history through one entry instead of four lifecycle files. Compatibility and migration Durable schema version 2 is unchanged. Version-1 projects still move their singular source to the canonical source collection before validation and the next save. Validation Focused tests cover source migration, segment parsing, time-grid alignment, measurement, pair-group summaries, and default-grid selection. The hidden GUI workflow loads representative metrics, presents them, and exports the CSV and manifest through real Runtime callbacks. Evidence [Response Review And Stats](../../../../apps/neurophysiology/response-review-stats/README.md) [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Nerve Response Analysis](../../../../apps/neurophysiology/nerve-response-analysis/README.md) Known limitations and follow-up Automated checks do not validate biological interpretation of a chosen baseline/noise window. The larger RHS Preview and Nerve Response Analysis Apps still require independent ownership review."},{"title":"Runtime V2 lifecycle ownership across the app fleet","url":"history/records/2026/07/LK-20260715-runtime-v2-app-migration.html","kind":"history","section":"history","keywords":"LK-20260715-runtime-v2-app-migration 2026-07-15 59 refactor breaking labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app `docs/` `docs/ui-runtime-redesign.md` runtime migration guidance","text":"Runtime V2 lifecycle ownership across the app fleet id: LK-20260715-runtime-v2-app-migration date: 2026-07-15 sequence: 59 type: refactor compatibility: breaking component: `labkit.ui` | `5.2.0 -> 6.0.0` component: `labkit_DICPostprocess_app` | `1.3.6 -> 1.4.0` component: `labkit_DICPreprocess_app` | `1.4.0 -> 1.5.0` component: `labkit_ChronoOverlay_app` | `1.3.6 -> 1.4.0` component: `labkit_CIC_app` | `1.3.8 -> 1.4.0` component: `labkit_CSC_app` | `1.3.10 -> 1.4.0` component: `labkit_EIS_app` | `1.3.4 -> 1.4.0` component: `labkit_VTResistance_app` | `1.3.8 -> 1.4.0` component: `labkit_GaitAnalysis_app` | `1.0.0 -> 1.1.0` component: `labkit_BatchImageCrop_app` | `1.6.8 -> 1.7.0` component: `labkit_CurvatureMeasurement_app` | `1.3.5 -> 1.4.0` component: `labkit_FLIRThermal_app` | `1.3.0 -> 1.4.0` component: `labkit_FocusStack_app` | `1.4.9 -> 1.5.0` component: `labkit_ImageEnhance_app` | `1.5.8 -> 1.6.0` component: `labkit_ImageMatch_app` | `1.5.8 -> 1.6.0` component: `labkit_VideoMarker_app` | `1.2.0 -> 1.3.0` component: `labkit_FigureStudio_app` | `0.1.5 -> 0.2.0` component: `labkit_NerveResponseAnalysis_app` | `1.3.5 -> 1.4.0` component: `labkit_ResponseReviewStats_app` | `1.3.5 -> 1.4.0` component: `labkit_RHSPreview_app` | `1.3.4 -> 1.4.0` component: `labkit_ECGPrint_app` | `1.3.5 -> 1.4.0` scope: `docs/` scope: `docs/ui-runtime-redesign.md` scope: runtime migration guidance Context App lifecycle, callback ordering, UI-derived state, project persistence, graphics resources, and result packaging were implemented repeatedly across apps. Migrating only the lifecycle names would have preserved the main author cost: each app would still need to understand raw controls, figure callbacks, and multiple competing state copies. Decision and rationale Make Runtime V2 the only write path and give the framework ownership of launch, startup, the FIFO event queue, atomic state commits, deterministic presentation, dialogs, interaction resources, project persistence, and result manifests. Apps retain explicit project/session/presentation/result contracts and all domain calculations, workflow decisions, plots, schemas, and exports. This deliberately optimizes ownership and learning cost rather than total app line count. Explicit app contracts can add code in a large scientific app, but they replace hidden closure state and callback plumbing with one inspectable model. The remaining 36 public UI functions have distinct reviewed contracts; the earlier 32-function planning target was not forced through vague APIs. Changes Migrated all twenty public apps to Runtime V2 definitions, standard launch, canonical project/session state, semantic events, pure presentation, and managed resources. Standardized current project writes and result manifests while retaining app-owned payload migrations and existing output files. Retired Runtime V1 writes and removed the public control-registry mutation, standalone editor/runtime, preview mutation, dialog, title, and dispatch compatibility surfaces. Kept supported V1 snapshots and named legacy app projects as read-only import formats; subsequent saves use the current project codec. Updated app requirements to `labkit.ui >=6 <7` and made UI 6 the breaking public facade boundary. Replaced Video Marker's optional `vision.PointTracker` branch with one deterministic app-owned multiscale patch matcher, retaining confidence, constant-velocity fallback, subpixel coordinates, and forward-cache behavior without Computer Vision Toolbox. Added traceable temporary MathWorks-product debt declarations: rapid app work must ship a base-MATLAB fallback plus deterministic and Toolbox-parity tests, and product analysis must continue to see the dependency until replacement. Re-audited already migrated apps, removed five dead or presenter-only helper files, and updated helper-quality classification from retired role packages to the current workflow-first lifecycle/state/result/UI boundaries. Corrected Runtime V2 interaction event construction so paired-anchor cell payloads remain one scalar semantic event. DIC Preprocess point matching now accepts alternating reference/moving points, enables alignment after two complete pairs, and completes the in-workbench rigid registration flow. Hardened the same scalar-envelope invariant for app-managed Runtime V2 resources and `runBusy` callback capture, including legitimate cell-form MATLAB callbacks and cell-valued resource payloads. Made project replacement invalidate the prior presentation cache before the fresh session is rendered, so opening an unchanged project still rebuilds app-owned preview graphics and other ephemeral visual resources. Corrected shared file-event index decoding for R2025a string results, so CIC and the other multi-file V2 apps can select and remove imported items. Stopped presentation commits from rerunning unchanged preview requests, and made DIC point-label updates preserve image handles and zoom viewports. Routed anchor-editor wheel input to the shared image zoom implementation without the retired wrapper's same-name recursion. Deferred CIC and VT batch DTA decoding until a file becomes visible or the batch is exported, so multi-file selection no longer blocks on every source. Let Gait Analysis read current Video Marker project/recovery envelopes and legacy Video/Image Marker autosaves directly as pose-coordinate inputs. Added interaction-mode subtitles for curve anchors, point marking, paired anchors, scale references, and fixed point slots; restored axes context menus after renderer resets; and promoted screenshot/project commands to top-level window entries. Moved Video Marker's Session controls to the top of the Video page, added an `Open MAT` project shortcut, and made New setup explicitly cancel, save, or discard before clearing the current project. Reorganized human documentation into getting-started, app workflow, public API, development, and focused-guide layers. Split app authoring from the user catalog, added the missing contract API reference, and made a project guardrail verify that every public `+labkit` function remains indexed under its owning facade. User and data impact App entrypoint names, scientific calculations, workflow decisions, plots, and existing export filenames remain stable. Current projects have a consistent save/reopen path, external sources use portable references, and result exports include a standard manifest. Runtime errors and modal interactions now pass through framework services, which also makes hidden validation deterministic. Compatibility and migration The migrated app versions require `labkit.ui >=6 <7`; they must not be copied into a UI 5 checkout. Supported V1 snapshots and documented legacy projects remain importable but are not written again. After import, save a new current project if continued editing or recovery is required. Validation Focused Runtime V2, project, interaction-hub, DIC point-matching, CIC, Figure Studio, app-boundary, and public-surface tests passed. The latest repair checkpoint additionally passed CIC/VT GUI workflows (2/2), Gait/app compatibility tests (21/21), Curvature and Video Marker GUI methods (6/6), focused framework menu/interaction tests (8/8), and three targeted interaction-hint methods (3/3). A real local legacy Video Marker autosave was read successfully without copying it into the repository. The documentation hierarchy checkpoint passed relative-link validation and 44/44 focused documentation, app-structure, build-task, and history guardrails. The Phase-6 `buildtool changedFast` checkpoint passed 15 framework GUI tests, 284 headless tests with one environment-assumption skip, and six representative GUI workflows. The final broad gate and any manual pointer or visual checks are reported with the merge handoff rather than embedded as mutable history. Video Marker replacement tests exercise integer and subpixel translation, flat-patch fallback, repeated-input determinism, prediction caching, and coordinate parity with `vision.PointTracker` when that product is installed. `buildtool baseMatlab` confirms the production source resolves only to MATLAB. Evidence `4454ca30` introduced the Runtime V2 kernel; the app migrations then ran from Chrono Overlay (`5fe76ee4`) through Figure Studio (`21936e8d`). `1ef46bfd` removed the retired UI runtime, and `8e109ff4` completed the framework contracts used by the migrated apps. The commits listed in the migration sequence above preserve the individual app checkpoints; this record explains their shared architectural result. Known limitations and follow-up Explicit domain contracts mean that large apps do not necessarily have fewer production lines. Further extraction is justified only by repeated mechanics, not by file-size targets. Pointer feel, drag ergonomics, and scientific visual judgment still require the documented manual app review before merge."},{"title":"Runtime diagnostics and workflow repair converge on one product path","url":"history/records/2026/07/LK-20260726-runtime-diagnostics-and-workflow-repair.html","kind":"history","section":"history","keywords":"LK-20260726-runtime-diagnostics-and-workflow-repair 2026-07-26 159 feat breaking labkit_launcher labkit.app labkit.dta labkit.image labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_TTestWizard_app labkit_ECGPrint_app Always-on session diagnostics and semantic App events Clean startup and explicit synthetic-input generation Launcher repair boundary and preserved product interaction Batch Crop native ROI interaction and preview resolution Explicit DTA units and EIS impedance display units","text":"Runtime diagnostics and workflow repair converge on one product path id: LK-20260726-runtime-diagnostics-and-workflow-repair date: 2026-07-26 sequence: 159 type: feat compatibility: breaking component: `labkit_launcher` | `1.6.0 -> 1.7.0` component: `labkit.app` | `1.2.4 -> 2.0.0` component: `labkit.dta` | `2.0.3 -> 3.0.0` component: `labkit.image` | `2.0.2 -> 2.0.3` component: `labkit_DICPostprocess_app` | `1.5.1 -> 1.6.0` component: `labkit_DICPreprocess_app` | `1.6.1 -> 1.7.0` component: `labkit_ChronoOverlay_app` | `1.5.1 -> 1.6.0` component: `labkit_CIC_app` | `1.5.1 -> 1.6.0` component: `labkit_CSC_app` | `1.5.1 -> 1.6.0` component: `labkit_EIS_app` | `1.5.2 -> 1.6.0` component: `labkit_VTResistance_app` | `1.5.1 -> 1.6.0` component: `labkit_GaitAnalysis_app` | `2.1.1 -> 2.2.0` component: `labkit_BatchImageCrop_app` | `1.8.1 -> 1.9.0` component: `labkit_CurvatureMeasurement_app` | `1.5.1 -> 1.6.0` component: `labkit_FLIRThermal_app` | `1.5.1 -> 1.6.0` component: `labkit_FocusStack_app` | `1.6.1 -> 1.7.0` component: `labkit_ImageEnhance_app` | `1.7.1 -> 1.8.0` component: `labkit_ImageMatch_app` | `1.7.1 -> 1.8.0` component: `labkit_VideoMarker_app` | `1.6.1 -> 1.7.0` component: `labkit_FigureStudio_app` | `0.6.5 -> 0.7.0` component: `labkit_NerveResponseAnalysis_app` | `1.5.1 -> 1.6.0` component: `labkit_ResponseReviewStats_app` | `1.5.1 -> 1.6.0` component: `labkit_RHSPreview_app` | `1.5.1 -> 1.6.0` component: `labkit_TTestWizard_app` | `1.2.0 -> 1.3.0` component: `labkit_ECGPrint_app` | `1.5.1 -> 1.6.0` scope: Always-on session diagnostics and semantic App events scope: Clean startup and explicit synthetic-input generation scope: Launcher repair boundary and preserved product interaction scope: Batch Crop native ROI interaction and preview resolution scope: Explicit DTA units and EIS impedance display units Context Debug launching had combined synthetic data, diagnostic persistence, and App startup into one mode, while ordinary incidents often left too little useful history. App-owned Log tabs and status/error adapters competed with a partial framework recorder. Batch Crop also exposed the practical consequence of testing construction without native interaction: its ROI could be visible yet fail to respond, and ordinary images were unnecessarily downsampled. The root Launcher had simultaneously accumulated installed-product behavior, repair implementation, and maintenance-tool logic in one rescue file. DTA inputs also carried ambiguous unit inference into downstream electrochemistry workflows. Decision and rationale Every normal App launch now owns one clean Runtime session with an always-on, privacy-bounded event journal. Runtime instruments framework operations; Apps add stable semantic events through `CallbackContext.log`. Trace capture, session viewing, and bundle export are runtime tools, while synthetic input generation is a separate explicit developer action that never loads its output or changes the open project. The public App SDK advances to 2.0 because launch diagnostic options and the old status, error, checkpoint, count, recorder, and debug-sample contracts were removed rather than kept as misleading compatibility aliases. Each migrated App requires `labkit.app >=2 <3`. The root Launcher remains a small self-contained repair entry. The installed launcher keeps the established catalog, status, App information, double-click launch, version browsing, and visual identity, while independent maintenance operations live under `tools/`. Changes Added canonical session events, durable bounded journals, active-session leases, degradation reporting, operation/result versus rollback semantics, a live session viewer, trace control, and diagnostic bundle export. Migrated all public Apps to semantic user/developer events, removed duplicate Log tabs, and exposed explicit anonymous synthetic-input generation from the ordinary Tools menu. Kept default launches data-free and action-free; hidden GUI tests now own their visibility fixture instead of relying on a test tag. Restored Batch Crop ROI body drag, center drag, and click-to-place behavior through managed native interaction, while preserving the viewport and using full-resolution previews unless an explicit high-resolution budget applies. Made DTA item units explicit, rejected unknown pulse modes, and added EIS impedance display choices from milliohms through megohms with kilohms as the default. Split Launcher rescue, installed composition, version management, cleanup, and documentation responsibilities without removing product capabilities. User and data impact Users can diagnose a problem after it occurs in an ordinary session, export a sanitized history, and deliberately generate anonymous reproduction inputs without restarting in a special mode. App status remains concise, while developer detail is available through Diagnostics. Existing scientific source files are not rewritten. Batch Crop keeps source resolution for ordinary images and provides responsive native ROI editing. EIS changes only display and export scaling selected by the user; calculations retain canonical ohms. Launcher repair remains explicit and never downloads or replaces files merely because it was opened. Compatibility and migration `labkit.app` 2.0 intentionally removes the former launch/debug and diagnostic adapter APIs. App source using those APIs must migrate to `BuildSyntheticSample` and semantic `CallbackContext.log` events. Saved App project schemas and their declared migrations are unchanged. `labkit.dta` 3.0 replaces ambiguous numeric unit inference with explicit unit metadata. Consumers must use declared canonical values. `labkit.image` 2.0.3 removes library-owned default megapixel policy; Apps choose preview budgets according to their own workflow. Validation Focused framework suites cover event schema, RNG preservation, operation outcomes, multi-process leases, privacy allowlists, projection degradation, viewer behavior, bundle export, Runtime instrumentation, and repository architecture. All 63 public hidden native App smoke cases pass. Every migrated App family has focused workflow coverage, including Batch Crop native ROI and preview-resolution contracts, EIS units, DTA parity, Launcher dispatch/repair, and synthetic-input state neutrality. Evidence The `debug-repair` branch contains purpose-based implementation and focused-test commits. Final evidence is the branch-review validation result, pull-request CI, merged-main CI, and developer-led manual checks recorded in the pull request. Known limitations and follow-up Hidden GUI tests do not establish pointer feel, dialog usability, visual quality, or the usefulness and privacy of exported incident bundles. Those remain explicit manual acceptance gates before merge."},{"title":"Runtime exposes GUI-free source-record creation","url":"history/records/2026/07/LK-20260716-gui-free-source-records.html","kind":"history","section":"history","keywords":"LK-20260716-gui-free-source-records 2026-07-16 100 feat compatible labkit.ui Runtime V2 sources App project migration","text":"Runtime exposes GUI-free source-record creation id: LK-20260716-gui-free-source-records date: 2026-07-16 sequence: 100 type: feat compatibility: compatible component: `labkit.ui` | `7.4.1 -> 7.4.2` scope: Runtime V2 sources scope: App project migration Context Runtime V2 owned the portable-reference schema and exposed a stable path accessor, but source-record creation was available only through callback-bound project services. GUI-free project migrations and legacy importers therefore could not create canonical sources without copying private reference fields. Batch Crop and Video Marker both contained such schema copies. Decision and rationale Expose the canonical source record, not the portable-reference implementation. `labkit.ui.runtime.sourceRecord` accepts stable App identity, semantic role, path, and required status, then delegates the nested representation to the Runtime. The injected callback service uses the same factory. Changes Added the GUI-free `labkit.ui.runtime.sourceRecord` factory with validation and complete MATLAB help. Routed `services.project.sourceRecord`, `upsertSource`, and `reconcileSources` through the same public contract. Extended source tests to cover factory output and invalid inputs. Updated the framework API map and portable-source guidance. User and data impact User project behavior is unchanged. App authors can now implement project migrations and legacy imports without opening a UI callback or constructing runtime-owned reference fields. Current file paths remain available only through `sourcePaths`. Compatibility and migration The addition is compatible within UI 7. Existing source records and injected services retain their shape and behavior. Apps should replace copied portable reference construction when their project/import code is migrated. Validation Focused Runtime source tests cover empty arrays, canonical creation, resolved path lookup, ordered/optional ID lookup, invalid references, and invalid factory arguments. Package-surface, version, documentation, and history guardrails protect the new public contract. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [App Framework](../../../../framework/README.md) [Architecture](../../../../development/build-apps/architecture.md) Known limitations and follow-up Existing Apps that still copy or read portable-reference fields must migrate to `sourceRecord` and `sourcePaths` before the legacy source boundary is fully retired."},{"title":"Runtime owns portable source-reference details","url":"history/records/2026/07/LK-20260716-source-path-accessor.html","kind":"history","section":"history","keywords":"LK-20260716-source-path-accessor 2026-07-16 91 feat compatible labkit.ui Runtime source boundary App maintenance cost","text":"Runtime owns portable source-reference details id: LK-20260716-source-path-accessor date: 2026-07-16 sequence: 91 type: feat compatibility: compatible component: `labkit.ui` | `7.3.0 -> 7.4.0` scope: Runtime source boundary scope: App maintenance cost Context Runtime V2 owned source creation, rebasing, relinking, and validation, but an App still had to read `reference.originalPath` whenever it loaded or displayed a source. This exposed the nested persistence representation throughout session factories, actions, presenters, and workflow packages. Several Apps then added their own identical path loops or ID lookup functions. Decision and rationale Keep source `id`, `role`, and `required` as stable App-facing project data, but treat the nested portable reference as a runtime-owned value. Add one pure `labkit.ui.runtime.sourcePaths` accessor that works before UI construction and therefore serves session creation as well as actions and presentation. Passing UI-bound injected services into `CreateSession` was rejected: it would couple otherwise pure state reconstruction to figure, queue, dialog, and resource-service construction merely to read a path. Changes Added `sourcePaths(sources)` for paths in source order. Added `sourcePaths(sources,ids)` for strict lookup in requested ID order. Defined empty, malformed-reference, and unknown-ID behavior in executable public help and unit tests. Stopped documenting portable-reference fields as an App read contract. Added the accessor to the guarded public UI runtime surface. User and data impact Saved projects and file resolution behave unchanged. App code can now read, compare, load, and present source paths without knowing how a portable reference stores its current or relative location. Compatibility and migration The addition is compatible within UI 7. Existing direct field reads continue to work temporarily, but tracked Apps will move to the accessor during their family-by-family Runtime V2 consolidation. No project migration is required. Validation Unit tests cover source order, requested ID order, empty results, missing IDs, and invalid runtime references. Package-surface and documentation guardrails cover the new public contract. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) [Complete App Tutorial](../../../../development/build-apps/complete-app.md) Known limitations and follow-up Existing App-local path loops remain until their owning App commits are migrated and behavior-tested. Once all consumers use this accessor, a contract guard will prevent production Apps from reading portable-reference fields."},{"title":"Runtime uses one source contract for launch, migration, and layout","url":"history/records/2026/07/LK-20260717-retired-runtime-compatibility.html","kind":"history","section":"history","keywords":"LK-20260717-retired-runtime-compatibility 2026-07-17 126 refactor breaking labkit.ui Runtime launch contract Project payload migration Semantic layout validation","text":"Runtime uses one source contract for launch, migration, and layout id: LK-20260717-retired-runtime-compatibility date: 2026-07-17 sequence: 126 type: refactor compatibility: breaking component: `labkit.ui` | `7.4.7 -> 7.5.0` scope: Runtime launch contract scope: Project payload migration scope: Semantic layout validation Context UI Runtime had already introduced one-definition App launch, one version-aware `Project.Migrate` callback, and presenter-owned managed interactions. Transitional branches still accepted separate requirements and version factories, ordered `Project.Migrations` cells, and a `toolPanel` layout kind whose public constructor no longer existed. No public or accepted private App used those branches; framework tests and stale guidance were their only remaining consumers. Decision and rationale Keep one writable source contract for each capability. `launch` receives one definition factory and reads product metadata and requirements from that definition. Runtime calls the App's single `Migrate(project,fromVersion)` callback for each missing payload step. Layout sections contain supported semantic controls, while presenter interaction specs own axes tools. This removes source-level compatibility that could hide an incomplete App migration. Read-only support for current saved project envelopes, declared legacy MAT imports, and older supported payload versions remains intact. Changes Removed requirements/version factory dispatch from `runtime.launch` and added a direct diagnostic for the retired call form. Rejected `Project.Migrations` and removed its project-reader fallback. Removed `toolPanel` validation, sizing, building, diagnostics, and current manual guidance. Converted Runtime tests to complete single definitions and added regression checks for both retired contracts. User and data impact Current Apps and their project files behave the same. App developers have one place to review identity, version, requirements, layout, and optional runtime capabilities. Source code still using a three-factory launch or `Project.Migrations` now fails immediately with migration guidance instead of silently taking a legacy path. Compatibility and migration This is a source-contract cleanup within UI 7. Call `labkit.ui.runtime.launch(@app.definition,varargin{:})`, move product metadata and `Requirements` into `definition.m`, and replace migration callback cells with one callback that switches on `fromVersion`. No saved-data rewrite is required. Validation Focused Runtime GUI tests cover single-definition launch, rejection of the retired launch form, ordered project migration and atomic load, rejection of `Project.Migrations`, managed interactions, and startup progress. Contract tests cover App layout structure and public surface drift. Evidence [Runtime and lifecycle](../../../../framework/guides/runtime.md) [App development](../../../../development/build-apps/app-development.md) [Public API reference](../../../../reference/README.md) Known limitations and follow-up Historical records retain their original descriptions of the transitional APIs. They document how the architecture evolved and are not current usage guidance."},{"title":"Runtime-only P-code app packages","url":"history/records/2026/07/LK-20260708-runtime-only-p-code-app-packages.html","kind":"history","section":"history","keywords":"LK-20260708-runtime-only-p-code-app-packages 2026-07-08 41 refactor compatible historical project evolution","text":"Runtime-only P-code app packages id: LK-20260708-runtime-only-p-code-app-packages date: 2026-07-08 sequence: 41 type: refactor compatibility: compatible scope: historical project evolution Context Single-app P-code packages included a P-coded copy of the full launcher even though their purpose was to run one protected app. That exposed maintenance and packaging actions that were meaningful only in a source checkout. Decision and rationale Make P-code output a minimal runtime package with a direct app entry script. Keep the full launcher in source and source-package distributions, where its installation, profiling, and packaging tools are available. Changes Project deployment tooling, no component version change. `Package P-code` now creates a runtime-only single-app package instead of shipping a P-coded LabKit launcher and launcher maintenance tools. P-code package manifests and README instructions point users to the direct `run_` entry file. P-code packaging no longer requires `labkit_launcher.m` or `labkit_launcher.p` to exist in the package root being used as the runtime source. User and data impact Recipients of a P-code package start the protected app with `run_` and no longer see unrelated launcher maintenance actions. The protected app and its data formats are unchanged. Compatibility and migration Users of P-code packages should run `run_` from the unzipped package instead of `labkit_launcher`. Source packages still include and support the launcher. Validation Commit `75f63f1` expanded `PackageLabKitAppToolTest` for runtime-only package contents and updated the generated package instructions. Evidence Mainline commit `75f63f1`. Known limitations and follow-up P-code packages intentionally omit source-oriented launcher features. Users who need installation or packaging tools should use a source or source-package distribution."},{"title":"Runtime-owned project shape validation","url":"history/records/2026/07/LK-20260716-runtime-owned-project-shape-validation.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-owned-project-shape-validation 2026-07-16 117 refactor compatible labkit.ui labkit_ChronoOverlay_app labkit_CIC_app labkit_CSC_app labkit_EIS_app labkit_VTResistance_app Runtime V2 project validation electrochemistry App maintenance cost","text":"Runtime-owned project shape validation id: LK-20260716-runtime-owned-project-shape-validation date: 2026-07-16 sequence: 117 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.4 -> 7.4.5` component: `labkit_ChronoOverlay_app` | `1.4.4 -> 1.4.5` component: `labkit_CIC_app` | `1.4.4 -> 1.4.5` component: `labkit_CSC_app` | `1.4.4 -> 1.4.5` component: `labkit_EIS_app` | `1.4.4 -> 1.4.5` component: `labkit_VTResistance_app` | `1.4.4 -> 1.4.5` scope: Runtime V2 project validation scope: electrochemistry App maintenance cost Context Runtime already validated canonical project buckets and portable source records after actions committed. During project restore, however, the App validator ran before that framework validation. Every persistent App consequently repeated the same bucket and source-record checks so malformed loaded projects would fail safely. Decision and rationale Validate framework-owned project structure before invoking App-owned project validation on both initial state and project restore. App validators should describe only their own schema and scientific rules. This gives canonical shape and source records one owner while retaining strict validation at the load boundary. Changes Added one private Runtime project-shape validator shared by restore and semantic state validation. Runtime now rejects a malformed loaded payload before its App validator can inspect domain fields. Removed 67 net lines of repeated canonical bucket and source-record checks from the five electrochemistry `projectSpec.m` files. Kept each App's legal choices, numeric bounds, result fields, and other domain-specific validation unchanged. User and data impact Valid projects and scientific results are unchanged. Malformed projects now receive the framework's consistent invalid-state error for canonical structure failures instead of an App-specific bucket or source error. No payload migration is required. Compatibility and migration The accepted project schema is unchanged. This is a validation-ownership change, not a data-format change, and current or older supported project versions continue through the same migration callbacks. Validation The Runtime project GUI test loads a payload missing a canonical bucket and verifies atomic rejection before App validation. Focused electrochemistry GUI tests exercise load, analysis, plotting, and export paths with the reduced App validators. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines the framework and App validation boundary. [App Development](../../../../development/build-apps/app-development.md) tells App authors which validation remains in `projectSpec.m`. Each affected [Electrochemistry App](../../../../apps/electrochemistry/README.md) manual identifies its domain-owned validator responsibilities. Known limitations and follow-up Other App families still repeat some canonical structure checks. They will move to the same boundary only after their role and cross-field constraints are separated from the generic checks and covered by focused tests."},{"title":"Runtime-owned session defaults across App families","url":"history/records/2026/07/LK-20260716-runtime-owned-session-defaults.html","kind":"history","section":"history","keywords":"LK-20260716-runtime-owned-session-defaults 2026-07-16 116 refactor compatible labkit.ui labkit_DICPostprocess_app labkit_DICPreprocess_app labkit_GaitAnalysis_app labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FLIRThermal_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app labkit_VideoMarker_app labkit_FigureStudio_app labkit_NerveResponseAnalysis_app labkit_ResponseReviewStats_app labkit_RHSPreview_app labkit_ECGPrint_app Runtime V2 session construction App maintenance cost","text":"Runtime-owned session defaults across App families id: LK-20260716-runtime-owned-session-defaults date: 2026-07-16 sequence: 116 type: refactor compatibility: compatible component: `labkit.ui` | `7.4.3 -> 7.4.4` component: `labkit_DICPostprocess_app` | `1.4.3 -> 1.4.4` component: `labkit_DICPreprocess_app` | `1.5.4 -> 1.5.5` component: `labkit_GaitAnalysis_app` | `2.0.4 -> 2.0.5` component: `labkit_BatchImageCrop_app` | `1.7.3 -> 1.7.4` component: `labkit_CurvatureMeasurement_app` | `1.4.3 -> 1.4.4` component: `labkit_FLIRThermal_app` | `1.4.3 -> 1.4.4` component: `labkit_FocusStack_app` | `1.5.2 -> 1.5.3` component: `labkit_ImageEnhance_app` | `1.6.3 -> 1.6.4` component: `labkit_ImageMatch_app` | `1.6.3 -> 1.6.4` component: `labkit_VideoMarker_app` | `1.5.2 -> 1.5.3` component: `labkit_FigureStudio_app` | `0.2.5 -> 0.2.6` component: `labkit_NerveResponseAnalysis_app` | `1.4.3 -> 1.4.4` component: `labkit_ResponseReviewStats_app` | `1.4.3 -> 1.4.4` component: `labkit_RHSPreview_app` | `1.4.3 -> 1.4.4` component: `labkit_ECGPrint_app` | `1.4.3 -> 1.4.4` scope: Runtime V2 session construction scope: App maintenance cost Context Runtime V2 already adds the canonical `selection`, `workflow`, `view`, and `cache` session buckets after an App factory returns. Its workflow service also creates `logLines` on the first log operation. Fifteen App factories still repeated empty buckets or initialized that framework-owned log, making their state declarations longer and obscuring which transient fields were genuinely App-specific. Decision and rationale Treat canonical session shape and workflow-log initialization as Runtime control-plane responsibilities. An App session factory now returns only the selection, workflow, view, and cache fields that express its own workflow. This preserves explicit App state while removing framework boilerplate. Changes Removed empty canonical session buckets from affected App factories. Removed every App-owned `logLines` initializer; the injected workflow service remains the only writer and lazy initializer. Removed Image Enhance's direct log-panel presenter binding; Runtime commits the workflow log to every declared log panel. Added `services.project.newState()` and moved full-project reset actions onto that Runtime-owned creation and normalization path. Added a fleet-wide structure guard that rejects empty canonical buckets and direct workflow-log initialization in `createSession.m`. Updated every affected App manual and patch version. User and data impact There is no workflow, scientific, persistence, or saved-data change. Runtime normalization produces the same complete in-memory session before validation, presentation, or the first callback. Existing project files remain compatible. Compatibility and migration The change is source-compatible within Runtime V2 and requires no project payload migration. Direct tests of an App's raw session factory should inspect only App-owned fields; tests that need canonical empty buckets should create state through Runtime. Validation The fleet structure guard covers every public App factory. Focused App unit and hidden-GUI suites verify restored state, initial presentation, workflow logs, and representative source-backed sessions across the affected families. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) defines canonical session normalization and workflow logging. [App Development](../../../../development/build-apps/app-development.md) requires `createSession.m` to rebuild only App-specific transient data. Each affected App manual describes its remaining session-owned fields. Known limitations and follow-up Most factories still perform necessary source decoding or reconstruction. They should not be removed merely to reduce file count. Further simplification requires evidence that a repeated reconstruction pattern is domain-neutral and belongs behind a stable Runtime service."},{"title":"Searchable MATLAB-generated documentation site and launcher workflow groups","url":"history/records/2026/07/LK-20260715-documentation-site.html","kind":"history","section":"history","keywords":"LK-20260715-documentation-site 2026-07-15 60 feat compatible labkit_launcher `docs/` `site/` `tools/docs/` `buildfile.m`","text":"Searchable MATLAB-generated documentation site and launcher workflow groups id: LK-20260715-documentation-site date: 2026-07-15 sequence: 60 type: feat compatibility: compatible component: `labkit_launcher` | `1.4.0 -> 1.5.0` scope: `docs/` scope: `site/` scope: `tools/docs/` scope: `buildfile.m` Context The documentation had become a set of large Markdown pages with no generated site, global search, code-entity pages, or enforced relationship between the public MATLAB surface and its reference material. Launcher actions were also presented as a mostly flat list, making routine app launch, installation, maintenance, profiling, and packaging appear equally related. Decision and rationale Keep authoring in tracked Markdown, structured navigation/catalog metadata, and MATLAB help blocks, then use one repository-owned MATLAB compiler to emit the tracked static site. Bind every non-private `labkit.*` API and each explicitly cataloged app-owned scientific API to its source declaration. Keep private helpers out of detailed reference pages. Group launcher actions by user intent and make documentation regeneration a first-level maintenance action. This follows the separation used by MATLAB and Qt documentation while keeping the build offline and dependency-free. Changes Added a MATLAB-only static documentation compiler, responsive site chrome, global client-side search, source links, related-API links, and deterministic generated-tree comparison. Added structured page and app-owned API catalogs. Generated reference now covers reusable library functions plus explicitly supported GUI-free app calculation entry points, while rejecting `private/` paths. Added public documentation build and consistency tasks and project guardrails for catalog coverage, search visibility, and generated output. Reorganized launcher buttons into Run Apps, Versions and Install, Development and Maintenance, and Package and Publish groups. Added Update Documentation to the launcher. It rebuilds `site/`, opens the generated home page, and reports narrative-page, API-page, file, and output location details. User and data impact Users can browse and search documentation without a MATLAB documentation server, move between related public functions, and regenerate the same site after source changes. Existing app data, projects, calculations, and exports are unchanged. Launcher maintenance actions are easier to distinguish from normal app launch and installation tasks. Compatibility and migration The feature is additive. Existing Markdown links remain valid in the source repository. `site/` is generated and must not be edited manually. Source-only or P-code app packages that omit documentation tools show the launcher action disabled with an explanatory tooltip. Validation The compiler generated 20 narrative pages, 133 public API pages, and 157 tracked files. Focused documentation/build guardrails passed after verifying a byte-for-byte rebuild. Hidden-GUI launcher layout, documentation generation, and missing-tool behavior tests passed 3/3. Evidence `tools/docs/renderLabKitDocs.m` and `docs/site.json` define the deterministic build and navigation contract. `docs/catalogs/api.json` defines the supported app-owned public API surface. Initial searchable site and launcher integration `bdeba572`. Per-app documentation publication `82734a47`. Later information-architecture rebuild `8b55e2b9`. Known limitations and follow-up The first generated site still contains transitional narrative pages that must be split into app-family, per-app, framework, and focused API guides. Cataloged scientific APIs need richer standalone examples and algorithm/unit sections before the documentation rewrite is complete."},{"title":"Session diagnostics expose actions and teardown failures","url":"history/records/2026/07/LK-20260726-actionable-session-diagnostics.html","kind":"history","section":"history","keywords":"LK-20260726-actionable-session-diagnostics 2026-07-26 161 fix compatible labkit.app Session log filter and action terminology Runtime close and resource-cleanup diagnostics","text":"Session diagnostics expose actions and teardown failures id: LK-20260726-actionable-session-diagnostics date: 2026-07-26 sequence: 161 type: fix compatibility: compatible component: `labkit.app` | `2.0.0 -> 2.0.1` scope: Session log filter and action terminology scope: Runtime close and resource-cleanup diagnostics Context The live session viewer exposed internal `Audience` and `Root` terminology without explaining their meaning. Root-action choices were bare `op-*` identifiers, so users could not tell which click or workflow event a correlated call chain represented. The TRACE-disabled notice also implied that ordinary lifecycle evidence might be missing even though only TRACE-severity records were disabled. Runtime callback and startup failures were instrumented, but application resource cleanup ran before recorder close without its own operation boundary. A cleanup exception could therefore prevent orderly adapter and journal teardown while leaving no canonical lifecycle failure. Decision and rationale Present diagnostic concepts in user-task language while retaining stable machine identifiers underneath. Reader intent becomes **View**, root correlation becomes **Action**, and each action choice combines time, a safe semantic message or callback alias, and its `op-*` identifier. Treat Runtime close as an ordinary instrumented lifecycle operation. Attempt independent resource and native cleanup even after one fails, persist the terminal close result, close the journal, and only then return the original or combined cleanup exception. Changes Replaced the viewer's Audience wording with Useful, Everything, User workflow, and Developer details choices plus explanatory tooltips. Replaced Root wording with Action and generated readable correlation labels without changing the underlying root-action filter value. Clarified that TRACE-off sessions still capture DEBUG lifecycle boundaries and every warning or error. Added `runtime.close.started`, `runtime.close.completed`, and `runtime.close.failed` operation records. Made sibling cleanup continue after a resource or adapter failure and guaranteed recorder close before the cleanup exception is returned. User and data impact Users can select a recognizable action instead of guessing which `op-*` value belongs to an incident. Default views remain concise, while earlier DEBUG callback boundaries remain available without having enabled TRACE. Scientific state, project data, App callbacks, and saved schemas are unchanged. Diagnostic records remain sanitized and use the existing bounded journal. Compatibility and migration This is a compatible App SDK patch. Public App-author APIs and the event schema are unchanged. The projection adds a private display-label field, and existing root-action identifiers remain stable for filtering and exported bundles. Validation Focused projection specifications verify audience filtering, readable action labels, and accurate TRACE limitations. Hidden-GUI viewer specifications verify the visible terminology and separate label/value behavior. Runtime logging specifications inject an application-resource cleanup failure and require both the failed close operation and orderly session close in memory and the durable journal. Evidence The focused framework results, generated documentation check, changed-file gate, and exact main-push CI run are the delivery evidence. Known limitations and follow-up No logger can synthesize a failed terminal record for native events that never reach Runtime, exceptions deliberately swallowed without logging, or a process that hangs or terminates abruptly. The last retained boundary and journal lease state remain the evidence in those cases."},{"title":"Shared image facade","url":"history/records/2026/06/LK-20260630-shared-image-facade.html","kind":"history","section":"history","keywords":"LK-20260630-shared-image-facade 2026-06-30 24 feat compatible labkit.image labkit_BatchImageCrop_app labkit_CurvatureMeasurement_app labkit_FocusStack_app labkit_ImageEnhance_app labkit_ImageMatch_app historical project evolution","text":"Shared image facade id: LK-20260630-shared-image-facade date: 2026-06-30 sequence: 24 type: feat compatibility: compatible component: `labkit.image` | `new -> 1.0.0` component: `labkit_BatchImageCrop_app` | `1.3.9 -> 1.4.0` component: `labkit_CurvatureMeasurement_app` | `1.2.3 -> 1.2.4` component: `labkit_FocusStack_app` | `1.2.5 -> 1.3.0` component: `labkit_ImageEnhance_app` | `1.3.5 -> 1.4.0` component: `labkit_ImageMatch_app` | `1.3.5 -> 1.4.0` scope: historical project evolution Context Batch Crop, Focus Stack, Image Enhance, and Image Match each carried similar code for supported extensions, path normalization, image reads, preview sizing, and basic enhancement. Small differences between those copies produced inconsistent file and display behavior. Decision and rationale Introduce `labkit.image` for GUI-free operations with neutral meaning, then move the image apps to that shared API. Keep crop geometry, focus fusion, matching, protected enhancement, and export schemas in their respective apps. Changes `labkit.image` `1.0.0` Batch Crop, Curvature, Focus Stack, Image Enhance, and Image Match advanced within their image-facade adoption lines. Added a GUI-free image facade for file input, display normalization, basic processing, and preview support. Adopted that facade across image-measurement apps. User and data impact Supported image selection, display-name handling, preview normalization, and common enhancement primitives became consistent across the migrated apps. Scientific app outputs and source files did not change format. Compatibility and migration Image apps kept their entry points and exports while adopting the shared operations. App code that called the retired local helpers needed to use the image facade or the owning app operation. Validation Commit `7023e87e` added a dedicated `LabKitImageFacadeTest` suite and updated app compatibility, package-boundary, public-surface, and focused image-app tests. Evidence Main commit `7023e87e`. Known limitations and follow-up The first facade release covered common MATLAB image formats and basic processing only. Thermal decoding and workflow-specific algorithms remained outside `labkit.image`."},{"title":"Single-click DIC rigid point matching","url":"history/records/2026/07/LK-20260713-dic-rigid-point-editor.html","kind":"history","section":"history","keywords":"LK-20260713-dic-rigid-point-editor 2026-07-13 50 feat compatible labkit.ui labkit_DICPreprocess_app Single-click DIC rigid point matching","text":"Single-click DIC rigid point matching id: LK-20260713-dic-rigid-point-editor date: 2026-07-13 sequence: 50 type: feat compatibility: compatible component: `labkit.ui` | `5.0.4 -> 5.1.0` component: `labkit_DICPreprocess_app` | `1.3.6 -> 1.4.0` scope: Single-click DIC rigid point matching Context DIC manual rigid matching had draggable points but maintained a separate pointer implementation and required a less consistent placement workflow than the ROI-center anchors used by related reconstruction workflows. Decision and rationale Extend the existing app-neutral anchor editor with a discrete point mode, then keep moving/fixed pair order, numbering, minimum pair count, and rigid-fit policy inside DIC. Changes Added `mode=\"points\"`: one blank click appends a point, dragging refines it, no connecting curve is drawn, and deletion remains under explicit controls. Migrated the DIC modal to two shared point-mode editors while preserving ordered moving/fixed pairs, labels, undo, cancel, and acceptance rules. Retained toolbox-free image display and rigid alignment behavior. User and data impact Feature placement now follows the same direct click-and-drag model as Imager ROI anchors. Point coordinates and the resulting rigid transform keep their existing N-by-2 pixel-coordinate contract. Compatibility and migration The default anchor-editor curve mode is unchanged. DIC exports and transform math are unchanged; this is an additive interaction improvement. Validation UI anchor-editor tests cover discrete point append and no-path behavior. The DIC GUI workflow covers toolbox-free modal cancellation and app launch wiring. Evidence Primary sources are `labkit.ui.interaction.anchorEditor` and `dic_preprocess.userInterface.selectRigidPointPairs`. Commit `392a073e` introduced their shared DIC rigid-point interaction. Known limitations and follow-up Automated hidden-GUI tests cannot judge pointing ergonomics; final interaction feel still requires a short manual placement-and-drag check."},{"title":"Single-definition App product contract","url":"history/records/2026/07/LK-20260716-single-definition-contract.html","kind":"history","section":"history","keywords":"LK-20260716-single-definition-contract 2026-07-16 77 feat compatible labkit.ui App authoring Runtime V2 launch metadata","text":"Single-definition App product contract id: LK-20260716-single-definition-contract date: 2026-07-16 sequence: 77 type: feat compatibility: compatible component: `labkit.ui` | `7.1.0 -> 7.2.0` scope: App authoring scope: Runtime V2 launch metadata Context Every App repeated product metadata and facade requirements in `version.m` and `requirements.m`, while `definition.m` separately described the product's runtime behavior. Thin entrypoints had to join all three factories, and the launcher and release guardrails treated the duplicate files as separate facts. Decision and rationale Make `definition.m` the one App product contract. Product identity, version, requirements, layout, and optional runtime capabilities now have one owner. The public entrypoint can delegate through `launch(@definition, varargin{:})`. Changes Added `Command`, optional `DisplayName`, `Family`, `AppVersion`, `Updated`, and `Requirements` fields to `labkit.ui.runtime.define`. Added single-definition launch and lightweight `version` and `requirements` request handling. Kept the previous three-factory launch form only as a temporary bridge while existing Apps move in reviewed family-sized changes. Extended the minimal real-GUI launch test to obtain all metadata from the same definition. User and data impact App users see no workflow or scientific change. App authors no longer need to keep three product declarations synchronized after their App is migrated. Compatibility and migration The addition is compatible within UI 7. Existing definitions continue to run during the migration window. Each App will move its exact metadata and facade ranges into `definition.m` before its redundant files are removed. The bridge is not a permanent supported architecture. Validation Focused runtime tests exercise normal launch plus `version` and `requirements` requests from one minimal definition. Documentation, facade-version, and App contract tests protect the metadata shape during the family migrations. Evidence [Runtime and Lifecycle](../../../../framework/guides/runtime.md) documents the single definition and field meanings. [App Development](../../../../development/build-apps/app-development.md) shows the reduced static App file set. Known limitations and follow-up Existing Apps, launcher static discovery, release version guards, tutorials, and the App-builder skill still need family-by-family migration. The legacy launch bridge is removed only after those consumers no longer use it."},{"title":"Source-adjacent Video Marker autosave","url":"history/records/2026/07/LK-20260716-source-adjacent-video-autosave.html","kind":"history","section":"history","keywords":"LK-20260716-source-adjacent-video-autosave 2026-07-16 69 fix compatible labkit.ui labkit_VideoMarker_app Runtime V2 project recovery Video Marker autosave location","text":"Source-adjacent Video Marker autosave id: LK-20260716-source-adjacent-video-autosave date: 2026-07-16 sequence: 69 type: fix compatibility: compatible component: `labkit.ui` | `6.0.3 -> 6.0.4` component: `labkit_VideoMarker_app` | `1.4.0 -> 1.4.1` scope: Runtime V2 project recovery scope: Video Marker autosave location Context The first explicit Runtime V2 autosave action wrote only the framework's hidden recovery generation. Video Marker already had a product contract for a visible, stable autosave beside the source video, and users select those MAT files directly in downstream workflows such as Gait Analysis. Decision and rationale Keep generic debounced recovery framework-owned, but let an app provide a deterministic autosave destination when that location is part of its workflow. Video Marker derives the destination from the source video and never asks the user to choose it. Changes Added `services.project.saveAutosave(state,filepath)` without changing named project ownership or dirty status. Restored the visible `Video Marker Autosaves` folder and stable `.video_marker.autosave.mat` filename. Enabled **Save autosave** only after a video is open and report write failures through the standard diagnostics and app-parented alert services. User and data impact Clicking **Save autosave** immediately overwrites the source video's current autosave using the current `labkitProject` envelope. It opens no location dialog. The file remains easy to locate, copy, and load into Gait Analysis. Compatibility and migration The filename and source-adjacent folder match the earlier Video Marker autosave convention. Generic Runtime recovery remains available independently. Validation The focused Video Marker GUI test opens a synthetic video, verifies the button is enabled, writes the expected source-adjacent MAT, and confirms the runtime still has no named project path. Evidence [Video Marker](../../../../apps/image-measurement/video-marker/README.md) documents the exact no-dialog destination. [Runtime and lifecycle](../../../../framework/guides/runtime.md) distinguishes generic recovery from an app-targeted autosave. Known limitations and follow-up The source folder must be writable. A write failure leaves the current project untouched and is shown to the user."},{"title":"Startup responsiveness","url":"history/records/2026/07/LK-20260702-startup-responsiveness.html","kind":"history","section":"history","keywords":"LK-20260702-startup-responsiveness 2026-07-02 31 perf compatible labkit_launcher labkit.ui Startup responsiveness","text":"Startup responsiveness id: LK-20260702-startup-responsiveness date: 2026-07-02 sequence: 31 type: perf compatibility: compatible component: `labkit_launcher` | `1.2.2 -> 1.2.3` component: `labkit.ui` | `3.4.2 -> 3.4.4` scope: Startup responsiveness Context Profiling showed that a substantial part of perceived startup delay occurred after MATLAB had created the window but before it allowed the operating system to paint it. Launcher discovery and preview scroll setup also ran before their results were needed, leaving users with a blank or apparently frozen window. Decision and rationale Paint the launcher and app shell as soon as their basic structure exists, then defer discovery and preview navigation setup until the UI can remain responsive. Preserve the same controls and app catalog once initialization finishes. Changes `labkit_launcher` `1.2.2 -> 1.2.3` `labkit.ui` `3.4.2 -> 3.4.4` Painted launcher and app windows earlier. Deferred launcher app discovery and lazy preview scroll setup. User and data impact The window appeared and responded earlier, making initialization visible instead of presenting a long white-screen interval. App discovery and scroll navigation still completed automatically; the change moved their timing rather than removing those capabilities. Compatibility and migration App definitions and saved data were unchanged. The launcher and runtime altered only when discovery and interaction setup occurred during startup. Validation Launcher profiler assertions checked the earlier paint point, while UI axes layout tests covered deferred scroll navigation in `7d4ef11e`. Evidence Main commit `7d4ef11e`. Known limitations and follow-up Earlier painting improved perceived responsiveness, but it also made progress communication important. Later runtime work introduced explicit startup stages and lifecycle-managed readiness reporting."},{"title":"Structured documentation history","url":"history/records/2026/07/LK-20260715-component-owned-history.html","kind":"history","section":"history","keywords":"LK-20260715-component-owned-history 2026-07-15 61 docs breaking `docs/history/records/` `tools/docs/` `tools/release/` `CHANGELOG.md`","text":"Structured documentation history id: LK-20260715-component-owned-history date: 2026-07-15 sequence: 61 type: docs compatibility: breaking scope: `docs/history/records/` scope: `tools/docs/` scope: `tools/release/` scope: `CHANGELOG.md` Context Project evolution was stored in one 3,571-line root changelog. App, framework, library, validation, launcher, and release decisions were mixed together, while the new documentation site already had component pages capable of presenting the relevant history in context. Decision and rationale Store each change explanation once as an independent Markdown page in the chronological `docs/history/records//