@getxflow/cli 1.0.0 → 1.0.2
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/dist/bin.js +5 -1
- package/dist/commands/pages.js +25 -0
- package/dist/flags.js +2 -0
- package/dist/help.js +10 -2
- package/dist/limits.js +3 -5
- package/dist/template.js +23 -23
- package/dist/version.js +1 -1
- package/package.json +24 -24
- package/skills/xflow/SKILL.md +43 -16
package/dist/bin.js
CHANGED
|
@@ -223,11 +223,15 @@ async function run(args) {
|
|
|
223
223
|
await (0, pages_1.pagesRollback)(rest);
|
|
224
224
|
return;
|
|
225
225
|
}
|
|
226
|
+
if (second === 'rm' || second === 'remove') {
|
|
227
|
+
await (0, pages_1.pagesRemove)(rest);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
226
230
|
if (second === undefined || second === 'list') {
|
|
227
231
|
await (0, pages_1.pagesList)(rest);
|
|
228
232
|
return;
|
|
229
233
|
}
|
|
230
|
-
throw new errors_1.CliError(`Unknown command: pages ${second}`, 'Available: list, create, deploy, status and
|
|
234
|
+
throw new errors_1.CliError(`Unknown command: pages ${second}`, 'Available: list, create, deploy, status, rollback and rm');
|
|
231
235
|
case 'status':
|
|
232
236
|
await (0, sources_1.status)();
|
|
233
237
|
return;
|
package/dist/commands/pages.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.pagesCreate = pagesCreate;
|
|
|
5
5
|
exports.pagesDeploy = pagesDeploy;
|
|
6
6
|
exports.pagesStatus = pagesStatus;
|
|
7
7
|
exports.pagesRollback = pagesRollback;
|
|
8
|
+
exports.pagesRemove = pagesRemove;
|
|
8
9
|
const node_fs_1 = require("node:fs");
|
|
9
10
|
const node_path_1 = require("node:path");
|
|
10
11
|
const api_1 = require("../api");
|
|
@@ -231,3 +232,27 @@ async function pagesRollback(args) {
|
|
|
231
232
|
(0, ui_1.warn)(`The page is switched off: version ${revision} is current and goes live when it is switched on`);
|
|
232
233
|
(0, ui_1.out)(page.url);
|
|
233
234
|
}
|
|
235
|
+
async function pagesRemove(args) {
|
|
236
|
+
const ref = args.words[1];
|
|
237
|
+
if (!ref) {
|
|
238
|
+
throw new errors_1.CliError('A page is required', 'The name from its address, or its id. The pages: xflow pages');
|
|
239
|
+
}
|
|
240
|
+
// The page is read first: deleting is confirmed by the name in the address, and the
|
|
241
|
+
// page can be named here by its id.
|
|
242
|
+
const client = pagesClient();
|
|
243
|
+
const { page } = await (0, api_1.apiJson)(client, pagePath(ref));
|
|
244
|
+
const query = (0, args_1.flagBool)(args, 'yes') ? `?confirm=${encodeURIComponent(page.slug)}` : '';
|
|
245
|
+
try {
|
|
246
|
+
await (0, api_1.apiJson)(client, `${pagePath(page.id)}${query}`, { method: 'DELETE' });
|
|
247
|
+
}
|
|
248
|
+
catch (e) {
|
|
249
|
+
// The platform names what goes and refuses until the answer is seen. Said in the
|
|
250
|
+
// words of this command, not of the API.
|
|
251
|
+
if (e instanceof api_1.ApiError && e.code === 'confirm_required') {
|
|
252
|
+
throw new errors_1.CliError(e.message, `To delete it anyway: xflow pages rm ${page.slug} --yes`);
|
|
253
|
+
}
|
|
254
|
+
throw e;
|
|
255
|
+
}
|
|
256
|
+
(0, ui_1.ok)(`The page is deleted: ${page.url} shows nothing now`);
|
|
257
|
+
(0, ui_1.note)((0, ui_1.dim)(` Its versions are gone for good, the name ${page.slug} stays reserved for the organization`));
|
|
258
|
+
}
|
package/dist/flags.js
CHANGED
package/dist/help.js
CHANGED
|
@@ -108,6 +108,7 @@ ${(0, ui_1.bold)('Pages')}
|
|
|
108
108
|
upload the site: live at once, there is no publish step
|
|
109
109
|
xflow pages status <page> the page and its versions
|
|
110
110
|
xflow pages rollback <page> <N> put an earlier version back on the address
|
|
111
|
+
xflow pages rm <page> --yes delete the page for good: the address empties
|
|
111
112
|
|
|
112
113
|
${(0, ui_1.bold)('Reference')}
|
|
113
114
|
xflow logs [--limit N] browser errors of the published apps
|
|
@@ -282,6 +283,8 @@ the platform builds, with versions, access levels and a database through functio
|
|
|
282
283
|
-m <text> what changed, one short line: it names the version
|
|
283
284
|
xflow pages status <page> the page and its versions
|
|
284
285
|
xflow pages rollback <page> <N> put version N back on the address
|
|
286
|
+
xflow pages rm <page> delete the page
|
|
287
|
+
--yes without it the command names what goes and stops
|
|
285
288
|
|
|
286
289
|
${(0, ui_1.bold)('<path>')} is a folder with index.html at the top, its .zip, or one .html file.
|
|
287
290
|
${(0, ui_1.bold)('<page>')} is the name from the address or the id. ${(0, ui_1.bold)('--json')} on list and status
|
|
@@ -304,9 +307,14 @@ add the page name there and run ${(0, ui_1.bold)('xflow deploy functions')}. The
|
|
|
304
307
|
Such a function is open to the whole internet. Search engines are asked not to index pages,
|
|
305
308
|
so do not promise SEO.
|
|
306
309
|
|
|
310
|
+
${(0, ui_1.bold)('Deleting is final.')} The address goes empty and every version of the page is gone: there
|
|
311
|
+
is no undo and no copy on the platform side. The name stays reserved for the organization, so
|
|
312
|
+
the same ${(0, ui_1.bold)('--slug')} is free for the next page of yours and taken for everyone else. A
|
|
313
|
+
throwaway page put up to try something is worth deleting the same way it was made.
|
|
314
|
+
|
|
307
315
|
A page takes the key of the whole organization, the one ${(0, ui_1.bold)('xflow login')} issues: a key
|
|
308
|
-
scoped to projects is refused.
|
|
309
|
-
platform interface.`,
|
|
316
|
+
scoped to projects is refused. A page is deleted by whoever made it, or by the owner of the
|
|
317
|
+
organization. Switching a page off for a while is for a person, in the platform interface.`,
|
|
310
318
|
update: `${(0, ui_1.bold)('xflow update')}: bring the CLI up to date
|
|
311
319
|
|
|
312
320
|
Installs the published version and, if anything changed, rewrites the platform
|
package/dist/limits.js
CHANGED
|
@@ -13,7 +13,7 @@ const DENIAL_LABELS = {
|
|
|
13
13
|
limit_functions: 'cloud functions',
|
|
14
14
|
limit_builds: 'builds this month',
|
|
15
15
|
limit_builds_concurrent: 'builds running at once',
|
|
16
|
-
limit_seats: '
|
|
16
|
+
limit_seats: 'people in the organization',
|
|
17
17
|
limit_schedule_interval: 'schedule frequency',
|
|
18
18
|
db_write_locked: 'database volume, writes are off',
|
|
19
19
|
plan_no_schedules: 'schedules',
|
|
@@ -38,8 +38,7 @@ const QUOTA_ORDER = [
|
|
|
38
38
|
'connections',
|
|
39
39
|
'pages',
|
|
40
40
|
'functions',
|
|
41
|
-
'
|
|
42
|
-
'members',
|
|
41
|
+
'people',
|
|
43
42
|
'database_mb',
|
|
44
43
|
'files_mb',
|
|
45
44
|
'function_minutes',
|
|
@@ -50,8 +49,7 @@ const QUOTA_LABELS = {
|
|
|
50
49
|
connections: 'Active connections',
|
|
51
50
|
pages: 'Public pages',
|
|
52
51
|
functions: 'Cloud functions',
|
|
53
|
-
|
|
54
|
-
members: 'Staff',
|
|
52
|
+
people: 'People',
|
|
55
53
|
database_mb: 'Database',
|
|
56
54
|
files_mb: 'Files',
|
|
57
55
|
function_minutes: 'Function minutes',
|
package/dist/template.js
CHANGED
|
@@ -2,30 +2,30 @@
|
|
|
2
2
|
/** Files the CLI lays into a new project root: the app itself comes from the platform template. */
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
exports.projectScaffold = projectScaffold;
|
|
5
|
-
const GITIGNORE = `node_modules/
|
|
6
|
-
dist/
|
|
7
|
-
.xflow/
|
|
8
|
-
.env
|
|
9
|
-
.env.*
|
|
5
|
+
const GITIGNORE = `node_modules/
|
|
6
|
+
dist/
|
|
7
|
+
.xflow/
|
|
8
|
+
.env
|
|
9
|
+
.env.*
|
|
10
10
|
`;
|
|
11
|
-
const README = `# %NAME%
|
|
12
|
-
|
|
13
|
-
A project on XFlow: one database, cloud functions and the apps that use them.
|
|
14
|
-
|
|
15
|
-
\`\`\`
|
|
16
|
-
xflow.json projectId, and which apps and pages may call each function
|
|
17
|
-
package.json dependencies of the functions
|
|
18
|
-
functions/<name>/ cloud functions, shared by every app
|
|
19
|
-
migrations/ the database of the project
|
|
20
|
-
apps/<folder>/ an app: its own code, versions and address
|
|
21
|
-
\`\`\`
|
|
22
|
-
|
|
23
|
-
\`\`\`bash
|
|
24
|
-
xflow deploy functions -m "..." # ship the functions and their callers
|
|
25
|
-
xflow deploy apps/<folder> -m "..." # build one app
|
|
26
|
-
xflow publish # inside an app folder: show the newest build
|
|
27
|
-
xflow status # what is on the server
|
|
28
|
-
\`\`\`
|
|
11
|
+
const README = `# %NAME%
|
|
12
|
+
|
|
13
|
+
A project on XFlow: one database, cloud functions and the apps that use them.
|
|
14
|
+
|
|
15
|
+
\`\`\`
|
|
16
|
+
xflow.json projectId, and which apps and pages may call each function
|
|
17
|
+
package.json dependencies of the functions
|
|
18
|
+
functions/<name>/ cloud functions, shared by every app
|
|
19
|
+
migrations/ the database of the project
|
|
20
|
+
apps/<folder>/ an app: its own code, versions and address
|
|
21
|
+
\`\`\`
|
|
22
|
+
|
|
23
|
+
\`\`\`bash
|
|
24
|
+
xflow deploy functions -m "..." # ship the functions and their callers
|
|
25
|
+
xflow deploy apps/<folder> -m "..." # build one app
|
|
26
|
+
xflow publish # inside an app folder: show the newest build
|
|
27
|
+
xflow status # what is on the server
|
|
28
|
+
\`\`\`
|
|
29
29
|
`;
|
|
30
30
|
function projectScaffold(projectName) {
|
|
31
31
|
return [
|
package/dist/version.js
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DEFAULT_API_URL = exports.CLI_VERSION = void 0;
|
|
4
4
|
/** Keep in sync with cli/package.json. */
|
|
5
|
-
exports.CLI_VERSION = '1.0.
|
|
5
|
+
exports.CLI_VERSION = '1.0.2';
|
|
6
6
|
/** Overridden by XFLOW_API_URL or the `api` field in xflow.json. */
|
|
7
7
|
exports.DEFAULT_API_URL = 'https://app.getxflow.com';
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@getxflow/cli",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "CLI for the XFlow platform: projects with apps, functions and a database; source sync, deployment and publishing",
|
|
5
|
-
"license": "UNLICENSED",
|
|
6
|
-
"engines": {
|
|
7
|
-
"node": ">=20"
|
|
8
|
-
},
|
|
9
|
-
"bin": {
|
|
10
|
-
"xflow": "dist/bin.js"
|
|
11
|
-
},
|
|
12
|
-
"files": [
|
|
13
|
-
"dist",
|
|
14
|
-
"skills"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
18
|
-
"build": "npm run clean && tsc -p tsconfig.json",
|
|
19
|
-
"prepublishOnly": "npm run build"
|
|
20
|
-
},
|
|
21
|
-
"publishConfig": {
|
|
22
|
-
"access": "public"
|
|
23
|
-
}
|
|
24
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@getxflow/cli",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "CLI for the XFlow platform: projects with apps, functions and a database; source sync, deployment and publishing",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=20"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"xflow": "dist/bin.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"skills"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
18
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/skills/xflow/SKILL.md
CHANGED
|
@@ -23,7 +23,7 @@ contains the fix.
|
|
|
23
23
|
|
|
24
24
|
## Keeping these instructions current
|
|
25
25
|
|
|
26
|
-
These instructions ship with xflow CLI 1.0.
|
|
26
|
+
These instructions ship with xflow CLI 1.0.2, so the copy you are reading can be older than
|
|
27
27
|
the CLI answering your commands.
|
|
28
28
|
|
|
29
29
|
When the CLI says this file does not match it, run `xflow skills --refresh` and then **read
|
|
@@ -108,7 +108,7 @@ One folder per project, apps in subfolders:
|
|
|
108
108
|
## Plan limits
|
|
109
109
|
|
|
110
110
|
The organization runs on a plan with limits: apps, functions, pages, active connections,
|
|
111
|
-
|
|
111
|
+
people in the organization, database and file storage, function minutes, builds per month and at once, schedules
|
|
112
112
|
and how often they may run, and which access levels an app may have. Most are finite; a plan
|
|
113
113
|
may leave one uncapped, and then `whoami` prints `no limit` instead of a ceiling.
|
|
114
114
|
`xflow whoami` prints every one of them next to what is used, and the rights of your own key
|
|
@@ -198,7 +198,7 @@ An app:
|
|
|
198
198
|
- The platform files stay byte for byte as they arrived: `src/lib/platform-auth.ts`,
|
|
199
199
|
`src/contexts/platform-auth-context.tsx`, `src/hooks/use-platform-auth.ts`,
|
|
200
200
|
`src/utils/error-logger.ts`, `src/lib/xflow.ts`. The code keeps calling `initPlatformAuth()`
|
|
201
|
-
and `initErrorLogger()
|
|
201
|
+
and `initErrorLogger()` and importing `index.css`, and the Tailwind
|
|
202
202
|
`content` globs keep covering `src/**`. A rejection names the revision to take the original
|
|
203
203
|
from: `xflow pull --revision N --into ./original`, then copy the file back.
|
|
204
204
|
|
|
@@ -214,10 +214,15 @@ Fix the first two before you tell the person the app is done.
|
|
|
214
214
|
## The template is a starter kit
|
|
215
215
|
|
|
216
216
|
- `src/components/ui` and `src/components/blocks` are a starting set: change them, restyle them
|
|
217
|
-
or delete what you do not use.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
217
|
+
or delete what you do not use. Any colors, your own color scheme, inline styles, a single
|
|
218
|
+
theme without dark mode: all allowed.
|
|
219
|
+
- The template looks like the xflow interface: warm grays, hairline controls (`ctl-solid`,
|
|
220
|
+
`ctl-raised`, `ctl-inset`, `ctl-select`), segments (`seg-track`, `seg-on`) and surfaces
|
|
221
|
+
(`surface-raised`, `surface-pop`, `surface-modal`) in `src/index.css`.
|
|
222
|
+
- While any template component is left, the theme token names in `:root` of `src/index.css`
|
|
223
|
+
and their `hsl(var(--name))` mapping in the Tailwind config stay: those components paint with
|
|
224
|
+
them. Values change freely; they are HSL components (`216 95% 40%`), not `#hex`. Your own
|
|
225
|
+
colors go under new names.
|
|
221
226
|
- Images and other assets: import them from `src/` (`import logo from './assets/logo.png'`)
|
|
222
227
|
so the build puts them next to the code. An absolute path into `public/` (`/logo.png`) goes
|
|
223
228
|
through the access check of the app and fails inside the xflow frame in Safari.
|
|
@@ -228,14 +233,19 @@ A person sets this in the web interface; you cannot, and you should not ask for
|
|
|
228
233
|
|
|
229
234
|
| Level | Who gets in |
|
|
230
235
|
|---|---|
|
|
231
|
-
| Project members | people
|
|
236
|
+
| Project members | people who have access to the project: the team through xflow, staff with a login |
|
|
232
237
|
| By list | the members named for this app |
|
|
233
238
|
| Anyone with the link | anybody, no sign-in at all |
|
|
234
239
|
|
|
235
240
|
- The plan decides which levels exist: on Free an app opens only inside xflow, Start adds the
|
|
236
|
-
direct address
|
|
241
|
+
direct address, "By list" and "Anyone with the link".
|
|
237
242
|
- At the two closed levels the platform signs people in, and a function receives the person in
|
|
238
|
-
`event.xflow.user` (`id`, `role` in the organization).
|
|
243
|
+
`event.xflow.user` (`id`, `role` in the organization, `name`, `login`).
|
|
244
|
+
- Staff (a doctor, a picker) never sign in to xflow: an admin gives them a login and a password,
|
|
245
|
+
they enter at the address of the app, `login` is set for them and null for the team. Keep
|
|
246
|
+
roles inside the app (doctor, receptionist) in your own table keyed by `user.id`.
|
|
247
|
+
- A "Sign out" button: `const r = await fetch('/_xflow/logout', { method: 'POST' })`, then
|
|
248
|
+
`location.href = (await r.json()).next`. It ends every sign-in of the person.
|
|
239
249
|
- At "Anyone with the link" the platform signs nobody in: `event.xflow.user` is null for
|
|
240
250
|
visitors. Accounts, sign-in and who sees which rows are the code of your app, see below.
|
|
241
251
|
- `usePlatformAuth()` in the frontend knows the person only inside the xflow frame, and even
|
|
@@ -383,7 +393,8 @@ exports.handler = async (event) => {
|
|
|
383
393
|
// project), 'external' (an outside service with a key), 'schedule',
|
|
384
394
|
// 'platform' (a person through xflow functions invoke or the web interface)
|
|
385
395
|
// slug: the address name of the calling app (<id>--<org>) or page, null otherwise
|
|
386
|
-
// user: { id, role } of a signed-in
|
|
396
|
+
// user: { id, role, name, login } of a signed-in person (login only for staff),
|
|
397
|
+
// null for anonymous visitors, pages, keys
|
|
387
398
|
// ip: the visitor's address, null for a schedule
|
|
388
399
|
}
|
|
389
400
|
```
|
|
@@ -399,7 +410,9 @@ exports.minRole = 'developer' // 'member' | 'developer' | 'owner'
|
|
|
399
410
|
|
|
400
411
|
The platform refuses anything below that role before your code runs; it applies to people
|
|
401
412
|
(apps, pages, `functions invoke`), not to keys and schedules. `member` is the people who only
|
|
402
|
-
use the apps (staff of a clinic, managers), `developer` and `owner` build them.
|
|
413
|
+
use the apps (staff of a clinic, managers), `developer` and `owner` build them. In the web
|
|
414
|
+
interface these roles are called «Просмотр» (`member`), «Редактор» (`developer`) and «Админ»
|
|
415
|
+
(`owner`). Without the line
|
|
403
416
|
every member of the project can call the function. Losing access
|
|
404
417
|
closes the function within a minute.
|
|
405
418
|
|
|
@@ -512,9 +525,11 @@ and zsh, cmd.exe wants double ones; a query with quotes of its own goes in a `.s
|
|
|
512
525
|
the project, `xflow db query ../q.sql`.
|
|
513
526
|
|
|
514
527
|
The platform keeps no database history and no backups, so anything that destroys data
|
|
515
|
-
(`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE
|
|
516
|
-
|
|
517
|
-
|
|
528
|
+
(`DROP TABLE`, `DROP COLUMN`, `TRUNCATE`, `DELETE`) needs both the `--allow-destructive` flag and
|
|
529
|
+
the right to destroy data on the key, which is off by default and only its owner turns on. It is
|
|
530
|
+
judged by the result, not the text: a migration that dropped or emptied a table, dropped a column
|
|
531
|
+
or deleted rows is rolled back, however the SQL was written. `BEGIN`/`COMMIT` in a migration are
|
|
532
|
+
refused: each one already runs in its own transaction. When the refusal is about the right, the flag changes nothing: say what
|
|
518
533
|
needs deleting and why, and let the person decide. With both in place the affected tables are
|
|
519
534
|
dumped first (up to 50 000 rows each) and kept for 7 days; getting a dump back is a request to
|
|
520
535
|
the person, no command does it. Check with `--dry-run`.
|
|
@@ -607,6 +622,10 @@ the address opens it, without signing in.
|
|
|
607
622
|
- **Both are public the moment the command finishes**: no draft, no publish step.
|
|
608
623
|
- `xflow pages status <page>` lists the versions, `xflow pages rollback <page> <N>` puts one back
|
|
609
624
|
in seconds; the files of the last 5 are kept.
|
|
625
|
+
- `xflow pages rm <page> --yes` deletes the page: the address goes empty and every version is
|
|
626
|
+
gone for good, without `--yes` the command names what goes and stops. The name stays reserved
|
|
627
|
+
for the organization. A page put up to try something out is yours to take down when you are
|
|
628
|
+
done: do not leave it standing.
|
|
610
629
|
- Names are 3-40 characters. At most 100 pages per organization, 20 new and 200 uploads a day,
|
|
611
630
|
10 seconds between uploads of one page: batch changes into one upload.
|
|
612
631
|
- Static files only, what the build writes (`dist`), never a folder with `package.json`. At most
|
|
@@ -618,7 +637,8 @@ the address opens it, without signing in.
|
|
|
618
637
|
- Search engines are asked not to index pages or apps: do not promise SEO.
|
|
619
638
|
- The tab icon is the platform's on every page: do not spend a request on a favicon of your own.
|
|
620
639
|
- The MCP `pages` tool uploads one self-contained HTML document up to 150 KB.
|
|
621
|
-
-
|
|
640
|
+
- A page is deleted by whoever made it, or by the owner of the organization. Switching a page
|
|
641
|
+
off for a while is for a person, in the platform interface.
|
|
622
642
|
|
|
623
643
|
## Organizations and keys
|
|
624
644
|
|
|
@@ -631,6 +651,10 @@ lists the commands that take them). A "not found" on a project you know exists u
|
|
|
631
651
|
key belongs to another organization: check `xflow org`; signing in to a missing organization is
|
|
632
652
|
`xflow login`, and that needs a person with a browser.
|
|
633
653
|
|
|
654
|
+
A key has its owner's level in each project. With view-only access reading works (`projects
|
|
655
|
+
get`, logs, `db query`, `pull`), and every change fails with `Your access to this project is
|
|
656
|
+
view-only`. That is not a bug to work around: tell the person, edit access is theirs to ask for.
|
|
657
|
+
|
|
634
658
|
## Direct access without the terminal
|
|
635
659
|
|
|
636
660
|
The platform also exposes an MCP server, connected with `xflow mcp install`. When its tools are
|
|
@@ -653,6 +677,9 @@ people are set by a person in the web interface.
|
|
|
653
677
|
|
|
654
678
|
- Take a block from `src/components/blocks` or a primitive from `src/components/ui` before
|
|
655
679
|
writing your own layout; replace or delete them when the design needs something else.
|
|
680
|
+
- Dropdowns, checkboxes, radios and dates through `Select`, `Checkbox`, `RadioGroup`,
|
|
681
|
+
`DatePicker` from `src/components/ui`: a bare `<select>` opens the system list in Safari
|
|
682
|
+
and Firefox, out of the app's style.
|
|
656
683
|
- Every screen starts with a clear header; header, filters and content in one column.
|
|
657
684
|
- Draw all four states: loading, error, empty, data. An empty screen says what appears here and
|
|
658
685
|
offers an action.
|