Repository navigation
Release/v0.2.0 - #27
Merged
Merged
Conversation
…nks read as links Auth(login=) takes components, with a ui.SignIn(provider id) where each way in goes, so a heading, a note or a line sits wherever it is wanted. The page is built before any session exists, so a component with a handler, or one that only talks to Python, is refused when the app starts. Left alone, the page draws a line between the provider buttons and the password form. ui.SignIn works on a public page too. The Microsoft and Google buttons carry their brand's mark, and OAuth(icon=) takes a brand, an image under public/ or an icon name. A link is underlined, faintly, since under the default theme its color is the color of the text. Links in a sidebar and the header stay plain. A card's content takes the height a taller neighbor gives the card, so the footer sits at the bottom of every card in a row. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A sign-in's lifetime was checked only when a page loaded or a socket connected, so a tab left open outlived it. A session now carries the id and expiry of the sign-in it runs on, and when that runs out every session on it sends its tab away and ends. A tab that comes back on a newer sign-in follows the newer one. Auth(on_logout=) gets the User and returns a path, a URL or None for the login page. It is asked on sign-out and once per expired sign-in. A hook that fails, is cancelled, takes over ten seconds or answers with something else is logged and counts as None, so it cannot keep a person signed in. The sign-out route now refuses a request from another origin, since it runs the app's hook and ends its sessions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s more Printing gave one sheet, since the shell is a viewport-high column whose body scrolls. In print the column and its scrolling boxes run as long as their content, the sidebar and header are left out, and the dark scheme steps aside. A theme's stylesheet is a CSS file under public/ linked after the theme, watched in debug mode like the theme file. A dropdown menu is at least as wide as its trigger and grows to its longest item. An Alert draws its children. The confirm dialog's buttons and the bundle's labels for screen readers come from the theme's strings. In the test harness, find and find_all look inside one node with within=, a type finds its subclasses so ui.Custom finds custom components, and choose picks an item of a dropdown menu. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ct paths win Text the framework writes itself now comes from the theme's strings: the table's row count, the upload box, the chat placeholder, what is wrong with a form field, and why an upload was refused. A label left at the framework's default (a form's Save, a Download, a pie chart's Other) follows the theme, while a word the app wrote stays the app's. Only a message pydantic wrote itself is replaced, so a validator's own words are shown as raised. ui.Button(href=) is a link drawn as a button. A ui.Link in a ui.Sidebar is drawn as a row to press, so a list of pages needs no classes. s.print() opens the print dialog after the page was drawn again, and in print a heading stays with what follows it. Where a path fits several pages, the one that is exact soonest wins: a literal segment, then a parameter with a converter, then a bare one, then the rest of the path. Before, the page added first won. In the test harness, find and find_all take props as keywords, and a click on a link or a button with an href takes the session there. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…current A page, a route or a mount takes allow=, a plain function of the signed-in user and of the path parameters it names. One place answers it, asked on a page load, when a tab connects or comes back, on navigation, for a route and for s.can(path), so a page cannot be reached around its rule, and a refused page never runs its connect hook. Only True admits. `millwork routes` lists who may reach what. Breaking: with auth, app.route and app.mount need a sign-in unless public=True, and a request that changes something, or a socket, has to come from the app's own origin. They were open to anyone before. Auth(recheck=) returns who a signed-in person is now, or None to end the sign-in, at most once per recheck_every for each identified user. Every caller waits for the one answer in flight. A session belongs to the user it started with: when that user changes it ends and the tab loads again, and a tab that reconnects gets its session back only unchanged. A hook that cannot answer refuses new access and ends open sessions once the last answer is twice the interval old. A session is over before its tab is told, in a task of the framework's own, so a tab that does not read, a caller that is cancelled or a second caller cannot leave it half ended. A sign-in's sessions end together. A page is found by its path as the browser spelled it, decoded once, on a page load and in the tab alike. Something an app mounts sees the app's own sign-in. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A tab that spoke while the app was still being asked where a sign-in's tabs should go was told a generic word and never sent there. It is now left on its socket until there is something to say, and so is a tab whose session ended while it was still connecting. A sign-in that ran out during a connect ends the session before anything of the page is sent, and a straggler of a sign-in that has just ended goes where the others went without the app being asked again. A page's close hook runs in a task of the framework's own, so it reaches its end whatever becomes of the handler or navigation that was leaving the page, may end the session it is closing, and its being cancelled from inside does not stop a shutdown. The end waits for a close that is already under way. Work started on a session after it ended is cancelled at once, a task the end cancelled does not come back through on_done, navigation is one of the session's own tasks, and a page whose connect finishes after the end is closed straight away. Making room for a session and signing out no longer wait on close hooks. A task cancelled in the moment it was started never ran a line and so never said that it ended: it stayed running and its awaiters waited for good. It now ends as cancelled. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
It drives the app through its ASGI surface the way browsers do, page loads and sign-ins over HTTP and each tab over a websocket that speaks the tab's protocol, on a virtual clock so a grace period or a slow hook costs nothing to wait for. A scenario is a seeded list of small actions: a click, a dropped connection, a role taken away, a tab that stops reading, time passing, the server stopping. The app writes down what its own code did and each tab what it was sent, and the oracle reads that record against what the manual promises: nothing runs on a session that is over, a session keeps its user, a guarded page is never acted on or sent past its rule, a change reaches a session within the recheck interval, a page is closed once, handlers take turns, no tab is left on a session it was not told has ended, and nothing is left behind. A failing scenario shrinks to the few actions that cause it. The test runs 250 scenarios and guards the promises that hold. The five that do not hold yet are listed in it, so the list can only get shorter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The app under test now holds a table with row actions, a form, an input, a chat that streams, an upload, a download, a custom component, a question with a form in it, and tasks that are delayed, failing or run in a thread. Sign-in hooks run in threads in some scenarios. A worker thread takes turns with the virtual loop, so a scenario with threads still runs the same way every time. The guard test now knows each failure by what the record says about it, so a new way of breaking a known promise fails the test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A request and its cancel in one read, a chat message and its stop in one read, a handler that waits on a task when its session ends, a connect hook that waits on a task, a page loaded again or duplicated in the middle of things, a session that says more than it keeps for a tab that is away, and a large download. A tab now holds what it has to say until it knows its session, as the bundle does. Today's code breaks a promise in 3,147 of 10,000 scenarios. The guard test knows each of the twelve kinds by what the record says about it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A scope owns everything that waits on someone's behalf and gives back what a child was handed, however the child ended. A turn is held by one handler at a time and is never gone on without. An outbox takes what a session has to say without waiting and keeps it for replay. A connection follows an outbox onto one socket and carries nothing but a goodbye once closed. Nothing in the package uses them yet. Each has tests of its own. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A session's state changes only in steps, none of which waits, and everything that waits for a session is a child of its scope or its own life. The life alone opens and closes pages. The session never holds a socket: it posts to an outbox, and a connection carries that to the tab. A sealed outbox, a cancelled scope and a closed connection are the gates that hold after the end. session.py holds the steps and has no async in it. work.py holds the life, the children and the awaited half of the API. The websocket endpoint is the life of one connection. The app has a scope for its sessions' lives and one for its background work, admits a session in one step, and ends a sign-in in one step with one remembered destination. Auth has a scope for its questions. A task is started through its session's scope and knows whether it has started. The test harness drives a real connection. Measured by the lifecycle fuzzer, the old code broke a promise in 3,147 of 10,000 scenarios; this breaks none in 30,000. tests/test_rules.py reads the design's rules off the source. Two reviews of the code found eleven holes in the core and four unintended differences, all fixed here. What an app can notice is in the changelog: nothing after a handler's own end runs, a cancelled connect gets no close, a close hook has thirty seconds, a page is drawn only once its connect has returned, a request or a chat message cancelled as it arrives is forgotten, and ending a session from outside returns at once with session.gone to wait on. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Four jobs, each on seeds the date picks, so every night runs scenarios nobody has run before and a failure is found once and replayed by its seed. When one fails, the job shows the smallest scenario of each failure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.