@colixsystems/widget-sdk 0.72.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -2
- package/dist/contract.cjs +9 -1
- package/dist/contract.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
|
|
|
54
54
|
|
|
55
55
|
## Status
|
|
56
56
|
|
|
57
|
-
`v0.
|
|
57
|
+
`v0.73.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
|
|
58
58
|
|
|
59
59
|
### What's new in 0.71.0
|
|
60
60
|
|
|
@@ -170,6 +170,10 @@ useEffect(() => {
|
|
|
170
170
|
|
|
171
171
|
**New linter rule `react-not-imported` — widget source must be self-contained (sc-2353).** The automatic JSX runtime binds `jsx`/`jsxs` from `react/jsx-runtime` but never `React` itself, so source that reaches for the bare `React` global (`React.createElement` / `React.Fragment` / `React.memo` / `React.useMemo`) without importing it bundles cleanly, then throws `ReferenceError: React is not defined` the moment a non-initial code path hits the reference. The platform does **not** inject a `React` binding — earlier behaviour that auto-injected one left source that broke as soon as it was downloaded and re-uploaded through a path that doesn't inject. The linter now flags a bare-`React` reference with no `import React from "react"` (or `import * as React`) as an error, so the failure becomes a publish/upload finding (and an AI-agent repair-loop finding) instead of a broken shipped widget. Plain JSX, which needs no React import, is never flagged; a `React` mention inside a comment or string is masked. Author fix: add `import React from "react";` (`react` is already vetted), or prefer a JSX fragment `<>…</>` plus the SDK hooks/primitives over reaching for `React` directly. `CONTRACT` is unchanged (no new field).
|
|
172
172
|
|
|
173
|
+
### What's new in 0.73.0
|
|
174
|
+
|
|
175
|
+
**`secrets` is now listed in `CONTRACT.actionScriptGlobals`.** The action runner has always exposed a frozen `secrets` object to server-action scripts — the tenant's stored key/value configuration, read as `secrets["STRIPE_API_KEY"]` (REQ-ACTION-SECRET) — but the contract never listed it, so the Developer guide and the AI widget agent's prompt, both of which derive their globals list from this array, never told an author it existed. That omission is why generated scripts inline a plaintext credential instead of reading one. **Never put an API key, token, or password in `scriptSource`** — a manifest is distributed with the widget, so a literal credential is published with it. Name the key, read it from `secrets`, and fail loudly when it is missing; the workspace operator supplies the value in the Studio. `CONTRACT.version` → `1.50.0`. Documentation-only against the runtime — the global was already there — but additive to the published contract, so it moves the minor.
|
|
176
|
+
|
|
173
177
|
### What's new in 0.54.0
|
|
174
178
|
|
|
175
179
|
**Generate & save PDFs from a widget (sc-2314).** New `usePdfExport({ spaceType, folderId? })` hook. `exportToPdf(html, { fileName?, folderId? })` renders the HTML to a PDF **server-side** (the platform's headless-Chromium pipeline) and saves it into the end-user's Filestore via `ctx.filestore.files.exportPdf`, resolving to the created file row (`application/pdf`). It reuses the filestore owner_id resolution + per-folder write gate and the existing `files.write:*` scope. Because the rendering is server-side, the capability behaves identically on the web Player and the native Expo export — no browser-only PDF library is added to the vetted set. Pairs with `@colixsystems/filestore-client@0.6.0`'s new `files.exportPdf(...)`. `CONTRACT.version` → `1.38.0`. Additive — no existing hook, primitive, manifest field, or token changed shape.
|
|
@@ -566,7 +570,8 @@ A widget that works but looks unfinished is only half done. `useTheme()` is the
|
|
|
566
570
|
- **Contain and elevate.** Wrap a logical unit in a surface: `colors.surface` + padding + `radii.lg` + `...theme.elevation.sm`. Give it the elevation **or** a `colors.border` hairline, not both — and prefer the elevation, because a hairline-only card reads as a wireframe. `theme.elevation` is a token table you spread into a style (`...theme.elevation.md`), covering `none / sm / md / lg / xl`; never hand-write `shadowOpacity` / `shadowRadius` / `boxShadow`. Use the status roles (`danger / success / warning / info`) for state.
|
|
567
571
|
- **Tint the supporting cast.** `colors.primarySoft` is a tint of the workspace accent over the surface and `colors.onPrimarySoft` is guaranteed readable on it (WCAG AA, on light and dark themes alike). Use the pair for chips, secondary buttons, progress tracks, icon badges and selected rows. One saturated accent moment surrounded by several pale echoes of the same hue is what reads as designed — a row of grey-outlined buttons reads as a form. Never hand-mix a tint with `rgba(...)` or a translucent overlay.
|
|
568
572
|
- **Spend one gradient.** `<Gradient colors={[theme.colors.primary, theme.colors.primaryStrong]} angle={160} style={…}>` is a `View` that paints a gradient behind its children, so it replaces the `View` you'd otherwise give a flat `backgroundColor`. `angle` is CSS degrees (0 = to top, 90 = to right, default 180); text on it uses `colors.onPrimary`. Exactly **one** per widget — on the focal element — and never behind body text. Both hosts render it identically (web paints CSS, native uses `expo-linear-gradient`), so there is no per-platform branching to write; don't import `expo-linear-gradient` yourself and don't write a `backgroundImage` string.
|
|
569
|
-
- **
|
|
573
|
+
- **Size to your container — measure it, don't stretch into it.** The same widget sits in a full-width desktop section (~1400px), a half-width grid cell (~700px) and a phone (~360px), so layout built only from `flex: 1` stretches to fill whatever it is handed — a month calendar ends up with 200px day cells and swallows the page. Measure your own width with `onLayout={(e) => setWidth(e.nativeEvent.layout.width)}` on the root `View` (a React Native primitive, so it behaves identically on both hosts), render nothing size-dependent while `width === 0`, and compute every threshold from the measured value: a widget gets no declared breakpoint prop, but it can always measure. **Cap a repeating cell** rather than giving a grid `flex: 1` — `const cell = Math.max(32, Math.min(Math.floor((usable - gap * (columns - 1)) / columns), 64));`, with a calendar day cell topping out at 56–72px on `aspectRatio: 1`, and the grid given its exact computed width plus `alignSelf: 'center'` when the cap leaves slack. **Split two co-equal surfaces above ~720px measured width** (`flexDirection: width >= 720 ? 'row' : 'column'`, each half `{ flex: 1, minWidth: 0 }`) — a picker beside the form it feeds on a wide canvas, stacked in reading order below it. Never hardcode a width, never put `flex: 1` / `height: '100%'` on a content widget's root, and don't read the screen with `Dimensions` — the screen is not the widget.
|
|
574
|
+
- **Compose forms — pair fields into rows, don't stack one per row.** Put short, related fields side by side (first + last name, city + postal code, expiry + CVC): a row of `{ flexDirection: 'row', flexWrap: 'wrap', gap: theme.spacing.md }` with each field cell `{ flexGrow: 1, flexBasis: 160 }` splits the width on a wide card and wraps to stacked on a narrow phone — the right recipe for field pairs because it needs no measurement (a fixed-width column overflows a phone; when a layout needs a real column count instead of wrapping, measure your width as above). Keep wide fields (email, address, notes) full-width, cap it at two–three per row, group a long form into labelled sections, and label every input above it (not placeholder-only).
|
|
570
575
|
- **Respond to touch.** Give every `Pressable` a pressed state via the function-style `style={({ pressed }) => [base, pressed && { opacity: 0.7 }]}`.
|
|
571
576
|
- **Drag and drop — show what is being dragged.** A drag where the item stays put reads as broken. Three things change the moment a drag starts: the **drag proxy** (the item lifts and follows the finger — `...theme.elevation.lg`, `{ scale: 1.03 }`, `opacity: 0.9`; for a tall or full-width item drag a compact `primarySoft` pill with its icon + one line of label instead), the **source placeholder** (the vacated slot keeps its height as a quiet `colors.surfaceMuted` block so the list doesn't collapse), and the **drop target** (one slot at a time highlighted with `primarySoft` or a 2px `colors.primary` border). Always animate the release — settle into the new slot, or `Animated.spring(pan, { toValue: { x: 0, y: 0 }, useNativeDriver: false })` back to the origin on cancel. Build it with `Animated` + `PanResponder` from `react-native` (the only mechanism that behaves identically on both hosts) — never HTML5 drag events (`draggable` / `onDragStart` / `dataTransfer` are web-only, and `document` / `window` are banned) — and start the drag from a `GripVertical` grip handle whenever the row is also tappable or sits in a `ScrollView`.
|
|
572
577
|
- **Use icons for clarity.** Pair a `lucide-react-native` icon with its label at a consistent size, coloured from the theme. The label never repeats the icon as a character — with a `Plus` icon the button says "Add item", never "+ Add item" (that renders a doubled plus).
|
package/dist/contract.cjs
CHANGED
|
@@ -1120,6 +1120,7 @@ const ACTION_TRIGGER_TYPES = [
|
|
|
1120
1120
|
// linter does NOT scan action scripts.
|
|
1121
1121
|
const ACTION_SCRIPT_GLOBALS = [
|
|
1122
1122
|
"datastore",
|
|
1123
|
+
"secrets",
|
|
1123
1124
|
"fetch",
|
|
1124
1125
|
"connectors",
|
|
1125
1126
|
"console",
|
|
@@ -2258,7 +2259,14 @@ const CONTRACT = deepFreeze({
|
|
|
2258
2259
|
// `body`, so an app that sets neither renders on one typeface exactly as
|
|
2259
2260
|
// before. Genuine headings — rich-text h1-h3 and container titles — take
|
|
2260
2261
|
// the heading face without the field.
|
|
2261
|
-
|
|
2262
|
+
// 1.50.0: additive — `secrets` joins `actionScriptGlobals`. The action
|
|
2263
|
+
// runner has always frozen the tenant's resolved key/value config onto
|
|
2264
|
+
// the sandbox as `secrets` (REQ-ACTION-SECRET), but the contract never
|
|
2265
|
+
// listed it, so the Developer guide and the agent prompt — both derived
|
|
2266
|
+
// from this list — never told an author it existed. That omission is why
|
|
2267
|
+
// a generated script inlines a plaintext credential instead of reading
|
|
2268
|
+
// `secrets["KEY"]`. No runtime change; the global was already there.
|
|
2269
|
+
version: "1.50.0",
|
|
2262
2270
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2263
2271
|
hooks: HOOKS,
|
|
2264
2272
|
primitives: PRIMITIVES,
|
package/dist/contract.js
CHANGED
|
@@ -1120,6 +1120,7 @@ const ACTION_TRIGGER_TYPES = [
|
|
|
1120
1120
|
// linter does NOT scan action scripts.
|
|
1121
1121
|
const ACTION_SCRIPT_GLOBALS = [
|
|
1122
1122
|
"datastore",
|
|
1123
|
+
"secrets",
|
|
1123
1124
|
"fetch",
|
|
1124
1125
|
"connectors",
|
|
1125
1126
|
"console",
|
|
@@ -2258,7 +2259,14 @@ const CONTRACT = deepFreeze({
|
|
|
2258
2259
|
// `body`, so an app that sets neither renders on one typeface exactly as
|
|
2259
2260
|
// before. Genuine headings — rich-text h1-h3 and container titles — take
|
|
2260
2261
|
// the heading face without the field.
|
|
2261
|
-
|
|
2262
|
+
// 1.50.0: additive — `secrets` joins `actionScriptGlobals`. The action
|
|
2263
|
+
// runner has always frozen the tenant's resolved key/value config onto
|
|
2264
|
+
// the sandbox as `secrets` (REQ-ACTION-SECRET), but the contract never
|
|
2265
|
+
// listed it, so the Developer guide and the agent prompt — both derived
|
|
2266
|
+
// from this list — never told an author it existed. That omission is why
|
|
2267
|
+
// a generated script inlines a plaintext credential instead of reading
|
|
2268
|
+
// `secrets["KEY"]`. No runtime change; the global was already there.
|
|
2269
|
+
version: "1.50.0",
|
|
2262
2270
|
sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
|
|
2263
2271
|
hooks: HOOKS,
|
|
2264
2272
|
primitives: PRIMITIVES,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colixsystems/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.0",
|
|
4
4
|
"description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|