Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 16 additions & 55 deletions +labkit/+app/+internal/+launcher/dispatch.m
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,13 @@
maintenanceGrid.Padding = [5 5 5 5];
maintenanceGrid.RowSpacing = 5;
maintenanceGrid.ColumnSpacing = 6;
docsToolButton = uibutton(maintenanceGrid, "Text", "Local Documentation");
docsToolButton = uibutton(maintenanceGrid, ...
"Text", "Generate Local Documentation");
codeButton = uibutton(maintenanceGrid, "Text", "Run Code Analyzer");
profileButton = uibutton(maintenanceGrid, "Text", "Profile Selected App");
cleanButton = uibutton(maintenanceGrid, "Text", "Clean Artifacts");
docsToolButton.Tooltip = ...
"Open local documentation, or offer to generate it when missing.";
"Rebuild the ignored local site from current documentation sources.";
codeButton.Tooltip = ...
"Run MATLAB Code Analyzer and write the repository report.";
profileButton.Tooltip = ...
Expand Down Expand Up @@ -211,7 +212,7 @@
releaseButton.ButtonPushedFcn = @(~, ~) manageVersion("stable");
versionsButton.ButtonPushedFcn = @(~, ~) manageVersion("browse");
cleanButton.ButtonPushedFcn = @(~, ~) runMaintenance("clean");
docsToolButton.ButtonPushedFcn = @(~, ~) openDocumentation("local");
docsToolButton.ButtonPushedFcn = @(~, ~) runMaintenance("docs");
codeButton.ButtonPushedFcn = @(~, ~) runMaintenance("codecheck");
profileButton.ButtonPushedFcn = @(~, ~) runMaintenance("profile");
packageButton.ButtonPushedFcn = @(~, ~) packageChecked("source");
Expand Down Expand Up @@ -332,24 +333,13 @@ function openDocumentation(source)
app.command + "...");
try
page = documentationPage(root, app.command, source);
catch cause
if source == "local" && string(cause.identifier) == ...
"labkit:app:internal:launcher:LocalDocumentationMissing"
[page, source] = resolveMissingLocalDocumentation(app);
if strlength(page) == 0
setStatus("Local documentation action cancelled.");
endAction();
return;
end
else
setStatus("Documentation unavailable: " + failureText(cause));
endAction();
return;
end
end
try
if launcherGuiTestMode() ~= "hidden"
web(page, "-browser");
browserStatus = web(char(page), "-browser");
if browserStatus ~= 0
error( ...
"labkit:app:internal:launcher:BrowserUnavailable", ...
"The system browser could not open the documentation.");
end
end
setStatus("Opened " + source + " documentation for " + ...
app.command + ".");
Expand All @@ -359,23 +349,6 @@ function openDocumentation(source)
endAction();
end

function [page, source] = resolveMissingLocalDocumentation(app)
source = "local";
choice = localDocumentationChoice(fig, app.command);
switch choice
case "Generate Local"
callTool(root, fullfile("tools", "docs"), ...
"renderLabKitDocs", fullfile(root, "docs"), ...
fullfile(root, "site"));
page = documentationPage(root, app.command, "local");
case "Open Online"
source = "online";
page = documentationPage(root, app.command, "online");
otherwise
page = "";
end
end

function manageVersion(mode)
if state.busy
return;
Expand Down Expand Up @@ -412,6 +385,11 @@ function runMaintenance(kind)
"ProgressFcn", @reportProgress);
setStatus("Clean Artifacts complete: " + ...
string(result.removedCount) + " target(s) removed.");
case "docs"
callTool(root, fullfile("tools", "docs"), ...
"renderLabKitDocs", fullfile(root, "docs"), ...
fullfile(root, "site"));
setStatus("Local documentation generated from current sources.");
case "codecheck"
callTool(root, fullfile("tools", "codecheck"), ...
"runCodecheckReport", root, ...
Expand Down Expand Up @@ -538,7 +516,7 @@ function updateInfo()
info = struct( ...
"name", "labkit_launcher", ...
"displayName", "LabKit App Launcher", ...
"version", "1.8.0", ...
"version", "1.8.1", ...
"updated", "2026-07-30");
end

Expand Down Expand Up @@ -967,23 +945,6 @@ function addPathIfMissing(folder, varargin)
end
end

function choice = localDocumentationChoice(fig, command)
if launcherGuiTestMode() == "hidden"
choice = "Open Online";
if isappdata(groot, "labkitLauncherDocumentationChoice")
choice = string(getappdata( ...
groot, "labkitLauncherDocumentationChoice"));
end
return;
end
choice = string(uiconfirm(fig, ...
"Local documentation is not available for " + command + ...
". Generate it now, or open the online documentation?", ...
"Local Documentation", ...
"Options", {"Open Online", "Generate Local", "Cancel"}, ...
"DefaultOption", 1, "CancelOption", 3));
end

function tf = isStructuralStartupFailure(cause)
id = string(cause.identifier);
tf = startsWith(id, "MATLAB:UndefinedFunction") || ...
Expand Down
3 changes: 1 addition & 2 deletions +labkit/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ Use `labkittest.explain` to find the exact owner and contract.
`Typical Call:`.
- Private helpers document caller, shapes, side effects, and assumptions.
- An app-facing facade change updates its `version.m`, owning manual, and one
component history record before the `develop` or `hotfix/*` PR is
merge-ready.
component history record before the `develop` PR is merge-ready.

## Validation

Expand Down
5 changes: 2 additions & 3 deletions .github/scripts/check_integration_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,9 @@ def validate_branch(
if event_name != "pull_request" or base_ref != "main":
return []
errors = []
if head_ref != "develop" and not head_ref.startswith("hotfix/"):
if head_ref != "develop":
errors.append(
"Pull requests to main must come from develop or hotfix/*, "
f'not "{head_ref}".'
f'Pull requests to main must come from develop, not "{head_ref}".'
)
if head_repository != repository:
errors.append(
Expand Down
9 changes: 3 additions & 6 deletions .github/scripts/test_check_integration_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@


class IntegrationPolicyTest(unittest.TestCase):
def test_main_pull_request_requires_integration_or_hotfix_branch(self):
def test_main_pull_request_requires_repository_develop_branch(self):
self.assertEqual(
MODULE.validate_branch(
"pull_request", "main", "feature", "owner/repo", "owner/repo"
),
[
"Pull requests to main must come from develop or hotfix/*, "
'not "feature".'
],
['Pull requests to main must come from develop, not "feature".'],
)
self.assertEqual(
MODULE.validate_branch(
Expand All @@ -42,7 +39,7 @@ def test_main_pull_request_requires_integration_or_hotfix_branch(self):
MODULE.validate_branch(
"pull_request", "main", "hotfix/export", "owner/repo", "owner/repo"
),
[],
['Pull requests to main must come from develop, not "hotfix/export".'],
)

def test_direct_semantic_steps_accept_only_one_public_transition(self):
Expand Down
39 changes: 18 additions & 21 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ tests, history, and details out of the public repository.
- Run the smallest source-aligned test during branch iteration. Before a branch
is ready for PR review, do not run broad changed-file or full-suite gates;
use focused fast tests for the current small step. Run `changedFast` once
when preparing `develop` or a `hotfix/*` branch for PR review. There is no
conservative local changed task; required PR CI owns complete validation.
when preparing `develop` for PR review. There is no conservative local
changed task; required PR CI owns complete validation.
The protected main-push run repeats only policy and the aggregate gate for
the exact squash commit because its tree is the already-validated PR result.
- After failure, fix and rerun the narrowest failed file, method, or suite; do not repeatedly
Expand All @@ -163,9 +163,10 @@ tests, history, and details out of the public repository.
## Git workflow

1. Inspect status and alignment before editing. Preserve unrelated user work.
Ordinary work starts on the permanent `develop` branch after fetching and
confirming that it contains `origin/main`. Never edit or commit directly on
`main`, including for documentation, CI, release preparation, and bug fixes.
All work starts on the canonical `develop` branch after fetching and
confirming that it was created from the current `origin/main`. Never edit
or commit directly on `main`, including for documentation, CI, release
preparation, emergency repairs, and bug fixes.
2. When a multi-commit migration needs a roadmap, write it in
`.agents/migration_guide.md` as a standalone `develop` checkpoint before
implementation. Do not add a future-state migration page under `docs/`.
Expand All @@ -176,25 +177,21 @@ tests, history, and details out of the public repository.
delivery stream. Push completed checkpoints promptly. Once a
`develop -> main` PR opens, freeze `develop` until the PR is merged or
closed; do not mix later work into its moving head.
4. A production emergency may use a short-lived `hotfix/<topic>` branch
created from aligned `origin/main`. Keep it bounded to the repair, validate
it like `develop`, and merge it only through a `hotfix/* -> main` PR.
Ordinary features, maintenance, documentation, and CI work are not hotfixes.
5. Keep branch work stable with small logical purpose-based commits and focused
4. Keep branch work stable with small logical purpose-based commits and focused
validation. Prepare user docs, component versions, and structured history as
the single net change that the PR will squash into; do not accumulate
release semantics from intermediate branch commits.
6. Main accepts PRs only from the repository-owned `develop` or `hotfix/*`
branch. Run `changedFast` once before final review, inspect required PR CI,
and read only failing logs. Squash-merge with an explicit compliant subject.
7. After the merge, inspect the exact lightweight main-push policy gate once
5. Main accepts PRs only from the repository-owned `develop` branch. Run
`changedFast` once before final review, inspect required PR CI, and read only
failing logs. Squash-merge with an explicit compliant subject.
6. After the merge, inspect the exact lightweight main-push policy gate once
and complete any authorized release from that exact commit. Do not repeat
the MATLAB matrix already required on the up-to-date PR. Then fetch, merge
`origin/main` into `develop`, verify that `origin/main` is an ancestor and
that the two branch trees have no diff, and push the sync merge before new
work starts. After a hotfix sync, delete only the merged `hotfix/*` branch;
never delete `develop`.
8. Never force-push without explicit approval. Stop and report permission,
the MATLAB matrix already required on the up-to-date PR. Before deleting
`develop`, verify its PR is merged, it has no unmerged commits, and no open
PR depends on it. Delete local and remote `develop`, recreate both at the
exact new `origin/main` commit, restore branch protection, and verify
`develop == origin/main` before new work starts. Never create a sync commit.
7. Never force-push without explicit approval. Stop and report permission,
protection, review, CI, sync, or cleanup blockers rather than bypassing them.
Branch protection must require `CI Gate`, PR review flow, linear main
history, and conversation resolution for administrators as well as ordinary
Expand Down Expand Up @@ -227,7 +224,7 @@ explicit compliant squash subject; do not rely on GitHub defaults.
and release records. Do not encode versions in package, folder, file,
function, class, type, protocol, test, or current-architecture names; use one
stable semantic name and let the version contract express compatibility.
- Before a `develop` or `hotfix/*` PR is merge-ready, source changes to a
- Before a `develop` PR is merge-ready, source changes to a
versioned app/facade or launcher update its source version, owning manual,
and one structured component history record. Compare the PR base and head:
each existing component advances by exactly one direct patch, minor, or
Expand Down
2 changes: 1 addition & 1 deletion apps/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ find the exact owner and contract; App authors never invent test paths.

- Source or user-visible behavior changes update `AppVersion` and `Updated` in
the App's `definition.m`, owned documentation, and component history before
the `develop` or `hotfix/*` PR is merge-ready.
the `develop` PR is merge-ready.
- Test GUI wiring semantically: controls, choices, events, workflow outcomes,
viewport behavior, and traces. A synthetic project is validated headlessly
and launched through the native adapter; test calculations and exports
Expand Down
7 changes: 4 additions & 3 deletions docs/apps/labkit-core/launcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ tool availability, or the active maintenance operation.
| 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 | **Local Documentation** | Opens the selected app's local manual. If `site/` is missing, offers **Open Online** (the default), **Generate Local**, or **Cancel**. |
| Development and Maintenance | **Generate Local Documentation** | Rebuilds the complete ignored `site/` folder from the current Markdown and public MATLAB help. It does not open a page or choose between online and local help. |
| 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. |
Expand Down Expand Up @@ -82,8 +82,9 @@ Documentation lookup uses the discovered public App folder and the unique
path-conventional manual at `docs/apps/<family>/<app>/README.md`. It does not
require a separately maintained App catalog. The visible launcher opens online
documentation by default. Local generation is an explicit source-checkout
convenience and writes the ignored `site/` folder; the deployed site is
generated independently from `main` by GitHub Actions.
convenience: **Generate Local Documentation** always rebuilds the ignored
`site/` folder and reports completion without opening a browser. The deployed
site is generated independently from `main` by GitHub Actions.

## App Discovery

Expand Down
5 changes: 3 additions & 2 deletions docs/development/maintain-and-release/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,9 @@ folder is ignored by Git and may be deleted or regenerated at any time.
The Documentation Pages workflow performs the same build from the exact
`main` source and deploys the resulting artifact to GitHub Pages. It never
commits generated files back to `main` or `develop`. The visible Launcher opens
that online site by default. Its **Local Documentation** action offers to
generate `site/` when a selected local page is absent.
that online site by default. Its **Generate Local Documentation** maintenance
action always rebuilds the complete ignored `site/` folder from current
sources; it does not open a page or choose a reading destination.

For direct MATLAB syntax, result fields, missing-output reconstruction, and
consistency-check behavior, see
Expand Down
36 changes: 18 additions & 18 deletions docs/development/maintain-and-release/release.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,18 @@ 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 the permanent `develop` branch, or on a bounded `hotfix/*` branch created
from `origin/main`, choose each component's final version directly from the PR
base, 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 describes that direct `main baseline -> PR final`
transition. CI verifies existing App, facade, and launcher transitions before
MATLAB setup. 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.
On the canonical `develop` branch created from `origin/main`, choose each
component's final version directly from the PR base, 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
describes that direct `main baseline -> PR final` transition. CI verifies
existing App, facade, and launcher transitions before MATLAB setup. 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

Expand All @@ -55,11 +54,12 @@ 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.

After the PR merge and its exact main-push CI complete, fetch the remote and
merge `origin/main` back into `develop`. Before starting new work, verify that
`origin/main` is an ancestor of `develop` and that their trees have no diff,
then push the sync merge. A merged `hotfix/*` branch is deleted only after this
sync; the permanent `develop` branch is never deleted or force-pushed.
After the PR merge and its exact main-push CI complete, verify that `develop`
has no unmerged commits and no open PR depends on it. Delete local and remote
`develop`, recreate both at the exact new `origin/main` commit, restore its
branch protection, and verify that the two refs resolve to the same SHA before
starting new work. Do not merge main back into develop and do not create a
branch-sync commit.

```bash
gh workflow run release.yml --ref main \
Expand Down
Loading