A user-authored 'spell' — a conditional rule that produces typed effects on the Anisota interface (hiding elements, switching layouts, showing a lockout/alert, redirecting, overriding a setting, and so on). Each spell is a complete, self-contained record so it can be shared by URL and 'learned' (copied) into another user's PDS without depending on the author's other records, mirroring the net.anisota.observatory.layout architecture. A spell is evaluated against a context snapshot (time, activity, player state, location, …); a spell with no conditions is a manual 'mode' that applies whenever it is cast. Whether a spell is currently cast and in what order lives in the separate net.anisota.spell.book index, not here. The effect 'value' is a heterogeneous union whose shape depends on target+type (a string, number, boolean, [start,end] pair, key list, or a config object such as a lockout/alert/sigil/setting-override), so it is left as 'unknown' and validated client-side.
Record Key
any
Any valid record key
Properties
How multiple conditions combine: 'all' (AND, the default) or 'any' (OR). Ignored when there are fewer than two conditions.
Known values: all, any
The 'if' clauses evaluated against the live context. Empty or absent means the spell has no conditions — it applies whenever it is cast (a manual 'mode' spell), or, for an interval-triggered spell, fires on every tick.
When this spell was first crafted
Optional description of what this spell does
maxLength: 500 bytes
The 'then' clauses — what the spell does when it applies. At least one is expected.
Display name for this spell
maxLength: 80 bytes
No description available.
Optional interval trigger. When present the spell fires on a timer rather than continuously; its conditions (if any) then act as a gate on whether the timed firing is allowed. Absent means the spell is condition-driven (continuous).
When this spell was last modified
View raw schema
{
"key": "any",
"type": "record",
"record": {
"type": "object",
"required": [
"name",
"effects",
"createdAt",
"updatedAt"
],
"properties": {
"name": {
"type": "string",
"maxLength": 80,
"description": "Display name for this spell"
},
"source": {
"ref": "#source",
"type": "ref"
},
"effects": {
"type": "array",
"items": {
"ref": "#effect",
"type": "ref"
},
"description": "The 'then' clauses — what the spell does when it applies. At least one is expected."
},
"trigger": {
"ref": "#trigger",
"type": "ref",
"description": "Optional interval trigger. When present the spell fires on a timer rather than continuously; its conditions (if any) then act as a gate on whether the timed firing is allowed. Absent means the spell is condition-driven (continuous)."
},
"createdAt": {
"type": "string",
"format": "datetime",
"description": "When this spell was first crafted"
},
"updatedAt": {
"type": "string",
"format": "datetime",
"description": "When this spell was last modified"
},
"conditions": {
"type": "array",
"items": {
"ref": "#condition",
"type": "ref"
},
"description": "The 'if' clauses evaluated against the live context. Empty or absent means the spell has no conditions — it applies whenever it is cast (a manual 'mode' spell), or, for an interval-triggered spell, fires on every tick."
},
"description": {
"type": "string",
"maxLength": 500,
"description": "Optional description of what this spell does"
},
"conditionLogic": {
"type": "string",
"description": "How multiple conditions combine: 'all' (AND, the default) or 'any' (OR). Ignored when there are fewer than two conditions.",
"knownValues": [
"all",
"any"
]
}
}
},
"description": "A user-authored 'spell' — a conditional rule that produces typed effects on the Anisota interface (hiding elements, switching layouts, showing a lockout/alert, redirecting, overriding a setting, and so on). Each spell is a complete, self-contained record so it can be shared by URL and 'learned' (copied) into another user's PDS without depending on the author's other records, mirroring the net.anisota.observatory.layout architecture. A spell is evaluated against a context snapshot (time, activity, player state, location, …); a spell with no conditions is a manual 'mode' that applies whenever it is cast. Whether a spell is currently cast and in what order lives in the separate net.anisota.spell.book index, not here. The effect 'value' is a heterogeneous union whose shape depends on target+type (a string, number, boolean, [start,end] pair, key list, or a config object such as a lockout/alert/sigil/setting-override), so it is left as 'unknown' and validated client-side."
}
One 'if' clause: a variable compared against a value with an operator. Composite conditions (variable 'activitySum' / 'engagementSum') additionally carry a timeframe and a set of metrics to sum. Words-seen conditions (variable 'wordsSeen') additionally carry the word set to watch for and a counting scope. Each condition also carries an undeclared 'value' property — the thing being compared against. Its shape depends on the variable's type (a number, a 'HH:MM' string, a boolean, a two-element ['HH:MM','HH:MM'] array for time 'between', an integer 0-6 for dayOfWeek, a moon-phase / route / modal / date key string, or a { uri, name } object for a feed), so it is deliberately left out of the schema and validated client-side; lexicon 'unknown' means 'any object', which would reject the scalar cases.
Properties
Composite conditions only: which metric keys to sum (e.g. 'postsRead', 'postsCreated', 'repliesSent', 'likesReceived'). See spellService COMPOSITE_METRICS.
Comparison operator. Numeric/composite use gt/gte/lt/lte/eq/neq; time uses those plus 'between'; booleans use is/isNot; enumerated types (dayOfWeek, moonPhase, route, modal, feed, handle, date) use eq/neq.
Known values: gt, gte, lt, lte, eq, neq, between, is, isNot
Words-seen ('wordsSeen') conditions only: the window the sighting count covers — 'today' (the calendar day, the default) or 'session' (since the app was opened).
Known values: today, session
Composite conditions only: the window over which the chosen metrics are summed.
Known values: today, week
The context variable to test, e.g. 'timeOfDay', 'dayOfWeek', 'currentDate', 'stamina', 'level', 'lightRemaining', 'moonPhase', 'unreadNotifications', 'sessionMinutes', 'postsReadToday', 'activitySum', 'wordsSeen', 'currentRoute', 'activeModal', 'currentFeed', 'currentProfileHandle', 'isOnExpedition'. The full catalog is defined client-side in spellService VARIABLE_GROUPS.
maxLength: 64 bytes
Words-seen ('wordsSeen') conditions only: the words to watch for, stored as trimmed lowercase strings. The condition counts posts containing any of these words as they flow through the viewer's feeds, and compares that count against 'value'.
View raw schema
{
"type": "object",
"required": [
"variable",
"operator"
],
"properties": {
"scope": {
"type": "string",
"description": "Words-seen ('wordsSeen') conditions only: the window the sighting count covers — 'today' (the calendar day, the default) or 'session' (since the app was opened).",
"knownValues": [
"today",
"session"
]
},
"words": {
"type": "array",
"items": {
"type": "string",
"maxLength": 100
},
"description": "Words-seen ('wordsSeen') conditions only: the words to watch for, stored as trimmed lowercase strings. The condition counts posts containing any of these words as they flow through the viewer's feeds, and compares that count against 'value'."
},
"metrics": {
"type": "array",
"items": {
"type": "string",
"maxLength": 64
},
"description": "Composite conditions only: which metric keys to sum (e.g. 'postsRead', 'postsCreated', 'repliesSent', 'likesReceived'). See spellService COMPOSITE_METRICS."
},
"operator": {
"type": "string",
"description": "Comparison operator. Numeric/composite use gt/gte/lt/lte/eq/neq; time uses those plus 'between'; booleans use is/isNot; enumerated types (dayOfWeek, moonPhase, route, modal, feed, handle, date) use eq/neq.",
"knownValues": [
"gt",
"gte",
"lt",
"lte",
"eq",
"neq",
"between",
"is",
"isNot"
]
},
"variable": {
"type": "string",
"maxLength": 64,
"description": "The context variable to test, e.g. 'timeOfDay', 'dayOfWeek', 'currentDate', 'stamina', 'level', 'lightRemaining', 'moonPhase', 'unreadNotifications', 'sessionMinutes', 'postsReadToday', 'activitySum', 'wordsSeen', 'currentRoute', 'activeModal', 'currentFeed', 'currentProfileHandle', 'isOnExpedition'. The full catalog is defined client-side in spellService VARIABLE_GROUPS."
},
"timeframe": {
"type": "string",
"description": "Composite conditions only: the window over which the chosen metrics are summed.",
"knownValues": [
"today",
"week"
]
}
},
"description": "One 'if' clause: a variable compared against a value with an operator. Composite conditions (variable 'activitySum' / 'engagementSum') additionally carry a timeframe and a set of metrics to sum. Words-seen conditions (variable 'wordsSeen') additionally carry the word set to watch for and a counting scope. Each condition also carries an undeclared 'value' property — the thing being compared against. Its shape depends on the variable's type (a number, a 'HH:MM' string, a boolean, a two-element ['HH:MM','HH:MM'] array for time 'between', an integer 0-6 for dayOfWeek, a moon-phase / route / modal / date key string, or a { uri, name } object for a feed), so it is deliberately left out of the schema and validated client-side; lexicon 'unknown' means 'any object', which would reject the scalar cases."
}
One 'then' clause: a typed effect on a UI surface. Continuously-applied effects flow through the live evaluation; the episodic types (showLockout, showAlert, redirect) are edge-triggered by their controllers. Each effect also carries an undeclared 'value' property — the payload, whose shape depends on target+type: an array of element/button/action/menu keys for the multi-select hide effects; a { rkey, name } layout reference or { rkey, name, keys } for hideElements; an array of word-list snapshots [{ rkey, name, words, strength, targetFeeds }] for activateList — the words are carried inline so a learned spell still works on a PDS where the referenced net.anisota.graph.wordlist record doesn't exist; or a config object for showLockout (message/scope/duration/bypass/…), showAlert (title/message/cooldownMinutes/…), redirect ({ route, cooldownMinutes }), sigilBeforePost ({ source, sigilShape }), overrideSetting ({ id, value }), or warnBefore* ({ action|element, message, gate }). toggleMode carries no value. It is left out of the schema and validated client-side because lexicon 'unknown' means 'any object', which would reject the array and string payloads.
Properties
The UI surface the effect acts on.
Known values: observatory, controlRow, layout, postCard, profileCard, footerControls, settings, wordLists, navigation, composer, interface, theme, alert
The specific effect within the target. Valid types depend on the target (see spellService EFFECT_TARGETS). hideButton/showButton/setButtons are legacy observatory effects kept for backward compatibility.
Known values: hideElements, setLayout, showLockout, hideButton, showButton, setButtons, disableAction, warnBeforeAction, hideCornerElement, warnBeforeCornerElement, hideElement, overrideSetting, activateList, hideMenuItem, redirect, sigilBeforePost, scramble, toggleMode, showAlert
View raw schema
{
"type": "object",
"required": [
"target",
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The specific effect within the target. Valid types depend on the target (see spellService EFFECT_TARGETS). hideButton/showButton/setButtons are legacy observatory effects kept for backward compatibility.",
"knownValues": [
"hideElements",
"setLayout",
"showLockout",
"hideButton",
"showButton",
"setButtons",
"disableAction",
"warnBeforeAction",
"hideCornerElement",
"warnBeforeCornerElement",
"hideElement",
"overrideSetting",
"activateList",
"hideMenuItem",
"redirect",
"sigilBeforePost",
"scramble",
"toggleMode",
"showAlert"
]
},
"target": {
"type": "string",
"description": "The UI surface the effect acts on.",
"knownValues": [
"observatory",
"controlRow",
"layout",
"postCard",
"profileCard",
"footerControls",
"settings",
"wordLists",
"navigation",
"composer",
"interface",
"theme",
"alert"
]
}
},
"description": "One 'then' clause: a typed effect on a UI surface. Continuously-applied effects flow through the live evaluation; the episodic types (showLockout, showAlert, redirect) are edge-triggered by their controllers. Each effect also carries an undeclared 'value' property — the payload, whose shape depends on target+type: an array of element/button/action/menu keys for the multi-select hide effects; a { rkey, name } layout reference or { rkey, name, keys } for hideElements; an array of word-list snapshots [{ rkey, name, words, strength, targetFeeds }] for activateList — the words are carried inline so a learned spell still works on a PDS where the referenced net.anisota.graph.wordlist record doesn't exist; or a config object for showLockout (message/scope/duration/bypass/…), showAlert (title/message/cooldownMinutes/…), redirect ({ route, cooldownMinutes }), sigilBeforePost ({ source, sigilShape }), overrideSetting ({ id, value }), or warnBefore* ({ action|element, message, gate }). toggleMode carries no value. It is left out of the schema and validated client-side because lexicon 'unknown' means 'any object', which would reject the array and string payloads."
}
Provenance. An 'original' spell was crafted here; a 'learned' spell was copied from another user, carrying a backlink to its immediate parent (originalUri/originalDid) and to the top of its lineage (rootUri/rootDid), modelled on Bluesky reply parent/root refs so counts can be aggregated across re-shares. A 'preset' spell was seeded from a built-in template.
Properties
DID of the user this spell was learned from.
AT URI of the spell this copy was learned from (its immediate parent).
Identifier of the built-in preset a 'preset' spell was seeded from.
maxLength: 64 bytes
DID of the original crafter at the top of the lineage.
AT URI of the first crafter's spell — the top of the lineage — propagated to every descendant.
No description available.
Known values: original, learned, preset
View raw schema
{
"type": "object",
"properties": {
"type": {
"type": "string",
"knownValues": [
"original",
"learned",
"preset"
]
},
"rootDid": {
"type": "string",
"format": "did",
"description": "DID of the original crafter at the top of the lineage."
},
"rootUri": {
"type": "string",
"format": "at-uri",
"description": "AT URI of the first crafter's spell — the top of the lineage — propagated to every descendant."
},
"presetId": {
"type": "string",
"maxLength": 64,
"description": "Identifier of the built-in preset a 'preset' spell was seeded from."
},
"originalDid": {
"type": "string",
"format": "did",
"description": "DID of the user this spell was learned from."
},
"originalUri": {
"type": "string",
"format": "at-uri",
"description": "AT URI of the spell this copy was learned from (its immediate parent)."
}
},
"description": "Provenance. An 'original' spell was crafted here; a 'learned' spell was copied from another user, carrying a backlink to its immediate parent (originalUri/originalDid) and to the top of its lineage (rootUri/rootDid), modelled on Bluesky reply parent/root refs so counts can be aggregated across re-shares. A 'preset' spell was seeded from a built-in template."
}
An interval (timer) trigger. The spell fires every everyMinutes minutes rather than reacting continuously to its conditions.
Properties
Minutes between firings (1 minute to 24 hours).
minimum: 1maximum: 1440
No description available.
Known values: interval
View raw schema
{
"type": "object",
"required": [
"type",
"everyMinutes"
],
"properties": {
"type": {
"type": "string",
"knownValues": [
"interval"
]
},
"everyMinutes": {
"type": "integer",
"maximum": 1440,
"minimum": 1,
"description": "Minutes between firings (1 minute to 24 hours)."
}
},
"description": "An interval (timer) trigger. The spell fires every everyMinutes minutes rather than reacting continuously to its conditions."
}