Documentation
¶
Overview ¶
Package wire holds the JSON shapes that cross the host ↔ guest boundary of a filex app plugin. It is imported by the host (internal/wasmplugin) and by the guest SDK (pkg/pluginkit), so the two can never drift: a field added here is a field on both sides.
Everything here is plain data. No behaviour, no dependencies beyond the standard library, so it compiles unchanged for GOOS=wasip1.
Index ¶
- Constants
- func IsRTL(tag string) bool
- func PersonalState(key string, userID int64) string
- func UILocaleKeyOK(k string) bool
- type Action
- type ActionRunInput
- type ActionRunOutput
- type Actor
- type Applies
- type CallContext
- type Condition
- type DescribeInput
- type Field
- type FieldI18n
- type FieldOption
- type FileRef
- type HostError
- type JobRequest
- type Limits
- type Manifest
- type Node
- type OpenRequest
- type Output
- type OutputRef
- type PagePurpose
- type PublicPage
- type ScheduleItem
- type Section
- type Surface
- type SurfaceAction
- type Text
- type TickInput
- type TickOutput
- type View
- type ViewEventInput
- type WasmSource
Constants ¶
const ( // MaxUILocaleBytes caps one language of one pack: the UTF-8 bytes of its // keys plus its values. 1 MiB is three times the largest complete // translation the catalogue allows today — room for the catalogue to // grow by half again in the wordiest two-byte script, and still a bound a // browser downloads in one breath. MaxUILocaleBytes = 1 << 20 // MaxUILocalesBytes caps every language of one manifest together, so an // app may bundle several complete languages (a regional set, say) // without a manifest becoming a way to park arbitrary data on a server. MaxUILocalesBytes = 4 << 20 // MaxUILocaleKeyBytes caps one key. The longest key in the catalogue is // 52 bytes (`userSettings.notifications.events.file_upload_failed`). MaxUILocaleKeyBytes = 128 // MaxUILocaleValueBytes caps one translated string. The longest English // string is 606 bytes (`demo.alsoIncluded`); a language that runs 30 % // longer in a script of two-byte letters needs ~1.6 KB, so 4 KiB is room // to spare without admitting a paragraph that would wreck a layout. MaxUILocaleValueBytes = 4096 // MaxManifestBytes caps the manifest DOCUMENT as it is uploaded or // fetched. The four mebibytes of strings above can arrive inflated: a // JSON writer that escapes non-ASCII (Python's json.dump does, by // default) writes each two-byte letter as a six-byte `\uXXXX` — three // times the bytes — so the document ceiling is the strings' ceiling times // three plus room for the rest of the manifest. MaxManifestBytes = 16 << 20 )
The catalogue these limits were sized against, re-measured on the shipped v0.43.0 tree: 3 593 keys — 1 555 in the explorer's catalogue (packages/core/src/locales/en.ts), 1 760 in the admin panel's (web/src/locales/en.json), 223 in the server's (the text filex writes into mail, notifications and the pages behind a link) and 55 drawn by both front-end catalogues — adding up to 194 961 bytes of UTF-8 for keys and values in English and 210 279 in Turkish. A script whose letters take two bytes (Arabic, Cyrillic, Greek, Hebrew) roughly doubles the value half, so a COMPLETE translation today is about 335 KB.
⚠ Re-measure it here when the catalogue moves: `node scripts/i18n-export.mjs` prints the key count, and the numbers above are what the ceilings below are justified by. They were still the 2026-09-21 figures (2 900 keys) after two waves of work had taken the catalogue to 3 593.
const ( // ScheduleMaxItems is how many items one wake-up may schedule. ScheduleMaxItems = 64 // ScheduleMaxPaths is how many files one item may name. ScheduleMaxPaths = 16 // ScheduleKeyPattern is what an item key must match. It cannot match the // empty string, which is how the host keeps its own wake-up row out of // reach of the apps it wakes. ScheduleKeyPattern = `^[A-Za-z0-9][A-Za-z0-9_.:@-]{0,63}$` )
The bounds a wake-up plans around. They are part of the CONTRACT, not the host's private business, because the guest is told them in every TickInput and the test kit checks an answer against them before a module is built — one set of numbers, named once.
const ( ErrPermissionDenied = "permission_denied" ErrNotFound = "not_found" ErrTooLarge = "too_large" ErrTimeout = "timeout" ErrInvalid = "invalid" ErrBusy = "busy" ErrInternal = "internal" // ErrIntegrity: a downloaded asset did not match the sha256 the app // pinned (asset_fetch). Nothing was stored; nothing may use it. ErrIntegrity = "integrity" )
Host function error codes.
const PersonalStateSuffix = "@me"
PersonalStateSuffix marks a PERSONAL state key: `<key>@<user id>` is kept on a file for one person, and filex shows it to that person — and to nobody else — as `<key>@me`. A rule names it that way (`state: ["todo@me"]`), so the signing app offers "Sign / Fill" only to the people who have something to sign on that file, while the key a listing exposes never says who else has. See PersonalState.
const ProtocolVersion = 1
ProtocolVersion is the manifest_version / ABI generation this package describes. A host refuses a plugin whose describe answer names another.
Variables ¶
This section is empty.
Functions ¶
func IsRTL ¶
IsRTL reports whether a language tag is written right to left. It is the ONE list: the `rtl` flag on the offered-language rows, the explorer's and the admin panel's layout direction, and the server-rendered pages' `dir` all come from it (docs/RTL.md).
func PersonalState ¶
PersonalState is the key an app writes for one person (`state_set`): PersonalState("todo", 7) is "todo@7", which user 7's listings show as "todo@me".
func UILocaleKeyOK ¶
UILocaleKeyOK reports whether k may be a key of a language pack: dotted segments of letters, digits, `_` and `-` — the shape every key of both catalogues has — and none of the three segments that name a JavaScript object's machinery.
⚠⚠ The browser turns a dotted key into nested objects to feed the admin panel's vue-i18n (`a.b.c` → {a: {b: {c}}}). A segment called `__proto__` would walk that loop straight into Object.prototype and plant a property on every object of every visitor's page — public share pages included. The shape rule alone admits `__proto__` (underscores are legal), so the three are refused by name, here and again in the browser.
Types ¶
type Action ¶
type Action struct {
ID string `json:"id"`
Label Text `json:"label"`
Icon string `json:"icon,omitempty"`
Applies Applies `json:"applies"`
View string `json:"view,omitempty"`
Confirm Text `json:"confirm,omitempty"`
MinRole string `json:"min_role,omitempty"` // viewer | editor | owner
Danger bool `json:"danger,omitempty"`
Output Output `json:"output"`
Limits Limits `json:"limits,omitempty"`
// Hidden keeps the action out of every menu: it is only ever started by
// a surface (`job`) or a public page — the second half of a flow, not a
// thing a person picks.
Hidden bool `json:"hidden,omitempty"`
}
Action is one entry a plugin adds to the file menu.
type ActionRunInput ¶
type ActionRunInput struct {
JobID string `json:"job_id"`
ActionID string `json:"action_id"`
Params map[string]any `json:"params,omitempty"`
Inputs []FileRef `json:"inputs"`
Output Output `json:"output"`
Actor Actor `json:"actor"`
Locale string `json:"locale,omitempty"`
Settings map[string]string `json:"settings,omitempty"` // non-secret only
Engines map[string]bool `json:"engines,omitempty"`
// the same reason a screen does — whatever it records about a link's
// life (the request, the freeze beside it, the mail it sends) has to be
// the life the link will actually get.
ShareMaxTTLDays *int `json:"share_max_ttl_days,omitempty"`
}
ActionRunInput is what the host hands `action_run`.
type ActionRunOutput ¶
type ActionRunOutput struct {
OK bool `json:"ok"`
Outputs []OutputRef `json:"outputs,omitempty"`
Message Text `json:"message,omitempty"`
Surface *Surface `json:"surface,omitempty"`
}
ActionRunOutput is what `action_run` returns.
type Actor ¶
type Actor struct {
ID int64 `json:"id"`
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
Role string `json:"role,omitempty"`
// IP is the address the person's request came from, as filex's own
// access log would write it (behind a trusted proxy, the forwarded
// client). Set on a VIEW event — the screen somebody is looking at — and
// empty on a job, which runs later and from nowhere in particular.
//
// ⚠ It exists for the same reason `data.page.visitor_ip` does on a
// public page: an app that records WHO acted (the signing app's audit
// trail, the IP line it can print under a signature) had that fact for
// a stranger on a link and not for a signed-in person doing the same
// thing — so the same box printed an address for one signer and "—" for
// the next. Personal data: an app that records it must show it to the
// person before it keeps it.
IP string `json:"ip,omitempty"`
}
Actor is the person a call runs as.
type Applies ¶
type Applies struct {
Kind string `json:"kind,omitempty"` // file | dir | any (default file)
Ext []string `json:"ext,omitempty"` // lower-case, no dot
Mime []string `json:"mime,omitempty"` // exact or prefix glob "image/*"
Multi bool `json:"multi,omitempty"`
Min int `json:"min,omitempty"`
Max int `json:"max,omitempty"`
// State narrows the offer to files on which THIS plugin keeps at least
// one of the named state keys (`state_set`), NoState to files that
// carry none of them. A signing app offers "Sign" only where a key
// `pending` is set and "Request signatures" only where it is not. Keys
// are the plugin's own; the listing exposes them as `<plugin>:<key>`.
State []string `json:"state,omitempty"`
NoState []string `json:"no_state,omitempty"`
// EngineExt adds extensions that are offered only while the named
// engine is installed and granted: `{"libreoffice": ["docx", "odt"]}`
// on top of `ext: ["pdf"]` offers the action on a .docx exactly when
// the host can turn one into a PDF. The host folds the list into Ext
// before anybody sees the action (the explorer's menu, the run check),
// so a client never evaluates it. It only ADDS to a non-empty ext/mime
// list — an empty one already means "any file".
//
// ⚠⚠ Why (2026-09-21, a tester): "İmzala…" and "İmza iste…" were
// offered on a .docx on an installation without LibreOffice, and the
// click opened a page saying it is not a PDF and there is no LibreOffice
// to convert it — while the Signatures screen itself said only PDFs can
// be signed. An action that can never work must not be offered.
EngineExt map[string][]string `json:"engine_ext,omitempty"`
// Writable says the action can only be completed where its file can be
// written, even though its own output is `none`: "Request signatures"
// writes nothing now, but the flow it starts ends in writing the signed
// document. filex hides such an action on a read-only storage (and
// refuses it there with 409 read_only), exactly as it hides an action
// whose output writes — instead of letting the app open a screen whose
// only possible answer is "this cannot be done here". It needs the
// files:write permission, and it counts as a write for the level the
// caller needs (editor).
Writable bool `json:"writable,omitempty"`
}
Applies says which selections an action or view is offered for.
type CallContext ¶
type CallContext struct {
Inputs []FileRef `json:"inputs,omitempty"`
Actor *Actor `json:"actor,omitempty"`
Locale string `json:"locale,omitempty"`
Settings map[string]string `json:"settings,omitempty"`
Engines map[string]bool `json:"engines,omitempty"`
// ANY new share link — the administrator's ceiling (Protection → share
// links, `share.max_ttl_days`; 7 unless it was changed). 0 means no
// ceiling; nil means the host did not say (older than v0.43.0) or this
// app holds no `public_pages` grant and so opens no links.
//
// ⚠⚠ share_create clamps every link to the LOWEST of this, the page's own
// `max_ttl_days` and 365. A screen that offers a longer life than that
// promises something the link will not have: the signing app's wizard
// said "links valid 14 days" while the link it opened lived 7, because
// the ceiling was invisible from inside the app. Read it, and offer no
// more than it allows.
ShareMaxTTLDays *int `json:"share_max_ttl_days,omitempty"`
// Home is where this person's results go by default when they cannot
// go beside their source (Output.Elsewhere): the root of the first
// storage, in the administrator's order, that the person may write.
// Adapter-qualified (`depo://`); empty when they may write nowhere.
Home string `json:"home,omitempty"`
}
CallContext is the shared context of a view or page call.
type DescribeInput ¶
type DescribeInput struct {
HostVersion string `json:"host_version"`
Locale string `json:"locale,omitempty"`
}
DescribeInput is what the host hands `describe`.
type Field ¶
type Field struct {
Key string `json:"key"`
Type string `json:"type"` // string | password | int | bool | select | text
Label string `json:"label"`
Help string `json:"help,omitempty"`
Required bool `json:"required,omitempty"`
Secret bool `json:"secret,omitempty"`
Default any `json:"default,omitempty"`
Placeholder string `json:"placeholder,omitempty"`
Options []FieldOption `json:"options,omitempty"`
Min *int `json:"min,omitempty"`
Max *int `json:"max,omitempty"`
// Multi turns a `select` into a several-of choice. The value is then a
// list of option values instead of one.
Multi bool `json:"multi,omitempty"`
// Style says how a `bool` is drawn: `switch` for an on/off setting a
// person flips in passing, `choice` for a decision they must read
// before answering (two buttons, Yes and No). Empty lets the client
// decide, which it does by guessing — say it when the answer matters.
Style string `json:"style,omitempty"`
// ShowWhen hides the field until another field in the same form holds
// one of the given values; RequiredWhen makes it required only then.
// Both are re-checked by the host at submit: a value belonging to a
// hidden field is dropped, and a required-when field that is empty
// refuses the job. A form may not present a contradiction (a file name
// asked for while the output is a new version of the same file).
ShowWhen *Condition `json:"show_when,omitempty"`
RequiredWhen *Condition `json:"required_when,omitempty"`
// I18n is the per-language spelling of Label, Help and Placeholder, when
// they were written as {"en": …, "tr": …} instead of one string. Label,
// Help and Placeholder then hold the English (or the only) spelling, so
// code that reads them as strings keeps working, and the field is
// written back out as the maps it arrived as.
//
// ⚠⚠ Why (2026-09-21, a tester): an app's settings were English inside
// the Turkish admin panel — "Add a time stamp to every signature",
// "Time-stamping authority" — because a manifest setting could only be
// ONE string, while every other text an app shows (its name, its
// actions, its screens) is a {en, tr} map the client already resolves.
I18n *FieldI18n `json:"-"`
}
Field is one input of a settings or action form. It mirrors the storage driver descriptor field the admin panel already renders (storage.Field), so a plugin's settings form is drawn by the same component.
func (Field) Localized ¶
Localized is the field with every text in `lang` (falling back to English) and no map left — for a client that reads plain strings.
func (Field) MarshalJSON ¶
MarshalJSON writes the maps back when the field has them.
func (*Field) UnmarshalJSON ¶
UnmarshalJSON accepts label, help and placeholder as a string or as a {lang: …} map.
type FieldOption ¶
type FieldOption struct {
Value string `json:"value"`
Label string `json:"label"`
LabelI18n Text `json:"-"`
}
FieldOption is one choice of a select field. Its label, like a Field's, may be one string or a {lang: …} map (then kept in LabelI18n).
func (FieldOption) Localized ¶
func (o FieldOption) Localized(lang string) FieldOption
Localized is the option with its label in `lang`.
func (FieldOption) MarshalJSON ¶
func (o FieldOption) MarshalJSON() ([]byte, error)
MarshalJSON writes the map back when the option has one.
func (*FieldOption) UnmarshalJSON ¶
func (o *FieldOption) UnmarshalJSON(b []byte) error
UnmarshalJSON accepts the label as a string or a {lang: …} map.
type FileRef ¶
type FileRef struct {
Ref string `json:"ref"`
Name string `json:"name"`
Size int64 `json:"size"`
Mime string `json:"mime,omitempty"`
PathRel string `json:"path_rel,omitempty"`
// Path is the adapter-qualified form (`docs://reports/nda.pdf`) — what
// the explorer navigates by and what a `pdf-fields`/`preview` node's
// `src.path` takes. Filled by the host whenever the storage is known.
Path string `json:"path,omitempty"`
// ReadOnly says the file's storage takes no writes: no new version, no
// file beside it. Filled by the host whenever the storage is known.
//
// ⚠⚠ Why an app needs to know BEFORE it writes (2026-09-21, a tester):
// a signing request on a read-only storage was accepted and "sent" —
// the file frozen, the signer notified — and the signer's answer then
// failed with 409 read_only, for ever, because the request writes
// nothing itself (its output mode is `none`) and the host's own
// read-only refusal only guards jobs that write. Only the app knows
// that the flow it starts ends in a write, so the app has to be told,
// and has to refuse at the start with a sentence.
ReadOnly bool `json:"read_only,omitempty"`
}
FileRef is one file the call may read through file_* host functions. Ref is an opaque, call-scoped handle — never a storage path.
type JobRequest ¶
type JobRequest struct {
ActionID string `json:"action_id"`
Params map[string]any `json:"params,omitempty"`
// Output, when set, replaces the action's manifest output for this one
// job — a wizard's "same file as a new version / new file beside it /
// custom name" choice. Mode is sibling | version | none; Name takes the
// same {stem}/{ext}/{name} pattern as the manifest (a literal name works
// too). Widening `none` to a writing mode is allowed only if the caller
// holds editor on the inputs — the host re-checks at submit.
Output *Output `json:"output,omitempty"`
}
JobRequest asks the host to enqueue an action with these params.
type Limits ¶
type Limits struct {
MemoryPages int `json:"memory_pages,omitempty"`
CallTimeoutS int `json:"call_timeout_s,omitempty"`
MaxInputBytes int `json:"max_input_bytes,omitempty"`
TimeoutS int `json:"timeout_s,omitempty"`
}
Limits are what a plugin asks for; the host clamps them.
type Manifest ¶
type Manifest struct {
ManifestVersion int `json:"manifest_version"`
Name string `json:"name"`
Version string `json:"version"`
Label Text `json:"label"`
Description Text `json:"description,omitempty"`
Icon string `json:"icon,omitempty"`
Homepage string `json:"homepage,omitempty"`
MinFilex string `json:"min_filex,omitempty"`
Permissions []string `json:"permissions"`
// Languages the plugin promises to speak. Every Text it returns must
// carry all of them, or the host refuses it at install: a screen half in
// one language is a bug the author should see before a person does.
// Empty means ["en"].
Languages []string `json:"languages,omitempty"`
// UILocales lets a plugin add a language to FILEX ITSELF — the key is a
// language tag, the value that language's strings by filex's own keys.
// They join the language list the interface offers (public pages
// included) and leave with the plugin. A missing key falls back to
// English, as everywhere else.
UILocales map[string]map[string]string `json:"ui_locales,omitempty"`
PermissionReasons map[string]Text `json:"permission_reasons,omitempty"`
Settings []Field `json:"settings,omitempty"`
Actions []Action `json:"actions,omitempty"`
Views []View `json:"views,omitempty"`
PublicPages []PublicPage `json:"public_pages,omitempty"`
Limits Limits `json:"limits,omitempty"`
Wasm *WasmSource `json:"wasm,omitempty"`
// Messages are texts FILEX says on the app's behalf, long after the call
// that caused them — today the reason beside a file lock ("signatures
// are being collected"), shown in the admin panel, the details panel
// and a refused rename, each in its READER's language. The app names
// one by key when it locks (pluginkit.FileLockMessage); `{placeholders}`
// are filled from the arguments it passes. Every declared language is
// required, like every other Text.
Messages map[string]Text `json:"messages,omitempty"`
}
Manifest is filex-app.json — and what `describe` must echo.
func (*Manifest) IsLanguagePack ¶
IsLanguagePack reports whether the manifest is a language pack and nothing else: it adds at least one language to filex and declares nothing a module could run — no action, no screen, no public page, no setting, no permission, and no module address.
⚠⚠ Such an app has NO module. It installs from its manifest alone, never starts a runtime instance, and is listed as a language pack. A translator must not need a Go toolchain to ship a translation, and a module that could do nothing would still be a binary the administrator is asked to trust.
⚠ `wasm` present means "there is a module" and keeps the manifest an ordinary app, so an app that was built the old way (a module whose only job was to carry `ui_locales`) keeps loading exactly as it did.
type Node ¶
type Node struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Props map[string]any `json:"props,omitempty"`
Children []Node `json:"children,omitempty"`
}
Node is one component. Type is from the host's catalogue; Props are that component's properties; Children only for layout nodes.
type OpenRequest ¶
type OpenRequest struct {
Path string `json:"path"`
Action string `json:"action,omitempty"`
View string `json:"view,omitempty"`
}
OpenRequest is "go to this file, and start this there". Path is adapter-qualified (`docs://reports/nda.pdf`). Action and View are this plugin's own; naming neither just opens the file.
type Output ¶
type Output struct {
Mode string `json:"mode"` // sibling | version | none | folder (per job)
Name string `json:"name,omitempty"` // pattern: {stem}, {ext}, {name}
// Elsewhere (manifest, with mode sibling) says the result may go into a
// folder the PERSON chooses instead of beside its source: where the
// source's storage is read-only, filex still offers the action and the
// app asks where the result should go (a `file-chooser` with kind "dir",
// starting at CallContext.Home), then answers the job with Output{Mode:
// "folder", Dir: <the chosen folder>}. filex checks that folder — the
// storage, the person's level on it, locks and filex's own folders —
// before the job is queued and again when the file is written.
Elsewhere bool `json:"elsewhere,omitempty"`
// Dir is the chosen folder for mode "folder", adapter-qualified
// (`docs://reports`). Only a JobRequest carries it.
Dir string `json:"dir,omitempty"`
}
Output says where an action's result files land.
type PagePurpose ¶
type PagePurpose struct {
// Label names the kind of link in a list: "Signing request".
Label Text `json:"label"`
// Revoke is said before the link is revoked, plainly: "Revoking this
// link cancels the signing request."
Revoke Text `json:"revoke,omitempty"`
// Section is the section of the app's `home` view the row opens
// (Surface.Sections); empty opens the home view as it comes.
Section string `json:"section,omitempty"`
}
PagePurpose names what an app's link is part of.
⚠⚠ Why (2026-09-21, the owner's decision): signing links appeared in My shares as plain `/teklif.pdf` shares offering "Copy PIN" and "Revoke", with no hint they belonged to a signing request — and revoking one broke the request. They stay listed, but marked: named for what they are, opening the app's page for them, and saying before a revoke what the revoke does.
type PublicPage ¶
type PublicPage struct {
ID string `json:"id"`
Label Text `json:"label"`
PIN string `json:"pin,omitempty"` // optional | required | none
DefaultTTLDays int `json:"default_ttl_days,omitempty"`
MaxTTLDays int `json:"max_ttl_days,omitempty"`
// Purpose says what a link of this page IS, for the person who sees it
// in a list of links (My shares, the admin's Shares): it belongs to
// something the app keeps, ending it ends that, and there is a place in
// the app that shows it. Optional; without it a link lists as a share.
Purpose *PagePurpose `json:"purpose,omitempty"`
}
PublicPage is a flow an outside participant reaches through a share link, /s/<token> (the retired /p/<token> answers a 301 to it).
type ScheduleItem ¶
type ScheduleItem struct {
// Key is the app's own name for this piece of work, and the reason a
// restart cannot run it twice: the host keeps at most one item per
// (app, key), so naming the same key again moves the item rather than
// adding a second one. A signing app keys by envelope
// (`expire:7f3a…`). 1–64 characters of [A-Za-z0-9] plus `_.:@-`.
Key string `json:"key"`
// DueAt is when the work runs, to the minute (to the second, in fact).
// A time already past runs at once; a time beyond WindowEnd is not
// scheduled.
DueAt time.Time `json:"due_at"`
// ActionID is the app's action to run. It must exist in the manifest
// and not be switched off by the administrator.
ActionID string `json:"action_id"`
// Paths are the files the job runs on, adapter-qualified
// (`docs://reports/nda.pdf`) — the spelling `state_list` answers with.
// At least one, at most MaxPaths, all on the same storage.
Paths []string `json:"paths"`
Params map[string]any `json:"params,omitempty"`
}
ScheduleItem is one piece of work an app wants run at a named minute.
It is an action of the app's own — usually a `hidden` one, the half of a flow nobody picks from a menu — run as an ordinary plugin job: the same ops row, the same sandbox, the same output rules. The wake-up decides WHAT and WHEN; the job does the work.
type Section ¶
type Section struct {
ID string `json:"id"`
Label Text `json:"label"`
// Count is drawn beside the label (a number of rows waiting there);
// nil draws none.
Count *int `json:"count,omitempty"`
}
Section is one entry of a home page's menu.
type Surface ¶
type Surface struct {
Title Text `json:"title,omitempty"`
Size string `json:"size,omitempty"`
State map[string]any `json:"state,omitempty"`
Nodes []Node `json:"nodes"`
Actions []SurfaceAction `json:"actions,omitempty"`
Toast Text `json:"toast,omitempty"`
Done bool `json:"done,omitempty"`
Job *JobRequest `json:"job,omitempty"`
Errors map[string]Text `json:"errors,omitempty"`
// Open sends the person to a FILE — the one thing a screen could not do
// before. A home screen lists documents; a row has to be able to open
// the one that was clicked, and open it on the right screen. The host
// checks the path and the target before the answer leaves.
Open *OpenRequest `json:"open,omitempty"`
// Sections turn a `home` screen into a page with a MENU: one entry per
// section, the frame drawing the menu and the plugin drawing whichever
// section is open. Section says which one this surface is.
//
// ⚠⚠ Why the frame and not a node. The owner, 2026-09-21: the
// Signatures screen should be "its own page … each in a separate menu",
// with the browser's Back button working and a notification landing on
// the right section. A node can draw tabs, but only the FRAME owns the
// address bar: it keeps the open section in the URL (`?section=`), so
// Back walks the sections and a link can name one. Opening a view with a
// section in its address hands the plugin `data.section` on the `open`
// event; everything after that is an ordinary conversation.
Sections []Section `json:"sections,omitempty"`
Section string `json:"section,omitempty"`
}
Surface is a declarative screen: nodes drawn by filex's own components.
type SurfaceAction ¶
type SurfaceAction struct {
ID string `json:"id"`
Label Text `json:"label"`
Primary bool `json:"primary,omitempty"`
Danger bool `json:"danger,omitempty"`
Disabled bool `json:"disabled,omitempty"`
}
SurfaceAction is a footer button.
type Text ¶
Text is a label in one or more languages. `en` is required everywhere a Text appears; other languages fall back to it.
func FillMessage ¶
FillMessage fills `{name}` placeholders in every language of t.
type TickInput ¶
type TickInput struct {
// Now is the host's clock when the wake-up fired, in UTC.
Now time.Time `json:"now"`
// WindowStart and WindowEnd bound the due times this answer may name.
// WindowStart equals Now; WindowEnd is the next hourly boundary, which
// is when the next wake-up comes. An item due outside the window is not
// scheduled and no error is raised — say it again at the wake-up whose
// window contains it. After a restart at 03:37 the window is 03:37 to
// 04:00, so it is shorter than an hour and the app is told so.
WindowStart time.Time `json:"window_start"`
WindowEnd time.Time `json:"window_end"`
// MaxItems is how many items this answer may carry. Extra ones are
// dropped (the app's log says how many), so put the urgent ones first.
MaxItems int `json:"max_items"`
// MaxPaths is how many files one item may name.
MaxPaths int `json:"max_paths"`
Locale string `json:"locale,omitempty"`
Settings map[string]string `json:"settings,omitempty"` // non-secret only
Engines map[string]bool `json:"engines,omitempty"`
}
TickInput is what the host hands `tick`.
type TickOutput ¶
type TickOutput struct {
Items []ScheduleItem `json:"items,omitempty"`
// Note is a line for the app's log in the admin panel — what this
// wake-up decided, in the app's own words. It is not shown to anyone
// else; there is nobody there.
Note Text `json:"note,omitempty"`
}
TickOutput is what `tick` returns.
type View ¶
type View struct {
ID string `json:"id"`
Placement string `json:"placement"` // modal | page | inspector | home
Label Text `json:"label"`
Applies Applies `json:"applies,omitempty"`
Size string `json:"size,omitempty"`
}
View is a declarative screen the plugin can be asked to draw.
Placement: `modal` opens over the explorer; `page` opens as a full page in a new tab (a wizard with a document beside it — anything a modal's double scroll would cramp); `inspector` is a section of the details panel; `home` is a standalone screen listed under Apps.
type ViewEventInput ¶
type ViewEventInput struct {
ViewID string `json:"view_id"`
Event string `json:"event"` // open | change | submit | action
ActionID string `json:"action_id,omitempty"`
State map[string]any `json:"state,omitempty"`
Data map[string]any `json:"data,omitempty"`
Context CallContext `json:"context"`
}
ViewEventInput is what the host hands `view_event`.
type WasmSource ¶
type WasmSource struct {
URL string `json:"url,omitempty"`
SHA256 string `json:"sha256,omitempty"`
}
WasmSource says where the module comes from when installing from a manifest fetched off a repository (GitHub URL install).