Repository navigation
Release: SDK and kernel work, the new viewport, the editor in code mode only - #73
Merged
Merged
Conversation
…s over it features.py is a hand-curated table of 24 operations (primitives, profiles, booleans, hole, fillet, chamfer, shell, patterns, transforms) with typed slots and a template that renders the cells; a drift test checks every template against the live signatures. gui_features.py adds, edits and deletes features through the editor's own cell commands (an edit carries the code it was made from, so an agent's concurrent edit is never overwritten) and keeps an undo/redo history. Routes: GET /api/features/specs, GET /api/features, POST /api/features. Generated cells are ordinary cells (params cell + feature cell, selectors inline, a '# gui:' line to rebuild the form). Primitives and extrudes carry readable face tags; a selection recipe tries the tags first and checks the count. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…, and the protocol in the skill sca new --kind blank writes a part notebook with no cells; the first GUI feature adds the product cell that builds the Part from it, and later features that consume that body move it along. editor-collaboration.md says what a GUI cell looks like, that GUI commands never overwrite an edit made in between, and how the inline selection recipes behave. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r dialogs The Features tab lists the notebook's features (GUI-made ones editable, code ones marked), shows each one's errors, offers undo/redo and a banner when the product does not build from the tip. Operations are added from a floating command bar (category chips, keyword search, Up/Down/Enter, docked top or bottom, collapsible) fed by a command registry; each one opens a non-modal dialog generated from the operation's typed slots, with viewport picks for faces and edges and a 'use picked face' helper for holes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nt for a canvas sketch_doc.py is the document a 2D canvas edits (points, lines, circles, arcs, 24 constraint kinds, the profile that becomes the face): validation (every id and number that reaches code is checked), the sketch cell it renders to (dimensions become scad.var sliders in the params cell), and solve(): the SDK solver without recording anything, returning status, degrees of freedom, solved points, diagnostics and the profiles (about 1.3 ms for a small sketch). POST /api/sketch/solve serves it. features.py gets a face-producing 'sketch' operation and extrude/revolve/cut/boss from a sketch; the feature service keeps solids and profiles apart (tip, open bodies, defaults, the plane normal a profile extrudes along). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
assembly_doc.py is the document a GUI edits (components with placements, the grounded one, fixed/revolute/prismatic/gear/belt/ rack-pinion constraints between connector frames): validation, and three cells it renders to (params sliders for placements and joint drives, one scad.use per child, one layout cell that builds and solves the assembly in a single pass so no cell carries the whole assembly through a chain). The feature service adds, edits, deletes and undoes the three cells together and keeps the undo history valid when a cell is re-created. GET /api/assembly/parts lists the workspace's parts and, with ?path=, the connectors a part declares. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An SVG 2D editor for the sketch document: select/move, point, line, circle, arc, construction, auto horizontal/vertical, pan and zoom, snapping; a constraint toolbar that enables by the current selection; dimensions edited in place (double-click the label); live solve at every drag step with a temporary fix on the dragged point; status, degrees of freedom and diagnostics; closed loops found client-side and matched to the solver's profiles so the outer profile and the holes can be picked; plane selector. The sketch feature dialog hosts it in a wide docked panel; profile slots are selects over the notebook's sketches and the extrude direction follows the profile's plane normal. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
set_connectors replaces the list of connector frames of the part the product cell builds; the list lives in a marker line the GUI reads back, each frame becomes an add_connector_rpart call, and the first product the GUI makes is a recorded make_part_rpart so connectors can be added to it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ormal
The sketch plane is a name (XY, XZ, YZ) or a frame {origin, x_axis, y_axis} (the SDK already takes one), validated to be unit and
square; profiles report their normal vector, and the sketch-based operations take 'normal of the sketch plane' as a direction,
which is the default. This is what sketching on a picked face needs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…DK operations Trim removes the part of a line, circle or arc that contains a point, between the nearest crossings (or the ones named in against); extend lengthens a line until it meets the next entity. Both work on the solved geometry, keep the sketch connected (point_on ties, horizontal/vertical kept, dimensions that cannot hold dropped), are recorded in the operation graph and replay from model JSON. diff_sketch_rsketchdiff reports what an edit created, removed and changed. C++ in src/cpp/src/sketch/edit.cpp, bound in bind_sketch.cpp, with doctest cases, Python tests and API pages; the two operations join the canonical core op set. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…edits are the SDK's operations Sketching happens in the viewport (the camera turns to the chosen plane or face, the model stays visible and orbitable, the drawing is an SVG overlay projected through the live camera); the separate canvas panel is gone. The sketch plane can be a face frame. Trim and extend are not editor features: sketch_doc.apply_edit builds the SDK Sketch from the document, calls scad.<op>_rsketch and turns the result back; the command line (sca sketch op) and /api/sketch/op are the same thin bridge. Cut/boss/extrude from a sketch accept 'against the normal'; profiles carry a plane label; an edit that removes parameters writes the feature cell before the params cell so no cell reads a name that is gone. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Trim (T) and Extend (E) in sketch mode call /api/sketch/op, which runs the SDK's trim_rsketch / extend_rsketch; the frontend only builds the request, picks the nearer end and adopts the answer (undoable, errors shown, created entities flash). The Assembly tab edits the assembly document (components from the workspace's parts, placements, grounding, constraints with connector pickers), shows connector frames as gizmos in the viewport (clickable to fill a picker) and writes the part-side connectors from picked faces. The feature state's undo count now follows the open notebook. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nd explode_rassembly fillet_corner/chamfer_corner round or bevel the corner of two lines that share a point (tangent and radius/length constraints keep it defined); project_rsketch brings geometry from another sketch or from the edges of a shape into a sketch as fixed construction geometry, which is how sketches in different planes are related; explode_rassembly returns an assembly with the component placements displaced for an exploded view (factor, centre or axis, per-component directions and weights). C++, bound, recorded, replayable, stubbed, documented and tested like trim/extend. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…sembly document /api/sketch/op reaches fillet_corner, chamfer_corner and project (the source is another sketch feature of the notebook, named by from_sketch); the assembly document can carry an exploded view: the product becomes scad.explode_rassembly of the solved assembly and the factor is a slider variable. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… errors shown where they happen Corner tools (R, H) pick a corner or two lines and ask for the radius or distance; Project (J) brings another sketch's geometry in as muted fixed reference geometry; the Assembly panel gets an exploded-view section. All three send the request and adopt the SDK's answer. A feature that fails to run keeps its dialog open with the error, and the next submit edits it instead of adding a duplicate. The feature dialog docks below the Inspector header. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… by its code Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…r components Connector frames were SVG lines whose screen directions were normalised to a fixed length, which destroyed foreshortening when the camera rotated; they are now three.js objects in the viewer's scene (X red, Y green, Z blue with cone heads, Z longer, a translucent contact plane), depth-tested with a faint ghost pass for the hidden ones, sized from the model and following the component's node. The pieces are reusable: scene/layers (a layer host with picking, hover/selected state, apparent-size scaling, ghost materials, anchoring to a node) and components/scene (SceneLabel anchored to a 3D point, AxisTriad, useSceneLayer, axis colours). The first camera view now fits the whole bounding sphere with a margin. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tual action bar, sketch on a face Finished sketches are drawn in the viewport all the time (dim when an extrusion already uses them, an eye per sketch remembered per notebook, hover and selection linked with the tree); sketches are selectable (a SKETCH selection mode, or armed from a form) and the Extrude/Revolve/Cut/Boss forms take the selected sketch as their profile; 'Finish and extrude' finishes and opens the extrude form with the new sketch. A contextual action bar shows the commands that fit the selection (a planar face: Sketch on this face, Hole here, Shell; a sketch: Extrude, Revolve, Cut, Boss, Edit), driven by a when(selection) predicate on registry commands. The plane chooser highlights faces on hover and refuses curved ones with the reason; the cut form shows the direction as an arrow with a flip button. General pieces live in components/ (ActionBar, HintBanner, SelectionBar). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…tersect) A part holds one solid, so the operations that make a new solid (primitives, extrusions, revolutions, lofts, sweeps, and the sketch extrude/revolve) get an Operation slot and a Combine-with slot instead of leaving a second body beside the first: the solid is built as a private tool and joined to, cut from or intersected with the body so far in the same cell, the feature's role follows the operation, and the product follows. 'New body' starts the first body. Slots can be shown only for some values of another slot (show_when), and the feature state defaults the operation to join when a body exists. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…lid is made The Operation slot of a new solid is a segmented control (New body only when there is no body yet), the Combine-with slot shows as a line when one body is open, slots can be shown only for some values of another slot, and the stand-alone cut/union/intersect and sketch cut/boss commands are hidden unless several bodies are open: a selected sketch offers Extrude (join) and Extrude (cut) as presets of the one Extrude dialog. The chooser and its keyboard logic are general components. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…les); the editor draws the SDK's loops instead of finding its own The canvas found bounded faces of the line/arc graph itself and matched them to the solver's profiles by area and centroid; the SDK offers only simple loops and chains, so a branched or tangent-trimmed sketch showed loops the SDK did not have and 'the profile is still being matched' never cleared. The solve now returns the SDK's own list (indices are the ones make_face_from_sketch_rface takes) plus why an edge set is none, and the canvas draws exactly those. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The viewport camera keeps its class (trackball, occlusion pass, layers hold it); its projection matrix is swapped while a sketch is open, framed so the target plane keeps its size. Pick rays, apparent-size scaling and the sketch plane's own ray follow; ambient occlusion is off while orthographic. The 3D view stays perspective. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The resizable panels default to overflow:auto; a plane polygon or label projected far outside the viewport made scrollbars appear, which resized the canvas, which moved the overlay again (a flashing loop around the plane chooser). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…draggable operation cards, sketch crossings, join lines end to end Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ee vector Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e panels slide out and in Manual mode: features and assemblies, the command bar and the selection bar. Code mode: notebook and workspace (the original design). The two are never on one page; both side panels leave to the left and right with an ease-in and the other mode's arrive with a decelerating curve. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e), no sketch in between Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… radius, sweep); typed lengths and radii stay as dimensions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… body is the Part The header records the kind when the notebook is made. find_product (the one runtime function the headless run and the editor kernel share) falls back to the open body: a public top-level solid no cell reads and no other candidate was built from (the operation graph says so). Several open bodies are an error naming them. An explicit make_part_rpart cell still wins (connectors, a body other than the tip). The editor no longer writes a product cell for such notebooks and shows the implicit product. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…planes, curves, tag splits, a partial preview, a placer or drawing in progress, the active workplane, sketch picks) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… again by the kernel, POST /api/preview/refine, until its chords are under half a pixel; same keys and entities, highlights rebuilt), parts several nodes draw are instanced (faces per colour, edges batched; the nodes stay for picks and visibility), picks go through BVHs (three-mesh-bvh, indirect: the face and edge ranges keep their order), the axis gizmo composited by the output node from its own transparent target Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…etric cylinder is --kind sample); an empty notebook is where it starts, told as info, not a ProductNotFoundError; an operation or drawing started with no notebook open asks for one first and creates it in place Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…loat over it (browser top left and collapsible, stepping aside for a task's own panel; inspector under the axis gizmo, collapsible; selection kinds top centre; the command bar stays the floating search bar and docks under them); code mode keeps its columns and the viewport is never remounted; rows of buttons and tabs scroll sideways (scroll-row, wheel too) instead of wrapping Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y, zh-CN.json maps it; the browser's language unless chosen in the header, remembered); the chrome, notices, feature dialog, command bar and the operation catalog (names, groups, fields, options, descriptions) translated, a Chinese query finds operations by their Chinese names; a test fails when a literal key has no Chinese entry. Not yet: the assembly panel, sketch mode and the selection hints Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f a dialog when the selection is all it needs: it is added at once with small defaults (fillet and chamfer 0.5 mm), its numbers float in a panel by the selection and every change rebuilds the model (a failure shows in the panel, the log stays shut), Enter keeps it (the numbers are written into the notebook), Esc takes it back (undone; an edited feature gets its numbers back), the dialog has every option; a feature of the tree is tuned in place the same way. The catalog and SDK index are revalidated (no-cache with an ETag) so an updated editor never serves an old catalog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d (the face splits into its inner face and ring, roles inset.inner / inset.ring, tracked as fragments, replayable); the editor's Inset on a picked flat face, tuned in place like every operation, so inset then pull builds steps and bosses without a sketch; the operation panel's Enter handler no longer assumes its target is an element Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rism joined) or presses it in (cut), recorded as its extrude and boolean steps; the editor's Press / pull on a picked flat face replaces Pull face / Push in on the selection bar: an arrow on the face along its normal drags the distance (0.5 mm snap, Shift free) while the panel types it, across zero; the face's frame comes from its triangles on screen, at once Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the mode switch on top of it, run all there in code mode; only its content slides on a mode change), the inspector and the selection kinds float over the viewport in both modes, the header keeps the notebook, status, theme and language (undo and redo are the feature tree's) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ls side by side on one strip, no gap; to code it moves left, to manual right); the command bar sits at the bottom only, sliding up into manual mode and down out of it (docking to the top removed) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… operations' icons in the feature tree - the XY grid is drawn per pixel (scene/ground-grid.ts): power-of-ten spacings fading into each other with the zoom, the coarser level strong, fading out a few camera distances from the target; the X, Y and Z axes are red, green and blue lines through the origin as far as the camera sees - the axis gizmo: a click on an end of an axis turns the view to look from that side (slerped about the target), the arrows in the band around it turn the view by 45° about the screen's axes, and a button beside it switches between perspective and parallel projection (remembered in the browser; the sketch canvas keeps its own and gives the chosen one back) - the floating inspector and the operation panel fold into a one-line pill and back by growing or shrinking (lib/useSizeTransition.ts); a cell folds and unfolds by its height - the feature tree shows each operation's icon (a code feature: a code icon); the sketch, revolve, curve and assembly operations have theirs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…straints), the drawer (status, constraint list, diagnostics, profile), the plane chooser, the bar (name, finish, face/wire), typed entry, value and corner boxes, project, the hints and edit notes of the sketch editor, the sketch rows of the inspector and the name check; a test checks that the keys held in the sketch tables (tool names, constraint labels and shorts, typed fields, solver states) have their Chinese entries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…find_product raises NoProductYetError (a ProductNotFoundError, still raised where a product is needed: export, a headless run with a product) and the editor previews it as an empty model ("no solid yet" in the log) instead of reporting ProductNotFoundError; the editor server's own empty-notebook case is gone (the kernel's empty preview covers it), and a notebook's first model is framed when it has one
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he cup is there at once with a 1 mm wall; an arrow on the opened face drags the thickness into the body (at least 0.1 mm), the panel lists the openings and takes one out, a click on a face of the model (the shelled body itself) opens it too; Esc undoes the feature with every opening added or taken out meanwhile. Fillet and chamfer take more edges the same way (any operation whose one selection takes several picks) Server: the feature list carries what each selection slot holds as references (gui.refs), an edit keeps the references it is given for items with no entity on screen (a face a shell opened is not drawn), and a reference given twice is selected once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…selection mode): hovered and picked on screen (within a few pixels of the drawn line), drawn wider and in the selection colour, shown in the inspector (kind, edges, length); a picked curve offers Sweep (op.sweep_rsolid with the curve as its path; a sketch picked with it is the profile, and with both given it starts at once), the selection bar hangs at the curve's middle The kernel reports the kind of every top-level value (live_value_kinds) and the feature list uses it for cells written as code: a path or a face made in code is no longer taken for a body, so it is not the product and it is offered where a wire or a face is asked for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rve feature per line, arc or spline (path, path_2 ...), each starting at the end of the one before (a point `end_of` another curve: `path.get_end_vertex().get_coordinates()`, so it follows that curve), a path closed on its start ends at the first curve's start, and a point snapped to an end of another curve is kept at it; all of them are added or none. An older curve cell of several segments is still one wire. The feature list takes each value's kind from the kernel for every feature (a curve is an edge, a sketch a face or a wire) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n, a face boundary, a binormal guide): the wire of that one edge, made by the recorded make_wire_from_edges_rwire so a graph sees where it came from (as_wire, edges_as_wires; Wire(edge) and an implicit Edge -> Wire conversion in Python). make_wire_from_edges_rwire takes its edges in any order (they are chained by their ends first, then added in turn so each keeps its identity) and an edge that touches none of the others is an error instead of being left out. loft_rshell of open sections (the surface between two curves) no longer fails looking for end wires: only a closed end section is a boundary loop of its own Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ake_wire_from_edges_rwire of the picked curves, in any order), and every form slot that asks for a wire takes a curve (an edge) too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…faces; recorded with its pieces, replayed the same), a TypeError for anything else Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- the face and shell values no later cell reads are drawn with the model (node/surface/<name>: light blue, seen from both sides; a sketch's face stays the sketch layer's) and picked whole in the "sketch / curve / surface" mode, highlighted and shown in the inspector - picked curves offer Ruled surface (two), Loft surface, Fill (a closed loop: the `fill` feature, make_surface_patch_rface), Flat face (a flat loop: `flat_face`, make_face_from_wire_rface with the loop's normal), Network surface (two families of crossing curves, found from where they cross: Gordon) besides Sweep and Join into a wire (lib/curve-net.ts finds loops, their plane and the families) - picked surfaces offer Sew (`sew`: sew_faces_rshell of faces and shells) and a shell Close into a solid (`solid_from_shell`), which becomes the part's body; each starts at once with its values filled from the picks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… exploded view, its checks and kind names), the selection hint under the command bar, the part count, and the log's own lines (added, created, exported, a failed or slow cell, re-evaluating); the coverage test also checks the context actions' words, the constraint kinds and the radius fields Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…) instead of path every time Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…off (MANUAL_MODE in lib/manual-mode.ts: no mode switch, command bar, manual keys or sketch/curve pick kind), its code stays; the viewport's renderer, level of detail, instancing and BVH picking stay Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nvert handle<Geom2d_Line> / handle<Geom2d_Circle> to handle<Geom2d_Curve> implicitly: the Linux builds failed); clang-format 18 on the five files it listed Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…n where a wire was refused is taken as its wire and fails on being open or disconnected (face_from_wire, face_from_wires, wire_from_edges: only the error fields that differ); the type stubs regenerated (import order) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rewritten to 148 lines each (was 769 and 650): three pillars (efficient, low-overhead, agent-native), two comparison charts (against CadQuery on 200 parts; tokens per task against a mature industrial CAD through its MCP; English and Chinese, drawn by tools/make_readme_charts.py), the two six-axis arms in the new editor, the skill (seven routes and how it is organised) and the plugin system. The update notes live in CHANGELOG.md and docs/updates. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Nov 9, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
131 commits ahead of
dev(branched fromfeat/gui-modeling), cut for a release while manual modelling gets a proper plan.scad.ref/ql.refstable references, tags and history through splits, multi-body parts, body appearance,press_pull_rsolid,inset_face_rsolid, edges taken wherever a wire is,sew_faces_rshellon faces and shells, the typed operation catalog.POST /api/preview/refine, no package in between), instanced parts, BVH picking, smooth shading from exact surface normals, the ground grid and view controls.MANUAL_MODE = falseineditor-web/src/lib/manual-mode.ts: no mode switch, no command bar, none of/,W, Space, Ctrl+Z on features, no sketch/curve pick kind; the notebook is where a model is made. Nothing of manual mode is deleted, so flipping the constant brings it back for the planning work.Checked
tsc,vitest(331 passed),vite build.selected_iteminto the notebook, zooming in calls/api/preview/refine, the model renders.🤖 Generated with Claude Code