A personality belongs to a person

Owner's correction to 1.4.0: a model's character is per (model, person), and only
the description and the notes stay instance-wide. Two people talking to one model
are not talking to the same personality, and neither can see the other's.

The administrator's box becomes the DEFAULT, resolved by `personas.effective` as
a fallback and never as a layer -- two personalities at once contradict each
other with nothing to say which is losing, which is the reasoning behind "system
prompts replace, never stack". `persona_write` takes no argument naming a model
or a person; both come from the ToolContext, so it can only write the character
it has with whoever it is talking to, and it never touches the default.

Impressions move to their own table. Not a `kind` column: 1.4.0 shipped
`UNIQUE(model_key, owner_id)`, SQLite cannot alter a constraint and this schema
is additive-only, so a discriminator would leave an upgraded instance unable to
hold both rows for one pair. That leaves the first MANUAL_STEPS entry this
project has had -- the two shapes are indistinguishable, so nothing rewrites
them: a repair would be guessing at text that is read back in the first person.

TWO BUGS FROM A PHONE

`min-width` beats both `width` and `max-width` -- CSS clamps width to max-width
and then raises the result to min-width -- so `.canvas` and `.terminal` were
384px wide on every screen narrower than that, their `min(…, 100vw)` cap
overruled, and `.inspector` had no cap at all on a width that is a preference
draggable to 2400px. None of it scrolled sideways, because all three are
`position: fixed` and fixed overflow does not extend the scrollable area -- which
is exactly why the 1.1.0 narrow pass reported these pages clean. `min-width: 0`
in the overlay query, full width below the phone breakpoint, tablet column kept.

And the install button now says why it is absent. Measured against the live
instance: the manifest meets every Chrome criterion and the blocker is a
certificate from a private CA, so the origin is not trustworthy, the service
worker is refused and no install is offered. `base.html` had been swallowing that
with an empty catch -- which kept the page working, the reason it was there, and
threw away the only evidence. It now records the outcome and `app.js` turns it
into a sentence naming the certificate, which is the cause the old hint did not
mention.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 12:20:40 +00:00
co-authored by Claude Opus 5
parent df52ec9d96
commit ac51dd46cc
20 changed files with 1019 additions and 201 deletions
+20 -1
View File
@@ -36,7 +36,26 @@ log = logging.getLogger(__name__)
# Schema changes that this module cannot perform. Kept as documentation so a
# failure has somewhere to point rather than being a mystery.
MANUAL_STEPS: list[str] = []
MANUAL_STEPS: list[str] = [
# 1.4.0 stored "what a model makes of you" in `personas`, identified by
# `owner_id` being set. From 1.5.0 that same shape means "this person's own
# personality", and impressions live in `impressions`. Nothing rewrites them
# automatically: the two are indistinguishable by shape, so a repair would be
# guessing at somebody's text, and a personality is read back to the model in
# the first person. Only an instance that actually ran 1.4.0 -- released and
# superseded the same day -- can have any.
#
# INSERT INTO impressions (id, model_key, owner_id, content, author,
# enabled, created_at, updated_at)
# SELECT id, model_key, owner_id, content, author, enabled,
# created_at, updated_at
# FROM personas WHERE owner_id IS NOT NULL;
# DELETE FROM personas WHERE owner_id IS NOT NULL;
#
# Or simply delete them: nothing had time to write one worth keeping.
"personas written by 1.4.0 with an owner are impressions, not personalities "
"-- see the comment in db/migrations.py to move or remove them",
]
def _default_shape(column: Column) -> type | None: