refactor: tighten nitro editor JSON typing with generic path helpers

This commit is contained in:
openhands committed 2026-08-09 12:38:30 +02:00
1 parent 6549ae1959
commit ca49c6b5a8
1 file changed
+12 -20
@@ -47,8 +47,7 @@ interface NitroVisualization {
} }
// The .nitro metadata is untyped JSON from disk; typed only for the fields the // The .nitro metadata is untyped JSON from disk; typed only for the fields the
// editor reads/writes, with an index signature so the generic path helpers can // editor reads/writes. Unknown keys are traversed via the path helpers below.
// traverse arbitrary nested keys.
interface NitroMetadata { interface NitroMetadata {
visualizationType?: string; visualizationType?: string;
logicType?: string; logicType?: string;
@@ -59,19 +58,18 @@ interface NitroMetadata {
}; };
}; };
visualizations?: NitroVisualization[]; visualizations?: NitroVisualization[];
[key: string]: unknown;
} }
type JsonObject = Record<string, unknown>; type JsonObject = Record<string, unknown>;
function updateNested<T extends JsonObject>( function updateNested<T extends object>(
obj: T, obj: T,
path: string, path: string,
value: unknown, value: unknown,
): T { ): T {
const clone: T = JSON.parse(JSON.stringify(obj)); const clone: T = JSON.parse(JSON.stringify(obj));
const keys = path.split("."); const keys = path.split(".");
let current: JsonObject = clone; let current: JsonObject = clone as JsonObject;
for (let i = 0; i < keys.length - 1; i++) { for (let i = 0; i < keys.length - 1; i++) {
const next = current[keys[i]]; const next = current[keys[i]];
if (!next || typeof next !== "object" || Array.isArray(next)) { if (!next || typeof next !== "object" || Array.isArray(next)) {
@@ -83,14 +81,14 @@ function updateNested<T extends JsonObject>(
return clone; return clone;
} }
function getNested(obj: unknown, path: string, fallback?: unknown): unknown { function getNested<T>(obj: unknown, path: string, fallback: T): T {
const keys = path.split("."); const keys = path.split(".");
let current: unknown = obj; let current: unknown = obj;
for (const key of keys) { for (const key of keys) {
if (current == null || typeof current !== "object") return fallback; if (current == null || typeof current !== "object") return fallback;
current = (current as JsonObject)[key]; current = (current as JsonObject)[key];
} }
return current ?? fallback; return current === undefined ? fallback : (current as T);
} }
const ALL_DIRECTIONS = [0, 1, 2, 3, 4, 5, 6, 7]; const ALL_DIRECTIONS = [0, 1, 2, 3, 4, 5, 6, 7];
@@ -255,14 +253,12 @@ export function NitroEditorDialog({
// ── Visual tab helpers ── // ── Visual tab helpers ──
const dims = ( const dims = metadata
metadata ? getNested(metadata, "logic.model.dimensions", { x: 1, y: 1, z: 0 })
? getNested(metadata, "logic.model.dimensions", { x: 1, y: 1, z: 0 }) : { x: 1, y: 1, z: 0 };
: { x: 1, y: 1, z: 0 } const directions = metadata
) as { x: number; y: number; z: number }; ? getNested(metadata, "logic.model.directions", [0])
const directions = ( : [0];
metadata ? getNested(metadata, "logic.model.directions", [0]) : [0]
) as number[];
const setDim = (axis: "x" | "y" | "z", value: string) => { const setDim = (axis: "x" | "y" | "z", value: string) => {
const num = const num =
@@ -271,11 +267,7 @@ export function NitroEditorDialog({
}; };
const toggleDirection = (dir: number) => { const toggleDirection = (dir: number) => {
const current = getNested( const current = getNested(metadata, "logic.model.directions", [0]);
metadata,
"logic.model.directions",
[0],
) as number[];
const next = current.includes(dir) const next = current.includes(dir)
? current.filter((d: number) => d !== dir) ? current.filter((d: number) => d !== dir)
: [...current, dir].sort((a: number, b: number) => a - b); : [...current, dir].sort((a: number, b: number) => a - b);