#Design

One principle explains most of Vision's behaviour, and in particular explains the thing people hit first:

Instances are weak. Visions are strong.

An instance is a disposable rendering of a declaration. A vision, and the values on it, are the thing that actually persists. Everything below follows from that.

#Two lifetimes

The declaration, the vision and the instances A declaration is captured into a vision, which mounts into instances. Cleanup destroys the instances Vision made; Mount builds new ones. The vision and its values survive both. The declaration yours, read at Capture The vision state, persistent The instances disposable { ClassName = "Frame",   event("Word", ...),   { ClassName = "TextLabel",     Text = fromEvent("Word") } } Interface   Word()  ->  "written" Frame   TextLabel what the tree is made of strong · survives weak · thrown away and remade, if Vision made them Capture Mount Cleanup destroys them, Mount builds them again untouched by both the values are still there

Cleanup destroys the instances and keeps the vision. Mount builds a new set from the same declaration and the same values.

The instances Vision made are not the same objects after a remount - they are a fresh rendering that happens to look the same. An instance Vision only adopted is the same object every time. See ownership.

#The creation loop

The creation loop A vision alternates between staged and live. Mount builds the instances in ten ordered steps; Cleanup tears them down in six. The values survive every trip round. Staged the values exist, no instances do Live instances built and wired Capture puts you here, building nothing Mount Cleanup 1. Build - make each node's instance 2. apply fields, styles, attributes, tags 3. parent every child 4. Activate - bind handlers to the instances 5. Settle - resolve the derives 6. fire every value once, with its current value 7. Paint - drawcalls, with the viewport size 8. parent the root 9. Ready - ready callbacks, deepest first 10. Attach and Observe - connect everything 1. Forget - the scope stops holding it 2. Teardown - ready teardowns, then cleanup 3. cancel the animations it owns 4. disconnect every connection 5. destroy the instances it owns 6. Deactivate - unbind the handlers the values are never touched

Capture leaves you at the top with nothing built. Every trip down the left side makes a complete set of instances; every trip up the right side throws them away again. The vision and its values sit outside the loop entirely, which is why a Cleanup followed by a Mount is a rebuild rather than a restart.

The order on the left is worth reading once. Every instance exists before any handler runs, and signals are connected last, after ready - which is why staging a property never triggers your own PropertyChanged callback.

It also explains a number people expect to be wrong. Five writes before Mount cost no property assignments at all, because nothing is connected yet. The mount then applies the last value once per binding, exactly as it would have with no writes.

#What survives a Cleanup

Measured, not asserted. A Frame Vision made, declared with BackgroundTransparency = 0.25 and an attribute Role = "Panel", poked at by hand while mounted:

before Cleanup after Mount again
the instance itself Frame a different Frame
a declared property you overwrote by hand 0.9 0.25, back to the declaration
a property you set that the declaration never mentions false whatever a fresh source gives you
a declared attribute you overwrote by hand "Touched" "Panel", back to the declaration
an attribute you added by hand "added at runtime" gone
a declared tag there there, re-applied
a tag you added by hand there gone
a child you parented in by hand there destroyed with its parent
everything Vision connected wired to this instance re-wired to the new one
a value on the vision "written" "written"
a property driven by that value follows it follows it

Two rows deserve their footnotes.

"Whatever a fresh source gives you" is the class default for ClassName, but the template's value for fromClone. Vision does not reset the property; the instance is simply new.

And a declared property that a value also drives comes back at the value's setting, not the literal beside it. Raise writes the declared fields, then Finish re-fires every value on top. So:

{ ClassName = "Frame", BackgroundTransparency = 0.25,
  event("Fade", 0.25, function(self, _, Value)
      self.BackgroundTransparency = Value
  end) }

after Fade(0.75), a Cleanup and a Mount, reads 0.75. The declaration is re-applied, and then the values are re-applied over it.

#Why properties do not save

Because saving them would mean the instance, not the declaration, was the source of truth. Vision refuses that on purpose.

There should be two places to look, not three. The declaration says what the tree is made of; the values say what state it is in. Between them they are a complete account. If a remount also restored arbitrary properties nobody named, you would need the whole history of the instance as well.

Reading state back off an instance is lossy. Roblox properties are typed, and several coerce what you give them: integer properties truncate a float, number properties are float32 rather than Lua's float64, and some clamp to a range. State that round-trips through a property is state you may have quietly changed. A value is plain Lua and holds what you put in it.

Saving would fight the declaration. Vision knows exactly which keys the declaration names, so it could save those. But on the next mount it would have to choose between the saved value and the declared one, and there is no answer that is right for both a tweak you want to keep and a default you want back. Naming the state yourself removes the question.

A value cannot hold nil

Writing nil is how you read a value - Interface.Word() and Interface.Word(nil) are the same call. So there is no way to clear a value once it is set. Use false or a sentinel of your own for absence.

#So put state in a value

If a property should survive, it is state, and state lives on the vision:

local Card = Scope:Capture({
    ClassName = "TextLabel",
    Text = fromEvent("Word", "hello"),

    mount(PlayerGui),
})

Card:Mount()
Card.Word("written")     -- state, on the vision

Card:Cleanup()           -- instances destroyed
Card:Mount()             -- Text is "written" again

Writing the property directly does not survive:

local Plain = Scope:Capture({
    ClassName = "TextLabel",
    Text = "hello",

    mount(PlayerGui),
})

Plain:Mount()
Plain:Open().Text = "written"    -- a property on a disposable object

Plain:Cleanup()
Plain:Mount()                    -- Text is "hello" again

Both look like they do the same thing. Only one of them is state.

The rule of thumb

If losing it on a remount would be a bug, it is a value. If it is presentation that the declaration already describes, leave it a property.

#Ownership decides destruction

Cleanup destroys the instances Vision owns, and because Destroy is recursive, everything inside them. Ownership is a property of a subtree root, not of each instance: anything sitting under an owned instance goes with it, whether Vision made it or not.

how the node got its instance owned Cleanup does
ClassName = "Frame" yes destroys it, and everything under it
fromClone(Template) yes destroys the copy, never the template
fromInstance(Existing) no disconnects, and leaves the instance standing
FromParent = "Child" no never destroys it directly, but it dies with its host if the host is owned

Ownership is decided per node, and is not inherited downward either. A ClassName or fromClone child declared inside a FromParent or fromInstance node is still something Vision made, so Cleanup destroys it, at any depth:

Scope:Capture({
    fromInstance(Shell),             -- kept

    {
        FromParent = "Slot",         -- kept, it was already there

        { ClassName = "UICorner" },  -- destroyed, Vision made it
        { fromClone(Template) },     -- destroyed, Vision made the copy
    },
})

The line the rule draws is not "did Vision touch this" but "did Vision make this" - with the caveat that a Destroy arriving from above takes everything underneath it regardless.

#Adoption is not restoration

An adopted instance is the exception that proves the principle. Vision did not make it, so this vision will not destroy it, and its properties survive a Cleanup:

local Kept = Scope:Capture({
    fromInstance(Guest),
    BackgroundTransparency = 0.5,
})

Kept:Mount()
Guest.BackgroundTransparency = 0.8

Kept:Cleanup()          -- Guest still exists, still 0.8
Kept:Mount()            -- 0.5 again, the declaration reapplies

Note the last line. Adoption saves the instance from destruction; it does not make the declaration stop being the source of truth. Mounting reapplies every declared property over whatever was there.

Two things Cleanup does not do for an adopted instance:

It is also still an ordinary instance. If it lives inside something another vision owns, that vision's Cleanup destroys it, and the vision that adopted it will not notice.

#The same principle, elsewhere

Nothing is created until Mount. Capture creates no instances, though it does parse and validate the declaration, so mistakes surface there.

A vision can be cloned; the copy is a second rendering, not a second object. Clone re-reads the same declaration table and copies the values across. It shares that table with the original rather than snapshotting it, so mutating the table between two clones changes what the second one builds. A vision that adopts with fromInstance refuses to clone at all - there is only one of that instance, which is what fromClone is for. See sleeping and cloning.

A guest holds its host, never the reverse. Under inject and receive, a host keeps no list of what was grafted into it, so cleaning a host cascades to its guests, but remounting a host brings back only the host. Declared children are different: they are part of one declaration, and a remount rebuilds the whole subtree.

Connections belong to the mount, not the vision. Everything Vision connected is disconnected by Cleanup and wired again on the next Mount, against the new instances. Your ready callbacks run again too, which is why ready hands you a teardown registrar rather than expecting you to track one.

A cleaned vision leaves its scope. Cleanup calls Scope:Forget and Mount calls Scope:Adopt, so while a vision is asleep a Scope:Update will not reach it. Writing to the value directly still works, and the write lands on the next mount.