← Recipe: build a small Constellation Orbit

3. Replace src/main.luau

luastra/ui · semantic spatial navigation · about 25 minutes
constellations → nodes → Focus Surface

One small state machine controls active depth and focused leaf; every visual placement decision remains host-owned.

When to use it

Replace the complete entry module. Keep the component IDs stable: relationships, selection, focus restoration, tests, and future routes all depend on semantic identity.

Focused example

--!strict

local UI = require("luastra/ui")

local App = require("luastra/app")
local Root = {}
local depth = "root"
local visitedBuild = false
local selected: string? = nil

local details = {
    ["orbit/learn"] = "Learn the semantic model before adding host capabilities.",
    ["orbit/ship"] = "Verify each target separately before making a platform claim.",
    ["orbit/build/interface"] = "Compose meaning in Luau and let the host place it.",
    ["orbit/build/adaptive"] = "The same nodes become a readable list when space is constrained.",
    ["orbit/build/accessible"] = "Keyboard, pointer, and assistive technology reach the same actions.",
}

local function titleFor(id: string?): string
    if id == "orbit/learn" then return "Learn" end
    if id == "orbit/ship" then return "Ship" end
    if id == "orbit/build/interface" then return "Interface" end
    if id == "orbit/build/adaptive" then return "Adaptive" end
    if id == "orbit/build/accessible" then return "Accessible" end
    return "Node details"
end

function Root.render(): UI.Node
    local focusTitle = titleFor(selected)
    local children: {UI.Node} = {
        UI.OrbitPath {
            id = "orbit/path",
            label = "Orbit path",
            UI.OrbitReturn {
                id = "orbit/path/root",
                text = "Luastra",
                onTap = "return-root",
                disabled = depth == "root",
            },
            UI.Text {
                id = "orbit/path/current",
                text = depth == "root" and "Home" or "Build",
            },
        },
        UI.Constellation {
            id = "orbit/root",
            label = "Luastra concepts",
            depth = 0,
            layerState = depth == "root" and "active" or "behind",
            UI.OrbitCenter {
                id = "orbit/root/center",
                title = "Luastra",
                description = "Build apps like games.",
            },
            UI.OrbitNode {
                id = "orbit/build",
                title = "Build",
                description = "Enter the next constellation.",
                nodeKind = "constellation",
                priority = 1,
                signalIcon = "compass",
                relatedTo = { "orbit/learn" },
                onTap = "open-build",
            },
            UI.OrbitNode {
                id = "orbit/learn",
                title = "Learn",
                description = "Understand the semantic model.",
                priority = 1,
                signalIcon = "book",
                status = "Guide",
                selected = selected == "orbit/learn",
                relatedTo = { "orbit/build", "orbit/ship" },
                onTap = "open-focus",
            },
            UI.OrbitNode {
                id = "orbit/ship",
                title = "Ship",
                description = "Verify web, desktop, and mobile.",
                priority = 2,
                signalIcon = "rocket",
                selected = selected == "orbit/ship",
                relatedTo = { "orbit/learn" },
                onTap = "open-focus",
            },
        },
    }

    if visitedBuild then
        table.insert(children, UI.Constellation {
            id = "orbit/build-space",
            label = "Build an interface",
            depth = 1,
            layerState = depth == "build" and "active" or "ahead",
            UI.OrbitCenter {
                id = "orbit/build-space/center",
                title = "Build",
                description = "Semantic, adaptive, accessible.",
            },
            UI.OrbitNode {
                id = "orbit/build/interface",
                title = "Interface",
                description = "Describe content and actions.",
                priority = 1,
                signalIcon = "spark",
                selected = selected == "orbit/build/interface",
                relatedTo = { "orbit/build/adaptive", "orbit/build/accessible" },
                onTap = "open-focus",
            },
            UI.OrbitNode {
                id = "orbit/build/adaptive",
                title = "Adaptive",
                description = "Keep every node available in a list.",
                priority = 2,
                selected = selected == "orbit/build/adaptive",
                relatedTo = { "orbit/build/interface" },
                onTap = "open-focus",
            },
            UI.OrbitNode {
                id = "orbit/build/accessible",
                title = "Accessible",
                description = "Preserve labels, focus, and actions.",
                priority = 1,
                signalIcon = "check",
                status = "Required",
                statusTone = "success",
                selected = selected == "orbit/build/accessible",
                relatedTo = { "orbit/build/interface" },
                onTap = "open-focus",
            },
        })
    end

    table.insert(children, UI.FocusSurface {
        id = "orbit/focus",
        label = focusTitle .. " details",
        open = selected ~= nil,
        onDismiss = "close-focus",
        UI.FocusHeader {
            id = "orbit/focus/header",
            UI.Text { id = "orbit/focus/title", text = focusTitle, variant = "heading" },
            UI.Button {
                id = "orbit/focus/close",
                text = "Return to the constellation",
                onTap = "close-focus",
            },
        },
        UI.Stack {
            id = "orbit/focus/content",
            gap = "md",
            UI.Text {
                id = "orbit/focus/copy",
                text = if selected == nil then "Choose a node." else details[selected],
            },
        },
    })

    return UI.Screen {
        id = "orbit-recipe",
        documentTitle = "Orbit recipe — Luastra",
        width = "wide",
        UI.Orbit {
            id = "orbit",
            label = "Luastra learning orbit",
            presentation = "auto",
            orbitTheme = "luastra",
            orbitMotion = "system",
            maxVisible = 8,
            table.unpack(children),
        },
    }
end

function Root.handle(action: string, target: string, _value: string)
    if action == "open-build" and target == "orbit/build" then
        depth = "build"
        visitedBuild = true
        selected = nil
    elseif action == "return-root" then
        depth = "root"
        selected = nil
    elseif action == "open-focus" and details[target] ~= nil then
        selected = target
    elseif action == "close-focus" then
        selected = nil
    end
end

function Root.snapshot()
    return { depth = depth, visitedBuild = visitedBuild, selected = selected }
end

return App.compose {
    render = function(_outputs) return Root.render() end,
    handle = Root.handle,
    snapshot = Root.snapshot,
}

Complete inventory

View all pages in Recipe: build a small Constellation Orbit
← Previous: 2. Replace luastra.jsonNext: 4. Replace tests/smoke.luau →