Concepts
A runtime is a running copy of a Scripted Decoration’s Lua source. Each reload creates a new generation with fresh Lua variables and runtime-only handles. Persistent storage is separate from that running copy; a Window handle or schedule handle is a value for using a resource in its owning generation, not something to save and reuse after reload.
Persistent Storage
Scripted Decorations have private persistent storage:
storage.set("count", 3)
local count = storage.getOrDefault("count", 0)
storage.set("count", count + 1)The available operations are:
| Operation | Purpose |
|---|---|
storage.set(key, value) | Store or replace a value |
storage.get(key) | Read a value, or nil when absent |
storage.getOrDefault(key, default) | Read a value with a fallback |
storage.exists(key) | Test whether a key exists |
storage.remove(key) | Remove one value |
storage.clear() | Remove ordinary stored values, preserving host-managed Window closed markers |
storage.keys() | Return stored keys |
storage.size() | Return the number of stored keys |
storage.flush(callback) | Confirm that preceding accepted writes have committed |
Storage belongs to the Scripted Decoration’s Name: spaces at either end are ignored and capitalization does not matter. It survives source changes, reloads, placement removal/re-addition, and a different applet taking over the script. Renaming moves the existing storage to the new Name. A cloned definition has its own Name and initially empty storage. Deleting a definition offers Keep Data or Delete Data; keeping data allows a later definition with the same Name to recover it. Source updates do not reset or reinterpret existing values; check their types if new code expects a different data shape.
set, remove, and clear return true when accepted, or nil, error for immediate rejection. An accepted write is visible to the current script before it is durable. Use a flush callback when the interface must report that saving has completed:
local accepted, writeError = storage.set("count", 3)
if not accepted then
log.error(writeError.message)
else
storage.flush(function(ok, commitError)
if ok then
decoration.text = "Saved"
else
decoration.text = "Save failed"
log.error(commitError.message)
end
end)
endA later persistence failure restores the script’s storage view to its last committed state, rejects dependent writes, calls onStorageError(error) if defined, and fails dependent flush callbacks. There is no automatic retry. Save important state when it changes, not only in onStop(): teardown is not a guarantee of a final commit. Keys beginning __taskmansync.window.closed. belong to TaskManSync; they are readable but cannot be set or removed by Lua.
The persisted storage is kept in TaskManSync’s SQLite database:
~/.local/share/taskmansync/taskmansync.dbThis is the same database used for other TaskManSync durable state. Script storage is kept inside that database; the database file itself is not a separate scripting database.
Storage is not a general database. Values are limited to ordinary structured data: nil, booleans, signed integers, finite numbers, valid UTF-8 strings, arrays, and string-keyed objects. Functions, threads, native objects, arbitrary userdata, cycles, and mixed/sparse tables are not storage values.
Semantic time values and rich-text values are not ordinary structured storage values. Store time as signed milliseconds and rich text as value:toHtml(), then reconstruct them after reading. The General API describes time conversions and value limits; Window inputs explains rich text.
Source and Metadata
A Scripted Decoration can have an optional metadata header in a Lua long comment at the beginning of its stored source:
--[[
Name: System Status
Description: Displays the current system status.
Taskbars: 1
Orientation: Vertical
]]The recognized keys are Name, Description, Taskbars, and Orientation; other keys (including Notes, Width, and WidthMode) are ignored except as described below. Key names are not case-sensitive, a key can appear only once, and Orientation is Horizontal or Vertical. A Description on one line needs no delimiters; one that spans several lines must be written between lines containing only """. After a single-line Description, a nonblank line must begin another supported key in the first column or end the header. Other text, including an unsupported key, is an error; put unused keys before Description or use its multi-line form. When Name is the only key, --[[ Name: System Status ]] is also accepted. Blank lines and ordinary Lua comments may precede the block. Metadata is not exposed as a Lua global.
On Import or Re-import:
TaskbarsandOrientationare copied into the Decoration’s settings and removed from the stored script’s header; export writes the saved settings when needed.NameandDescriptionremain in the script.Orientationaffects only the preview in Settings. Keys the file omits keep the Decoration’s current values on Re-import; they are not reset.- A non-blank metadata
Namereplaces the definition Name; otherwise the current Name is retained. If both are empty on initial Import, the filename without its extension supplies a one-time initial Name. Descriptionappears below the Settings preview and has no runtime effect.- A malformed header (an invalid value, a repeated key, or an unclosed block) rejects Import and Re-import. Import Decorations instead keeps the file and reports the problem when you open the Decoration. See Importing and Exporting.
Ordinary saves in the script editor do not read the header: they neither check it nor override the definition’s Name with it. To Lua the header is an ordinary comment, so a malformed header (other than a --[[ that is never closed, which is also a Lua syntax error) is not a Lua error; it matters only to Import and Re-import, and Import Decorations reports it. Fix the header before the next Import or Re-import.
The external source-file path, when present, records where the script was imported and supplies the base for relative files/modules. The stored script remains authoritative after import. Changing the file, deleting it, or exporting another copy does not replace that stored source. See Getting Started for the editing workflow.
Scheduling
There are two common ways to request future work:
decoration.intervalrequests periodiconTick()opportunities.scheduler.at(deadline, "callbackName")schedules one named global callback at a specified wall-clock instant and returns a handle withhandle:cancel().
Periodic updates are disabled when no interval is configured. Declare decoration.interval at the top level, not inside onLoad() or another callback. It must be an integer from 50 through 2,147,483,647 ms. Assigning nil or zero does not disable it; these values are errors. A runtime can declare one new interval; repeating that value is a no-op, but a second different declaration is rejected. The accepted interval is definition configuration, not an ordinary Lua variable.
For one-shot work, define the named function before calling scheduler.at; a function value is not accepted in place of its name. A past deadline runs at the next scheduler opportunity. Deadlines follow the computer’s system clock, so clock adjustments can advance or delay delivery. Cancellation is idempotent but cannot undo a callback that has already run.
Schedules and handles do not survive reload, removal, faults, hand-offs to another applet, or shutdown. Persist the deadline, not the schedule handle, and recreate the schedule in onLoad(). This reminder restores a pending deadline after a reload:
local reminder
function onReminder()
storage.remove("reminderDeadline")
decoration.text = "Reminder due"
end
function onLoad()
decoration.text = "Click for a five-minute reminder"
local saved = storage.get("reminderDeadline")
if saved then
reminder = scheduler.at(time.instant.milliseconds(saved), "onReminder")
decoration.text = "Reminder pending"
end
end
function onTaskLeftClick()
if reminder then reminder:cancel() end
local deadline = time.now() + time.duration.minutes(5)
local accepted, err = storage.set("reminderDeadline", time.instant.toMilliseconds(deadline))
if not accepted then
log.error(err.message)
return
end
reminder = scheduler.at(deadline, "onReminder")
decoration.text = "Reminder pending"
endAs with any accepted storage write, the deadline is durable only after commit. Add storage.flush or onStorageError handling when confirmation or recovery is needed. Window closure does not stop the script or cancel its schedules. Neither periodic nor one-shot scheduling promises exact timing or immediate painting; missed periodic ticks do not accumulate into a backlog.
Runtime Errors and Logs
A Lua error in a callback is contained to that Scripted Decoration runtime generation. It does not turn the error into a TaskManSync application-wide failure. If a running generation faults after it has successfully published output, the last successful output may remain visible while the Decoration is marked with fault status.
Use log.debug(), log.info(), log.warning(), and log.error() to report useful diagnostic information. These messages are associated with the placed Decoration instance and its runtime generation.
TaskManSync does not define a user-facing log file, log path, or retention policy for Scripted Decoration messages. The public scripting contract deliberately does not promise where those log messages are stored or how long they are retained.
Most service failures return nil, error, where error.operation and error.code identify the failure and error.message gives detail. Check both immediate admission errors and errors delivered later to callbacks. A rejected HTTP request, process, or content value is not itself a Lua exception; dereferencing its missing result without checking can cause one. Property-assignment errors can raise a Lua error instead. Desktop APIs explains service-specific failures.
| Symptom | What to check |
|---|---|
| The script is saved but absent from the taskbar | Place the definition; check its taskbar-instance selection and visibility settings |
| Old content remains after editing | Save the source with Reload Decoration checked; unsaved text and external file edits do not change active code |
| Content never refreshes | Define onTick() and request updates; put decoration.interval outside callbacks |
| No desktop Window appears in Settings preview | Preview Windows are logical only; test the placed script, and consider whether the Window was previously closed |
| A button does nothing | Define its named callback before setContent(); check required inputs and the returned content error |
| A file or module cannot be found | Check the imported-source directory, explicit path, file permissions, and module patterns |
| Output freezes after initially working | Inspect Settings errors and check callback errors, invalid values, and unchecked asynchronous results; fix and reload |
TDML diagnostics can be recoverable presentation problems rather than Lua runtime faults. Inspect the Settings preview error area, then check the TDML Guide and the exact API value being assigned. During a startup fault with no successful output, the definition’s fallback presentation is used; a later fault may leave the last valid output visible. A new runtime does not inherit the old runtime’s Lua variables, handles, or outstanding callbacks.
The runtime is not a hostile-code security sandbox. Import only scripts you trust and inspect external effects before running them. File paths are not confined to a TaskManSync folder and there are no separate filesystem permission prompts; a script can request reads/writes subject to normal OS permissions. A launched program can have its own effects beyond the Lua API. Capability/output limits protect the host, not the user’s files from malicious scripts.
Capabilities and Boundaries
Scripts receive typed values and opaque handles rather than native objects. In particular, a Scripted Decoration does not receive arbitrary:
- Qt objects;
- QML objects or JavaScript;
- Plasma objects;
- TaskManSync C++ objects;
- SQLite/database handles;
- task-order or task-identity objects.
This keeps the scripting API focused on the capabilities it actually provides.
The runtime uses Lua 5.5.1 and text-only source, not precompiled bytecode or native modules. Scripts can publish TDML, positioned text and bounded graphics, create their own Windows, store private values, and use the documented file, HTTP, process, system-information, and sound services. The TaskManSync API has no functions to enumerate/control application tasks, change task ordering or placement, execute QML/JavaScript, subscribe to global keyboard events, or add a right-click Decoration menu. This API boundary does not restrict what an explicitly launched external program can do. A Script-Created Window remains a surface owned by the same script, not a separate script type. See the API Reference for supported calls and limits.