Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9122d4c
Add alternate code editor layouts and direct navigation
kube Sep 11, 2026
f988705
Keep code editing in the Properties Panel and full screen
kube Sep 15, 2026
78db835
Preserve property sizes and review-mode code navigation
kube Sep 15, 2026
0e7d43f
Reset code editor layout and retained models when scope changes
kube Sep 15, 2026
ccbafff
Expand code sections within the Properties Panel
kube Sep 15, 2026
fa6c590
Document the section header return action
kube Sep 15, 2026
e2d962c
Animate SubView expansion and add parent breadcrumbs
kube Sep 16, 2026
f5df345
Keep SubView breadcrumbs legible and focusable
kube Sep 16, 2026
3506243
Include expanded code sections in navigation history
kube Sep 16, 2026
94238f1
Hold code section bounds until restoration completes
kube Sep 16, 2026
89406f6
Align SubView breadcrumbs and preserve nested titles
kube Sep 16, 2026
1ccf7da
Make Canvas and Notebook views of Edit
kube Sep 16, 2026
a5ef2f7
Float the Edit view selector and rename Notebook to Definitions
kube Sep 16, 2026
f7f8f47
Make the Edit view selector smaller and fade it until interaction
kube Sep 16, 2026
40d263b
Allow experiment results to render inline
kube Sep 15, 2026
c0adb6f
Add routed simulation panels with fullscreen controls
kube Sep 15, 2026
06828b7
Improve simulation panel resizing and list selection
kube Sep 15, 2026
139f4d6
Keep simulation tabs visible in fullscreen views
kube Sep 15, 2026
bbfb253
Remove the doubled border from fullscreen panels
kube Sep 16, 2026
431d786
Preserve simulation tabs with Edit views
kube Sep 16, 2026
8ec4964
Check fullscreen exits through Edit views
kube Sep 16, 2026
01fcfc1
Follow Definitions navigation in fullscreen tests
kube Sep 16, 2026
9bd90ad
Keep ad-hoc value errors visible while editing
kube Sep 15, 2026
1bbfe17
Polish scenario forms, keyboard navigation and computed values
kube Sep 15, 2026
75f70b7
Migrate bundled examples to ad-hoc scenarios
kube Sep 16, 2026
6ab1b0e
Align scenario controls and place token totals
kube Sep 16, 2026
ce008ef
Explain invalid sweep intervals beside experiment submission
kube Sep 15, 2026
53ec8d8
Simplify experiment metrics, objectives and constraint editors
kube Sep 15, 2026
7f9281d
Preserve native objective selection inside form focus groups
kube Sep 15, 2026
88b768b
Correct experiment guide heading levels
kube Sep 15, 2026
372d133
Fix metric selection and compact objective controls
kube Sep 16, 2026
a16ad0c
Show saved metric names as read-only text
kube Sep 16, 2026
1384959
Order sensitivity parameters by descending share
kube Sep 15, 2026
b78e19b
Simplify experiment result headers and summaries
kube Sep 15, 2026
9c2878d
Simplify experiment parameter sliders
kube Sep 16, 2026
41a2158
Show bounds below experiment sliders
kube Sep 16, 2026
240e4c2
Make sweep optimization an explicit choice
kube Sep 16, 2026
37d9b6d
Make experiment capabilities available without feature flags
kube Sep 16, 2026
a3ca06f
Group experiment constraints by what they check
kube Sep 16, 2026
9b0862e
Accept expressions in state constraints
kube Sep 16, 2026
f4af272
Regenerate state constraint schema documentation
kube Sep 16, 2026
c931c14
Tighten and label place-property checkboxes
kube Sep 15, 2026
1238d51
Polish properties panel controls and navigation
kube Sep 15, 2026
bb1a760
Refine place options and property previews
kube Sep 16, 2026
2c9ec87
Explain saved preferences consistently across settings
kube Sep 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ad-hoc-value-errors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut": patch
---

Improve scenario and experiment forms with keyboard navigation, stacked section headers, overlay scrollbars, default starting-place filtering with a compact toggle beside the section title, and source expressions over selected computed values. Align the Time Step label and info icon, align token totals with place headers, keep select chevrons inside spreadsheet cells, and keep validation errors visible while editing ad-hoc values.
5 changes: 5 additions & 0 deletions .changeset/clear-code-editor-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut-core": patch
---

Make the code editor guide available to the in-app assistant.
5 changes: 5 additions & 0 deletions .changeset/editable-example-scenarios.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut-core": patch
---

Make bundled example scenarios editable in the ad-hoc scenario form while preserving their parameters and initial states.
5 changes: 5 additions & 0 deletions .changeset/experiment-interval-feedback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut": patch
---

Offer GPU compute, parameter sweeps, and in-browser optimization without feature flags, with automatic metric names, grouped constraints that accept state expressions, keyboard controls, and explicit optimizer startup. Simplify results with manual optimizer controls, compact details, single-value parameter sliders, and sensitivity estimates ordered by importance.
5 changes: 5 additions & 0 deletions .changeset/experiment-settings-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut-core": patch
---

Accept state constraint expressions without an explicit return while preserving existing code bodies. Update the assistant's guide summaries for experiment controls and settings available without simulation feature flags.
5 changes: 5 additions & 0 deletions .changeset/property-checkbox-spacing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut": patch
---

Polish properties panels with readable, clickable checkbox labels, animated capacity options, a single equation link, consistent fields, and clearer section controls and dividers.
5 changes: 5 additions & 0 deletions .changeset/routed-simulation-panels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut": patch
---

Add resizable, routed Experiment and Scenario panels with keyboard selection and fullscreen views that keep the tabs and AI assistant accessible.
5 changes: 5 additions & 0 deletions .changeset/simulation-panel-guidance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut-core": patch
---

Add simulation panel guidance to the AI assistant's documentation catalog.
5 changes: 5 additions & 0 deletions .changeset/tidy-code-editor-layouts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut": patch
---

Expand code sections to fill the Properties Panel with smooth transitions, a parent breadcrumb, and browser history navigation. Make Canvas and Definitions available as Edit views with browser history navigation.
4 changes: 1 addition & 3 deletions apps/petrinaut-website/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,7 @@ use provider-pattern discovery instead.
The demo at [http://localhost:5173](http://localhost:5173) runs the optimizer
in the browser: the Optuna study runs in a Pyodide web worker and each
optimization step runs on Petrinaut's own experiments backend, so no Python
service is involved. With the experimental **Parameter sweeps** and
**In-browser optimization** settings on, under **Viewport controls > Settings >
Simulation**, a sweep's Parameters card in the Experiments tab offers
service is involved. A sweep's Parameters card in the Experiments tab offers
**Optimize**. The first optimization in a browser downloads the Python runtime
from jsDelivr and Optuna from PyPI; later runs use the browser cache.

Expand Down
9 changes: 6 additions & 3 deletions apps/petrinaut-website/scripts/generate-example-artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,12 @@ const generateRuntime = async (entry: ExampleCatalogEntry) => {
null,
) as Record<string, ScenarioHir>;
for (const scenario of definition.scenarios ?? []) {
const hir = lowerScenarioToHir({
parameterOverrides: scenario.parameterOverrides,
initialState: scenario.initialState,
const hir = lowerScenarioToHir(scenario, {
adHocContext: {
places: definition.places,
types: definition.types,
netParameters: definition.parameters,
},
});
const outcome = compileScenario(
scenario,
Expand Down
11 changes: 11 additions & 0 deletions apps/petrinaut-website/src/examples/catalog.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { describe, expect, it } from "vitest";

import { compileScenario } from "@hashintel/petrinaut-core";

import {
exampleCatalog,
exampleSlugs,
Expand Down Expand Up @@ -34,7 +36,16 @@ describe("example catalog", () => {
expect(example.catalog).toBe(entry);

for (const scenario of example.definition.scenarios ?? []) {
expect(scenario.initialState.type).toBe("adhoc");
expect(runtime.scenarioHirById).toHaveProperty(scenario.id);
const compiled = compileScenario(
scenario,
runtime.scenarioHirById[scenario.id]!,
example.definition.parameters,
example.definition.places,
example.definition.types,
);
expect(compiled.ok, JSON.stringify(compiled)).toBe(true);

for (const parameter of scenario.scenarioParameters) {
const bounds = entry.parameterBounds[parameter.identifier];
Expand Down
67 changes: 67 additions & 0 deletions apps/petrinaut-website/src/examples/example-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,67 @@ import {
} from "./example-search";

describe("example search contract", () => {
it("carries expanded sections as complete panel and section pairs", () => {
const expanded = {
expandedPanel: "transition-properties",
expandedSection: "transition-results",
};
expect(validateSharedExampleSearch(expanded)).toMatchObject(expanded);
expect(canonicalSearchString(expanded)).toBe(
"expandedPanel=transition-properties&expandedSection=transition-results",
);
for (const input of [
{ expandedPanel: "transition-properties" },
{ expandedSection: "transition-results" },
{ expandedPanel: 42, expandedSection: "transition-results" },
{ expandedPanel: "transition-properties", expandedSection: "" },
]) {
const search = validateSharedExampleSearch(input);
expect(search.expandedPanel).toBeUndefined();
expect(search.expandedSection).toBeUndefined();
}
});

it("validates complete resource links and fullscreen creation links", () => {
const resource = {
resourceType: "scenario",
resourceId: "scenario / one",
presentation: "fullscreen",
};
const validated = validateSharedExampleSearch(resource);
expect(validated).toMatchObject(resource);
expect(canonicalSearchString(validated)).toBe(
"presentation=fullscreen&resourceId=scenario+%2F+one&resourceType=scenario",
);
expect(
validateSharedExampleSearch({
overlay: "create-experiment",
presentation: "fullscreen",
}).presentation,
).toBe("fullscreen");
for (const invalid of [
{ resourceType: "unknown", resourceId: "one" },
{ resourceType: "scenario" },
{ resourceType: "experiment", resourceId: "" },
{ resourceId: "one" },
]) {
const search = validateSharedExampleSearch({
...invalid,
presentation: "fullscreen",
});
expect(search.resourceType).toBeUndefined();
expect(search.resourceId).toBeUndefined();
expect(search.presentation).toBeUndefined();
}
expect(
validateSharedExampleSearch({
resourceType: "metric",
resourceId: "one",
presentation: "fullscreen",
}).presentation,
).toBeUndefined();
});

it("validates settings sections only for the user settings dialog", () => {
expect(
validateSharedExampleSearch({
Expand All @@ -21,6 +82,12 @@ describe("example search contract", () => {
settings: "unknown",
}).settings,
).toBeUndefined();
expect(
validateSharedExampleSearch({
overlay: "user-settings",
settings: "simulation",
}),
).toMatchObject({ overlay: "user-settings", settings: undefined });
expect(
validateSharedExampleSearch({
overlay: "create-experiment",
Expand Down
91 changes: 70 additions & 21 deletions apps/petrinaut-website/src/examples/example-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ import {
* `navigation-search.ts` maps each of these onto the editor's own vocabulary
* with an exhaustive switch, so a rename on either side fails to compile.
*/
export const sharedModes = ["edit", "simulate", "actual", "notebook"] as const;
export const sharedModes = ["edit", "simulate", "actual"] as const;

export const sharedEditViews = ["canvas", "definitions"] as const;

export const sharedSimulateViews = [
"scenarios",
Expand All @@ -38,13 +40,15 @@ export const sharedOverlays = [
"create-experiment",
] as const;

export const sharedSettingsSections = [
"general",
"viewport",
"simulation",
"labs",
export const sharedSettingsSections = ["general", "viewport", "labs"] as const;

export const sharedResourceTypes = [
"scenario",
"metric",
"experiment",
] as const;

export type SharedEditView = (typeof sharedEditViews)[number];
export type SharedMode = (typeof sharedModes)[number];
export type SharedSimulateView = (typeof sharedSimulateViews)[number];
export type SharedOverlay = (typeof sharedOverlays)[number];
Expand All @@ -64,9 +68,15 @@ export type SharedExampleSearch = {
itemType?: SelectionItemType;
itemId?: string;
mode?: SharedMode;
editView?: SharedEditView;
view?: SharedSimulateView;
overlay?: SharedOverlay;
settings?: (typeof sharedSettingsSections)[number];
expandedPanel?: string;
expandedSection?: string;
resourceType?: (typeof sharedResourceTypes)[number];
resourceId?: string;
presentation?: "fullscreen";
};

/** The keys this contract owns. Anything else in a URL is foreign. */
Expand All @@ -76,9 +86,15 @@ const sharedSearchKeys = [
"itemType",
"itemId",
"mode",
"editView",
"view",
"overlay",
"settings",
"expandedPanel",
"expandedSection",
"resourceType",
"resourceId",
"presentation",
] as const satisfies readonly (keyof SharedExampleSearch)[];

// `.catch(undefined)` is the contract's whole validation story: anything a URL
Expand All @@ -90,6 +106,7 @@ const optionalSelectionItemType = z
.optional()
.catch(undefined);

const optionalEditView = z.enum(sharedEditViews).optional().catch(undefined);
const optionalMode = z.enum(sharedModes).optional().catch(undefined);
const optionalSimulateView = z
.enum(sharedSimulateViews)
Expand Down Expand Up @@ -121,22 +138,54 @@ export const selectionToSearch = (
*/
export const validateSharedExampleSearch = (
input: Record<string, unknown>,
): SharedExampleSearch => ({
scenario: optionalNonEmptyString.parse(input.scenario),
subnet: optionalNonEmptyString.parse(input.subnet),
mode: optionalMode.parse(input.mode),
view: optionalSimulateView.parse(input.view),
overlay: optionalOverlay.parse(input.overlay),
settings:
input.overlay === "user-settings"
? z
.enum(sharedSettingsSections)
.optional()
.catch(undefined)
.parse(input.settings)
): SharedExampleSearch => {
const resourceType = z
.enum(sharedResourceTypes)
.optional()
.catch(undefined)
.parse(input.resourceType);
const resourceId = optionalNonEmptyString.parse(input.resourceId);
const hasResource = resourceType !== undefined && resourceId !== undefined;
const canExpand =
(hasResource && resourceType !== "metric") ||
input.overlay === "create-scenario" ||
input.overlay === "create-experiment";

return {
scenario: optionalNonEmptyString.parse(input.scenario),
subnet: optionalNonEmptyString.parse(input.subnet),
mode: input.mode === "notebook" ? "edit" : optionalMode.parse(input.mode),
editView:
input.mode === "notebook"
? "definitions"
: optionalEditView.parse(
input.editView === "notebook" ? "definitions" : input.editView,
),
view: optionalSimulateView.parse(input.view),
overlay: optionalOverlay.parse(input.overlay),
expandedPanel: optionalNonEmptyString.parse(input.expandedSection)
? optionalNonEmptyString.parse(input.expandedPanel)
: undefined,
...selectionToSearch(selectionFromInput(input)),
});
expandedSection: optionalNonEmptyString.parse(input.expandedPanel)
? optionalNonEmptyString.parse(input.expandedSection)
: undefined,
settings:
input.overlay === "user-settings"
? z
.enum(sharedSettingsSections)
.optional()
.catch(undefined)
.parse(input.settings)
: undefined,
...selectionToSearch(selectionFromInput(input)),
resourceType: hasResource ? resourceType : undefined,
resourceId: hasResource ? resourceId : undefined,
presentation:
canExpand && input.presentation === "fullscreen"
? "fullscreen"
: undefined,
};
};

/** Canonical query string for a validated search: sorted, contract keys only. */
export const canonicalSearchString = (search: SharedExampleSearch): string => {
Expand Down
Loading
Loading