Skip to content

Commit 0f83180

Browse files
committed
Say it once, and say it plainly
The docs stacked four ideas into a sentence, explained the shell's internals to an app author, and pointed at another screen the author never sees. Both pages now use short sentences with a named actor, the contract carries the detail, and the guide carries only what an author does about it.
1 parent 72417fb commit 0f83180

5 files changed

Lines changed: 29 additions & 36 deletions

File tree

‎backend/druks/ui/fields.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,8 @@ class UploadField(Schema):
101101

102102

103103
class SecretField(Schema):
104-
"""A secret the operator hands over — a token, a key. It carries no
105-
``value``, the way ``UploadField`` carries none: a file input cannot be
106-
seeded, and a secret must not be, so an app can never echo a stored secret
107-
back to the browser by declaring one."""
104+
"""One secret the operator hands over: a token, a key. It has no ``value``,
105+
so a page cannot send a stored secret back to the browser."""
108106

109107
field: Literal["secret"] = "secret"
110108
name: str

‎docs/druks-ui.md‎

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,21 +1432,20 @@ ui.SecretField(name="token", label="Access token", help_text="From your account
14321432
{"field": "secret", "name": "token", "label": "Access token", "helpText": "From your account settings.", "isRequired": false}
14331433
```
14341434

1435-
One secret the operator hands over — a token, a key — and, like `UploadField`,
1436-
no starting `value`: a file input cannot be seeded, and a secret must not be, so
1437-
an app can never echo a stored secret back to the browser by declaring one.
1438-
1439-
The shell renders it as a masked `type="password"` input that the browser's
1440-
password managers leave alone, the same input the settings modal uses for a
1441-
bearer token. On a successful submit the form's reset returns the field to empty,
1442-
so nothing has to clear it.
1443-
1444-
Masking protects the screen, not the stored secret. When a route rejects the
1445-
submitted value, its 422 message can carry the secret back — Pydantic embeds the
1446-
submitted input in many of its messages. So the shell shows a fixed line on a
1447-
secret field, and a fixed form-level line for any refusal that names no field on
1448-
screen, whenever the form holds a secret. Every other field still shows the
1449-
server's own words.
1435+
One secret the operator hands over: a token, a key.
1436+
1437+
It has no `value`. A file input cannot be seeded, and a secret must not be. A
1438+
field with nowhere to put one cannot send a stored secret back to the browser.
1439+
1440+
The shell masks it and keeps it from the browser's password managers. A
1441+
successful submit leaves it empty.
1442+
1443+
Masking protects the screen. It does not protect the stored secret.
1444+
1445+
A refusal never repeats the server's words on a secret field. The shell shows a
1446+
fixed line, because a validation message can carry the submitted value back. It
1447+
shows a fixed line on the form too, for a refusal that names no field on screen.
1448+
Every other field keeps the server's own words.
14501449

14511450
## Page and Follows
14521451

‎docs/writing-an-app.md‎

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1321,18 +1321,15 @@ A form that needs a token takes a `ui.SecretField`:
13211321
ui.SecretField(name="token", label="Access token", help_text="From your account settings.")
13221322
```
13231323

1324-
It renders masked, is kept from the browser's password managers, carries no
1325-
starting value, and clears itself on a successful submit. When a route rejects
1326-
the value, the shell shows a fixed line rather than the server's — a validation
1327-
message can echo the submitted token back — and does the same for any refusal
1328-
that names no field on screen while the form holds a secret.
1329-
1330-
That masking protects the screen, not the stored secret. Your operation receives
1331-
the plaintext token and stays responsible for keeping it safe. For one secret per
1332-
record — a credential per connected account — store it in an
1333-
`EncryptedJsonField` column or a `SecretsMapping`, never a plain string. A token
1334-
the whole app shares belongs in `AppSettings` with a `Secret` field instead,
1335-
where the platform masks, encrypts, and never reads it back — not in a form.
1324+
Druks masks it, keeps it from the browser's password managers, and leaves it
1325+
empty after a successful submit. A refusal shows a fixed line, never the
1326+
server's words, because a validation message can carry the token back.
1327+
1328+
The masking protects the screen. Your operation receives the token in plain
1329+
text and owns it from there. Keep one secret per record in an
1330+
`EncryptedJsonField` column or a `SecretsMapping`. A token the whole app shares
1331+
belongs in `AppSettings` as a `Secret`, where Druks encrypts it and never reads
1332+
it back.
13361333

13371334
Two routes of one app cannot share an `operation_id`. An action that names an
13381335
operation the app does not declare, a GET route, or a route with a query

‎frontend/src/druksui/Fields.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,8 +216,8 @@ function Input({
216216
<input
217217
{...shared}
218218
className="dui-input"
219-
// The same masked, password-manager-ignored input the settings modal
220-
// uses for the MCP bearer token.
219+
// The set the settings modal uses for its bearer token. Keep them
220+
// together.
221221
type="password"
222222
autoComplete="new-password"
223223
data-1p-ignore=""

‎frontend/src/druksui/Form.tsx‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,8 @@ function problems(
325325
continue
326326
}
327327
if (hasSecret) {
328-
// No field on screen owns this — a model-level validator, or a route
329-
// parameter named otherwise. Either could have seen the secret, so keep
330-
// its words off the screen.
328+
// No field on screen owns this one. Whatever raised it saw the whole
329+
// body, so keep its words off the screen.
331330
redactForm = true
332331
continue
333332
}

0 commit comments

Comments
 (0)