One build, fourteen apps: see what users made of Memory Tin

Documentation

Catalog

Documentation menu

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: true parts may appear on any screen, user screens included. portable: false parts stay on the screens whose default holds them, and the parts browser shows your boundReason.
  • 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.