Skip to content

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:

OperationPurpose
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)
end

A 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.db

This 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:

  • Taskbars and Orientation are copied into the Decoration’s settings and removed from the stored script’s header; export writes the saved settings when needed. Name and Description remain in the script. Orientation affects 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 Name replaces 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.
  • Description appears 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.interval requests periodic onTick() opportunities.
  • scheduler.at(deadline, "callbackName") schedules one named global callback at a specified wall-clock instant and returns a handle with handle: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"
end

As 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.

SymptomWhat to check
The script is saved but absent from the taskbarPlace the definition; check its taskbar-instance selection and visibility settings
Old content remains after editingSave the source with Reload Decoration checked; unsaved text and external file edits do not change active code
Content never refreshesDefine onTick() and request updates; put decoration.interval outside callbacks
No desktop Window appears in Settings previewPreview Windows are logical only; test the placed script, and consider whether the Window was previously closed
A button does nothingDefine its named callback before setContent(); check required inputs and the returned content error
A file or module cannot be foundCheck the imported-source directory, explicit path, file permissions, and module patterns
Output freezes after initially workingInspect 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.