The catalog is the complete list of what TweakLane may draw in your app. A layout can only reference components registered here. Examples use the iOS SDK; every SDK registers the same fields.
Register a component
catalog.register("stats.list", displayName: "List", alternatives: ["stats.chart"],
props: titled, defaultProps: ["title": "This week"], baseHeight: 160) { props, context in
StepsList(title: props.string("title"))
}
| Parameter | Meaning |
|---|---|
id |
Stable id referenced from layout JSON. Never reuse an id for a different component. |
props |
PropsSchema: allowed keys and types (string, number, integer, bool, array, object, oneOf). A node with invalid props is dropped. |
defaultProps |
Used when a swap lands on props the target doesn't accept. |
alternatives |
Components this one may be swapped for (symmetric: listing B on A also allows B → A). |
minSDKSchema |
Lowest layout schema version that may reference this component. |
baseHeight |
Optional minimum height in points, scaled by Dynamic Type and the node's height hint. Never a fixed height. |
lock |
.none, .required or .fixed. See Locks. |
Names and labels are keys, never translated text. Register with
register(_:name: LocalizedStringResource, …) and set catalog.labelBundle to the bundle that
translates them. The manifest and every stored or AI-proposed layout carry only ids and keys, so
the manifest hash is the same in every language.
Parts that travel
User screens and cross-screen edits use what you declare:
catalog.declare(DataFieldSpec("streak.current", kind: .number, displayName: "day streak"))
catalog.declare(ActionSpec("deck.open", params: PropsSchema(properties: ["kind": .oneOf(["today"])], required: ["kind"])))
catalog.declare(ActionSpec("purchase.buy", screens: ["paywall"])) // runs only there
catalog.declare(ComponentBundle("purchase", members: [...], rule: .homeOnly)) // never leaves its screen
catalog.allowStrings(["Today"])
catalog.userScreens = UserScreenPolicy(maxScreens: 6, maxParts: 20, icons: ["sun.max", "house"])
catalog.update("home.ring") { spec in spec.portable = true }
catalog.registerGenericPieces() // tl.text, tl.stat, tl.progress, tl.image, tl.button, tl.divider, tl.spacer
portable: trueparts may appear on any screen, user screens included.portable: falseparts stay on the screens whose default holds them, and the parts browser shows yourboundReason.- Generic pieces bind a declared data field and a format (
plain,count,compact,percent,bytes,date,year), text keys you allowed, and declared action ids. The validator checks each of them, including the screens an action may run on. - Bundles keep parts together (
.together) or at home (.homeOnly).
Content options
A part can let users choose what it shows inside itself. Every option's default is the part as it ships.
catalog.update("journey.badges") { spec in
spec.options = [
ContentOption("count", name: "How many", kind: .count(min: 1, max: 10, allowsAll: true), default: 4),
ContentOption("filter", name: "Which badges", kind: .choice(["earnedFirst", "earned", "all"])),
ContentOption("heading", name: "Heading", kind: .toggle),
]
}
catalog.update("paywall.links") { spec in
spec.options = [ContentOption("links", name: "Links", kind: .subset(["terms", "privacy"], ordered: true, minCount: 1),
required: ["terms", "privacy"])]
}
| Kind | Value |
|---|---|
count(min:max:allowsAll:) |
a whole number in range, or "all" |
choice([…]) |
one string |
subset([…], ordered:minCount:) |
distinct strings, in display order when ordered |
numbers(min:max:) |
distinct whole numbers, or "all" |
toggle |
true or false |
required values must stay in a subset; locked: true keeps the default. A fixed part takes
no options at all.
History policy
catalog.history = HistoryPolicy(undoDepth:versionLimit:designLimit:nameMaxLength:) sets the undo
depth, how many versions and named designs each user keeps, and the longest design name.
The manifest
At launch, before the first save, the SDK uploads the catalog manifest (POST /catalogs):
component ids, display names, props schemas, default props, lock levels, alternatives, options
and your default documents. The server validates every write against it. Manifests are
content-addressed by the sha256 of their bytes and immutable.